ES6 找對(duì)象(Vue.js 源碼啟示錄)

重鑄前端霸權(quán),吾輩義不容辭!Hello World 大家好,在下大家的林語(yǔ)冰~
Vue.js 源碼啟示錄
最近在看 Vue.js 源碼的時(shí)候(我也不知道哪來(lái)的勇氣,反正就是硬看),語(yǔ)冰發(fā)現(xiàn)源碼中為判斷對(duì)象特別 DIY 了兩個(gè)一龍一豬的工具人。Why?


有圖有真相,于是乎語(yǔ)冰多想了一層,發(fā)現(xiàn)事情并不簡(jiǎn)單。
對(duì)象的正確打開(kāi)方式
在 ES6 中,我們大約有幾種常用的方法可以直接或間接地找對(duì)象:
Object.prototype.toString()
typeof
instanceof
Object.prototype.isPrototypeOf()
constructor
Object.prototype.getPrototypeOf()/__proto__
But 上面幾種方法可以按需判斷不同范圍的對(duì)象,功能上并不“圖靈等價(jià)”。
“純種對(duì)象”
舉個(gè)粒子,我們只想找 Object 構(gòu)造函數(shù)的“直系純種子代后裔”,即 Object 構(gòu)造函數(shù)的實(shí)例對(duì)象,我們可以使用 Object.prototype.toString() 方法。

isPlainObject()?工具人找到的是“純種對(duì)象”,譬如說(shuō)數(shù)組等派生類(lèi)實(shí)例對(duì)象都會(huì)被無(wú)視,換而言之,找到的對(duì)象必須是“兒子”這一代,不能是“孫子及其后裔”。
BTW,此處的 Reflect.apply()?會(huì)自動(dòng)直接使用 Object.prototype.toSting() 方法,所以 toString()?不是無(wú)中生有,也不需要預(yù)先聲明。實(shí)際開(kāi)發(fā)中的最佳實(shí)踐是在模塊作用域中聲明變量緩存 toString(),減少全局作用域的查找,防止模塊同名變量聲明覆蓋引發(fā)看不見(jiàn)的 BUG。
JSON?兼容對(duì)象
舉個(gè)粒子,我們只想要找 JSON-compliant(JSON 兼容)的對(duì)象,我們可以使用 typeof 操作符。

JSON-compliant?對(duì)象只能依法序列化 JSONObject(JSON 對(duì)象)和 JSONArray(JSON 數(shù)組),函數(shù)會(huì)被“無(wú)視”,Set?等對(duì)象會(huì)被特殊處理。
isJSONObject()?工具人可以拿捏 JSON-compliant 對(duì)象,包括但不限于“純種對(duì)象”。換而言之,isJSONObject() 是 isPlainObject()?的超集,isPlainObject()?是 isJSONObject() 的子集。(TypeScript?給語(yǔ)冰彼芯,然后罵罵咧咧退出了直播間......)
BTW,此處語(yǔ)冰的寫(xiě)法與 Vue.js 源碼近乎一致,但是大同小異。此處語(yǔ)冰使用的是 == 而非 ===,是因?yàn)?==?可以過(guò)濾 nullish,從而在短路的時(shí)候少判斷一次。But Vue.js 考慮到工程化,其編程風(fēng)格是使用 === 確保語(yǔ)義化和 KISS 原則。
對(duì)象全都要
語(yǔ)冰大學(xué)舍友的游戲 ID 類(lèi)似于“九億少女的夢(mèng)”,可見(jiàn)其對(duì)象之多。倘若我們想找到比 JSON 對(duì)象更大范圍的對(duì)象,譬如說(shuō)所有對(duì)象,我們可以怎么做呢?當(dāng)然不是給微信二維碼啦。
魯迅先生曾經(jīng)說(shuō)過(guò),“在我的后園,可以看見(jiàn)墻外有兩株樹(shù),一株是棗樹(shù),還有一株也是棗樹(shù)。”——《秋夜》
推而廣之,世界上只有“10”種數(shù)據(jù)類(lèi)型,要么是原子類(lèi)型,要么不是原子類(lèi)型。
舉個(gè)粒子,有一個(gè)比較直男的黑科技就是“二極管思維”——只要不是原子類(lèi)型,我們就認(rèn)為祂是引用類(lèi)型。所以我們可以 DIY 一個(gè)判斷原始值工具人 isPrimitive(),然后反證法可得,DIY 一個(gè) isRef()?或 isObject()?工具人就可以了。

BTW,此處我們可以簡(jiǎn)化一下。


實(shí)際上 Vue.js 源碼中也有 isPrimitive() 工具函數(shù),不過(guò)是用 TypeScript 寫(xiě)的,而且考慮到具體剛需沒(méi)有覆蓋所有的基本類(lèi)型,不過(guò)源碼整體思路和我們的實(shí)現(xiàn)一毛一樣。綜上所述,我們成功找到所有對(duì)象了,但是代碼量比較大。
BTW,其實(shí) isPrimitive() 和 isObject() 都可以一行代碼優(yōu)雅解決,大家不妨試一下腦筋急轉(zhuǎn)彎。沒(méi)有什么剛需是一行代碼不能稿定的,倘若有,那就再想一想,或者再碼一行。
某前端的無(wú)限腦洞
機(jī)智如你可能已經(jīng)要素察覺(jué),為什么前文中語(yǔ)冰只給兩個(gè)常用的找對(duì)象方法拉勾?因?yàn)樗揭詾榈k們是找對(duì)象的最佳實(shí)踐,而其他方法在實(shí)現(xiàn)過(guò)程中會(huì)有看不見(jiàn)的 BUG 魔鬼在細(xì)節(jié)。此處我們不妨靈魂拷問(wèn)一下自己:
instanceof?等方法為何不完備?什么 edge cases(邊緣用例)會(huì)讓 instanceof 等方式無(wú)能為力?
isJSONObject()?雖然可以判斷 JSON?兼容對(duì)象而不報(bào)錯(cuò),但是會(huì)把 Set?等 JavaScript 對(duì)象“和諧”。倘若只想嚴(yán)格兼容 JSONObject 和 JSONArray 該如何實(shí)現(xiàn)?
如何 DIY structuredClone()(結(jié)構(gòu)化克隆算法)支持的對(duì)象?
完結(jié)撒花
吾乃前端的虔信徒,傳播 BUG 的福音。本期前端圓桌就到這里啦,歡迎大家自由言論。我們一期一會(huì),不散不見(jiàn)~
魔法連接目錄
[Vue.js 源碼](https://github.com/vuejs/core/blob/main/packages/shared/src/index.ts)