Array, Array Constructor, for in loop, typeof, instanceOf


Posted in Javascript onSeptember 13, 2011

注意: JavaScript 中数组不是 关联数组。 JavaScript 中只有对象 来管理键值的对应关系。但是关联数组是保持顺序的,而对象不是。

由于 for in 循环会枚举原型链上的所有属性,唯一过滤这些属性的方式是使用 `hasOwnProperty` 函数,因此会比普通的 for 循环慢上好多倍。

遍历(Iteration)
为了达到遍历数组的最佳性能,推荐使用经典的 for 循环。

var list = [1, 2, 3, 4, 5, ...... 100000000]; 
for(var i = 0, l = list.length; i < l; i++) { 
console.log(list[i]); 
}

上面代码有一个处理,就是通过 l = list.length 来缓存数组的长度。

虽然 length 是数组的一个属性,但是在每次循环中访问它还是有性能开销。 可能最新的 JavaScript 引擎在这点上做了优化,但是我们没法保证自己的代码是否运行在这些最近的引擎之上。

实际上,不使用缓存数组长度的方式比缓存版本要慢很多。

`length` 属性(The `length` property)
length 属性的 getter 方式会简单的返回数组的长度,而 setter 方式会截断数组。

var foo = [1, 2, 3, 4, 5, 6]; 
foo.length = 3; 
foo; // [1, 2, 3] foo.length = 6; 
foo; // [1, 2, 3]

译者注:在 Firebug 中查看此时 foo 的值是: [1, 2, 3, undefined, undefined, undefined] 但是这个结果并不准确,如果你在 Chrome 的控制台查看 foo 的结果,你会发现是这样的: [1, 2, 3] 因为在 JavaScript 中 undefined 是一个变量,注意是变量不是关键字,因此上面两个结果的意义是完全不相同的。

// 译者注:为了验证,我们来执行下面代码,看序号 5 是否存在于 foo 中。
5 in foo; // 不管在 Firebug 或者 Chrome 都返回 false
foo[5] = undefined;
5 in foo; // 不管在 Firebug 或者 Chrome 都返回 true
为 length 设置一个更小的值会截断数组,但是增大 length 属性值不会对数组产生影响。

结论(In conclusion)
为了更好的性能,推荐使用普通的 for 循环并缓存数组的 length 属性。使用 for in 遍历数组被认为是不好的代码习惯并倾向于产生错误和导致性能问题。

Array 构造函数
由于 Array 的构造函数在如何处理参数时有点模棱两可,因此总是推荐使用数组的字面语法 - [] - 来创建数组。

[1, 2, 3]; // 结果: [1, 2, 3]
new Array(1, 2, 3); // 结果: [1, 2, 3]

[3]; // 结果: [3]
new Array(3); // 结果: []
new Array('3') // 结果: ['3']
译者注:这里的模棱两可指的是数组的两种构造函数语法 var arr1 = new Array(arrayLength); var arr2 = new Array(element0, element1, ..., elementN);

// 译者注:因此下面的代码将会使人很迷惑
new Array(3, 4, 5); // 结果: [3, 4, 5]
new Array(3) // 结果: [],此数组长度为 3
由于只有一个参数传递到构造函数中(译者注:指的是 new Array(3); 这种调用方式),并且这个参数是数字,构造函数会返回一个 length 属性被设置为此参数的空数组。需要特别注意的是,此时只有 length 属性被设置,真正的数组并没有生成。译者注:在 Firebug 中,你会看到 [undefined, undefined, undefined],这其实是不对的。在上一节有详细的分析。

var arr = new Array(3);
arr[1]; // undefined
1 in arr; // false, 数组还没有生成
这种优先于设置数组长度属性的做法只在少数几种情况下有用,比如需要循环字符串,可以避免 for 循环的麻烦。

new Array(count + 1).join(stringToRepeat);
// 译者注:new Array(3).join('#') 将会返回 "##"
结论(In conclusion)
应该尽量避免使用数组构造函数创建新数组。推荐使用数组的字面语法。它们更加短小和简洁,因此增加了代码的可读性。

for in 循环
和 in 操作符一样,for in 循环同样在查找对象属性时遍历原型链上的所有属性。

注意: for in 循环不会遍历那些 enumerable 设置为 false 的属性;比如数组的 length 属性。

// 修改 Object.prototype 
Object.prototype.bar = 1; var foo = {moo: 2}; 
for(var i in foo) { 
console.log(i); // 输出两个属性:bar 和 moo 
}

由于不可能改变 for in 自身的行为,因此有必要过滤出那些不希望出现在循环体中的属性,这可以通过 Object.prototype 原型上的 `hasOwnProperty` 函数来完成。

注意: 由于 for in 总是要遍历整个原型链,因此如果一个对象的继承层次太深的话会影响性能。

使用 `hasOwnProperty` 过滤(Using `hasOwnProperty` for filtering)

// foo 变量是上例中的 
for(var i in foo) { 
if (foo.hasOwnProperty(i)) { 
console.log(i); 
} 
}

这个版本的代码是唯一正确的写法。由于我们使用了 hasOwnProperty,所以这次只输出 moo。如果不使用 hasOwnProperty,则这段代码在原生对象原型(比如 Object.prototype)被扩展时可能会出错。

一个广泛使用的类库 Prototype 就扩展了原生的 JavaScript 对象。因此,但这个类库被包含在页面中时,不使用 hasOwnProperty 过滤的 for in 循环难免会出问题。

最佳实践(Best practices)
推荐总是使用 hasOwnProperty。不要对代码运行的环境做任何假设,不要假设原生对象是否已经被扩展了。

typeof 操作符
typeof 操作符(和 `instanceof` 一起)或许是 JavaScript 中最大的设计缺陷,因为几乎不可能从它们那里得到想要的结果。

尽管 instanceof 还有一些极少数的应用场景,typeof 只有一个实际的应用(译者注:这个实际应用是用来检测一个对象是否已经定义或者是否已经赋值),而这个应用却不是用来检查对象的类型。

注意: 由于 typeof 也可以像函数的语法被调用,比如 typeof(obj),但这并是一个函数调用。那两个小括号只是用来计算一个表达式的值,这个返回值会作为 typeof 操作符的一个操作数。实际上不存在名为 typeof 的函数。

JavaScript 类型表格(The JavaScript type table)
Value Class Type
-------------------------------------
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function function
/abc/g RegExp object (function in Nitro/V8)
new RegExp("meow") RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object
上面表格中,Type 一列表示 typeof 操作符的运算结果。可以看到,这个值在大多数情况下都返回 "object"。

Class 一列表示对象的内部属性 [[Class]] 的值。

JavaScript 标准文档中定义: [[Class]] 的值只可能是下面字符串中的一个: Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String.

为了获取对象的 [[Class]],我们需要使用定义在 Object.prototype 上的方法 toString。

对象的类定义(The Class of an object)
JavaScript 标准文档只给出了一种获取 [[Class]] 值的方法,那就是使用 Object.prototype.toString。

function is(type, obj) { 
var clas = Object.prototype.toString.call(obj).slice(8, -1); 
return obj !== undefined && obj !== null && clas === type; 
} is('String', 'test'); // true 
is('String', new String('test')); // true

上面例子中,Object.prototype.toString 方法被调用,this 被设置为了需要获取 [[Class]] 值的对象。

译者注:Object.prototype.toString 返回一种标准格式字符串,所以上例可以通过 slice 截取指定位置的字符串,如下所示:

Object.prototype.toString.call([]) // "[object Array]" 
Object.prototype.toString.call({}) // "[object Object]" 
Object.prototype.toString.call(2) // "[object Number]"

ES5 提示: 在 ECMAScript 5 中,为了方便,对 null 和 undefined 调用 Object.prototype.toString 方法,其返回值由 Object 变成了 Null 和 Undefined。

译者注:这种变化可以从 IE8 和 Firefox 4 中看出区别,如下所示:

// IE8 
Object.prototype.toString.call(null) // "[object Object]" 
Object.prototype.toString.call(undefined) // "[object Object]" // Firefox 4 
Object.prototype.toString.call(null) // "[object Null]" 
Object.prototype.toString.call(undefined) // "[object Undefined]" 
测试为定义变量(Testing for undefined variables) 
typeof foo !== 'undefined'

上面代码会检测 foo 是否已经定义;如果没有定义而直接使用会导致 ReferenceError 的异常。这是 typeof 唯一有用的地方。

结论(In conclusion)
为了检测一个对象的类型,强烈推荐使用 Object.prototype.toString 方法;因为这是唯一一个可依赖的方式。正如上面表格所示,typeof 的一些返回值在标准文档中并未定义,因此不同的引擎实现可能不同。

除非为了检测一个变量是否已经定义,我们应尽量避免使用 typeof 操作符。

instanceof 操作符
instanceof 操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义。如果用来比较内置类型,将会和 typeof 操作符 一样用处不大。

比较自定义对象(Comparing custom objects)

function Foo() {} 
function Bar() {} 
Bar.prototype = new Foo(); new Bar() instanceof Bar; // true 
new Bar() instanceof Foo; // true 
// 如果仅仅设置 Bar.prototype 为函数 Foo 本省,而不是 Foo 构造函数的一个实例 
Bar.prototype = Foo; 
new Bar() instanceof Foo; // false 
`instanceof` 比较内置类型(Using `instanceof` with native types) 
new String('foo') instanceof String; // true 
new String('foo') instanceof Object; // true 
'foo' instanceof String; // false 
'foo' instanceof Object; // false

有一点需要注意,instanceof 用来比较属于不同 JavaScript 上下文的对象(比如,浏览器中不同的文档结构)时将会出错,因为它们的构造函数不会是同一个对象。

结论(In conclusion)
instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。正如 `typeof` 操作符一样,任何其它的用法都应该是避免的。

Javascript 相关文章推荐
Javascript操纵Cookie实现购物车程序
Nov 23 Javascript
初探jquery——表单应用范例
Feb 20 Javascript
js 遍历json返回的map内容示例代码
Oct 29 Javascript
在Firefox下js select标签点击无法弹出
Mar 06 Javascript
JavaScript中函数(Function)的apply与call理解
Jul 08 Javascript
jQuery移动端日期(datedropper)和时间(timedropper)选择器附源码下载
Apr 19 Javascript
hovertree插件实现二级树形菜单(简单实用)
Dec 28 Javascript
BootStrap Fileinput的使用教程
Dec 30 Javascript
微信小程序 解决swiper不显示图片的方法
Jan 04 Javascript
jquery操作checkbox的常用方法总结【附测试源码下载】
Jun 10 jQuery
Vue使用NProgress进度条的方法
Sep 21 Javascript
js实现简单的无缝轮播效果
Sep 05 Javascript
容易被忽略的JS脚本特性
Sep 13 #Javascript
Javascript学习笔记-详解in运算符
Sep 13 #Javascript
使用原生javascript创建通用表单验证——更锋利的使用dom对象
Sep 13 #Javascript
ie下动态加态js文件的方法
Sep 13 #Javascript
使用Json比用string返回数据更友好,也更面向对象一些
Sep 13 #Javascript
TextArea不支持maxlength的解决办法(jquery)
Sep 13 #Javascript
JQuery与JSon实现的无刷新分页代码
Sep 13 #Javascript
You might like
如何使用PHP实现javascript的escape和unescape函数
2013/06/29 PHP
php无限级评论嵌套实现代码
2018/04/18 PHP
背景图跟随鼠标移动的Mootools插件实现代码
2011/12/12 Javascript
深入理解javascript中return的作用
2013/12/30 Javascript
ExtJS4如何给同一个formpanel不同的url
2014/05/02 Javascript
javascript实现的简单计时器
2015/07/19 Javascript
jQuery取得iframe中元素的常用方法详解
2016/01/14 Javascript
浅析BootStrap栅格系统
2016/06/07 Javascript
用AngularJS的指令实现tabs切换效果
2016/08/31 Javascript
jQuery实现多张图片上传预览(不经过后端处理)
2017/04/29 jQuery
MUI顶部选项卡的用法(tab-top-webview-main)详解
2017/10/08 Javascript
React Native中导航组件react-navigation跨tab路由处理详解
2017/10/31 Javascript
微信小程序签到功能
2018/10/31 Javascript
微信小程序wx.request的简单封装
2019/11/13 Javascript
js中apply和call的理解与使用方法
2019/11/27 Javascript
详解JavaScript 异步编程
2020/07/13 Javascript
解决ant design vue中树形控件defaultExpandAll设置无效的问题
2020/10/26 Javascript
[01:10:48]完美世界DOTA2联赛PWL S2 GXR vs PXG 第一场 11.18
2020/11/18 DOTA
[01:52]PWL S2开团时刻第四期——DOTA2成语故事
2020/12/03 DOTA
matplotlib绘图实例演示标记路径
2018/01/23 Python
Python骚操作之动态定义函数
2019/03/26 Python
Django在admin后台集成TinyMCE富文本编辑器的例子
2019/08/09 Python
python实现两个文件夹的同步
2019/08/29 Python
python处理RSTP视频流过程解析
2020/01/11 Python
pandas DataFrame 数据选取,修改,切片的实现
2020/04/24 Python
Python图像阈值化处理及算法比对实例解析
2020/06/19 Python
Python在后台自动解压各种压缩文件的实现方法
2020/11/10 Python
python代码实现图书管理系统
2020/11/30 Python
SEPHORA丝芙兰捷克官网:购买香水、化妆品和护肤品
2018/11/26 全球购物
French Connection官网:女装、男装及家居用品
2019/03/18 全球购物
司机岗位职责
2013/11/15 职场文书
心理健康心得体会
2014/01/02 职场文书
中学生打架检讨书
2014/02/10 职场文书
电气工程自动化求职信
2014/03/14 职场文书
餐饮周年庆活动方案
2014/08/14 职场文书
员工家属慰问信
2015/03/24 职场文书