打印Proxy對(duì)象和ref對(duì)象的包實(shí)現(xiàn)詳解
前因
平時(shí)工作的時(shí)候,我喜歡用console.log調(diào)試大法。但Vue3更新后,控制臺(tái)都是打印的Proxy對(duì)象和ref對(duì)象,想看里邊的值,就需要很麻煩的一層一層的展開(kāi)。
為了解決這個(gè)問(wèn)題,我試過(guò)在編輯器中寫(xiě)一個(gè)新的快捷鍵,快速寫(xiě)出console.log(JSON.parse(JSON.stringify()))。
但我用的是webStorm,它自帶的.log快捷鍵太舒服了,比如這樣:abc.log 點(diǎn)擊tab鍵,就自動(dòng)替換為console.log(abc)。
我試了好久,終究還是沒(méi)能拓展類似的代碼。所以才有了重寫(xiě)console.log()的想法。
目標(biāo)
我希望新的console.log可以像現(xiàn)在的console.log一模一樣,只是當(dāng)打印Proxy和ref對(duì)象時(shí)可以直接輸出它的源對(duì)象或ref.value。并且,還保留記錄當(dāng)前文件和行數(shù)的功能,可以讓我看到到底是哪個(gè)文件哪個(gè)步驟執(zhí)行的打印。
結(jié)果
先說(shuō)結(jié)果:
我翻了好久的文檔,終究還是不能達(dá)到我想要的效果,控制臺(tái)右側(cè)展示出的打印文檔及行號(hào)終究還是不能直接顯示源文件,如果有大神能看到這篇文章的話,希望告訴我怎么怎么才能實(shí)現(xiàn)這個(gè)想法。

但退而求其次,我用console.trace 和Error.stack兩種方式十分簡(jiǎn)陋的完成了這個(gè)目標(biāo)。

各位可以去 下載試試,源碼也就不到200行,有興趣的同學(xué)可以看看。
實(shí)現(xiàn)(直接看源碼的同學(xué)可以略過(guò))
判斷一個(gè)對(duì)象是否是Proxy
這個(gè)不好判斷,Vue3添加了isProxy 方法,但如果不是Vue環(huán)境的話,那這個(gè)方法就失效了。 而且就這么一個(gè)簡(jiǎn)單的小功能,實(shí)在沒(méi)必要依賴其他的包。 最終是選擇在用戶new Proxy之前,把Proxy對(duì)象改造。
// 記錄用戶new Proxy操作的所有對(duì)象
// WeakSet,WeakMap,都是弱引用,不干預(yù)其他模塊的垃圾回收機(jī)制
export const proxyMap = new WeakMap()
let OriginalProxy = null
export function listenProxy() {
if (OriginalProxy) { // 防止用戶多次調(diào)用監(jiān)聽(tīng)
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的源對(duì)象
按說(shuō)我們上一步已經(jīng)監(jiān)控了用戶動(dòng)作,可以獲取源對(duì)象,等用戶log的時(shí)候,我們直接輸出源對(duì)象就可以了。但這也有個(gè)問(wèn)題,Proxy畢竟不是普通的對(duì)象,通過(guò)Proxy獲取的結(jié)果,很可能跟源對(duì)象沒(méi)有一毛錢關(guān)系。所以只能通過(guò)深克隆返回源對(duì)象的值,但這也有個(gè)問(wèn)題,就是對(duì)于某些不能遍歷的對(duì)象或?qū)傩?,就打印不了?hellip;…
問(wèn)題貌似鎖死了,但,我們實(shí)際運(yùn)用中,只是為了簡(jiǎn)簡(jiǎn)單單輸出一個(gè)不用展開(kāi)的源對(duì)象而已,甚至運(yùn)用場(chǎng)景都特別單一:Vue3! 用戶如果覺(jué)得打印的不準(zhǔn)確,換一個(gè)api不完了嗎,比如我們監(jiān)控的是console.log,那用戶就用console.info一樣能輸出相同的結(jié)果。 把選擇權(quán)交給用戶就好了。在引用包的時(shí)候,再寫(xiě)多一個(gè)配置項(xiàng),讓用戶自己選平時(shí)的使用場(chǎng)景哪個(gè)正確結(jié)果比較多,就選哪個(gè)。想要完全正確,就換一個(gè)其他的api。
我簡(jiǎn)直是個(gè)天才,哈哈哈
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 對(duì)象添加 `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這不是一個(gè)錯(cuò)誤,請(qǐng)點(diǎn)擊第二行的"at",跳轉(zhuǎn)到對(duì)應(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;
}
// 簡(jiǎn)單輸入模式,控制臺(tái)看起來(lái)是簡(jiǎn)單了,卻失去了點(diǎn)擊鏈接直接跳轉(zhuǎn)到對(duì)應(yīng)文件的功能
const stackArr = stack.match(/at.*\s/g) || []
log(stackArr[1])
groupEnd()
}
}
至此已全部結(jié)束。
再加上一點(diǎn)ts的解釋文件,那這個(gè)庫(kù)就能運(yùn)行在所有平臺(tái)了
以上就是打印Proxy對(duì)象和ref對(duì)象的包實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于打印Proxy ref對(duì)象包的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS實(shí)現(xiàn)移動(dòng)端雙指縮放和旋轉(zhuǎn)方法
這篇文章主要介紹了JS實(shí)現(xiàn)移動(dòng)端雙指縮放和旋轉(zhuǎn)方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
JavaScript實(shí)現(xiàn)數(shù)組去重的14種方法大全
親愛(ài)的小伙伴,對(duì)于數(shù)組javascript中的數(shù)組去重方法你知道多少種呢?學(xué)會(huì)如何對(duì)數(shù)組進(jìn)行去重對(duì)于javascript的學(xué)習(xí)來(lái)說(shuō)也是十分重要的,下邊就讓我來(lái)分享一下我所知道的集中數(shù)組去重的方法吧,感興趣的小伙伴跟著小編一起來(lái)看看吧2025-03-03
JavaScript引擎實(shí)現(xiàn)async/await的方法實(shí)例
大家應(yīng)該都知道隨著Node 7的發(fā)布,越來(lái)越多的人開(kāi)始研究據(jù)說(shuō)是異步編程終級(jí)解決方案的async/await,下面這篇文章主要給大家介紹了關(guān)于JavaScript引擎是如何實(shí)現(xiàn)async/await的相關(guān)資料,需要的朋友可以參考下2022-03-03
Bootstrap學(xué)習(xí)筆記之環(huán)境配置(1)
這篇文章主要為大家詳細(xì)介紹了Bootstrap學(xué)習(xí)筆記之環(huán)境配置的具體操作方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
我要點(diǎn)爆”微信小程序云開(kāi)發(fā)之項(xiàng)目建立與我的頁(yè)面功能實(shí)現(xiàn)
這篇文章主要介紹了我要點(diǎn)爆”微信小程序云開(kāi)發(fā)之項(xiàng)目建立與我的頁(yè)面功能實(shí)現(xiàn),本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-05-05
使用 JavaScript如何獲取當(dāng)月的第一天和最后一天
這篇文章主要介紹了使用 JavaScript如何獲取當(dāng)月的第一天和最后一天,通過(guò)本文學(xué)習(xí)了如何使用 JavaScript 中的Date.getFullYear()和?Date.getMonth()方法獲得某個(gè)特定月份的第一天和最后一天,需要的朋友可以參考下2023-05-05
javascript實(shí)現(xiàn)秒表計(jì)時(shí)器的制作方法
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)秒表計(jì)時(shí)器的制作方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02

