经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
打印Proxy对象和ref对象的包实现详解
来源:jb51  时间:2022/11/19 17:13:05  对本文有异议

前因

平时工作的时候,我喜欢用console.log调试大法。但Vue3更新后,控制台都是打印的Proxy对象和ref对象,想看里边的值,就需要很麻烦的一层一层的展开。

为了解决这个问题,我试过在编辑器中写一个新的快捷键,快速写出console.log(JSON.parse(JSON.stringify()))

但我用的是webStorm,它自带的.log快捷键太舒服了,比如这样:abc.log 点击tab键,就自动替换为console.log(abc)

我试了好久,终究还是没能拓展类似的代码。所以才有了重写console.log()的想法。

目标

我希望新的console.log可以像现在的console.log一模一样,只是当打印Proxyref对象时可以直接输出它的源对象或ref.value。并且,还保留记录当前文件和行数的功能,可以让我看到到底是哪个文件哪个步骤执行的打印。

结果

先说结果:

我翻了好久的文档,终究还是不能达到我想要的效果,控制台右侧展示出的打印文档及行号终究还是不能直接显示源文件,如果有大神能看到这篇文章的话,希望告诉我怎么怎么才能实现这个想法。

但退而求其次,我用console.traceError.stack两种方式十分简陋的完成了这个目标。

各位可以去 下载试试,源码也就不到200行,有兴趣的同学可以看看。

实现(直接看源码的同学可以略过)

判断一个对象是否是Proxy

这个不好判断,Vue3添加了isProxy 方法,但如果不是Vue环境的话,那这个方法就失效了。 而且就这么一个简单的小功能,实在没必要依赖其他的包。 最终是选择在用户new Proxy之前,把Proxy对象改造。

  1. // 记录用户new Proxy操作的所有对象
  2. // WeakSet,WeakMap,都是弱引用,不干预其他模块的垃圾回收机制
  3. export const proxyMap = new WeakMap()
  4. let OriginalProxy = null
  5. export function listenProxy() {
  6. if (OriginalProxy) { // 防止用户多次调用监听
  7. return
  8. }
  9. OriginalProxy = window.Proxy
  10. window.Proxy = new Proxy(Proxy, {
  11. construct(target, args) {
  12. const newProxy = new OriginalProxy(...args)
  13. proxyInstances.set(newProxy, target)
  14. return newProxy
  15. },
  16. get(obj, prop) {
  17. // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance
  18. if (prop === Symbol.hasInstance) { // 监控 `instanceof` 关键字
  19. return instance => proxyMap.has(instance)
  20. }
  21. // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get
  22. return Reflect.get(...arguments)
  23. }
  24. })
  25. }
  26. export function unListenProxy() {
  27. window.Proxy = OriginalProxy || window.Proxy
  28. }

输出用户log的源对象

按说我们上一步已经监控了用户动作,可以获取源对象,等用户log的时候,我们直接输出源对象就可以了。但这也有个问题,Proxy毕竟不是普通的对象,通过Proxy获取的结果,很可能跟源对象没有一毛钱关系。所以只能通过深克隆返回源对象的值,但这也有个问题,就是对于某些不能遍历的对象或属性,就打印不了了……

问题貌似锁死了,但,我们实际运用中,只是为了简简单单输出一个不用展开的源对象而已,甚至运用场景都特别单一:Vue3! 用户如果觉得打印的不准确,换一个api不完了吗,比如我们监控的是console.log,那用户就用console.info一样能输出相同的结果。 把选择权交给用户就好了。在引用包的时候,再写多一个配置项,让用户自己选平时的使用场景哪个正确结果比较多,就选哪个。想要完全正确,就换一个其他的api。

我简直是个天才,哈哈哈

  1. export function getOrg(obj) {
  2. return proxyMap.get(obj)
  3. }
  4. // 深克隆
  5. export function clone(obj, _refs = new WeakSet()) {
  6. if (obj === null || obj === undefined) return null
  7. if (typeof obj !== 'object') return obj
  8. if (obj.constructor === Date) return new Date(obj)
  9. if (obj.constructor === RegExp) return new RegExp(obj)
  10. const newObj = new obj.constructor() //保持继承的原型
  11. for (const key in obj) {
  12. if (obj.hasOwnProperty(key)) {
  13. const val = obj[key]
  14. if (typeof val === 'object' && !_refs.has(val)) {
  15. newObj[key] = clone(val)
  16. } else {
  17. newObj[key] = val
  18. }
  19. }
  20. }
  21. return newObj
  22. }

最后暴露出去给用户调用

  1. import { listenProxy, unListenProxy, clone, getOrg } from "./until";
  2. let config = {
  3. key: 'log', // any String
  4. type: 'trace', // 'trace' | 'error' | 'any String'
  5. cloneProxy: getOrg
  6. }
  7. let Vue = {}
  8. export default function (obj = {}, vue) {
  9. Vue = vue || {}
  10. config = { ...config, ...obj }
  11. if (obj.copy === 'clone') {
  12. config.cloneProxy = clone
  13. }
  14. listenLog(config)
  15. }
  16. // ----------------------------------------
  17. const { groupCollapsed, groupEnd, trace, log } = console
  18. // const type = 'trace' | 'error' | ''
  19. function listenLog() {
  20. const isRef = Vue.isRef || (obj => {
  21. return typeof obj === 'object' && !!obj.constructor && obj.constructor.name === 'RefImpl'
  22. })
  23. const unref = Vue.unref || (obj => obj.value)
  24. const { key, type, cloneProxy } = config
  25. if (!key) {
  26. console.error('Missing required parameter: key')
  27. }
  28. listenProxy() // 为 new Proxy 对象添加 `instanceof` 支持
  29. console[key] = function (...arr) {
  30. const newArr = arr.map(i => {
  31. if (isRef(i)) {
  32. return unref(i)
  33. } else if (i instanceof Proxy) {
  34. return cloneProxy(i)
  35. } else {
  36. return i
  37. }
  38. })
  39. groupCollapsed(...newArr)
  40. // 以 trace
  41. if (type === 'trace') {
  42. // trace(...newArr)
  43. console.log('第二行即为调用者所在的文件位置')
  44. trace('The second line is the file location of the caller')
  45. groupEnd()
  46. return
  47. }
  48. let stack = new Error().stack || ''
  49. // stack = stack.replace('Error', 'Log')
  50. if (type === 'error') {
  51. log('%c这不是一个错误,请点击第二行的"at",跳转到对应的文件', 'color: #008000')
  52. log('%cThis is not an error. Please click "at" in the second line to jump to the corresponding file', 'color: #008000')
  53. log(stack)
  54. groupEnd()
  55. return;
  56. }
  57. // 简单输入模式,控制台看起来是简单了,却失去了点击链接直接跳转到对应文件的功能
  58. const stackArr = stack.match(/at.*\s/g) || []
  59. log(stackArr[1])
  60. groupEnd()
  61. }
  62. }

至此已全部结束。

再加上一点ts的解释文件,那这个库就能运行在所有平台了

以上就是打印Proxy对象和ref对象的包实现详解的详细内容,更多关于打印Proxy ref对象包的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号