【underscore.js 源码解读】for ... in 存在的浏览器兼容问题你造吗

Why underscore

近来最先看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 设想中。

浏览一些有名框架类库的源码,就彷佛和一个个巨匠对话,你会学到许多。为何是 underscore?最主要的原因是 underscore 简短精干(约 1.5k 行),封装了 100 多个有效的要领,耦合度低,异常合适逐一要领浏览,合适楼主如许的 JavaScript 初学者。从中,你不仅可以学到用 void 0 替代 undefined 防止 undefined 被重写等一些小技能 ,也可以学到变量范例推断、函数撙节&函数去抖等经常运用的要领,还可以学到许多浏览器兼容的 hack,更可以学到作者的团体设想思绪以及 API 设想的道理(向后兼容)。

以后楼主会写一系列的文章跟人人分享在源码浏览中进修到的学问。

迎接围观~ (假如有兴致,迎接 star & watch~)您的关注是楼主继续写作的动力

for … in

本日要跟人人聊聊 for … in 在浏览器中的兼容问题。

for … in 人人应当都不生疏,轮回只遍历可罗列属性。像 Array 和 Object 运用内置组织函数所建立的对象都邑继续自 Object.prototype 和 String.prototype 的不可罗列属性,比方 String 的 indexOf() 要领或许 Object 的 toString 要领。轮回将迭代对象的一切可罗列属性和从它的组织函数的 prototype 继续而来的(包含被掩盖的内建属性)。

我们举个简朴的例子:

var obj = {name: 'hanzichi', age: 30};

for (var k in obj) {
  console.log(k, obj[k]);
}

// 输出
// name hanzichi
// age 30

等等,你跟我说 for … in 这玩意有浏览器兼容性?!从来没注重过啊,彷佛工作中也没碰到过如许的兼容性问题啊!确切云云,for … in 要出问题,得满足两个前提,其一是在 IE < 9 浏览器中(又是万恶的 IE!!),其二是被罗列的对象重写了某些键,比方 toString。

照样举个简朴的例子:

var obj = {toString: 'hanzichi'};

for (var k in obj) {
  alert(k);
}

ok,在 chrome 中我们 alert 出了预期的 “toString”,而在 IE 8 中啥都没有弹出。

我们转头看看 for … in 的作用,轮回遍历 可罗列属性,那末显著 IE 8 将 toString “内定” 成了不可罗列属性(只管已被重写)。那末怎样推断是不是在相似 IE 8 如许的环境中呢?underscore 中有个 hasEnumBug 函数就是用来做这个推断的:

// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
// IE < 9 下 不能用 for key in ... 来罗列对象的某些 key
// 比方重写了对象的 `toString` 要领,这个 key 值就不能在 IE < 9 下用 for in 罗列到
// IE < 9,{toString: null}.propertyIsEnumerable('toString') 返回 false
// IE < 9,重写的 `toString` 属性被以为不可罗列
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');

代码一览无余,用了 propertyIsEnumerable 要领。

那末哪些属性被重写以后不能用 for … in 在 IE < 9 下罗列到呢?有以下这些:

// IE < 9 下不能用 for in 来罗列的 key 值鸠合
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
                    'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

恩,应当还漏了个 constructor。

我们来看看 underscore 是怎样做的。

function collectNonEnumProps(obj, keys) {
  var nonEnumIdx = nonEnumerableProps.length;
  var constructor = obj.constructor;

  // proto 是不是是继续的 prototype
  var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;

  // Constructor is a special case.
  // `constructor` 属性须要特别处置惩罚
  // 假如 obj 有 `constructor` 这个 key
  // 而且该 key 没有在 keys 数组中
  // 存入数组
  var prop = 'constructor';
  if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
  
  // nonEnumerableProps 数组中的 keys
  while (nonEnumIdx--) {
    prop = nonEnumerableProps[nonEnumIdx];
    // prop in obj 应当一定返回 true 吧?是不是不必要?
    // obj[prop] !== proto[prop] 推断该 key 是不是来自于原型链
    if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
      keys.push(prop);
    }
  }
}

proto 变量保留了原型,一个对象的原型可以经由过程 obj.constructor.prototype 猎取,然则假如重写了 constructor 很显著就没法如许猎取了,则用 Object.prototype 替代。如许比方说重写了 toString,我们只须要比较 obj.toString 是不是和 proto.toString 援用雷同即可。个人以为源码中的 prop in obj 推断过剩了,这不一定返回 true 吗?假如有明白毛病,望指出。

而关于重写了 constructor 的状况,underscore 用 hasOwnProperty 举行推断。

关于重写了以上几种属性的状况,underscore 确切可以猎取其在 IE < 9 中的键,然则爱钻牛角尖的楼主也非常不解,constructor 真的有必要和其他属性分开来检测吗?

关于 toString 如许的属性被重写,underscore 的推断异常好,假如没有被重写,那末对象的 toString 要领一定是继续于原型链的,推断对象的 toString 要领是不是和原型链上的一致即可,然则用 hasOwnProperty 能推断吗?楼主以为也是可以的,hasOwnProperty 要领用来推断对象的 key 是不是是自有属性,即是不是来自于原型链,假如被重写了,那末应当会返回 true,不然 false。

而被重写的 constructor 可否用 obj[prop] !== proto[prop] 来推断呢?楼主以为也是可以的,假如没有被重写,那末 obj.constructor === obj.constructor.prototype.constructor 返回 true,假如被重写,obj.constructor === Object.prototype.constructor 返回 false。

关于这点,楼主也是百思不得其解,然则很显著 constructor 属性和其他属性是有显著区分的,从代码明白角度来看,也是 underscore 如许处置惩罚比较容易接受。假如是楼主明白有相差的处所,还望指出!

末了,小结下,关于 for … in 在 IE < 9 下的兼容问题,楼主觉得并没有那末主要,毕竟谁会没事去重写这些属性呢!所以,晓得有这么一回事就可以了。

末了的末了,给出这部份源码位置,有兴致的同砚可以看下 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L904-L946

    原文作者:fish
    原文地址: https://segmentfault.com/a/1190000005177883
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞