本系列經過閱讀 underscore 源碼與實戰進而體驗函數式編程的思想, 而非經過冗長的文字教程, 細讀精度
約 1500 行的 underscore 有利於寫出耦合度低, 符合函數式編程思想的代碼, 而且能夠學到 call 與 apply 執行效率的不一樣進而進行代碼性能優化的技巧等.git
歡迎你們 star 或者 watch 本系列, 您的關注是做者的最大動力, 讓咱們一塊兒持續進步.
本系列倉庫: github.com/zhangxiang9…github
咱們在遍歷對象的時候每每會使用 for...in 循環獲得 object 的鍵值對,可是其實 for...in 是有兼容性問題的。
for in 循環是有 bug 的, 不過 bug 的觸發條件是有限制的.
條件有兩個:chrome
被枚舉的對象被重寫了一些不可枚舉屬性.
下面這段代碼是用來兼容 for in 中的 bug 的。在 underscore 裏面不多會使用 for..in 來遍歷對象, 做者經過 .keys 方法加上 for 循環代替了 for...in, 可是仍是沒法避免地在 .keys 中使用了 for...in 循環:編程
if (hasEnumBug) collectNonEnumProps(obj, keys);複製代碼
上面這段代碼就是在 _.keys 函數內部中的 for...in 循環中的一句代碼, 判斷是否有兼容問題, 如有則作兼容處理。
下面是兼容的詳細代碼:數組
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
var collectNonEnumProps = function(obj, keys) {
var nonEnumIdx = nonEnumerableProps.length;
var constructor = obj.constructor;
var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;
// Constructor is a special case.
var prop = 'constructor';
if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
while (nonEnumIdx--) {
prop = nonEnumerableProps[nonEnumIdx];
if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
keys.push(prop);
}
}
};複製代碼
這段代碼就是用來兼容 bug 的. 瀏覽器
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');複製代碼
第一: hasEnumBug 是用來判斷有沒有 for in bug 的, 由於重寫了Object 原型中的 toString 屬性,對於有兼容問題的瀏覽器而言,是打印不出對象中 toString 屬性的值的, 由於 toString 屬性在那些有問題的瀏覽器中屬於不可重寫的不可枚舉屬性,因此根據這個來判斷是否有兼容問題。
而後. 核心在於在 collectNonEnumProps 函數, 第一先肯定哪些屬性名在低版本瀏覽器是不可枚舉的, 分別是 valueOf, isPrototypeOf, toString, propertyIsEnumberable, hasOwnProperty toLocalString 這幾個方法, 固然還有 constructor, 那爲何 constructor 屬性須要獨立抽取出來作特殊判斷呢?性能優化
var constructor = obj.constructor;
var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;
// Constructor is a special case.
var prop = 'constructor';
if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);複製代碼
由於 constructor 屬性有其特殊性,由於對於 toString 那些屬性而言, 判斷是否須要加入到 keys 數組中是根據 obj[prop0] !== proto[prop] 來判斷的, 不同說明重寫了,說明這個屬性是用戶新定義的屬性,須要做爲一個數據屬性名放到 keys 數組中去,可是 constructor 不能作這個判斷,不能單純使用 === 來判斷是否被重寫,由於對象在繼承的時候 constructor 函數一般都不會等於 Object, 因此 constructor 須要被提出來單獨判斷。
在函數最後,獲得不可枚舉的屬性名列表接下來就好辦了, 就是判斷對象裏面的和原型鏈裏面的同屬姓名的值相不相同, 不相同就是重寫了, 將這個屬性名加到 keys 數組裏面, 若是相同就不加. 這就是爲何一進入函數就須要存儲 obj.prototype.
以前有人說說循環裏面 prop in obj 多餘, 這個並非, 能夠試試: 下面是我在 chrome 瀏覽器作的測試代碼bash
var obj = Object.create(null);
'toString' in obj; // false複製代碼
這樣是 false 的, underscore 爲了不這樣的狀況發生, 添加了判斷.
最後說一下感覺, for in 循環到底有沒有必要進行兼容, 不兼容的危害大不大? 我認爲並不大, 可是做爲一個類庫, 代碼的嚴謹性是須要的, 可是咱們在平時使用 for in 循環的時候有沒有必要擔驚受怕呢?
其實沒有必要的, 咱們若是不是特別須要, IE9 如下的兼容狀況會愈來愈少, 並且通常來講咱們也不會在對象裏面去覆蓋像 toString 這樣的原型屬性.app
若是以爲有收穫, 請到 github 給做者一個 star 表示支持吧, 謝謝你們.函數式編程