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

  • JavaScript Garden - 原文

数组遍历与属性

虽然在 JavaScript 中数组是是对象,但是没有好的理由去使用`for in` 循环遍历数组。 相反,有一些好的理由不去使用for in遍历数组。

注意: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 中,为了方便,对nullundefined调用Object.prototype.toString方法, 其返回值由Object变成了NullUndefined

译者注:这种变化可以从 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`操作符一样,任何其它的用法都应该是避免的。

你可能感兴趣的:(json,javascript)