首页 文章

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

提问于
浏览
1597

我被告知不要在JavaScript中使用带有数组的 for...in . 为什么不?

25 回答

  • 15

    此外,由于语义, 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"
    
  • 361

    因为如果你不小心的话,它会迭代属于原型链上的对象的属性 .

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

  • 1392

    因为...枚举通过保存数组的对象,而不是数组本身 . 如果我向数组原型链添加一个函数,那么它也将被包含在内 . 即

    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 = foo
    1 = bar
    myOwnFunction = function() { alert(this); }
    

    因为你永远无法确定什么都不会被添加到原型链中,所以只需使用for循环来枚举数组:

    for(i=0,x=a.length;i<x;i++){
     document.write(i + ' = ' + a[i]);
    }
    

    这将写:

    0 = foo
    1 = bar
    
  • 29

    除了其他问题,“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'
    
  • 8

    主要有两个原因:

    One

    像其他人所说的那样,你可能会获得不在你的数组中或从原型继承的键 . 因此,如果,库,则会向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循环迭代任何对象都是如此 .

    Two

    通常数组中项的顺序很重要,但是for-in循环不一定以正确的顺序迭代,这是因为它将数组视为一个对象,这是它在JS中实现的方式,而不是作为一个数组 . 这似乎是一件小事,但它确实搞砸了应用程序,很难调试 .

  • 37

    您不应该使用 for..in 迭代数组元素有三个原因:

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

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

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

  • 21

    它不一定是坏的(基于你正在做的事情),但是在数组的情况下,如果已经将某些内容添加到 Array.prototype ,那么你希望这个循环运行三次:

    var arr = ['a','b','c'];
    for (var key in arr) { ... }
    

    如果一个名为 helpfulUtilityMethod 的函数已被添加到 Arrayprototype ,那么你的循环将最终运行四次: key 将是 012helpfulUtilityMethod . 如果你只是期望整数,哎呀 .

  • 10

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

  • 5

    一个重要的方面是 for...in 仅迭代对象中包含的 enumerable 属性设置为true的属性 . 因此,如果尝试使用 for...in 迭代对象,则如果其可枚举属性属性为false,则可能会遗漏任意属性 . 很可能更改普通Array对象的enumerable属性属性,以便不枚举某些元素 . 虽然通常属性属性倾向于应用于对象内的函数属性 .

    可以通过以下方式检查属性的可枚举属性属性的值:

    myobject.propertyIsEnumerable('myproperty')
    

    或者获取所有四个属性属性:

    Object.getOwnPropertyDescriptor(myobject,'myproperty')
    

    这是ECMAScript 5中提供的一项功能 - 在早期版本中,无法更改enumerable属性的值(它始终设置为true) .

  • 3

    A for ... in循环始终枚举键 . 对象属性键总是String,甚至是数组的索引属性:

    var myArray = ['a', 'b', 'c', 'd'];
    var total = 0
    for (elem in myArray) {
      total += elem
    }
    console.log(total); // 00123
    
  • 5

    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]);
    
  • 1

    for-in 语句本身不是"bad practice",但是它可能被误用,例如,迭代数组或类似数组的对象 .

    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
      }
    }
    
  • 14

    TL&DR: 在数组中使用 for in 循环不是邪恶,实际上恰恰相反 .

    我认为如果在数组中正确使用, for in 循环是JS的宝石 . 您应该完全控制您的软件并知道您在做什么 . 让我们看看上面提到的缺点并逐一反驳 .

    • It loops through inherited properties as well: 首先, Array.prototype 的任何扩展应该通过使用Object.defineProperty()来完成,并且它们的 enumerable 描述符应该设置为 false . 任何不这样做的图书馆都不应该被使用 .

    • Properties those you add to the inheritance chain later get counted: 通过 Object.setPrototypeOf 或类 extend 进行数组子类化时 . 您应该再次使用 Object.defineProperty() ,默认情况下将 writableenumerableconfigurable 属性描述符设置为 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 循环现在是安全的,因为你关心你的代码 .

    • The for in loop is slow: 地狱没有 . 它's by far the fastest method of iteration if you are looping over sparse arrays which are needed time to time. This is one of the most important performance tricks that one should know. Let'看到一个例子 . 我们将遍历稀疏数组 .
    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:");
    
  • 0

    for...in 在处理JavaScript中的对象时非常有用,但对于数组却没有用,但我们仍然不能说它不推荐使用's a wrong way, but it',请使用 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
    

    正如你看到的结果一样......

    但是让's try something, let'的原型成为 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
    

    你看到 someoneelse !!! ...在这种情况下我们实际上循环遍历新的Array对象!

    所以这就是我们需要仔细使用 for..in 的原因之一,但情况并非总是如此......

  • 21

    原因是一个结构:

    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
    */
    
  • 27

    对数组使用 for...in 循环没有错,虽然我可以猜到为什么有人告诉你:

    1.)已经有一个更高阶的函数或方法,它具有数组的目的,但具有更多的功能和更精简的语法,称为'forEach': Array.prototype.forEach(function(element, index, array) {} );

    2.)数组总是有一个长度,但 for...inforEach 不对任何 'undefined' 值执行函数,仅对具有定义值的索引执行 . 因此,如果您只分配一个值,这些循环将只执行一次函数,但由于枚举了一个数组,因此它的长度始终会达到具有已定义值的最高索引,但使用这些时长度可能会被忽略循环 .

    3.)循环标准将在参数中定义多次执行函数,并且由于数组已编号,因此定义要执行函数的次数更有意义 . 与其他循环不同,for循环可以为数组中的每个索引执行一个函数,无论该值是否已定义 .

    从本质上讲,您可以使用任何循环,但您应该记住它们的工作原理 . 理解不同循环重复的条件,它们各自的功能,并意识到它们或多或少适合于不同的场景 .

    此外,通常使用 forEach 方法比使用 for...in 循环更好的做法,因为它更容易编写并具有更多功能,因此您可能希望养成仅使用此方法和标准的习惯,但是你的电话 .

    请参见下文,前两个循环只执行一次console.log语句,而循环标准执行函数的次数与此相同,在本例中为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]
    
  • 7

    for ... in ... 的问题 - 当程序员没有真正的错误或任何东西时,这只会成为一个问题 - 它会迭代一个对象的所有成员(好吧,所有可枚举的成员,但这是现在的细节) . 当你想迭代数组的索引属性时,保持语义一致性的唯一保证方法是使用整数索引(即 for (var i = 0; i < array.length; ++i) 样式循环) .

    任何对象都可以具有与之关联的任意属性 . 特别是将额外的属性加载到数组实例上没有什么可怕的 . 因此,只想查看类似索引的数组属性的代码必须坚持整数索引 . 完全了解 for ... in 确实需要查看所有属性的代码,那么那也没关系 .

  • 7

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

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections

  • 50

    简短回答:这不值得 .


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


    答案很长:这不值得,原因如下:

    • 使用 for (var i in array) {} 将导致'array'被解释为任何其他纯对象,遍历对象属性链并最终执行比基于索引的 for 循环慢 .

    • 无法保证按照预期的顺序返回对象属性 .

    • 使用 hasOwnProperty()isNaN() 检查来过滤对象属性是一个额外的开销,导致它执行(甚至更多)更慢 . 而且,引入这样的附加逻辑否定了首先使用它的关键原因,即由于更简洁的格式 .

    由于这些原因,在性能和便利性之间进行可接受的权衡并没有任何好处,除非意图将数组视为纯对象并对数组对象的属性执行操作 .

  • 103

    因为它通过对象字段而不是索引进行枚举 . 你可以用索引“长度”获得 Value ,我怀疑你想要这个 .

  • 1

    截至2016年(ES6),我们可能会使用 for…of 进行数组迭代,正如John Slegers已经注意到的那样 .

    我想添加这个简单的演示代码,以使事情更清晰:

    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 . 它显示了数组 values .

    • for...in 仅列出 5 ,忽略未定义的数组索引,但添加 foo . 它显示了数组 property names .

  • 7

    除了 for ... in 遍历所有可枚举属性(与"all array elements"不同)之外,请参阅http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf,第12.6.4节(第5版)或13.7.5.15(第7版):

    枚举属性的机制和顺序......未指定...

    (强调我的 . )

    这意味着如果浏览器想要,它可以按照插入顺序浏览属性 . 或者按数字顺序排列 . 或者以词汇顺序("30"在_131701之前出现!记住所有对象键 - 因此,所有数组索引 - 实际上都是字符串,因此这是完全有意义的) . 如果它将对象实现为哈希表,它可以通过桶进行检查 . 或者采取任何一个并添加"backwards" . 浏览器甚至可以随机迭代并且符合ECMA-262,只要它只访问每个属性一次即可 .

    实际上,大多数浏览器目前都喜欢以大致相同的顺序迭代 . 但没有什么可说的 . 这是特定的实现,并且如果发现另一种方式更有效,则可以随时改变 .

    无论哪种方式, for ... in 都没有带有顺序的内涵 . 如果您关心订单,请明确它并使用带索引的常规 for 循环 .

  • 15

    孤立地,在数组上使用for-in没有任何问题 . For-in迭代对象的属性名称,对于"out-of-the-box"数组,属性对应于数组索引 . (像 lengthtoString 等内置属性不包含在迭代中 . )

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

    一些JS框架,如Prototype,修改了Array原型 . 像JQuery这样的其他框架没有,所以使用JQuery可以安全地使用for-in .

    如果您有疑问,可能不应该使用for-in .

    迭代数组的另一种方法是使用for循环:

    for (var ix=0;ix<arr.length;ix++) alert(ix);
    

    但是,这有一个不同的问题 . 问题是JavaScript数组可以有"holes" . 如果将 arr 定义为:

    var arr = ["hello"];
    arr[100] = "goodbye";
    

    然后数组有两个项目,但长度为101.使用for-in将产生两个索引,而for循环将产生101个索引,其中99的值为 undefined .

  • 7

    我认为我没有太多要补充,例如 . Triptych's answerCMS's answer关于为什么在某些情况下应避免使用 for-in .

    但是,我想补充说 in modern browsers 还有一个替代 for-in ,可用于无法使用 for-in 的情况 . 那个替代方案是for-of

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

    注意:

    遗憾的是,没有任何版本的Internet Explorer支持此功能(Edge 12+),因此您必须等待一段时间才能在客户端 生产环境 代码中使用它 . 但是,在服务器端JS代码中使用它应该是安全的(如果使用Node.js) .

  • 6

    除了在其他答案中给出的原因之外,如果你需要对计数器变量进行数学运算,你可能不想使用“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);
    

    在循环中,但第一个结构更直接 .

相关问题