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的一点点认识总结《javascript高级程序设计》读书笔记
Nov 30 Javascript
浅谈javascript的分号的使用
May 12 Javascript
jquery简单实现带渐显效果的选项卡菜单代码
Sep 01 Javascript
jquery单击事件和双击事件冲突解决方案
Mar 02 Javascript
浅谈$('div a') 与$('div&gt;a')的区别
Jul 18 Javascript
通过示例彻底搞懂js闭包
Aug 10 Javascript
jQuery实现html双向绑定功能示例
Oct 09 jQuery
浅谈Webpack核心模块tapable解析
Sep 11 Javascript
利用百度echarts实现图表功能简单入门示例【附源码下载】
Jun 10 Javascript
javascript 构建模块化开发过程解析
Sep 11 Javascript
Bootstrap实现模态框效果
Sep 30 Javascript
Vue中用JSON实现刷新界面不影响倒计时
Oct 26 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 用数组降低程序的时间复杂度
2009/12/04 PHP
php使用正则表达式获取图片url的方法
2015/01/16 PHP
基于jQuery制作迷你背词汇工具
2010/07/27 Javascript
Easy.Ajax 部分源代码 支持文件上传功能, 兼容所有主流浏览器
2011/02/24 Javascript
40个有创意的jQuery图片和内容滑动及弹出插件收藏集之三
2012/01/03 Javascript
JavaScript中的eval()函数详解
2013/08/22 Javascript
html文件中jquery与velocity变量中的$冲突的解决方法
2013/11/01 Javascript
表单元素与非表单元素刷新区别详细解析
2013/11/06 Javascript
js中的setInterval和setTimeout使用实例
2014/05/09 Javascript
javascript使用call调用微信API
2014/12/15 Javascript
Jquery简单实现GridView行高亮的方法
2015/06/15 Javascript
Bootstrap自定义文件上传下载样式
2016/05/26 Javascript
jQuery基于ID调用指定iframe页面内的方法
2016/07/06 Javascript
深入浅析JavaScript中的scrollTop
2016/07/11 Javascript
jquery计算出left和top,让一个div水平垂直居中的简单实例
2016/07/13 Javascript
vue中将网页打印成pdf实例代码
2017/06/15 Javascript
浅谈Angularjs中不同类型的双向数据绑定
2018/07/16 Javascript
vue - vue.config.js中devServer配置方式
2019/10/30 Javascript
详解Python中heapq模块的用法
2016/06/28 Python
一个简单的python爬虫程序 爬取豆瓣热度Top100以内的电影信息
2018/04/17 Python
Python编程中NotImplementedError的使用方法
2018/04/21 Python
Python实现简单的用户交互方法详解
2018/09/25 Python
python opencv 读取图片 返回图片某像素点的b,g,r值的实现方法
2019/07/03 Python
浅谈Python中range与Numpy中arange的比较
2020/03/11 Python
Django利用elasticsearch(搜索引擎)实现搜索功能
2020/11/26 Python
Juicy Couture Beauty官方网站:香水和化妆品
2019/03/12 全球购物
英国计算机商店:Technextday
2019/12/28 全球购物
新西兰购物网站:TheMarket NZ
2020/09/19 全球购物
自我鉴定书面格式
2014/01/13 职场文书
大学生职业规划范文:象牙塔生活的四年计划
2014/01/14 职场文书
环境工程专业自荐信
2014/03/03 职场文书
毕业寄语大全
2014/04/09 职场文书
学校安全防火方案
2014/06/07 职场文书
工作作风整顿个人剖析材料
2014/10/11 职场文书
分享MySQL常用 内核 Debug 几种常见方法
2022/03/17 MySQL
如何基于python实现单目三维重建详解
2022/06/25 Python