打印Proxy對象和ref對象的包實現(xiàn)詳解
前因
平時工作的時候,我喜歡用console.log
調(diào)試大法。但Vue3更新后,控制臺都是打印的Proxy對象和ref對象,想看里邊的值,就需要很麻煩的一層一層的展開。
為了解決這個問題,我試過在編輯器中寫一個新的快捷鍵,快速寫出console.log(JSON.parse(JSON.stringify()))
。
但我用的是webStorm
,它自帶的.log
快捷鍵太舒服了,比如這樣:abc.log
點(diǎn)擊tab鍵,就自動替換為console.log(abc)
。
我試了好久,終究還是沒能拓展類似的代碼。所以才有了重寫console.log()的想法。
目標(biāo)
我希望新的console.log可以像現(xiàn)在的console.log一模一樣,只是當(dāng)打印Proxy
和ref
對象時可以直接輸出它的源對象或ref.value。并且,還保留記錄當(dāng)前文件和行數(shù)的功能,可以讓我看到到底是哪個文件哪個步驟執(zhí)行的打印。
結(jié)果
先說結(jié)果:
我翻了好久的文檔,終究還是不能達(dá)到我想要的效果,控制臺右側(cè)展示出的打印文檔及行號終究還是不能直接顯示源文件,如果有大神能看到這篇文章的話,希望告訴我怎么怎么才能實現(xiàn)這個想法。
但退而求其次,我用console.trace
和Error.stack
兩種方式十分簡陋的完成了這個目標(biāo)。
各位可以去 下載試試,源碼也就不到200行,有興趣的同學(xué)可以看看。
實現(xiàn)(直接看源碼的同學(xué)可以略過)
判斷一個對象是否是Proxy
這個不好判斷,Vue3添加了isProxy 方法,但如果不是Vue環(huán)境的話,那這個方法就失效了。 而且就這么一個簡單的小功能,實在沒必要依賴其他的包。 最終是選擇在用戶new Proxy之前,把Proxy對象改造。
// 記錄用戶new Proxy操作的所有對象 // WeakSet,WeakMap,都是弱引用,不干預(yù)其他模塊的垃圾回收機(jī)制 export const proxyMap = new WeakMap() let OriginalProxy = null export function listenProxy() { if (OriginalProxy) { // 防止用戶多次調(diào)用監(jiān)聽 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) { // 監(jiān)控 `instanceof` 關(guān)鍵字 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的源對象
按說我們上一步已經(jīng)監(jiān)控了用戶動作,可以獲取源對象,等用戶log的時候,我們直接輸出源對象就可以了。但這也有個問題,Proxy畢竟不是普通的對象,通過Proxy獲取的結(jié)果,很可能跟源對象沒有一毛錢關(guān)系。所以只能通過深克隆返回源對象的值,但這也有個問題,就是對于某些不能遍歷的對象或?qū)傩?,就打印不了?hellip;…
問題貌似鎖死了,但,我們實際運(yùn)用中,只是為了簡簡單單輸出一個不用展開的源對象而已,甚至運(yùn)用場景都特別單一:Vue3
! 用戶如果覺得打印的不準(zhǔn)確,換一個api不完了嗎,比如我們監(jiān)控的是console.log
,那用戶就用console.info
一樣能輸出相同的結(jié)果。 把選擇權(quán)交給用戶就好了。在引用包的時候,再寫多一個配置項,讓用戶自己選平時的使用場景哪個正確結(jié)果比較多,就選哪個。想要完全正確,就換一個其他的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 }
最后暴露出去給用戶調(diào)用
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('第二行即為調(diào)用者所在的文件位置') 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這不是一個錯誤,請點(diǎn)擊第二行的"at",跳轉(zhuǎn)到對應(yīng)的文件', '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; } // 簡單輸入模式,控制臺看起來是簡單了,卻失去了點(diǎn)擊鏈接直接跳轉(zhuǎn)到對應(yīng)文件的功能 const stackArr = stack.match(/at.*\s/g) || [] log(stackArr[1]) groupEnd() } }
至此已全部結(jié)束。
再加上一點(diǎn)ts的解釋文件,那這個庫就能運(yùn)行在所有平臺了
以上就是打印Proxy對象和ref對象的包實現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于打印Proxy ref對象包的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Javascript實現(xiàn)文件實時加載進(jìn)度的方法
不知道大家有沒有發(fā)現(xiàn)在現(xiàn)在的移動頁面上,有很多情況需要加載大量的資源。但是移動端的訪問速度和pc還是有很大的差距,有些時候需要一些取巧的方式來提升用戶體驗,而實時顯示加載進(jìn)度就是其中一種。這篇文章就給大家分享了Javascript實現(xiàn)文件實時加載進(jìn)度的方法。2016-10-10BootStrap實現(xiàn)響應(yīng)式布局導(dǎo)航欄折疊隱藏效果(在小屏幕、手機(jī)屏幕瀏覽時自動折疊隱藏)
這篇文章主要介紹了BootStrap實現(xiàn)導(dǎo)航欄的響應(yīng)式布局,當(dāng)在小屏幕、手機(jī)屏幕瀏覽時自動折疊隱藏的效果,非常不錯,具有參考借鑒價值,對bootstrap 響應(yīng)式布局導(dǎo)航欄功能感興趣的朋友一起學(xué)習(xí)吧2016-11-11JavaScript駕馭網(wǎng)頁-獲取網(wǎng)頁元素
這篇文章主要介紹了JavaScript駕馭網(wǎng)頁-獲取網(wǎng)頁元素的相關(guān)資料,需要的朋友可以參考下2016-03-03JavaScript中使用webuploader實現(xiàn)上傳視頻功能(demo)
這篇文章主要介紹了webuploader實現(xiàn)上傳視頻功能,通過本文給大家介紹了上傳視頻和上傳圖片的區(qū)別講解,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-04-04微信小程序云開發(fā)修改云數(shù)據(jù)庫中的數(shù)據(jù)方法
這篇文章主要介紹了微信小程序云開發(fā)修改云數(shù)據(jù)庫中的數(shù)據(jù)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05js下獲得客戶端操作系統(tǒng)的函數(shù)代碼(1:vista,2:windows7,3:2000,4:xp,5:2003,6:20
有時候需要在客戶端獲取操作系統(tǒng)的版本,以便更好的給用戶合適的信息,提高用戶體驗,好不容易從網(wǎng)站發(fā)現(xiàn)了這段代碼,分享給大家。2011-10-10