underscore.js 源码解读:for … in 存在的浏览器兼容问题

Posted 前端大全

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了underscore.js 源码解读:for … in 存在的浏览器兼容问题相关的知识,希望对你有一定的参考价值。


链接:http://web.jobbole.com/86205/


Why underscore


最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中。


阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多。为什么是 underscore?最主要的原因是 underscore 简短精悍(约 1.5k 行),封装了 100 多个有用的方法,耦合度低,非常适合逐个方法阅读,适合楼主这样的 javascript 初学者。从中,你不仅可以学到用 void 0 代替 undefined 避免 undefined 被重写等一些小技巧 ,也可以学到变量类型判断、函数节流&函数去抖等常用的方法,还可以学到很多浏览器兼容的 hack,更可以学到作者的整体设计思路以及 API 设计的原理(向后兼容)。


之后楼主会写一系列的文章跟大家分享在源码阅读中学习到的知识。


  • underscore-1.8.3 源码全文注释 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/underscore-1.8.3-analysis.js

  • underscore-1.8.3 源码解读系列文章 https://github.com/hanzichi/underscore-analysis/issues


欢迎围观~ (如果有兴趣,欢迎 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


还是举个简单的例子:


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


代码一目了然,用了 propertyIsEnumerable 方法。


那么哪些属性被重写之后不能用 for … in 在 IE


// IE


恩,应该还漏了个 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


对于 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


最后的最后,给出这部分源码位置,有兴趣的同学可以看下 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L904-L946



专栏作者简介 (  


韩子迟:a JavaScript beginner

打赏支持作者写出更多好文章,谢谢!



【今日微信公号推荐↓】

更多推荐请看



以上是关于underscore.js 源码解读:for … in 存在的浏览器兼容问题的主要内容,如果未能解决你的问题,请参考以下文章

underscore.js源码研究

underscore.js源码解析

underscore.js源码解析

underscore.js源码阅读

underscore.js源码解析函数

underscore.js 源码分析5 基础函数和each函数的使用