[Effective JavaScript 笔记]第58条:区分数组对象和类数组对象
示例
设想有两个不同类的API。
第一个是位向量:有序的位集合
var bits=new BitVector();
bits.enable(4);
bits.enable([1,3,8,17]);
bits.bitAt(4);//1
bits.bitAt(8);//1
bits.bitAt(9);//0
enable方法被重载了,可以传入一个索引或索引的数组。
第二个类的API是字符串集合:无序的字符串集合
var set=new StringSet();
set.add('Hamlet');
set.add(['Rosencrantz','Guildenstern']);
set.add({'Ophelia':1,'Polonius':1,'Horatio':1});
set.contains('Polonius');//true
set.contains('Guildenstern');//true
set.contains('Falstaff');//false
其中add方法被重载了,除了可以接收字符串和字符串数组外,还可以接收一个字典对象。
函数重载
为了实现BitVerctor.prototype.enable方法,可以通过测试其他情况来避免如何判断一个对象是否为数组的问题。
BitVector.prototype.enable=function(x){
if(typeof x === 'number'){
this.enableBit(x);
}else{
for(var i=0,n=x.length;i< n;i++){
this.enableBit(x[i]);
}
}
};
这里只实现了索引及索引数组的判断,很容易实现。那StringSet.prototype.add方法怎么实现呢?这里需要区分数组和对象。在js中数组就是一个对象。这里真正想做的是分离数组对象和非数组对象。
这样的区分和js的灵活的类数组对象的概念是有争执的。任何对象都可被视为数组,只要它遵循正确的接口。而且也没有明确的方法来测试一个对象是否满足一个接口。可以尝试把具有length属性的对象视为数组,但这也会有错误出现,比如碰巧一个字典对象有length属性呢?
dimensions.add({
'length':1,
'height':1,
'width':1
});
使用不精确的启发探索法来确定接口是一个容易被误解和滥用的方法。猜测一个对象是否实现了结构类型有时被称为鸭子测试,这是不好的实践。因为对象没有明确的信息标记来表示它们实现的结构类型,并没有可靠的编程方法来检测信息。
重载两种类型意味着必须有一种方法来区分这两种情况。不可能检测一个值是否实现了一种结构性的接口。
规则-API绝不应该重载与其他类型有重叠的类型
instanceof
对于StringSet,开始就不要使用结构性的类数组接口。相反,我们应当选择一种类型,这种类型具有明确的定义的“标签”,可以表明用户真想将其作为一个数组。一个显而易见但不完美的选择是使用instanceof操作符来测试一个对象是否继承自Array.prototype。
StringSet.prototype.add=function(x){
if(typeof x === 'string'){
this.addString(x);
}else if(x instaceof Array){
x.forEach(function(s){
this.addString(s);
},this);
}
};
任何时候一个Array的实例,它的行为会像一个数组。但有时候,在一些可以允许多个全局对象的环境中,可能会有标准的Array构造函数和原型对象的多份副本。在浏览器中有这种情况,每个frame会有标准库的一份单独副本。当跨frame通信时,一个frame中的数组不会继承自另一个frame的Array.prototype。
Array.isArray
出于上面这种情况,ES5引入了Array.isArray函数,其用于测试一个值是否是数组,而不管原型继承。在ES标准中,该函数测试对象的内部[[Class]]属性值是否是Array。当需要测试一个对象是否是真数组,而不仅仅是类数组对象,Array.isArray方法比instanceof操作符更好。
StringSet.prototype.add=function(x){
if(typeof x === 'undefined'){
this.addString(x);
}else if(Array.isArray(x)){
x.forEach(function(s){
this.addString(s);
},this);
}else{
for(var key in x){
this.addString(key);
}
}
};
Object.prototype.toString
在不支持ES5的环境中,可以使用标准的Object.prototype.toString方法测试一个对象是否为数组。
var toString=Object.prototype.toString;
functin isArray(x){
return toString.call(x) === '[object Array]';
}
Object.prototype.toString函数使用对象内部的[[Class]]属性创建结果字符串,所以测试一个对象是否为数组时,它比instanceof操作符更准确。
注意:该版本的add方法存在不同的影响该API的使用者的行为。重载API的数组版本不接收随意的类数组对象。例如,你不能传入arguments对象并期待它视为数组。
function MyClass(){
this.keys=new StringSet();
//...
}
MyClass.prototype.update=function(){
this.keys.add(arguments);
};
Array.prototype.slice
这里会把arguments作为字典来对待。可以把arguments对象转化为真正的数组。
MyClass.prototype.update=function(){
this.keys.add([].slice.call(arguments));
};
当调用者传入多个参数时,会把它们转化为真正的数组,然后再使用add操作。当调用者想传递一个类数组对象给一个期望接收真正数组的API时,都需要做这种转换。可以在API的文档中标明各API接收哪些类型的参数。如上面的例子中,enable方法接收数字和类数组对象。add方法接收字符串、真数组和非数组对象。
提示
绝不重载与其他类型有重叠的结构类型
当重载一个结构类型与其他类型时,先测试其他类型
当重载其他对象类型时,接收真数组而不是类数组对象
文档标注你的API是否接收真数组或类数组
使用ES5提供的Array.isArray方法测试真数组
[Effective JavaScript 笔记]第58条:区分数组对象和类数组对象的更多相关文章
- [Effective JavaScript 笔记]第55条:接收关键字参数的选项对象
53节建议保持参数顺序的一致约定对于帮助程序员记住每个参数在函数调用中的意义很重要.参数较少这个主意不错,但如果参数过多后,就出现麻烦了,记忆和理解起来都不太容易. 参数蔓延 如下面这些代码: var ...
- [Effective JavaScript 笔记]第40条:避免继承标准类
ECMAScript标准库里配备了许多重要的类,如Array,function,以及Date等.扩展这些类生成子类可以方便完成很多工作,但它们的定义具有很多特殊的行为,所以很难写出行为正确的类. Ar ...
- [Effective JavaScript 笔记]第48条:避免在枚举期间修改对象
注册列表示例 一个社交网络有一组成员,每个成员有一个存储其朋友信息的注册列表. function Member(name){ this.name=name; this.friends=[]; } va ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法
前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数argument ...
- [Effective JavaScript 笔记]第52条:数组字面量优于数组构造函数
js的优雅很大程序要归功于程序中常见的构造块(Object,Function及Array)的简明的字面量语法.字面量是一种表示数组的优雅方法. var a=[1,2,3,5,7,8]; 也可以使用构造 ...
随机推荐
- IT男的”幸福”生活"系列暂停更新通知
首先谢谢博客园,这里给了我很多快乐.更给了大家一个学习的好地方. 在这几天更新过程中,看到了很多哥们的关注,在这里我谢谢你们,是你们给了我动力,是你们又一次给了我不一样的幸福. 在续5中我已回复了,博 ...
- 第十二章:window对象
第十一章介绍了window对象及其客户端javascript所扮演的核心角色:它是客户端javascript程序的全局对象.本章介绍window对象的属性和方法,这些属性定义了不同的API,但是只有一 ...
- addLoadEvent方法解析
onload方法在网页加载完毕时,会自动执行,但是该方法有个缺点就是只能执行一个方法. onload的限制 比如下面的代码: <script type="text/javascript ...
- [转载]NSString中判断中文,英文,数字
曾有需求做个用户名中非法字符的判断,要求是只能输入中英文和数字,其他字符一律非法,故写了下边一个程序mark一下吧~~ NSString *testString = @"春1mianBU觉晓 ...
- All thanks
How to make a salad?——Silun Wang 这是一次成功的团队合作,我为所有组员感到骄傲和自豪!感谢你们! 上个学期期末,和@老钱他们一起去五道口吃Pizza,谈到了大三上学期的 ...
- .net架构设计读书笔记--第三章 第9节 域模型实现(ImplementingDomain Model)
我们长时间争论什么方案是实现域业务领域层架构的最佳方法.最后,我们用一个在线商店案例来说明,其中忽略了许多之前遇到的一些场景.在线商店对很多人来说更容易理解. 一.在线商店项目简介 1. 用例 ...
- Android中用URL模拟一个简单的图片加载器
首先,需要添加权限. <uses-permission android:name="android.permission.INTERNET"/> 整体代码如下: pac ...
- BZOJ1083 繁忙的都市
Description 城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造.城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口 ...
- POJ1067 取石子游戏
Description 有两堆石子,数量任意,可以不同.游戏开始由两个人轮流取石子.游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子:二是可以在两堆中同时取走相同数量的石子.最后 ...
- poj 3070 矩阵快速幂模板
题意:求fibonacci数列第n项 #include "iostream" #include "vector" #include "cstring& ...