【跟着子迟品 underscore】for ... in 存在的浏览器兼容问题你造吗
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
- 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 < 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
【跟着子迟品 underscore】for ... in 存在的浏览器兼容问题你造吗的更多相关文章
- 【跟着子迟品 underscore】如何优雅地写一个『在数组中寻找指定元素』的方法
Why underscore (觉得这部分眼熟的可以直接跳到下一段了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...
- 【跟着子迟品 underscore】JavaScript 中如何判断两个元素是否 "相同"
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】Array Functions 相关源码拾遗 & 小结
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】JavaScript 数组展开以及重要的内部方法 flatten
Why underscore (觉得这一段眼熟的童鞋可以直接跳到正文了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...
- 【跟着子迟品 underscore】Object Functions 相关源码拾遗 & 小结
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】常用类型判断以及一些有用的工具方法
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品underscore】从用 `void 0` 代替 `undefined` 说起
Why underscore 最近开始看 underscore源码,并将 underscore源码解读 放在了我的 2016计划 中. 阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多 ...
- Vue中子组件数据跟着父组件改变和父组件数据跟着子组件改变的方法
一,子组件数据跟着父组件改变 父组件的代码 <template> <div class="home"> <img alt="Vue logo ...
- Underscore _.template 方法使用详解
为什么用「void 0」代替「undefined」 undefined 并不是保留词(reserved word),它只是全局对象的一个属性,在低版本 IE 中能被重写. 事实上,undefined ...
随机推荐
- 操作DOM
操作dom一般是如下4个:更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容:遍历:遍历该DOM节点下的子节点,以便进行进一步操作:添加:在该DOM节点下新增一个子节点,相当于 ...
- golang官网可以打开了 go语言
golang.org之前国内一直打不开,今天看了一下居然可以打开了,除了页面上youtube的视频加载不了.页面自动识别中文. 再也不用为下载go的源码发愁了.http://www.cnblogs.c ...
- js中查找相同的几种函数
function findInArr(arr,num){ for(var i=0;i<arr.length;i++){ if(arr[i]==num){ return true; } } ret ...
- ThingkPHP对数据库进行改操作
public function test_check(){ $Experiment = M("Experiment");//实例化Experiment对象.这个对象是跟数据库的表对 ...
- iOS crash 追终 ,iOS 如何定位crash 位置
https://developer.apple.com/library/ios/technotes/tn2151/_index.html 错误分析是基于设备中的crash log 与 编译文件时生成的 ...
- iOS Xcode, 解决“Could not insert new outlet connection: Could not find any information for the class named”的问题。
在Xcode中,我们可以在StoryBoard编辑界面或者是xib编辑界面中通过“Control键+拖拽“的方式将某个界面元素和对应的代码文件连接起来,在代码文件中创建outlet. 不过,如果你的运 ...
- python之import子目录文件
问题: 在pre_tab.py文件下: print("AA") from test.te import login1 login1() from test.te import ...
- Ubuntu1404安装gogs过程
一.Gogs介绍 Gogs (Go Git Service) 是一款极易搭建的自助 Git 服务. Gogs 的目标是打造一个最简单.最快速和最轻松的方式搭建自助 Git 服务.使用 Go 语言开发使 ...
- C#:委托和自定义事件
1. 委托概述 “委托”相当于C++中的“函数指针”,委托必须与所要“指向”的函数在“参数”和“返回类型”上保持一致; // 定义Person类 public class Person { publi ...
- iOS多线程到底不安全在哪里?
iOS多线程安全的概念在很多地方都会遇到,为什么不安全,不安全又该怎么去定义,其实是个值得深究的话题. 共享状态,多线程共同访问某个对象的property,在iOS编程里是很普遍的使用场景,我们就从P ...