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 相关文章推荐
URL编码转换,escape() encodeURI() encodeURIComponent()
Dec 27 Javascript
科讯商业版中用到的ajax空间与分页函数
Sep 02 Javascript
对象特征检测法判断浏览器对javascript对象的支持
Jul 25 Javascript
js+数组实现网页上显示时间/星期几的实用方法
Jan 18 Javascript
jQuery+css实现炫目的动态块漂移效果
Jan 28 Javascript
基于Bootstrap的网页设计实例
Mar 01 Javascript
关于页面刷新vuex数据消失问题解决方案
Jul 03 Javascript
bootstrap-table组合表头的实现方法
Sep 07 Javascript
基于vue-upload-component封装一个图片上传组件的示例
Oct 16 Javascript
Vue CLI3中使用compass normalize的方法
May 30 Javascript
jsonp格式前端发送和后台接受写法的代码详解
Nov 07 Javascript
ES6使用新特性Proxy实现的数据绑定功能实例
May 11 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 升级到 5.3+ 后出现的一些错误,如 ereg(); ereg_replace(); 函数报错
2015/12/07 PHP
php字符串截取函数mb_substr用法实例分析
2019/06/25 PHP
PHP的new static和new self的区别与使用
2019/11/27 PHP
extjs 为某个事件设置拦截器
2010/01/15 Javascript
JS学习之一个简易的日历控件
2010/03/24 Javascript
js操作textarea 常用方法总结
2012/12/03 Javascript
jQuery 选择器项目实例分析及实现代码
2012/12/28 Javascript
jQuery获得IE版本不准确webbrowser的解决方法
2014/02/23 Javascript
解决自定义$(id)的方法与jquery选择器$冲突的问题
2014/06/14 Javascript
jQuery构造函数init参数分析
2015/05/13 Javascript
极力推荐一款小巧玲珑的可视化编辑器bootstrap-wysiwyg
2016/05/27 Javascript
JS组件Bootstrap Table使用实例分享
2016/05/30 Javascript
jQuery实现手机版页面翻页效果的简单实例
2016/10/05 Javascript
KnockoutJS 3.X API 第四章之数据控制流with绑定
2016/10/10 Javascript
详解ECharts使用心得总结
2016/12/06 Javascript
jQuery动态生成不规则表格(前后端)
2017/02/21 Javascript
谈谈vue中mixin的一点理解
2017/12/12 Javascript
微信小程序实现二维码签到考勤系统
2020/01/16 Javascript
vue watch监控对象的简单方法示例
2021/01/07 Vue.js
Python操作MongoDB详解及实例
2017/05/18 Python
python并发2之使用asyncio处理并发
2017/12/21 Python
Python tkinter三种布局实例详解
2020/01/06 Python
泰国的头号网上婴儿用品店:Motherhood.co.th
2019/04/09 全球购物
有机婴儿毛毯和衣服:Monica + Andy
2020/03/01 全球购物
百度JavaScript笔试题
2015/01/15 面试题
信息管理员岗位职责
2013/12/01 职场文书
青年文明号事迹材料
2014/01/18 职场文书
财产保全担保书范文
2014/04/01 职场文书
安全目标管理责任书
2014/07/25 职场文书
2014年重阳节活动策划方案书
2014/09/16 职场文书
医学专业大学生职业生涯规划书
2014/10/25 职场文书
职工年度考核评语
2014/12/31 职场文书
六年级语文下册教学计划
2015/01/22 职场文书
大学生个人学习总结
2015/02/15 职场文书
小学四年级作文之写景
2019/08/23 职场文书
MyBatis XPathParser解析器使用范例详解
2022/07/15 Java/Android