打印Proxy對象和ref對象的包實現詳解
前因
平時工作的時候,我喜歡用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一模一樣,隻是當打印Proxy
和ref
對象時可以直接輸出它的源對象或ref.value。並且,還保留記錄當前文件和行數的功能,可以讓我看到到底是哪個文件哪個步驟執行的打印。
結果
先說結果:
我翻瞭好久的文檔,終究還是不能達到我想要的效果,控制臺右側展示出的打印文檔及行號終究還是不能直接顯示源文件,如果有大神能看到這篇文章的話,希望告訴我怎麼怎麼才能實現這個想法。
但退而求其次,我用console.trace
和Error.stack
兩種方式十分簡陋的完成瞭這個目標。
各位可以去 下載試試,源碼也就不到200行,有興趣的同學可以看看。
實現(直接看源碼的同學可以略過)
判斷一個對象是否是Proxy
這個不好判斷,Vue3添加瞭isProxy 方法,但如果不是Vue環境的話,那這個方法就失效瞭。 而且就這麼一個簡單的小功能,實在沒必要依賴其他的包。 最終是選擇在用戶new Proxy之前,把Proxy對象改造。
// 記錄用戶new Proxy操作的所有對象 // WeakSet,WeakMap,都是弱引用,不幹預其他模塊的垃圾回收機制 export const proxyMap = new WeakMap() let OriginalProxy = null export function listenProxy() { if (OriginalProxy) { // 防止用戶多次調用監聽 return } OriginalProxy = window.Proxy window.Proxy = new Proxy(Proxy, { construct(target, args) { const newProxy = new OriginalProxy(...args) proxyInstances.set(newProxy, target) return newProxy }, get(obj, prop) { // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance if (prop === Symbol.hasInstance) { // 監控 `instanceof` 關鍵字 return instance => proxyMap.has(instance) } // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get return Reflect.get(...arguments) } }) } export function unListenProxy() { window.Proxy = OriginalProxy || window.Proxy }
輸出用戶log的源對象
按說我們上一步已經監控瞭用戶動作,可以獲取源對象,等用戶log的時候,我們直接輸出源對象就可以瞭。但這也有個問題,Proxy畢竟不是普通的對象,通過Proxy獲取的結果,很可能跟源對象沒有一毛錢關系。所以隻能通過深克隆返回源對象的值,但這也有個問題,就是對於某些不能遍歷的對象或屬性,就打印不瞭瞭……
問題貌似鎖死瞭,但,我們實際運用中,隻是為瞭簡簡單單輸出一個不用展開的源對象而已,甚至運用場景都特別單一:Vue3
! 用戶如果覺得打印的不準確,換一個api不完瞭嗎,比如我們監控的是console.log
,那用戶就用console.info
一樣能輸出相同的結果。 把選擇權交給用戶就好瞭。在引用包的時候,再寫多一個配置項,讓用戶自己選平時的使用場景哪個正確結果比較多,就選哪個。想要完全正確,就換一個其他的api。
我簡直是個天才,哈哈哈
export function getOrg(obj) { return proxyMap.get(obj) } // 深克隆 export function clone(obj, _refs = new WeakSet()) { if (obj === null || obj === undefined) return null if (typeof obj !== 'object') return obj if (obj.constructor === Date) return new Date(obj) if (obj.constructor === RegExp) return new RegExp(obj) const newObj = new obj.constructor() //保持繼承的原型 for (const key in obj) { if (obj.hasOwnProperty(key)) { const val = obj[key] if (typeof val === 'object' && !_refs.has(val)) { newObj[key] = clone(val) } else { newObj[key] = val } } } return newObj }
最後暴露出去給用戶調用
import { listenProxy, unListenProxy, clone, getOrg } from "./until"; let config = { key: 'log', // any String type: 'trace', // 'trace' | 'error' | 'any String' cloneProxy: getOrg } let Vue = {} export default function (obj = {}, vue) { Vue = vue || {} config = { ...config, ...obj } if (obj.copy === 'clone') { config.cloneProxy = clone } listenLog(config) } // ---------------------------------------- const { groupCollapsed, groupEnd, trace, log } = console // const type = 'trace' | 'error' | '' function listenLog() { const isRef = Vue.isRef || (obj => { return typeof obj === 'object' && !!obj.constructor && obj.constructor.name === 'RefImpl' }) const unref = Vue.unref || (obj => obj.value) const { key, type, cloneProxy } = config if (!key) { console.error('Missing required parameter: key') } listenProxy() // 為 new Proxy 對象添加 `instanceof` 支持 console[key] = function (...arr) { const newArr = arr.map(i => { if (isRef(i)) { return unref(i) } else if (i instanceof Proxy) { return cloneProxy(i) } else { return i } }) groupCollapsed(...newArr) // 以 trace if (type === 'trace') { // trace(...newArr) console.log('第二行即為調用者所在的文件位置') trace('The second line is the file location of the caller') groupEnd() return } let stack = new Error().stack || '' // stack = stack.replace('Error', 'Log') if (type === 'error') { log('%c這不是一個錯誤,請點擊第二行的"at",跳轉到對應的文件', 'color: #008000') log('%cThis is not an error. Please click "at" in the second line to jump to the corresponding file', 'color: #008000') log(stack) groupEnd() return; } // 簡單輸入模式,控制臺看起來是簡單瞭,卻失去瞭點擊鏈接直接跳轉到對應文件的功能 const stackArr = stack.match(/at.*\s/g) || [] log(stackArr[1]) groupEnd() } }
至此已全部結束。
再加上一點ts的解釋文件,那這個庫就能運行在所有平臺瞭
以上就是打印Proxy對象和ref對象的包實現詳解的詳細內容,更多關於打印Proxy ref對象包的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- JavaScript console對象與控制臺使用示例詳解
- Vue中如何優雅的捕獲 Promise 異常詳解
- 前端Vue中常用rules校驗規則詳解
- web開發js字符串拼接占位符及conlose對象Api詳解
- javascript進階篇深拷貝實現的四種方式