《献给你,我深爱的ECMAScript》之Object篇
本文主要想说说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



