如何優雅地鏈式取值
開發中,鏈式取值是非常正常的操作,如:
res.data.goods.list[0].price 複製程式碼
但是對於這種操作報出類似於Uncaught TypeError: Cannot read property 'goods' of undefined
這種錯誤也是再正常不過了,如果說是res資料是自己定義,那麼可控性會大一些,但是如果這些資料來自於不同端(如前後端),那麼這種資料對於我們來說我們都是不可控的,因此為了保證程式能夠正常執行下去,我們需要對此校驗:
if (res.data.goods.list[0] && res.data.goods.list[0].price) { // your code } 複製程式碼
如果再精細一點,對於所有都進行校驗的話,就會像這樣:
if (res && res.data && res.data.goods && res.data.goods.list && res.data.goods.list[0] && res.data.goods.list[0].price){ // your code } 複製程式碼
不敢想象,如果資料的層級再深一點會怎樣,這種實現實在是非常不優雅,那麼如果優雅地來實現鏈式取值呢?
一、optional chaining
這是一個出於stage 2的ecma新語法,目前已經有了babel的外掛ofollow,noindex">babel-plugin-transform-optional-chaining ,這種語法在swift中有,可以看下官方給的例項
a?.b// undefined if `a` is null/undefined, `a.b` otherwise. a == null ? undefined : a.b a?.[x]// undefined if `a` is null/undefined, `a[x]` otherwise. a == null ? undefined : a[x] a?.b()// undefined if `a` is null/undefined a == null ? undefined : a.b() // throws a TypeError if `a.b` is not a function // otherwise, evaluates to `a.b()` a?.()// undefined if `a` is null/undefined a == null ? undefined : a()// throws a TypeError if `a` is neither null/undefined, nor a function // invokes the function `a` otherwise 複製程式碼
二、通過函式解析字串
我們可以通過函式解析字串來解決這個問題,這種實現就是lodash的_.get
方法
var object = { a: [{ b: { c: 3 } }] }; var result = _.get(object, 'a[0].b.c', 1); console.log(result); // output: 3 複製程式碼
實現起來也非常簡單,只是簡單的字串解析而已:
function get (obj, props, def) { if((obj == null) || obj == null || typeof props !== 'string') return def; const temp = props.split('.'); const fieldArr = [].concat(temp); temp.forEach((e, i) => { if(/^(\w+)\[(\w+)\]$/.test(e)) { const matchs = e.match(/^(\w+)\[(\w+)\]$/); const field1 = matchs[1]; const field2 = matchs[2]; const index = fieldArr.indexOf(e); fieldArr.splice(index, 1, field1, field2); } }) return fieldArr.reduce((pre, cur) => { const target = pre[cur] || def; if(target instanceof Array) { return [].concat(target); } if(target instanceof Object) { return Object.assign({}, target) } return target; }, obj) } 複製程式碼
var c = {a: {b : [1,2,3] }} get(c ,'a.b')// [1,2,3] get(c, 'a.b[1]')// 2 get(c, 'a.d', 12)// 12 複製程式碼
三、使用解構賦值
這個思路是來自github上You-Dont-Need-Lodash-Underscore 這個倉庫,看到這個的時候真的佩服
const c = {a:{b: [1,2,3,4]}} const { a: result } = c; // result : {b: [1,2,3,4]} cosnt {a: { c: result = 12 }} = c // result: 12 複製程式碼
當然,這個時候為了保證不報uncaught Typeerror,我們仍然需要定義預設值, 就像這樣,貌似如果不加lint可讀性堪憂
const {a: {c: {d: result2} = {}}} = c 複製程式碼
四、使用Proxy
這個是組內同事提到的,一個簡單實現如下:
function pointer(obj, path = []) { return new Proxy({}, { get (target, property) { return pointer(obj, path.concat(property)) }, apply (target, self, args) { let val = obj; let parent; for(let i = 0; i < path.length; i++) { if(val === null || val === undefined) break; parent = val; val = val[path[i]] } if(val === null || val === undefined) { val = args[0] } return val; } }) } 複製程式碼
我們可以這樣使用:
let c = {a: {b: [1, ,2 ,3]}} pointer(c).a();// {b: [1,2,3]} pointer(c).a.b(); // [1,2,3] pointer(d).a.b.d('default value');// default value 複製程式碼
這差不多就是心中所謂的優雅了。
綜上,在實際工作中,使用方法四會是最優雅,可讀性也非常強,但考慮到瀏覽器的話,可能方法二會更加常用,當然,如果你所要取的值層級不是太深,你組內的同事要嚴格的lint,方法三也不失為一種好的選擇。