沒有哪種數據結構比JavaScript的對象更簡單靈活了。作為一個弱動態類型語言,JavaScript對對象的屬性沒有任何約束, 這帶來的結果就是,在使用的時候很爽,想加啥屬性直接加上去就行了,根本沒有類或模板的限制, 想讀啥屬性直接“點”出來就行了,寫出來那是相當簡潔;然而這樣的代碼在運行的時候呢……
JavaScript這種靈活性最大的一個問題也是沒有約束。比如一個網店系統有兩個部分,一部分產生訂單對象, 另一部分拿到訂單對象來展示。咱們前端程序員自然是干後面展示那部分事兒的,比如要在頁面上展示個訂單裡面商品的價格, 就會掉order.product.price.sum。然而寫產生訂單哪部分代碼的哥們兒不一定靠譜, 訂單裡有個贈品就是沒價格,price字段干脆沒有,前端還是調order.product.price.sum,得,js代碼執行到這就算死了, 後面啥也出不來了。更可怕的是現在很多系統引入了node,把前後端分離界限往後移了一截, 這回在node上調用order.product.price.sum,得,整個頁面都出不來了。
是不是覺得這個例子挺low的?估計你們公司已經規定好了訂單數據裡的產品一定會有price字段,哪怕真沒價格後端大哥也會給你傳個{}。 況且人家後端用的是java,強靜態類型語言,各種屬性早就定義好了,咋能丟呢? 再說上線前要測試那麼長時間,怎麼會在線上出這種問題呢?
現在JSON已經是橫飛於網絡之間的標准數據結構,即便你們系統後端是用java寫的,前端調用這個寫後端接口時得到的還是JSON。 可是這個JSON是從啥轉過來的呢?你指望一定是某個類的對象嗎?夠嗆。後端大哥也許早發現了Java太死板, 為了貼合靈活的JSON,人家早用map了。再說,即便後端老老實實的用class,如果你們的網店系統很大, 有好幾個店不同種類的訂單,這些訂單恐怕不是出自於同一個class吧,字段難免有差別,你能保證你們開發團隊文檔完整到你能掌握所有類的屬性嗎?
反正我們沒有這麼全的文檔,即使有時候有點文檔,後端大哥也會善意的告訴我:以代碼為准!
如果想在上線前把這種缺失字段的bug全都測出來,我覺得太難為QA兄弟了,後端的數據又不是他們說了算, 保不齊上線很長時間後突然真給少點啥。
人總是不靠譜的,還得從代碼上想辦法。
最直接的辦法是小心翼翼步步為營:在每調用一個有風險的屬性的時候都判斷一下其父節點是否存在。 這判斷謹慎到什麼程度就要估計一下數據來源的靠譜程度了。對於前面商品總價的例子,一個訂單裡總不會連商品都沒有吧, 所以代碼可以是這個樣子:
var showPrice
if(order.product.price)
showPrice = order.product.price
實際情況往往比較復雜,比如我現在要取的數據是訂單中一款產品的生產廠家的聯系方式裡面若干電話中的第一個, 而且我不太信任後端接口,連訂單中有沒有產品都不敢保證,那麼……
var temp, temp1, temp2, temp3, phone;
if ((temp = order.product) != null) {
if ((temp1 = temp.seller) != null) {
if ((temp2 = temp1.contact) != null) {
if ((temp3 = temp2.phone) != null) {
phone = temp3[0];
}
}
}
}
如此拙劣!如果JS要這麼寫我寧願回頭做後端去寫Java!
並沒有~~ ヾ(¯∇ ̄๑)
我用coffeescript。其實我們現在所做的項目沒有構建與coffee之上的,但是我用coffee,怎麼用是另一個話題, 反正用了coffee,這段代碼就是這樣:
phone = order.product?.seller?.contact?.phone?[0]
遺憾的是,我是個多少有些強迫症的人,每當看到那麼多問號就會聯想到編譯後的那一堆if,我都替電腦累。 再說這問號很容易遺漏,悄悄地少個問號很難發現。總之我只會在我自認為有可能缺失的屬性前面加個問號。
其實做這些判斷要做的無非就是一旦調用鏈上某個屬性缺失就直接返回undefined。js本身不提供這玩意兒,自己封裝一個。
Object.prototype.getAttr = function(path){
attrLink = path.split('.');
var ref = this[attrLink[0]];
for(var i=1; i < attrLink.length; i++){
if(ref)
ref = ref[attrLink[i]];
else
return undefined;
}
return ref;
}
於是訪問屬性就成了這樣:
order.getAttr('product.seller.contact.phone.0')
這樣就安全了。可是調試的時候我寧願讓解釋器用紅色的異常告訴我到底是誰沒定義或者是空也不想悄咪咪的就得到一個未定義值。 辦法是有的,上面的代碼在ref為空的時候輸出個日志就行了。最後的問題是getAttr方法並非是獲取屬性的強制方法, 它不像Java,只要把屬性私有了就只能用getter。別說保證其它小伙伴一定要用這個方法取屬性,就連我自己可能都寫著寫著就直接點上了。 最終,這個Object.getAttr的辦法我在實際開發中從未用過。
感覺上前面那種在cofee裡面加問號的辦法也就夠了,如果我們自己都覺得這個屬性必須得有而後端接口就是沒傳那也太那個啥了。 但真實情況是一切皆有可能,這也不能怪後端大哥,沒准這訂單數據是從其他系統發送過來的或者是很古老的奇葩數據。
咋鬧?我的原則是盡可能把損失降到最低,也就是把異常影響的代碼范圍縮到最小。這就要靠try..catch。
在哪兒try要看代碼的情況,總不可能逮哪try哪。不過還是有些規律的。
那些不靠譜的數據應該不是我們自己寫的,而是來源於外部接口,對這個接口數據利用的代碼肯定是有一定范圍的, 最起碼的情況是把這一段給try了。這還太粗糙。在看看數據的特征,大量外部數據往往是可迭代的, 比如會顯示一個訂單列表,對這個訂單列表肯定會有個循環,那麼針對循環內部try一下,這樣頂多是一個訂單無法顯示。 還有局部數據有沒有統一處理的地方,比如我做的node項目使用handlebars模板,模板會用到很多自定義的helper, 這些helper大多數是用於把數據處理成顯示的結果,比如格式化價格什麼的。比較巧的是這些helper都有一個統一的代碼入口:
helpers = _.extend(
require('./helper1'),
require('./helper2'),
require('./helper3')
)
// “_”是underscore或者lodash
其實也就是把各個文件中的helpers整合成了一個大對象,然後:
module.exports = _.reduce(helpers, function(memo, f, k){
memo[k] = function(){
try {
return f.apply(this, arguments)
} catch (e) {
console.error('handlebars helper error', e)
return ''
}
}
return memo
}, {})
在export前把所有helper函數都包裹在一個try...catch中。這樣在一個helper中有字段缺失的錯誤也就會引起一小點東西顯示不出來。
其實我把所有的helper集中到一個大對象中其初衷並非是為了這個,而是由於把每一個helper文件都注冊一遍看起來重復太多,覺得不爽。 不是有一個著名的代碼維護原則大概是說“任何重復的代碼都應該避免”嗎?這就看出來好處了。
好像看起來完美了,也不然。後來一哥們兒又寫了個helper,沒放到我的這些helpers裡面,而是在別的文件中單獨注冊去了, 結果奇葩的數據真的出現了,悲劇真的防不勝防地上演了。。。
好吧,說到底想盡各種奇技淫巧也抵不過不按規矩出牌。其實要真的前後端所有同志把數據格式規定好了,並且嚴格執行。
大話設計模式(帶目錄完整版) PDF+源代碼 http://www.linuxidc.com/Linux/2014-08/105152.htm
JavaScript設計模式 中文清晰掃描版PDF http://www.linuxidc.com/Linux/2015-09/122725.htm