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 继承而来的(包括被覆盖的内建属性)。

我们举个简单的例子:

  1. var obj = {name: 'hanzichi', age: 30};
  2. for (var k in obj) {
  3. console.log(k, obj[k]);
  4. }
  5. // 输出
  6. // name hanzichi
  7. // age 30

等等,你跟我说 for ... in 这玩意有浏览器兼容性?!从来没注意过啊,好像工作中也没碰到过这样的兼容性问题啊!确实如此,for ... in 要出问题,得满足两个条件,其一是在 IE < 9 浏览器中(又是万恶的 IE!!),其二是被枚举的对象重写了某些键,比如 toString。

还是举个简单的例子:

  1. var obj = {toString: 'hanzichi'};
  2. for (var k in obj) {
  3. alert(k);
  4. }

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

我们回头看看 for ... in 的作用,循环遍历 可枚举属性,那么显然 IE 8 将 toString "内定" 成了不可枚举属性(尽管已经被重写)。那么如何判断是否在类似 IE 8 这样的环境中呢?underscore 中有个 hasEnumBug 函数就是用来做这个判断的:

  1. // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
  2. // IE < 9 下 不能用 for key in ... 来枚举对象的某些 key
  3. // 比如重写了对象的 `toString` 方法,这个 key 值就不能在 IE < 9 下用 for in 枚举到
  4. // IE < 9,{toString: null}.propertyIsEnumerable('toString') 返回 false
  5. // IE < 9,重写的 `toString` 属性被认为不可枚举
  6. var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');

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

那么哪些属性被重写之后不能用 for ... in 在 IE < 9 下枚举到呢?有如下这些:

  1. // IE < 9 下不能用 for in 来枚举的 key 值集合
  2. var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
  3. 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

恩,应该还漏了个 constructor。

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

  1. function collectNonEnumProps(obj, keys) {
  2. var nonEnumIdx = nonEnumerableProps.length;
  3. var constructor = obj.constructor;
  4. // proto 是否是继承的 prototype
  5. var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;
  6. // Constructor is a special case.
  7. // `constructor` 属性需要特殊处理
  8. // 如果 obj 有 `constructor` 这个 key
  9. // 并且该 key 没有在 keys 数组中
  10. // 存入数组
  11. var prop = 'constructor';
  12. if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
  13. // nonEnumerableProps 数组中的 keys
  14. while (nonEnumIdx--) {
  15. prop = nonEnumerableProps[nonEnumIdx];
  16. // prop in obj 应该肯定返回 true 吧?是否不必要?
  17. // obj[prop] !== proto[prop] 判断该 key 是否来自于原型链
  18. if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
  19. keys.push(prop);
  20. }
  21. }
  22. }

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

【跟着子迟品 underscore】for ... in 存在的浏览器兼容问题你造吗的更多相关文章

  1. 【跟着子迟品 underscore】如何优雅地写一个『在数组中寻找指定元素』的方法

    Why underscore (觉得这部分眼熟的可以直接跳到下一段了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...

  2. 【跟着子迟品 underscore】JavaScript 中如何判断两个元素是否 "相同"

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  3. 【跟着子迟品 underscore】Array Functions 相关源码拾遗 & 小结

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  4. 【跟着子迟品 underscore】JavaScript 数组展开以及重要的内部方法 flatten

    Why underscore (觉得这一段眼熟的童鞋可以直接跳到正文了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...

  5. 【跟着子迟品 underscore】Object Functions 相关源码拾遗 & 小结

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  6. 【跟着子迟品 underscore】常用类型判断以及一些有用的工具方法

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  7. 【跟着子迟品underscore】从用 `void 0` 代替 `undefined` 说起

    Why underscore 最近开始看 underscore源码,并将 underscore源码解读 放在了我的 2016计划 中. 阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多 ...

  8. Vue中子组件数据跟着父组件改变和父组件数据跟着子组件改变的方法

    一,子组件数据跟着父组件改变 父组件的代码 <template> <div class="home"> <img alt="Vue logo ...

  9. Underscore _.template 方法使用详解

    为什么用「void 0」代替「undefined」 undefined 并不是保留词(reserved word),它只是全局对象的一个属性,在低版本 IE 中能被重写. 事实上,undefined ...

随机推荐

  1. MySQL外键与外键关系说明(简单易懂)

    MySQL主键和外键使用及说明 一.外键约束 MySQL通过外键约束来保证表与表之间的数据的完整性和准确性. 外键的使用条件:  1.两个表必须是InnoDB表,MyISAM表暂时不支持外键(据说以后 ...

  2. UIPickerView的使用(三)

    前两篇文章 UIPickerView的使用(一) . UIPickerView的使用(二),学习了UIPickerView的单列选择器和双列选择器的使用. 现在我们一起学习相互依赖的多列选择器 1.遵 ...

  3. SharePoint 2013 Excel Services ECMAScript 示例之明日限行

    前言:最近遇到一个“明日限行”的功能,北京的交通啊,这个不在今天讨论范围内,暂不吐槽,想想代码开发,还要写WebPart部署,很麻烦,而且部署服务器,需要领导审批,想绕过这个麻烦事儿,就想到客户端了, ...

  4. 开发培训及技术研讨会PPT下载链接

    8月到9月,我们做了一圈巡回演出,脚步踏遍祖国大江南北,包括沈阳.武汉.深圳.香港.上海和北京等几个城市,给大家带来Autodesk最新的云技术和桌面产品技术动态,得到了大家的热烈响应.感谢各位合作伙 ...

  5. Mou常用快捷键

    title: Mou常用快捷键date: 2015-11-08 17:16:38categories: 编辑工具 tags: mou 小小程序猿我的博客:http://daycoding.com Vi ...

  6. 谈谈AppDelegate

    谈谈AppDelegate 前言 每个iOS程序都会有一个AppDelegate的类,这个类就是一个代理类,我们新建一个Project的时候,里面都会带有这个类.现在就让我们看看这个类. 开始介绍 对 ...

  7. iOS---设置输入框的光标位置

    //这里设置光标位置,让光标位置后移10 textField.leftView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 10, 0)]; tex ...

  8. iOS 9.3真机适配-Could not find Developer Disk Image问题

    Could not find Developer Disk Image 这是由于真机系统过高或者过低,Xcode中没有匹配的配置包文件,我们可以通过这个路径进入配置包的存放目录: /Applicati ...

  9. 适配ios10(iTunes找不到构建版本)

    前两天上架App遇到一个比较神奇的问题,打包好的项目使用Application Loader上传成功,但是在iTunes里面却找不到构建版本,App的活动页面也没有相应的版本. 之前了解IOS10对用 ...

  10. SQL Server 2008 master 数据库损坏解决总结

    SQL Server 2008 master数据库损坏后,SQL SERVER服务启动失败,查看错误日志,你会看到下面错误信息: 2015-10-27 10:15:21.01 spid6s      ...