有人告诉我不要for...in
在JavaScript中使用数组。为什么不?
为什么在数组迭代中使用“ for…in”是个坏主意?
由于JavaScript元素被保存为标准对象属性,因此不建议使用for ... in循环遍历JavaScript数组,因为将列出常规元素和所有可枚举的属性。
来自https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Indexed_collections
您应该for(var x in y)
仅在属性列表上使用,而不能在对象上使用(如上所述)。
因为如果不小心,它将遍历原型链中对象所属的属性。
您可以使用for.. in
,只需确保使用hasOwnProperty检查每个属性。
TL&DR:for in
在数组中使用循环并不有害,实际上恰恰相反。
我认为for in
如果正确在数组中使用,循环是JS的瑰宝。您应该完全控制您的软件,并且知道自己在做什么。让我们看看所提到的缺点,并逐一加以证明。
- It loops through inherited properties as well: First of all any extensions to the
Array.prototype
should have been done by usingObject.defineProperty()
and theirenumerable
descriptor should be set tofalse
. Any library not doing so should not be used at all. - Properties those you add to the inheritance chain later get counted: When doing array sub-classing by
Object.setPrototypeOf
or by Classextend
. You should again useObject.defineProperty()
which by default sets thewritable
,enumerable
andconfigurable
property descriptors tofalse
. Lets see an array sub-classing example here...
function Stack(...a){
var stack = new Array(...a);
Object.setPrototypeOf(stack, Stack.prototype);
return stack;
}
Stack.prototype = Object.create(Array.prototype); // now stack has full access to array methods.
Object.defineProperty(Stack.prototype,"constructor",{value:Stack}); // now Stack is a proper constructor
Object.defineProperty(Stack.prototype,"peak",{value: function(){ // add Stack "only" methods to the Stack.prototype.
return this[this.length-1];
}
});
var s = new Stack(1,2,3,4,1);
console.log(s.peak());
s[s.length] = 7;
console.log("length:",s.length);
s.push(42);
console.log(JSON.stringify(s));
console.log("length:",s.length);
for(var i in s) console.log(s[i]);
So you see.. for in
loop is now safe since you cared about your code.
- 该
for in
循环缓慢:该死的。如果要遍历稀疏数组,这是迄今为止最快的迭代方法。这是应该知道的最重要的性能技巧之一。让我们来看一个例子。我们将遍历一个稀疏数组。
var a = [];
a[0] = "zero";
a[10000000] = "ten million";
console.time("for loop on array a:");
for(var i=0; i < a.length; i++) a[i] && console.log(a[i]);
console.timeEnd("for loop on array a:");
console.time("for in loop on array a:");
for(var i in a) a[i] && console.log(a[i]);
console.timeEnd("for in loop on array a:");
问题是for ... in ...
—仅在程序员不真正理解该语言时才成为问题。它并不是真正的bug或其他任何东西,而是它遍历对象的所有成员(嗯,所有可枚举的成员,但这是现在的细节)。当你想迭代只是一个数组中,只保证的方式,让事情语义一致的索引属性是使用整数索引(即for (var i = 0; i < array.length; ++i)
风格的循环)。
任何对象都可以具有与之关联的任意属性。特别是将额外的属性加载到数组实例上并没有什么可怕的。想要看到的代码仅阵列状的索引属性,因此必须坚持一个整数索引。完全知道做什么for ... in
和真正需要查看所有属性的代码,那么那也没关系。
我认为我没有什么要补充的。在某些情况下应避免使用Triptych的答案或CMS的答案for...in
。
但是,我确实想补充一点,在现代浏览器中,有一种替代方法for...in
可以在无法使用的情况下for...in
使用。该替代方法是for...of
:
for (var item of items) {
console.log(item);
}
注意 :
不幸的是,没有任何版本的Internet Explorer支持for...of
(Edge 12+支持),因此您必须等待更长的时间才能在客户端生产代码中使用它。但是,在服务器端JS代码中使用它应该是安全的(如果使用Node.js)。
主要有两个原因:
一
就像其他人说的那样,您可能会得到不在数组中或从原型继承的键。因此,如果说,一个库向Array或Object原型添加了一个属性:
Array.prototype.someProperty = true
您将获得它作为每个数组的一部分:
for(var item in [1,2,3]){
console.log(item) // will log 1,2,3 but also "someProperty"
}
您可以使用hasOwnProperty方法解决此问题:
var ary = [1,2,3];
for(var item in ary){
if(ary.hasOwnProperty(item)){
console.log(item) // will log only 1,2,3
}
}
但这适用于使用for-in循环迭代任何对象。
二
通常,数组中项目的顺序很重要,但是for-in循环不一定会按正确的顺序进行迭代,这是因为它将数组视为对象,这是在JS中实现的方式,而不是作为数组。这似乎是一件小事,但它确实会使应用程序搞砸,并且难以调试。
因为它是通过对象字段而不是索引枚举的。您可以通过索引“ length”获得价值,我怀疑您是否想要这样做。
除了一个事实,即for
... in
遍历所有枚举的属性(这是不一样的“所有数组元素”!),看到http://www.ecma-international.org/publications/files/ECMA-ST/Ecma -262.pdf,第12.6.4节(第5版)或13.7.5.15节(第7版):
未指定枚举属性的机制和顺序 ...
(强调我的。)
这意味着,如果浏览器愿意,可以按插入属性的顺序浏览属性。或按数字顺序。或按词法顺序(“ 30”在“ 4”之前!!请记住,所有对象键-因此,所有数组索引-实际上都是字符串,因此完全有意义)。如果将对象实现为哈希表,则可以按桶进行遍历。或采取任何一种并添加“向后”。只要浏览器恰好访问每个属性一次,它甚至可以随机迭代并符合ECMA-262。
实际上,当前大多数浏览器都喜欢以大致相同的顺序进行迭代。但是没有什么可说的。这是特定于实现的,如果发现另一种方法更加有效,则可以随时更改。
无论哪种方式,for
... in
都没有顺序的含义。如果您关心顺序,请明确说明顺序,并使用for
带有索引的常规循环。
简短的回答:这是不值得的。
更长的答案:即使不需要顺序的元素顺序和最佳性能,这也不值得。
长答案:这不值得...
- 使用
for (var property in array)
将导致array
被迭代为一个对象,遍历对象原型链并最终比基于索引的for
循环执行得慢。 for (... in ...)
不能保证可以按预期顺序返回对象属性。- 使用
hasOwnProperty()
和!isNaN()
检查来过滤对象属性是一个额外的开销,这导致它执行起来甚至更慢,并且抵消了最初使用它的关键原因,即由于格式更加简洁。
由于这些原因,甚至不存在性能和便利性之间可接受的折衷。除非意图是将数组作为对象处理并对数组的对象属性执行操作,否则实际上没有任何好处。
正如for…of
John Slegers已经注意到的那样,自2016年(ES6)起,我们可以将其用于数组迭代。
我只想添加以下简单的演示代码,以使事情更清楚:
Array.prototype.foo = 1;
var arr = [];
arr[5] = "xyz";
console.log("for...of:");
var count = 0;
for (var item of arr) {
console.log(count + ":", item);
count++;
}
console.log("for...in:");
count = 0;
for (var item in arr) {
console.log(count + ":", item);
count++;
}
控制台显示:
for...of:
0: undefined
1: undefined
2: undefined
3: undefined
4: undefined
5: xyz
for...in:
0: 5
1: foo
换一种说法:
for...of
从0到5计数,也忽略Array.prototype.foo
。它显示数组值。for...in
仅列出5
,忽略未定义的数组索引,但添加foo
。它显示了数组属性名称。
孤立地,在数组上使用for-in并没有错。for-in遍历对象的属性名称,对于“开箱即用”的数组,属性对应于数组索引。(内置样propertes length
,toString
等不包括在迭代)。
但是,如果您的代码(或所使用的框架)将自定义属性添加到数组或数组原型,则这些属性将包含在迭代中,这可能不是您想要的。
一些JS框架(例如Prototype)修改了Array原型。其他框架(如JQuery)则没有,因此使用JQuery可以安全地使用for-in。
如果您有疑问,则可能不应该使用for-in。
遍历数组的另一种方法是使用for循环:
for (var ix=0;ix<arr.length;ix++) alert(ix);
但是,这有一个不同的问题。问题是JavaScript数组可能有“漏洞”。如果定义arr
为:
var arr = ["hello"];
arr[100] = "goodbye";
那么该数组有两个项目,但是长度为101。使用for-in将产生两个索引,而for-loop将产生101个索引,其中99的值为undefined
。
为什么不应该使用for..in
遍历数组元素的三个原因:
for..in
将遍历所有不是数组对象的自身和继承的属性DontEnum
;这意味着,如果有人将属性添加到特定的数组对象(有正当的理由-我自己这样做)或已更改Array.prototype
(在代码中被认为是不当做法,应该与其他脚本一起使用),则这些属性将也要遍历;可以通过检查排除继承的属性hasOwnProperty()
,但这对您在数组对象本身中设置的属性没有帮助for..in
不保证保留元素顺序这很慢,因为您必须遍历数组对象及其整个原型链的所有属性,并且仍将仅获取该属性的名称,即要获取该值,将需要进行额外的查找
尽管此问题未专门解决,但我想补充一下,有一个很好的理由,不要在...中使用...
NodeList
(因为可以从querySelectorAll
调用中获得,因为它根本看不到返回的元素,而是仅在NodeList属性上进行迭代。在单一结果的情况下,我得到:
这就解释了为什么我
for (node in nodes) node.href = newLink;
失败了。