为什么在数组迭代中使用“ for…in”是个坏主意?

JavaScript

L神无Mandy

2020-03-09

有人告诉我不要for...in在JavaScript中使用数组。为什么不?

第222篇《为什么在数组迭代中使用“ for…in”是个坏主意?》来自Winter(https://github.com/aiyld/aiyld.github.io)的站点

14个回答
小哥Eva 2020.03.09

尽管此问题未专门解决,但我想补充一下,有一个很好的理由,不要在...中使用... NodeList(因为可以从querySelectorAll调用中获得,因为它根本看不到返回的元素,而是仅在NodeList属性上进行迭代。

在单一结果的情况下,我得到:

var nodes = document.querySelectorAll(selector);
nodes
▶ NodeList [a._19eb]
for (node in nodes) {console.log(node)};
VM505:1 0
VM505:1 length
VM505:1 item
VM505:1 entries
VM505:1 forEach
VM505:1 keys
VM505:1 values

这就解释了为什么我for (node in nodes) node.href = newLink;失败了。

Near神奇 2020.03.09

由于JavaScript元素被保存为标准对象属性,因此不建议使用for ... in循环遍历JavaScript数组,因为将列出常规元素和所有可枚举的属性。

来自https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Indexed_collections

Jim老丝梅 2020.03.09

您应该for(var x in y)仅在属性列表上使用,而不能在对象上使用(如上所述)。

泡芙神无伽罗 2020.03.09

因为如果不小心,它将遍历原型链中对象所属的属性。

您可以使用for.. in,只需确保使用hasOwnProperty检查每个属性

泡芙小胖 2020.03.09

TL&DR:for in在数组中使用循环并不有害,实际上恰恰相反。

我认为for in如果正确在数组中使用循环是JS的瑰宝您应该完全控制您的软件,并且知道自己在做什么。让我们看看所提到的缺点,并逐一加以证明。

  1. It loops through inherited properties as well: First of all any extensions to the Array.prototype should have been done by using Object.defineProperty() and their enumerable descriptor should be set to false. Any library not doing so should not be used at all.
  2. Properties those you add to the inheritance chain later get counted: When doing array sub-classing by Object.setPrototypeOf or by Class extend. You should again use Object.defineProperty() which by default sets the writable, enumerable and configurable property descriptors to false. 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.

  1. 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:");

小小卡卡西小卤蛋 2020.03.09

问题是for ... in ...—仅在程序员不真正理解该语言时才成为问题。它并不是真正的bug或其他任何东西,而是它遍历对象的所有成员(嗯,所有可枚举的成员,但这是现在的细节)。当你想迭代只是一个数组中,只保证的方式,让事情语义一致的索引属性是使用整数索引(即for (var i = 0; i < array.length; ++i)风格的循环)。

任何对象都可以具有与之关联的任意属性。特别是将额外的属性加载到数组实例上并没有什么可怕的。想要看到的代码阵列状的索引属性,因此必须坚持一个整数索引。完全知道做什么for ... in和真正需要查看所有属性的代码,那么那也没关系。

西门村村古一 2020.03.09

我认为我没有什么要补充的。在某些情况下应避免使用Triptych的答案CMS的答案for...in

但是,我确实想补充一点,在现代浏览器中,有一种替代方法for...in可以在无法使用的情况下for...in使用。该替代方法是for...of

for (var item of items) {
    console.log(item);
}

注意 :

不幸的是,没有任何版本的Internet Explorer支持for...ofEdge 12+支持),因此您必须等待更长的时间才能在客户端生产代码中使用它。但是,在服务器端JS代码中使用它应该是安全的(如果使用Node.js)。

猿路易 2020.03.09

主要有两个原因:

就像其他人说的那样,您可能会得到不在数组中或从原型继承的键。因此,如果说,一个库向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中实现的方式,而不是作为数组。这似乎是一件小事,但它确实会使应用程序搞砸,并且难以调试。

老丝阿飞 2020.03.09

因为它是通过对象字段而不是索引枚举的。您可以通过索引“ length”获得价值,我怀疑您是否想要这样做。

西门小宇宙 2020.03.09

除了一个事实,即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带有索引的常规循环。

さ恋旧る 2020.03.09

简短的回答:这是不值得的。


更长的答案:即使不需要顺序的元素顺序和最佳性能,这也不值得。


长答案:这不值得...

  • 使用for (var property in array)将导致array被迭代为一个对象,遍历对象原型链并最终比基于索引的for循环执行得慢
  • for (... in ...) 不能保证可以按预期顺序返回对象属性。
  • 使用hasOwnProperty()!isNaN()检查来过滤对象属性是一个额外的开销,这导致它执行起来甚至更慢,并且抵消了最初使用它的关键原因,即由于格式更加简洁。

由于这些原因,甚至不存在性能和便利性之间可接受的折衷。除非意图是将数组作为对象处理并对数组的对象属性执行操作,否则实际上没有任何好处

Tony阳光 2020.03.09

正如for…ofJohn 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它显示了数组属性名称

樱A 2020.03.09

孤立地,在数组上使用for-in并没有错。for-in遍历对象的属性名称,对于“开箱即用”的数组,属性对应于数组索引。(内置样propertes lengthtoString等不包括在迭代)。

但是,如果您的代码(或所使用的框架)将自定义属性添加到数组或数组原型,则这些属性将包含在迭代中,这可能不是您想要的。

一些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

Itachi阳光 2020.03.09

为什么不应该使用for..in遍历数组元素的三个原因

  • for..in将遍历所有不是数组对象的自身和继承的属性DontEnum这意味着,如果有人将属性添加到特定的数组对象(有正当的理由-我自己这样做)或已更改Array.prototype(在代码中被认为是不当做法,应该与其他脚本一起使用),则这些属性将也要遍历;可以通过检查排除继承的属性hasOwnProperty(),但这对您在数组对象本身中设置的属性没有帮助

  • for..in 不保证保留元素顺序

  • 这很慢,因为您必须遍历数组对象及其整个原型链的所有属性,并且仍将仅获取该属性的名称,即要获取该值,将需要进行额外的查找

问题类别

JavaScript Ckeditor Python Webpack TypeScript Vue.js React.js ExpressJS KoaJS CSS Node.js HTML Django 单元测试 PHP Asp.net jQuery Bootstrap IOS Android