本文主要想说说ECMAScript中的Object相关的东西,主要内容会定位在5和6。
题外话:
其实我自己博客一直维护着一个分类叫《js-object》,里面收录了一些框架关于Object扩展的业务api和ECMAScript已有以及新增相关. 本文中部分内容也来自这个分类,有兴趣的可以看看。
我们先看一段代码:
/* prototype 1.6.1 * 这个版本很简单: * 没有hasOwnProperty的过滤,采用push * 但是prototype的代码命名很正统,语义化意识明显 */ function keys(object){ var results = []; for(var property in object){ results.push(property); } return results; } /* prototype 1.7 * 辅助函数 -- 其实如果自己抽离的话,就不需要这样设计 * 对参数进行了类型验证 * 加入了hasOwnProperty过滤 */ function Type(o){ switch(o){ case null : return NULL_TYPE; case (void 0) : return UNDEFINED_TYPE; } var type = typeof o; switch(type){ case 'boolean' : return BOOLEAN_TYPE; case 'number' : return NUMBER_TYPE; case 'string' : return STRING_TYPE; } return OBJECT_TYPE; } function keys(object){ //加了一层类型判定 if(Type(object) !== OBJECT_TYPE){ throw new TypeError(); } var results = []; for(var property in object){ //改进了hasOwnProperty if(object.hasOwnProperty(property)){ results.push(property); } } return results; } /* underscore 1.5,1 * 加入了ES5的兼容分支,同时也有参数类型验证 * 也辅助函数_.has的过滤 */ _.has = function(obj,key){ Object.prototype.hasOwnProperty.call(obj,key); }; var nativeKeys = Object.keys; _.keys = nativeKeys || function(obj){ if(obj !== Object(obj)){ throw new TypeError('Invalid object'); } var keys = []; for(var key in obj){ if(_.has(obj,key)){ keys.push(key); } } return keys; }; /* kissy 1.3 * 这个和MDN上差不多,加了一层hasEnumBug的判定 */ var hasEnumBug = !({toString:1}.propertyIsEnumerable('toString')), enumProperties = [ "constructor", "hasOwnProperty", "isPrototypeof", "propertyIsEnumerable", "toString", "toLocaleString", "valueOf" ]; keys:function(o){ //一次定义变量 var result = [], p, i; for(p in o){ result.push(p); } if(hasEnumBug){ for(i = enumProperties.length-1;i>=0;i--){ p = enumProperties[i]; if(o.hasOwnProperty(p)){ result.push(p); } } } return result; }; /* tangram 2.0.2.4 * 没有采用push,而是用length++的方式装载 */ baidu.object.keys = function(source){ //参数一次定义 var result = [], resultLen = 0, k; for(k in source){ if(source.hasOwnProperty(k)){ //没有使用push result[resultLen++] = k; } } }; /* qwrap 1.1.5 * 比较标准,过滤了hasOwnProperty */ keys:function(obj){ var ret = []; for(var key in obj){ if(obj.hasOwnProperty(key)){ ret.push(key); } } return ret; };
代码有点多,但是我相信很多同学应该都能从里面看出点东西~
1. 首先解释一下这个方法是干嘛的?
就是从一个Object类型的变量里面取它所有的keys,然后返回一个包含所有keys的数组。
2. 关于各大框架的罗列?
这个是很有心思的,大致是我个人研习的框架的顺序,并不是一般人认为的”远离国产,首选外国货“。
在这里小春真心感谢所有以上源码的作者们~
3. 为什么还标注版本?
我承认我是一个标准的源码版本控,当然这个在我研习各大框架源码实习细节上有很大的帮助,让我看到了作者对一些api的优化过程,从而自我反思一些东西。
这点如果看过我博客的里面介绍《jquery数据存储》的同学,应该有一定的体会。
好吧,入正题:
其实想从现有的一些框架源码设计的细节角度,去看看对ECMAScript Object的支持情况。
每次说到js,都会被问道一个很基本的问题,这个针对低版本浏览器不?
关注兼容性的同学可以猛戳: 这里
我们可以看到,其实大致: IE 9+ FF 4+ Chrome 5+ 是支持的,IE6-8目前是不支持的,但是从上面的源码展示里面我们看到underscore是双分支兼容的。
我个人是非常坚持在类库设计的时候来更新这些原生方法的,原因很简单: 浏览器本身支持的方法“基本上”是最快的,当然以前的数组操作还是有一些差异的,不过高级浏览器也优化了部分。
其他一些细节简单地回顾一下:
如何判定参数是Object类型:
_.isObject = function(obj){ return obj === Object(obj); }; //tangram 2.0.0.0 //与原版本参数命名不一样,个人不推荐unknow baidu.isObject = function(source){ return typeof source === "function" || (typeof source === "object"&& source != null) }; //qwrap 1.1.5 isObject:function(obj){ return obj !== null && typeof obj == 'object' };
很多关注ECMAScript的同学应该和我一样,会有一个问题,既然有keys了,为啥不直接再来一个values呢?
其实它出现过,不是在ES5里面,而是在Ees3.1的一个官方wiki讨论里面
当然也有人在bugzilla讨论Object.keys的时候,给了一个 方案
//有的外国人就是不喜欢{}这东西 Object.values = function (O) Object.keys(O).map(function(k) O[k]);
那我们如何应该是values呢?
/* underscore1.4.1 * https://github.com/jashkenas/underscore/blob/1.4.1/underscore.js * 依赖_.has的过滤,还是push的方式 */ _.values = function(obj){ var values = []; for(var key in obj){ if(_.has(obj,key)){ //只是存的东西变成了obj[key] value.push(obj[key]); } } return values; }; /* underscore 1.5.1第一个版本 * 依赖_.keys,先取出keys,然后按照keys的length进行for-in */ _.values = function(obj){ //一次定义变量 var keys = _.keys(obj), values = [], i = 0, length = keys.length; for(;i < length;i++){ values[i] = obj[key[i]]; } return values; }; //2013-7-24 braddunbar完善了一把 _.values = function(obj){ var keys = _.keys(obj), length = keys.length, //直接定义一个有长度的Array,而不是[] values = new Array(length), i = 0; for(;i < length;i++){ values[i] = obj[keys[i]]; } return values; }; /* tangram 2.0.0.0 * 而是不采用push方式,length++装载值 */ baidu.object.values = function(source){ //一次定义变量 var result = [], resultlen = 0, k; for(k in source){ if(source.hasOwnProperty(k)){ result[resultLen++] = source[k]; } } }; /* qwrap 1.1.5 * 一如既往的朴实风格,和keys基本代码一致,就是装载的东西变了一下下 */ values: function(obj){ var ret = []; for(var key in obj){ if(obj.hasOwnProperty(key)){ //和keys实现基本一样,只是push的对象不一样 ret.push(obj[key]); } } return ret; };
简单提提kissy:
关于kissy,我个人是一个长期源码观察者,除了代码设计层面给我的一些启发外, 还有一些小细节:
比如关于某一个方法在某些操作系统下的浏览器支不支持的注释, 让我比较佩服,
毕竟测试覆盖到如此之细~ 比如:kissy 1.3 的3474行:ie 8.0.7600.16315@win7 json bug!
所以针对一些源码source版本很干净的,注释很少的,我基本会“适度”研习而之丢弃。
其实对于Object,我们以前在频繁操作它的时候有很多痛: 比如:看看它的长度,也得遍历一遍,取出keys,看keys数组的长度来知道这个Object的长度。
下面是underscore 1.5.1的代码:
/* underscore 1.5.1 * return the number of values in the list * 参数过滤,支持Array和Object */ _.size = function(obj){ //参数过滤,如果null或者undefined,返回0 if(obj == null){ return 0; } //支持Array类型,方式还是比较独特的 //如果是Array,直接length //如果是Object,取keys,这个方法上面已经给出 return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; } //示例: _.size({name:"zhangyaochun",job:"fe",for:"w3cplus"}); //3 _.size([1,2,3]); //3
这个方法,返回一个Object或者Array的长度。
再看看ECMAScript6 这次给出新的方法~
Object.is
用来判定两个值是否相等
语法:
var isSame = Object.is(value1,value2);
我们先来看看代码示例:
Object.is("1",1); //false Object.is("name","name"); //true Object.is(0,-0); //false Object.is(NaN,NaN); //true Object.is(NaN,0/0); //true
基本上,功能和===差不多,但是有特例:0 和 -0
用心一点的同学是不是发现:这个方法也可以用来判定是否是NaN嘛
下面是提供的一种兼容方式:
if(!Object.is){ Object.is = function(v1,v2){ if(v1 === 0 && v2 === 0){ return 1 / v1 === 1 / v2; } if(v1 !== v1){ return v2 !== v2; } return v1 === v2; }; }
小知识点:
braddunbar 这个人有人认识不?
他自己的博客描述:
Hey, I'm Brad,a husband,dad,javascripter and enjoyer of peanut butter sandwiches (preferably with honey and raisins).
其实他和undersorce、backbone、jquery、jquery ui都有关系。
在我眼中,他虽然只是一个contributor,但他是一个值得尊敬的人~
扩展阅读:
如需转载,烦请注明出处:http://www.w3cplus.com/js/ecmascript-lesson-2.html