示例

设想有两个不同类的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条:区分数组对象和类数组对象的更多相关文章

  1. [Effective JavaScript 笔记]第55条:接收关键字参数的选项对象

    53节建议保持参数顺序的一致约定对于帮助程序员记住每个参数在函数调用中的意义很重要.参数较少这个主意不错,但如果参数过多后,就出现麻烦了,记忆和理解起来都不太容易. 参数蔓延 如下面这些代码: var ...

  2. [Effective JavaScript 笔记]第40条:避免继承标准类

    ECMAScript标准库里配备了许多重要的类,如Array,function,以及Date等.扩展这些类生成子类可以方便完成很多工作,但它们的定义具有很多特殊的行为,所以很难写出行为正确的类. Ar ...

  3. [Effective JavaScript 笔记]第48条:避免在枚举期间修改对象

    注册列表示例 一个社交网络有一组成员,每个成员有一个存储其朋友信息的注册列表. function Member(name){ this.name=name; this.friends=[]; } va ...

  4. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  5. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  6. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  7. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  8. [Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法

    前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数argument ...

  9. [Effective JavaScript 笔记]第52条:数组字面量优于数组构造函数

    js的优雅很大程序要归功于程序中常见的构造块(Object,Function及Array)的简明的字面量语法.字面量是一种表示数组的优雅方法. var a=[1,2,3,5,7,8]; 也可以使用构造 ...

随机推荐

  1. 一起用HTML5 canvas做一个简单又骚气的粒子引擎

    前言 好吧,说是"粒子引擎"还是大言不惭而标题党了,离真正的粒子引擎还有点远.废话少说,先看demo 本文将教会你做一个简单的canvas粒子制造器(下称引擎). 世界观 这个简单 ...

  2. android之文件权限问题

    当我们在手机上安装一个应用的时候,linux会为每个APP创建一个用户名和用户组 xidian.dy.com.chujia是系统为每个应用创建的一个独立的文件夹,我们可以看到这个文件的所有者为app_ ...

  3. Codeforces Beta Round #6 (Div. 2 Only) D. Lizards and Basements 2 dp

    题目链接: http://codeforces.com/problemset/problem/6/D D. Lizards and Basements 2 time limit per test2 s ...

  4. sphinx在c#.net平台下使用(一)

    Sphinx是由俄罗斯人Andrew Aksyonoff开发的一个可以结合MySQL,PostgreSQL全文检索引擎.意图为其他应用提供高速.低空间占用.高结果 相关度的全文搜索功能.是做站内全文搜 ...

  5. 【CodeForces 557B】Pasha and Tea

    题 题意 总共有 w 克蛋糕,2n 个盘子,第 i 个盘子容量为 ai ,n 个女孩和 n 个男孩,男孩得到的是女孩得到的蛋糕的两倍,求他们得到蛋糕的最大值. 分析 把盘子从小到大排序,然后 女生得到 ...

  6. 【Gym 100712A】Who Is The Winner?

    题 题意 解题数目越多越排前,解题数目相同罚时越少越排前,求排第一的队伍名字. 分析 用结构体排序. 代码 #include<cstdio> #include<algorithm&g ...

  7. RedGate .NET Reflector注册问题(反注册)

    Reflector分为桌面版和VS集成版本,当我们使用注册机注册的时候如果注册了Standvard版本,那么我们的VS就不能集成查看,也不能Debug,那么这 显然不是我们想要的,我们会选择重新注册, ...

  8. POJ2226 Muddy Fields

    Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 10149   Accepted: 3783 Description Rain ...

  9. [IOS Delegate和协议]

    转载请注明出处 http://blog.csdn.net/pony_maggie/article/details/25655443 作者:小马 代理和协议的语法这里不赘述,自己查资料. 这个demo的 ...

  10. Jquery easyui datagrid 删除多行问题

    http://www.cnblogs.com/Dtscal/archive/2012/07/04/2576639.html 最近模仿了刘冬大哥的<开源框架完美组合之Spring.NET + NH ...