我被告知不要for...in
在 JavaScript 中使用数组。为什么不?
27 回答
原因是一个构造:
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
*/
该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
}
此外,谈到继承的属性,例如,如果您扩展Array.prototype
对象(就像 MooTools 那样的某些库),也会枚举该属性:
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
将遍历数组对象的所有自己和继承的属性,这些属性不是DontEnum
; 这意味着如果有人将属性添加到特定的数组对象(这是有正当理由的 - 我自己已经这样做了)或更改Array.prototype
(这在代码中被认为是不好的做法,应该与其他脚本一起工作),这些属性将也被迭代;可以通过检查来排除继承的属性hasOwnProperty()
,但这对您在数组对象本身中设置的属性没有帮助for..in
不能保证保留元素顺序它很慢,因为您必须遍历数组对象的所有属性及其整个原型链,并且仍然只会获取属性的名称,即要获取值,需要进行额外的查找
因为 for...in 枚举的是保存数组的对象,而不是数组本身。如果我在数组原型链中添加一个函数,那也将包括在内。IE
Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = 'foo';
a[1] = 'bar';
for(x in a){
document.write(x + ' = ' + a[x]);
}
这将写:
0 = 富 1 = 酒吧 myOwnFunction = function() { alert(this); }
而且由于您永远无法确定不会将任何内容添加到原型链中,因此只需使用 for 循环来枚举数组:
for(i=0,x=a.length;i<x;i++){
document.write(i + ' = ' + a[i]);
}
这将写:
0 = 富 1 = 酒吧
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 (var property in array)
将导致array
作为对象进行迭代,遍历对象原型链并最终执行比基于索引的for
循环慢。 for (... in ...)
正如人们所期望的那样,不能保证按顺序返回对象属性。- 使用
hasOwnProperty()
和!isNaN()
检查来过滤对象属性是一个额外的开销,导致它执行得更慢,并且否定了首先使用它的关键原因,即因为更简洁的格式。
由于这些原因,甚至不存在性能和便利性之间可接受的权衡。除非打算将数组作为对象处理并对数组的对象属性执行操作,否则实际上没有任何好处。
单独来看,在数组上使用 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 (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);
在循环中,但第一个结构更直接。
除了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
带有索引的常规循环。
主要有两个原因:
一
就像其他人所说的那样,您可能会得到不在数组中或从原型继承的键。因此,假设一个库向 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中实现的方式,而不是作为一个数组。这似乎是一件小事,但它确实会搞砸应用程序并且难以调试。
我认为我没有太多要添加的内容。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)。
因为它通过对象字段而不是索引进行枚举。你可以用索引“长度”获得价值,我怀疑你想要这个。
问题for ... in ...
- 只有当程序员并不真正理解该语言时,这才会成为问题;它不是真正的错误或任何东西——它迭代了一个对象的所有成员(嗯,所有可枚举的成员,但现在这是一个细节)。当您只想迭代数组的索引属性时,保持语义一致的唯一保证方法是使用整数索引(即for (var i = 0; i < array.length; ++i)
样式循环)。
任何对象都可以具有与之关联的任意属性。特别是,将附加属性加载到数组实例上并没有什么可怕的。因此,想要仅查看索引的类数组属性的代码必须坚持使用整数索引。完全了解什么for ... in
以及真正需要查看所有属性的代码,那也没关系。
TL&DR:在数组中使用for in
循环并不是坏事,事实上恰恰相反。
如果在数组中正确使用,我认为for in
循环是 JS 的瑰宝。您应该完全控制您的软件并知道自己在做什么。让我们看看提到的缺点并一一反驳它们。
- 它还遍历继承的属性:首先,对 的任何扩展都
Array.prototype
应该通过 using 完成,Object.defineProperty()
并且它们的enumerable
描述符应该设置为false
. 根本不应该使用任何不这样做的库。 - 稍后添加到继承链中的属性会被计算在内:
Object.setPrototypeOf
当按或按 Class进行数组子类化时extend
。您应该再次使用Object.defineProperty()
which 默认将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
处理数组的方式(即与任何其他 JavaScript 对象相同)与其他流行语言不一致。
// 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"
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”语法可能更慢,因为索引是字符串,而不是整数。
var a = ["a"]
for (var i in a)
alert(typeof i) // 'string'
for (var i = 0; i < a.length; i++)
alert(typeof i) // 'number'
一个重要的方面是for...in
只迭代对象中包含的属性,这些属性的可枚举 属性属性设置为 true。因此,如果尝试使用任何属性迭代对象,for...in
则如果它们的可枚举属性属性为假,则可能会错过任意属性。很可能更改普通 Array 对象的可枚举属性属性,以便不枚举某些元素。尽管通常属性属性倾向于应用于对象内的函数属性。
可以通过以下方式检查属性的可枚举属性的值:
myobject.propertyIsEnumerable('myproperty')
或者获取所有四个属性属性:
Object.getOwnPropertyDescriptor(myobject,'myproperty')
这是 ECMAScript 5 中可用的功能 - 在早期版本中,无法更改可枚举属性的值(它始终设置为 true)。
因为如果您不小心,它将遍历原型链上属于对象的属性。
您可以使用for.. in
,只需确保使用hasOwnProperty检查每个属性。
这不一定是坏事(基于你在做什么),但在数组的情况下,如果添加了一些东西Array.prototype
,那么你会得到奇怪的结果。您希望此循环运行 3 次的地方:
var arr = ['a','b','c'];
for (var key in arr) { ... }
如果调用的函数helpfulUtilityMethod
已添加到Array
's prototype
,那么您的循环将最终运行四次:key
分别是0
、1
、2
和helpfulUtilityMethod
。如果你只期待整数,哎呀。
您应该使用for(var x in y)
only on 属性列表,而不是对象(如上所述)。
以下是为什么这是(通常)不好的做法的原因:
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
对数组使用循环并没有错,尽管我可以猜到为什么有人告诉你:
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]
A for...in loop always enumerates the keys. Objects properties keys are always String, even the indexed properties of an array :
var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
total += elem
}
console.log(total); // 00123
for...in在 JavaScript 中处理对象时很有用,但不适用于数组,但我们仍然不能说这是错误的方式,但不推荐使用for...in循环查看下面的示例:
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
如您所见,结果相同...
But let's try something, let's prototype something to Array...
Array.prototype.someoneelse = "someoneelse";
Now we create a new 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
You see the someoneelse!!!... We actually looping through new Array object in this case!
So that's one of the reasons why we need to use for..in carefully, but it's not always the case...
Since JavaScript elements are saved as standard object properties, it is not advisable to iterate through JavaScript arrays using for...in loops because normal elements and all enumerable properties will be listed.
From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections
虽然这个问题没有具体解决,但我要补充一点,有一个很好的理由永远不要使用 for...in 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;
的失败。