提问人:lYriCAlsSH 提问时间:2/1/2009 最后编辑:Ry-lYriCAlsSH 更新时间:5/16/2023 访问量:1029078
为什么使用“for...in“对于数组迭代是个坏主意?
Why is using "for...in" for array iteration a bad idea?
答:
因为它通过对象字段而不是索引进行枚举。你可以用索引“length”来获得值,我怀疑你想要这个。
评论
原因是有一个结构:
var a = []; // Create a new empty array.
a[5] = 5; // Perfectly legal JavaScript that resizes the array.
for (var i = 0; i < a.length; i++) {
// Iterate over numeric indexes from 0 to 5, as everyone expects.
console.log(a[i]);
}
/* Will display:
undefined
undefined
undefined
undefined
undefined
5
*/
有时可能与另一个完全不同:
var a = [];
a[5] = 5;
for (var x in a) {
// Shows only the explicitly set index of "5", and ignores 0-4
console.log(x);
}
/* Will display:
5
*/
还要考虑 JavaScript 库可能会执行如下操作,这将影响您创建的任何数组:
// Somewhere deep in your JavaScript library...
Array.prototype.foo = 1;
// Now you have no idea what the below code will do.
var a = [1, 2, 3, 4, 5];
for (var x in a){
// Now foo is a part of EVERY array and
// will show up here as a value of 'x'.
console.log(x);
}
/* Will display:
0
1
2
3
4
foo
*/
评论
(var x in a)
(x in a)
for (var key in object)
for(var i = 0; i < array.length; i += 1)
因为...in 枚举保存数组的对象,而不是数组本身。如果我将函数添加到数组原型链中,该函数也将包括在内。J.F.
Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = 'foo';
a[1] = 'bar';
for (var x in a) {
document.write(x + ' = ' + a[x]);
}
这将写:
0 = foo
1 = bar
myOwnFunction = function() { alert(this); }
由于您永远无法确定不会向原型链添加任何内容,因此只需使用 for 循环来枚举数组:
for (var i=0,x=a.length; i<x; i++) {
document.write(i + ' = ' + a[i]);
}
这将写:
0 = foo
1 = bar
评论
孤立地讲,在数组上使用 for-in 并没有错。For-in 循环访问对象的属性名称,对于“开箱即用”数组,属性对应于数组索引。(内置属性(如 等)不包括在迭代中。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 循环将产生 101 个索引,其中 99 的值为 。undefined
有三个原因不应该用于迭代数组元素:for..in
for..in
将遍历数组对象的所有自有和继承的属性,这些属性不是;这意味着如果有人向特定的数组对象添加属性(这是有正当理由的 - 我自己也这样做了)或更改(这在代码中被认为是不好的做法,应该与其他脚本一起工作),这些属性也将被迭代;可以通过检查 排除继承的属性,但这对数组对象本身中设置的属性没有帮助DontEnum
Array.prototype
hasOwnProperty()
for..in
不保证保留元素顺序它很慢,因为您必须遍历数组对象及其整个原型链的所有属性,并且仍然只能获取属性的名称,即要获取值,需要额外的查找
除了其他答案中给出的原因外,您可能不想使用“for...in“结构,如果你需要对计数器变量进行数学运算,因为循环遍历对象属性的名称,所以变量是一个字符串。
例如
for (var i=0; i<a.length; i++) {
document.write(i + ', ' + typeof i + ', ' + i+1);
}
会写
0, number, 1
1, number, 2
...
而
for (var ii in a) {
document.write(i + ', ' + typeof i + ', ' + i+1);
}
会写
0, string, 01
1, string, 11
...
当然,这可以通过包括
ii = parseInt(ii);
在循环中,但第一个结构更直接。
评论
+
parseInt
parseInt()
parseInt("025");
parseInt
parseInt("025", 10)
因为如果你不小心,它会遍历属于原型链上游对象的属性。
您可以使用 ,只需确保使用 hasOwnProperty 检查每个属性即可。for.. in
评论
true
hasOwnProperty()
isNaN
isNaN
该语句本身并不是一个“坏做法”,但是它可能会被误用,例如,迭代数组或类似数组的对象。for-in
该语句的用途是枚举对象属性。此语句将在原型链中向上,也枚举继承的属性,这有时是不希望的。for-in
此外,规范不保证迭代顺序,这意味着如果要“迭代”数组对象,则使用此语句无法确定属性(数组索引)将按数字顺序访问。
例如,在 JScript (IE <= 8) 中,即使在 Array 对象上,枚举的顺序也是在创建属性时定义的:
var array = [];
array[2] = 'c';
array[1] = 'b';
array[0] = 'a';
for (var p in array) {
//... p will be "2", "1" and "0" on IE
}
此外,谈到继承的属性,例如,如果您扩展对象(例如MooTools等某些库),则该属性也将被枚举:Array.prototype
Array.prototype.last = function () { return this[this.length-1]; };
for (var p in []) { // an empty array
// last will be enumerated
}
正如我之前所说,要遍历数组或类似数组的对象,最好的办法是使用顺序循环,例如普通的 / 循环。for
while
如果只想枚举对象(未继承的属性)的自身属性,可以使用以下方法:hasOwnProperty
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
// prop is not inherited
}
}
有些人甚至建议直接从 中调用该方法,以避免在有人向我们的对象添加名为的属性时出现问题:Object.prototype
hasOwnProperty
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// prop is not inherited
}
}
评论
for..in
for..in
for (var p in array) { array[p]; }
问题所在——只有当程序员不真正理解这门语言时,这才会成为一个问题;这并不是一个真正的错误或任何东西——它是遍历一个对象的所有成员(好吧,所有可枚举的成员,但现在这是一个细节)。当您只想循环访问数组的索引属性时,保持语义一致的唯一保证方法是使用整数索引(即样式循环)。for ... in ...
for (var i = 0; i < array.length; ++i)
任何对象都可以具有与之关联的任意属性。特别是,将其他属性加载到数组实例上并没有什么可怕的。因此,只想查看类似索引数组的属性的代码必须坚持使用整数索引。完全了解什么并且真正需要查看所有属性的代码,那么这也没关系。for ... in
评论
for in
in
for ... in
for ... in
[{a:'hey',b:'hi'},{a:'hey',b:'hi'}]
这不一定是坏事(基于你正在做的事情),但是在数组的情况下,如果已经添加了一些东西,那么你会得到奇怪的结果。您希望此循环运行三次:Array.prototype
var arr = ['a','b','c'];
for (var key in arr) { ... }
如果调用的函数已添加到 中,则您的循环最终将运行四次:将是 、 、 和 。如果你只期待整数,哎呀。helpfulUtilityMethod
Array
prototype
key
0
1
2
helpfulUtilityMethod
您应该仅在属性列表上使用 ,而不是在对象上使用(如上所述)。for(var x in y)
评论
/ 使用两种类型的变量:哈希表(关联数组)和数组(非关联)。for
in
JavaScript 将自动确定其通过项目的方式。因此,如果您知道您的数组确实是非关联的,则可以使用 ,并跳过自动检测迭代。for (var i=0; i<=arrayLen; i++)
但在我看来,最好使用 /,自动检测所需的过程非常小。for
in
这个问题的真正答案将取决于浏览器如何解析/解释 JavaScript 代码。它可以在浏览器之间更改。
我想不出其他目的来不使用for
/in
;
//Non-associative
var arr = ['a', 'b', 'c'];
for (var i in arr)
alert(arr[i]);
//Associative
var arr = {
item1 : 'a',
item2 : 'b',
item3 : 'c'
};
for (var i in arr)
alert(arr[i]);
评论
for ... in
使用对象。没有自动检测这样的东西。
除了......遍历所有可枚举属性(与“所有数组元素”不同!),请参见 http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf, 第 12.6.4 节(第 5 版)或 13.7.5.15(第 7 版):for
in
枚举属性的机制和顺序......未指定...
(强调我的。
这意味着,如果浏览器愿意,它可以按照插入属性的顺序遍历属性。或按数字顺序排列。或者按词汇顺序(其中“30”在“4”之前!请记住,所有对象键 - 因此,所有数组索引 - 实际上都是字符串,所以这完全有意义)。如果它将对象实现为哈希表,则它可以通过存储桶遍历它们。或者采用其中任何一个并添加“倒退”。浏览器甚至可以随机迭代并符合 ECMA-262 标准,只要它只访问每个属性一次。
在实践中,大多数浏览器目前都喜欢以大致相同的顺序进行迭代。但没有什么说他们必须这样做。这是特定于实现的,如果发现另一种方法更有效,则可能随时更改。
无论哪种方式,......它没有秩序的内涵。如果您关心顺序,请明确说明它并使用带有索引的常规循环。for
in
for
除了其他问题外,“for..in“语法可能较慢,因为索引是字符串,而不是整数。
var a = ["a"]
for (var i in a)
alert(typeof i) // 'string'
for (var i = 0; i < a.length; i++)
alert(typeof i) // 'number'
评论
var i in a
a[i+offset] = <value>
简短的回答:这不值得。
更长的答案:即使不需要顺序元素顺序和最佳性能,这也是不值得的。
长答案:这不值得......
- using 将导致作为对象进行迭代,遍历对象原型链,并最终比基于索引的循环执行得更慢。
for (var property in array)
array
for
for (... in ...)
不能保证按顺序返回对象属性,正如人们所期望的那样。- 使用 和 检查来过滤对象属性是一种额外的开销,导致其执行速度更慢,并且否定了首先使用它的关键原因,即因为更简洁的格式。
hasOwnProperty()
!isNaN()
由于这些原因,甚至不存在性能和便利性之间可接受的权衡。除非目的是将数组作为对象进行处理并对数组的对象属性执行操作,否则实际上没有任何好处。
一个重要的方面是,仅循环访问对象中包含的属性,这些属性的可枚举属性属性设置为 true。因此,如果尝试遍历一个对象,则如果任意属性的可枚举属性属性为 false,则可能会丢失任意属性。很有可能更改普通 Array 对象的 enumerable 属性特性,以便不枚举某些元素。尽管通常属性特性往往适用于对象中的函数属性。for...in
for...in
可以通过以下方式检查属性的可枚举属性属性的值:
myobject.propertyIsEnumerable('myproperty')
或者获取所有四个属性属性:
Object.getOwnPropertyDescriptor(myobject,'myproperty')
这是 ECMAScript 5 中提供的一项功能 - 在早期版本中,无法更改可枚举属性属性的值(它始终设置为 true)。
此外,由于语义的原因,处理数组的方式(即与任何其他 JavaScript 对象相同)与其他流行语言不一致。for, in
// C#
char[] a = new char[] {'A', 'B', 'C'};
foreach (char x in a) System.Console.Write(x); //Output: "ABC"
// Java
char[] a = {'A', 'B', 'C'};
for (char x : a) System.out.print(x); //Output: "ABC"
// PHP
$a = array('A', 'B', 'C');
foreach ($a as $x) echo $x; //Output: "ABC"
// JavaScript
var a = ['A', 'B', 'C'];
for (var x in a) document.write(x); //Output: "012"
评论
主要有两个原因:
一
就像其他人说的那样,你可能会得到不在数组中或从原型继承的键。因此,假设一个库向 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 中实现的方式,而不是数组。 这似乎是一件小事,但它确实会搞砸应用程序并且难以调试。
评论
Object.keys(a).forEach( function(item) { console.log(item) } )
遍历一组自己的属性键,而不是从 prototype 继承的键。
array.forEach
我认为我没有什么要补充的。Triptych 的答案或 CMS 关于为什么在某些情况下应避免使用的答案。for...in
但是,我确实想补充一点,在现代浏览器中,有一种替代方案可以在无法使用的情况下使用。该替代方案适用于...的
:for...in
for...in
for (var item of items) {
console.log(item);
}
注意:
遗憾的是,没有版本的 Internet Explorer 支持(Edge 12+ 支持),因此您必须等待更长的时间,才能在客户端生产代码中使用它。但是,在服务器端 JS 代码中使用它应该是安全的(如果您使用 Node.js)。for...of
从 2016 年(ES6)开始,我们可以将其用于数组迭代,正如 John Slegers 已经注意到的那样。for…of
我只想添加这个简单的演示代码,以使事情更清楚:
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
TL&DR:在数组中使用循环并不是坏事,实际上恰恰相反。for in
我认为如果在数组中使用得当,该循环是 JS 的瑰宝。您应该完全控制您的软件并知道自己在做什么。让我们看看提到的缺点并一一反驳它们。for in
- 它还遍历继承的属性:首先,应该使用
Object.defineProperty()
完成对 的任何扩展,并且其描述符应设置为 .任何不这样做的库都不应该被使用。Array.prototype
enumerable
false
- 稍后添加到继承链的属性将被计算在内:当按或按类进行数组子类化时。您应该再次使用它,默认情况下,它将 和 属性描述符设置为 。让我们在这里看一个数组子类示例......
Object.setPrototypeOf
extend
Object.defineProperty()
writable
enumerable
configurable
false
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]);
所以你看.. 循环现在是安全的,因为你关心你的代码。for in
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
1.) 已经有一个更高阶的函数或方法,它对数组具有这种用途,但具有更多的功能和更精简的语法,称为“forEach”:Array.prototype.forEach(function(element, index, array) {} );
2.) 数组总是有长度,但不会对任何值执行函数,只对定义了值的索引执行函数。因此,如果只分配一个值,则这些循环只会执行一次函数,但是由于数组是枚举的,因此它的长度将始终达到具有定义值的最高索引,但是在使用这些循环时,该长度可能会被忽视。for...in
forEach
'undefined'
3.) 标准 for 循环将执行一个函数的次数与您在参数中定义的次数一样多,并且由于数组是编号的,因此定义要执行函数的次数更有意义。与其他循环不同,for 循环可以对数组中的每个索引执行一个函数,无论是否定义了值。
从本质上讲,您可以使用任何循环,但您应该准确记住它们的工作原理。了解不同循环重复的条件、它们各自的功能,并意识到它们或多或少适用于不同的场景。
此外,使用该方法可能被认为是比一般循环更好的做法,因为它更容易编写并且具有更多功能,因此您可能希望养成仅使用此方法和标准的习惯,但您的调用。forEach
for...in
如下图所示,前两个循环仅执行一次 console.log 语句,而标准 for 循环执行函数的次数与指定的次数相同,在本例中为 array.length = 6。
var arr = [];
arr[5] = 'F';
for (var index in arr) {
console.log(index);
console.log(arr[index]);
console.log(arr)
}
// 5
// 'F'
// => (6) [undefined x 5, 6]
arr.forEach(function(element, index, arr) {
console.log(index);
console.log(element);
console.log(arr);
});
// 5
// 'F'
// => Array (6) [undefined x 5, 6]
for (var index = 0; index < arr.length; index++) {
console.log(index);
console.log(arr[index]);
console.log(arr);
};
// 0
// undefined
// => Array (6) [undefined x 5, 6]
// 1
// undefined
// => Array (6) [undefined x 5, 6]
// 2
// undefined
// => Array (6) [undefined x 5, 6]
// 3
// undefined
// => Array (6) [undefined x 5, 6]
// 4
// undefined
// => Array (6) [undefined x 5, 6]
// 5
// 'F'
// => Array (6) [undefined x 5, 6]
为。。。in 在 JavaScript 中处理对象时很有用,但对于 Array 则不然,但我们仍然不能说这是一种错误的方式,但不建议这样做,看看下面的这个例子,用于...在循环中:
let txt = "";
const person = {fname:"Alireza", lname:"Dezfoolian", age:35};
for (const x in person) {
txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35
好了,现在让我们用 Array 来做:
let txt = "";
const person = ["Alireza", "Dezfoolian", 35];
for (const x in person) {
txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35
正如你所看到的,结果是一样的......
但是让我们尝试一些东西,让我们将一些东西原型化到 Array......
Array.prototype.someoneelse = "someoneelse";
现在我们创建一个新的 Array();
let txt = "";
const arr = new Array();
arr[0] = 'Alireza';
arr[1] = 'Dezfoolian';
arr[2] = 35;
for(x in arr) {
txt += arr[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 someoneelse
你看到别人了吗!!...在这种情况下,我们实际上遍历了新的 Array 对象!
所以这就是我们需要用于的原因之一。小心翼翼,但情况并非总是如此......
由于 JavaScript 元素被保存为标准对象属性,因此它 不建议使用 for 遍历 JavaScript 数组...在 循环,因为普通元素和所有可枚举属性都将 上市。
从 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections
一个用于...in 循环始终枚举键。 对象属性键始终是 String,即使是数组的索引属性:
var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
total += elem
}
console.log(total); // 00123
虽然这个问题没有具体解决,但我想补充一点,有一个很好的理由不用于......(就像从调用中获得的那样,因为它根本不看到返回的元素,而是只遍历 NodeList 属性。NodeList
querySelectorAll
在单个结果的情况下,我得到了:
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;
以下是这(通常)是一种不良做法的原因:
for...in
循环遍历它们自己的所有可枚举属性及其原型的可枚举属性。通常,在数组迭代中,我们只想遍历数组本身。即使你自己可能不会向数组添加任何内容,你的库或框架也可能会添加一些东西。
示例:
Array.prototype.hithere = 'hithere';
var array = [1, 2, 3];
for (let el in array){
// the hithere property will also be iterated over
console.log(el);
}
for...in
循环不保证特定的迭代顺序。尽管如今在大多数现代浏览器中通常可以看到订单,但仍然没有 100% 的保证。for...in
循环忽略数组元素,即尚未分配的数组元素。undefined
示例::
const arr = [];
arr[3] = 'foo'; // resize the array to 4
arr[4] = undefined; // add another element with value undefined to it
// iterate over the array, a for loop does show the undefined elements
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
console.log('\n');
// for in does ignore the undefined elements
for (let el in arr) {
console.log(arr[el]);
}
for in 循环在遍历数组时将索引转换为字符串。 例如,在下面的代码中,在第二个循环中,使用 i+1 初始化 j,i 是索引,但在字符串(“0”、“1”等)中,js 中的数字 + 字符串是字符串。如果 JS 遇到 “0” + 1,它将返回 “01”。
var maxProfit = function(prices) {
let maxProfit = 0;
for (let i in prices) {
for (let j = i + 1; j < prices.length; j++) {
console.log(prices[j] - prices[i], "i,j", i, j, typeof i, typeof j);
if ((prices[j] - prices[i]) > maxProfit) maxProfit = (prices[j] - prices[i]);
}
}
return maxProfit;
};
maxProfit([7, 1, 5, 3, 6, 4]);
评论
var i = hCol1.length; for (i;i;i--;) {}
for
while
obfuscate
var i = 1000; for (i; i; i--) {}
var b =1000 for (b; b--;) {}
for(var i = 0, l = myArray.length; i < l; ++i) ...