深層屬性,輕鬆提取

深層屬性,輕鬆提取

本文所介紹的工具是JavaScript 中獲取深層嵌套對象的一種解決方案,同時也是ES6 Proxy 與TypeScript 結合的落地應用。

面臨的問題

假設有這樣一個對象,表示的是用戶是否啟用了回复通知的設置

constsettings={notification:{reply:{active:true}// ...其他设置项
}// ...其他设置项
}

當開發者想要提取active的值,最直接的方法是這麼做

constisNotificationReplyActive=settings.notification.reply.active

但這是不安全的,因為JavaScript 中通常使用`null` 或`undefined` 分別表示未定義或未聲明的值

typeofsomeVar==='undefined'// 未声明
letsomeVar=null// 已声明,未定义

實際開發過程中可能因為比如節省資源的考慮,當用戶未進行過設置時,它的notification 或者更深的某一級的值是`null` 或`undefined`,而非對象。

// 比如当未设置回复通知时,它是这样的const settings = { notification : { // 没有reply } } // 这种情况下, settings.notification.reply 的值是undefined // JS 中试图获取undefined 上的key 时会触发TypeError const isNotificationReplyActive = settings . notification . reply . active // TypeError!

於是有的開發者採取了這樣的措施

constisNotificationReplyActive=settings&&settings.notification&&settings.notification.reply&&settings.notification.reply.active// 或者
try{constisNotificationReplyActive=settings.notification.reply.active}catch(err){// 错误处理
}

經驗豐富的開發者都知道,這樣做的缺點很多,在此就不展開了。

於是一些工具函數誕生了,比如lodash 的`_.get`

import_from'lodash'constisNotificationReplyActive=_.get(settings,'notification.reply.active')

雖然它保證了開發者在提取屬性的過程中不會因為遇到`undefined` 或`null` 之類的值而拋出TypeError ,但缺點也很明顯——

  1. 屬性的路徑被寫成了字符串,開發者無法獲得IDE/編輯器的自動補全與智能糾錯。
  2. 不能使用便捷的解構語法——
const{notification:{reply:{active}}}=settings

簡直是一夜回到解放前。

解決方法—— safe-touch

現在讓我們來回顧一下本文開頭的那張圖,它即是本文的主角、上述所有問題的解決方案——safe-touch。

來看一下如何使用它:

//引入importsafeTouchfrom'safe-touch'constsettings={/* ... */}//包裹要提取的對象consttouched=safeTouch(settings)//把它當作函數調用,可以獲得原始值touched()===settings// true//亦可以直接獲取settings上存在的屬性,同樣通過調用取得屬性值//在現代化的IDE/編輯器中,這一過程可以給出智能提示與自動補全touched.notification.reply.active()//若依本文開頭給出的例子,值為true//可以安全地獲取並不存在的屬性,返回undefined ,不會拋出TypeErrortouched.something.does.not.exist[Math.random()]()// undefined//支持解構const{notification:{reply:{active,notExistingKey}}}=touchedactive()// truenotExistingKey()// undefined

在VS Code 等現代化工具中可以獲得智能提示:

支持自動補全
支持解構

性能

我做了一套簡單的性能測試,分別在NodeJS v8.9.1和v10.7.0進行了測試,結果在這個gist

測試用的代碼在這裡

我簡單總結一下測試結果——

  1. 幾乎僅在取的屬性的嵌套層級多於五層且所有層級的屬性都存在的情況下,即能成功取到深層屬性的情況下,safe-touch 的性能是低於try...catch 的。說明生成很多Proxy 實例的開銷大於創建一個try...catch 塊但未觸發錯誤的開銷。
  2. 如果提取屬性的過程中遇到錯誤,即取不存在的屬性下的屬性, 則形勢大反,safe-touch 的性能基本是try...catch 的幾十倍。
  3. 社區中(評論裡提到的)一些其他類似的工具,不乏通過try...catch 實現的,因此綜合開發體驗來說, safe-touch 可能是更好的選擇。

通過測試也發現在NodeJS v10 下用Proxy 的性能遠好於v8 下的,相比大約是5 倍。

我的測試腳本是運行十萬次的耗時統計,在實際開發的應用中採取上述任何一種方式可能並不會有用戶體驗上的差別。但不管哪種方式,都比不上原生的chain方式,因此我很期待?.操作符的正式問世,即使它的作用只是轉化成等價的chain形式的表達式,也會是非常棒的。

babel已經提供了?.操作符@babel/plugin-proposal-optional-chaining ,感興趣的可以玩一下。

另外還有一個反直覺的現象,在測試結果的最後部分,deepChainedRetrieve比deepRetrieve快,即a && ab && ... && abcdefghi的方式要比直接abcdefghi更快(翻倍)!這太神奇,也許是我的測試腳本存在bug,或者引擎有黑科技,改天重新研究下,大概會是我下一篇的文章主題。

源代碼在這裡,歡迎Star/issue:

What do you think?

Written by marketer

2018 Google 移動技術最新進展速覽

Webpack 中的sideEffects 到底該怎麼用?