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 相关文章推荐
animate动画示例(泪奔的小孩)及stop和delay的使用
May 06 Javascript
JavaScript支持的最大递归调用次数分析
Jun 24 Javascript
jQuery简单创建节点的方法
Sep 09 Javascript
Bootstrap标签页(Tab)插件使用方法
Mar 21 Javascript
vue-cli如何引入bootstrap工具的方法
Oct 19 Javascript
vue实现nav导航栏的方法
Dec 13 Javascript
微信公众平台获取access_token的方法步骤
Mar 29 Javascript
vue中如何实现后台管理系统的权限控制的方法步骤
Sep 05 Javascript
用js限制网页只在微信浏览器中打开(或者只能手机端访问)
Dec 24 Javascript
jquery实现有过渡效果的tab切换
Jul 17 jQuery
前端vue如何使用高德地图
Nov 05 Javascript
详解如何解决使用JSON.stringify时遇到的循环引用问题
Mar 23 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与XML联手进行网站编程代码实例
2008/07/10 PHP
PHP MemCached高级缓存配置图文教程
2010/08/05 PHP
php preg_replace替换实例讲解
2013/11/04 PHP
php_imagick实现图片剪切、旋转、锐化、减色或增加特效的方法
2014/12/15 PHP
PHP使Laravel为JSON REST API返回自定义错误的问题
2018/10/16 PHP
jQuery Clone Bug解决代码
2010/12/22 Javascript
我的javascript 函数链之演变
2011/04/07 Javascript
使用js简单实现了tree树菜单
2013/11/20 Javascript
批量修改标签css样式以input标签为例
2014/07/31 Javascript
使用ajax+jqtransform实现动态加载select
2014/12/01 Javascript
Bootstrap开发实战之响应式轮播图
2016/06/02 Javascript
AngularJS基础 ng-class-odd 指令示例
2016/08/01 Javascript
轻松实现js弹框显示选项
2016/09/13 Javascript
axios学习教程全攻略
2017/03/26 Javascript
JavaScript获取tr td 的三种方式全面总结(推荐)
2017/08/15 Javascript
微信小程序progress组件使用详解
2018/01/31 Javascript
从vue源码解析Vue.set()和this.$set()
2018/08/30 Javascript
在vue项目中集成graphql(vue-ApolloClient)
2018/09/08 Javascript
[47:26]完美世界DOTA2联赛 LBZS vs Forest 第二场 11.07
2020/11/09 DOTA
使用Python &amp; Flask 实现RESTful Web API的实例
2017/09/19 Python
python中通过预先编译正则表达式提高效率
2017/09/25 Python
Tensorflow 合并通道及加载子模型的方法
2018/07/26 Python
django的ORM操作 增加和查询
2019/07/26 Python
解决Django Static内容不能加载显示的问题
2019/07/28 Python
Python 操作 ElasticSearch的完整代码
2019/08/04 Python
python3实现微型的web服务器
2019/09/03 Python
Pandas实现dataframe和np.array的相互转换
2019/11/30 Python
马来西亚奢侈品牌购物商城:Valiram 247
2020/09/29 全球购物
写好自荐信的技巧
2013/11/08 职场文书
师德师风演讲稿
2014/05/05 职场文书
企业员工薪酬方案
2014/06/04 职场文书
个人整改措施落实情况汇报
2014/10/29 职场文书
学校教师师德师风承诺书
2015/04/28 职场文书
六年级作文之预言作文
2019/10/25 职场文书
Python制作动态字符画的源码
2021/08/04 Python
MySql中的json_extract函数处理json字段详情
2022/06/05 MySQL