underscore.js源碼分析第四篇,前三篇地址分別是,若是你對這個系列感興趣,歡迎點擊watch,隨時關注動態。javascript
教你認清這8大殺手鐗java
那些不起眼的小工具?git
(void 0)與undefined之間的小九九github
逗我呢?哥!你要說什麼bug,什麼bug,什麼bug,我最討厭bug。去他妹的bug。瀏覽器
客觀別急,今天真的是要說一個bug,也許你早已知曉,也許你時常躺槍於他手,悄悄地,咱們慢慢開始。函數
for in 遍歷對象屬性時存在bug
工具
for in 遍歷對象屬性時存在bug
源碼分析
for in 遍歷對象屬性時存在bug
this
使用for in
去遍歷一個對象俺們再熟悉不過了,常常幹這種事,那他到底能夠遍歷一個對象哪些類型的屬性呢? 長得帥的
仍是看起來美美的
,瞎說,它可以遍歷的是對象身上那些可枚舉標誌([[Enumerable]])爲true
的屬性。
對於經過直接的賦值和屬性初始化的屬性,該標識值默認爲即爲 true
對於經過 Object.defineProperty 等定義的屬性,該標識值默認爲 false
舉個例子哪些屬性能夠被枚舉
let Person = function (name, sex) { this.name = name this.sex = sex } Person.prototype = { constructor: Person, showName () { console.log(this.name) }, showSex () { console.log(this.sex) } } Person.wrap = { sayHi () { console.log('hi') } } var p1 = new Person('qianlongo', 'sex') p1.sayBye = () => { console.log('bye') } p1.toString = () => { console.log('string') } Object.defineProperty(p1, 'info', { enumerable: false, configurable: false, writable: false, value: 'feDev' });Ï for (var key in p1) { console.log(key) } // name // sex // sayBye // constructor // showName // showSex // toString
能夠看到咱們手動地用defineProperty,給某個對象設置屬性時,enumerable爲false此時該屬性是不可枚舉的
Person繼承自Object構造函數,可是for in
並無枚舉出Object原型上的一些方法
手動地覆蓋對象原型上面的方法toString
也是可枚舉的
方式其實很簡單,使用原生js提供的
Object.propertyIsEnumerable
來判斷
let obj = { name: 'qianlongo' } let obj2 = { name: 'qianlongo2', toString () { return this.name } } obj.propertyIsEnumerable('name') // true obj.propertyIsEnumerable('toString') // false obj2.propertyIsEnumerable('name') // true obj2.propertyIsEnumerable('toString') // true
爲何obj判斷toString爲不可枚舉屬性,而obj2就是可枚舉的了呢?緣由很簡單,obj2將toString
重寫了,而一個對象自身直接賦值的屬性是可被枚舉的
說了這麼多,接下來咱們來看一下下劃線中涉及到遍歷的部分對象方法,come on!!!
判斷對象obejct是否包含key屬性
平時你可能常常這樣去判斷一個對象是否包含某個屬性
if (obj && obj.key) { // xxx }
可是這樣作有缺陷,好比某個屬性其對應的值爲0,null,false,''空字符串呢?這樣明明obj有如下對應的屬性,卻由於屬性值爲假而經過不了驗證
let obj = { name: '', sex: 0, handsomeBoy: false, timer: null }
因此咱們能夠採用下劃線中的這種方式
源碼
var hasOwnProperty = ObjProto.hasOwnProperty; _.has = function(obj, key) { return obj != null && hasOwnProperty.call(obj, key); };
獲取object對象全部的屬性名稱。
使用示例
let obj = { name: 'qianlongo', sex: 'boy' } let keys = _.keys(obj) // ["name", "sex"]
源碼
_.keys = function(obj) { // 若是obj不是object類型直接返回空數組 if (!_.isObject(obj)) return []; // 若是瀏覽器支持原生的keys方法,則使用原生的keys if (nativeKeys) return nativeKeys(obj); var keys = []; // 注意這裏一、for in會遍歷原型上的鍵,因此用_.has來確保讀取的只是對象自己的屬性 for (var key in obj) if (_.has(obj, key)) keys.push(key); // Ahem, IE < 9. // 這裏主要處理ie9如下的瀏覽器的bug,會將對象上一些本該枚舉的屬性認爲不可枚舉,詳細能夠看collectNonEnumProps分析 if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; };
該函數爲下劃線中的內部函數一枚,專門處理ie9如下的枚舉bug問題,
for in
到底有啥bug,終於能夠說出來了。
簡單地說就是若是對象將其原型上的相似toString
的方法覆蓋了的話,那麼咱們認爲toString
就是可枚舉的了,可是在ie9如下的瀏覽器中仍是認爲是不能夠枚舉的,又是萬惡的ie
源碼
// 判斷瀏覽器是否存在枚舉bug,若是有,在取反操做前會返回false var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); // 全部須要處理的可能存在枚舉問題的屬性 var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; // 處理ie9如下的一個枚舉bug function collectNonEnumProps(obj, keys) { var nonEnumIdx = nonEnumerableProps.length; var constructor = obj.constructor; // 讀取obj的原型 var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; // 這裏我有個疑問,對於constructor屬性爲何要單獨處理? // Constructor is a special case. var prop = 'constructor'; if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); while (nonEnumIdx--) { prop = nonEnumerableProps[nonEnumIdx]; // nonEnumerableProps中的屬性出如今obj中,而且和原型中的同名方法不等,再者keys中不存在該屬性,就添加進去 if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { keys.push(prop); } } }
代碼看起來並不複雜,可是有一個小疑問,對於constructor屬性爲何要單獨處理呢?各個看官,若是知曉,請教我啊
獲取object中全部的屬性,包括原型上的。
舉個簡單的例子說明
let Person = function (name, sex) { this.name = name this.sex = sex } Person.prototype = { constructor: Person, showName () { console.log(this.name) } } let p = new Person('qianlongo', 'boy') _.keys(p) // ["name", "sex"] 只包括自身的屬性 _.allKeys(p) // ["name", "sex", "constructor", "showName"] 還包括原型上的屬性
接下來看下源碼是怎麼幹的
源碼
// 獲取對象obj的全部的鍵 // 與keys不一樣,這裏包括繼承來的key // Retrieve all the property names of an object. _.allKeys = function(obj) { if (!_.isObject(obj)) return []; var keys = []; // 直接讀遍歷取到的key,包括原型上的 for (var key in obj) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); // 一樣處理一下有枚舉問題的瀏覽器 return keys; };
能夠看到和_.keys的惟一的不一樣就在於遍歷obj的時候有沒有用hasOwnProperty
去判斷
返回object對象全部的屬性值。
使用案例
let obj = { name: 'qianlongo', sex: 'boy' } _.values(obj) // ["qianlongo", "boy"]
源碼
// Retrieve the values of an object's properties. _.values = function(obj) { // 用到了前面已經寫好的keys函數,因此values認爲獲取的屬性值,不包括原型 var keys = _.keys(obj); var length = keys.length; var values = Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } return values; };
返回一個object副本,使其鍵(keys)和值(values)對換。
使用案例
let obj = { name: 'qianlongo', secName: 'qianlongo', age: 100 } _.invert(obj) // {100: "age", qianlongo: "secName"}
注意喲,若是對象中有些屬性值是相等的,那麼翻轉過來的對象其key取最後一個
源碼
_.invert = function(obj) { var result = {}; // 因此也只是取對象自己的屬性 var keys = _.keys(obj); for (var i = 0, length = keys.length; i < length; i++) { // 值爲key,key爲值,若是有值相等,後面的覆蓋前面的 result[obj[keys[i]]] = keys[i]; } return result; };
返回一個對象裏全部的方法名, 並且是已經排序的(注意這裏包括原型上的屬性)
源碼
_.functions = _.methods = function(obj) { var names = []; for (var key in obj) { // 是函數,就裝載進去 if (_.isFunction(obj[key])) names.push(key); } return names.sort(); // 最後返回通過排序的數組 };
夜深人靜,悄悄地說一個bug這個鬼故事講完了,各位good night。