Câu hỏi Tại sao lại sử dụng “for… in” với phép lặp mảng là một ý tưởng tồi?


Tôi đã được bảo không sử dụng for...in với các mảng trong JavaScript. Tại sao không?


1540
2018-02-01 09:46


gốc


Tôi đã nhìn thấy câu hỏi gần đây mà một người nào đó đã nói điều đó với bạn, nhưng họ chỉ có nghĩa là cho Mảng. Nó được coi là thực hành xấu để lặp qua mảng nhưng không nhất thiết phải lặp lại thông qua các thành viên của một đối tượng. - mmurch
Rất nhiều câu trả lời với các vòng "for" như 'for (var i = 0; i <hColl.length; i ++) {}' so với 'var i = hColl.length; trong khi (i--) {} 'trong đó, khi có thể sử dụng dạng sau, nó nhanh hơn đáng kể. Tôi biết điều này là tiếp tuyến nhưng tôi nghĩ rằng tôi sẽ thêm bit này. - Mark Schultheiss
@MarkSchultheiss nhưng đó là lặp lại ngược lại. Có một phiên bản chuyển tiếp nào khác nhanh hơn không? - ma11hew28
@Wynand sử dụng var i = hCol1.length; for (i;i;i--;) {} cache i vì nó sẽ tạo ra sự khác biệt và đơn giản hóa bài kiểm tra. - trình duyệt cũ hơn, sự khác biệt giữa forvà while LUÔN LUÔN cache bộ đếm "i" - và dĩ nhiên là tiêu cực không phải lúc nào cũng phù hợp với tình huống, và âm trong khi obfuscate  mã một chút cho một số người. và lưu ý var i = 1000; for (i; i; i--) {}và var b =1000 for (b; b--;) {} nơi tôi đi 1000 đến 1 và b đi 999 đến 0. - trình duyệt cũ hơn, càng nhiều thời gian có xu hướng ưu tiên cho hiệu suất. - Mark Schultheiss
Bạn cũng có thể thông minh. for(var i = 0, l = myArray.length; i < l; ++i) ... là nhanh nhất và tốt nhất bạn có thể nhận được với chuyển tiếp về phía trước. - Mathieu Amiot


Các câu trả lời:


Lý do là một cấu trúc:

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

đôi khi có thể hoàn toàn khác nhau:

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

Cũng xem xét điều đó JavaScript thư viện có thể làm những việc như thế này, điều này sẽ ảnh hưởng đến bất kỳ mảng nào bạn tạo:

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


1342
2018-02-01 10:08



Trong lịch sử, một số trình duyệt thậm chí đã lặp lại qua 'length', 'toString', v.v. - bobince
Nhớ sử dụng (var x in a) thay vì (x in a) - không muốn tạo ra toàn cầu. - Chris Morgan
Vấn đề đầu tiên không phải là một lý do nó xấu, chỉ có một sự khác biệt trong ngữ nghĩa. Vấn đề thứ hai dường như với tôi một lý do (trên đầu trang của các xung đột giữa các thư viện làm như vậy) mà thay đổi nguyên mẫu của một kiểu dữ liệu được xây dựng trong là xấu, hơn là for..in là xấu. - Stewart
@Stewart: Tất cả các đối tượng trong JS là kết hợp. Một mảng JS là một đối tượng, do đó, có, nó là kết hợp quá, nhưng đó không phải là những gì nó cho. Nếu bạn muốn lặp qua đối tượng phím, sử dụng for (var key in object). Nếu bạn muốn lặp qua một mảng yếu tố, tuy nhiên, sử dụng for(var i = 0; i < array.length; i += 1). - Martijn
Bạn đã nói ví dụ đầu tiên, rằng nó Lặp lại các chỉ mục số từ 0 đến 4, như mọi người mong đợi, Tôi mong đợi nó lặp lại từ 0 đến 5! Vì nếu bạn thêm một phần tử ở vị trí 5, mảng sẽ có 6 phần tử (5 phần tử không xác định). - stivlo


Các for-in tuyên bố của chính nó không phải là một "thực hành xấu", tuy nhiên nó có thể sử dụng sai, ví dụ, để lặp lại qua mảng hoặc đối tượng giống mảng.

Mục đích của for-in tuyên bố là liệt kê trên các thuộc tính đối tượng. Tuyên bố này sẽ đi lên trong chuỗi nguyên mẫu, cũng liệt kê thừa hưởng tài sản, một điều đôi khi không được mong muốn.

Ngoài ra, thứ tự lặp lại không được đảm bảo bởi spec, có nghĩa là nếu bạn muốn "iterate" một đối tượng mảng, với câu lệnh này bạn không thể chắc chắn rằng các thuộc tính (array indexes) sẽ được truy cập theo thứ tự số.

Ví dụ, trong JScript (IE <= 8), thứ tự của liệt kê ngay cả trên các đối tượng mảng được định nghĩa là các thuộc tính được tạo ra:

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
}

Ngoài ra, hãy nói về các thuộc tính được thừa kế, ví dụ: nếu bạn mở rộng Array.prototype đối tượng (như một số thư viện như MooTools), thuộc tính đó cũng sẽ được liệt kê:

Array.prototype.last = function () { return this[this.length-1]; };

for (var p in []) { // an empty array
  // last will be enumerated
}

Như tôi đã nói trước đây lặp lại trên mảng hoặc đối tượng giống mảng, điều tốt nhất là sử dụng vòng lặp tuần tự, chẳng hạn như một đồng bằng cũ for/while vòng lặp.

Khi bạn muốn liệt kê chỉ tài sản riêng của một đối tượng (đối tượng không được kế thừa), bạn có thể sử dụng hasOwnProperty phương pháp:

for (var prop in obj) {
  if (obj.hasOwnProperty(prop)) {
    // prop is not inherited
  }
}

Và một số người thậm chí khuyên bạn nên gọi phương thức trực tiếp từ Object.prototype để tránh gặp sự cố nếu ai đó thêm thuộc tính có tên hasOwnProperty đối tượng của chúng tôi:

for (var prop in obj) {
  if (Object.prototype.hasOwnProperty.call(obj, prop)) {
    // prop is not inherited
  }
}

356
2017-11-23 21:22



Xem thêm bài viết của David Humphrey Lặp lại các đối tượng trong JavaScript một cách nhanh chóng - cho mảng for..in chậm hơn nhiều so với các vòng "bình thường". - Chris Morgan
Câu hỏi về điểm cuối cùng về "hasOwnProperty": Nếu ai đó ghi đè "hasOwnProperty" trên một đối tượng, bạn sẽ gặp sự cố. Nhưng bạn sẽ không gặp phải vấn đề tương tự nếu ai đó ghi đè "Object.prototype.hasOwnProperty"? Dù bằng cách nào họ đang lừa bạn và nó không phải là trách nhiệm của bạn phải không? - Scott Rippey
Bạn nói for..in không phải là thực hành xấu, nhưng nó có thể bị lạm dụng. Bạn đã có một ví dụ thực tế về thực hành tốt, nơi bạn thực sự muốn đi qua tất cả các thuộc tính của đối tượng bao gồm các thuộc tính kế thừa? - rjmunro
@ScottRippey: Nếu bạn muốn mang nó ở đó: youtube.com/watch?v=FrFUI591WhI - Nathan Wall
với câu trả lời này tôi thấy rằng có thể truy cập giá trị bằng for (var p in array) { array[p]; } - Equiman


Có ba lý do tại sao bạn không nên sử dụng for..in để lặp qua các phần tử mảng:

  • for..in sẽ lặp lại tất cả các thuộc tính riêng và được thừa kế của đối tượng mảng không DontEnum; điều đó có nghĩa là nếu ai đó thêm thuộc tính vào đối tượng mảng cụ thể (có các lý do hợp lệ cho việc này - tôi đã tự làm như vậy) hoặc thay đổi Array.prototype (được coi là thực hành xấu trong mã được cho là hoạt động tốt với các tập lệnh khác), các thuộc tính này cũng sẽ được lặp lại; các thuộc tính kế thừa có thể được loại trừ bằng cách kiểm tra hasOwnProperty(), nhưng điều đó sẽ không giúp bạn với các thuộc tính được đặt trong chính đối tượng mảng

  • for..in không được bảo đảm để bảo toàn trật tự phần tử

  • nó chậm vì bạn phải đi bộ tất cả các thuộc tính của đối tượng mảng và chuỗi nguyên mẫu của nó và sẽ vẫn chỉ nhận được tên của thuộc tính, tức là để có được giá trị, một tra cứu bổ sung sẽ được yêu cầu


101
2018-02-01 14:04





Bởi vì đối với ... trong liệt kê thông qua đối tượng chứa mảng, không phải mảng đó. Nếu tôi thêm một hàm vào chuỗi nguyên mẫu mảng, nó cũng sẽ được bao gồm. I E.

Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = 'foo';
a[1] = 'bar';
for(x in a){
 document.write(x + ' = ' + a[x]);
}

Điều này sẽ viết:

0 = foo
1 = thanh
myOwnFunction = function () {alert (this); }

Và kể từ khi bạn không bao giờ có thể chắc chắn rằng không có gì sẽ được thêm vào chuỗi nguyên mẫu chỉ cần sử dụng một vòng lặp for để liệt kê mảng:

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

Điều này sẽ viết:

0 = foo
1 = thanh

48
2018-02-01 10:08



Mảng là Đối tượng, không có "đối tượng chứa mảng". - RobG


Trong sự cô lập, không có gì sai với việc sử dụng các mảng trên. Cho-in lặp qua các tên thuộc tính của một đối tượng, và trong trường hợp của một mảng "out-of-the-box", các thuộc tính tương ứng với các chỉ mục mảng. (Các built-in thích hợp như length, toString và như vậy không được bao gồm trong lần lặp lại.)

Tuy nhiên, nếu mã của bạn (hoặc khung bạn đang sử dụng) thêm thuộc tính tùy chỉnh vào mảng hoặc mẫu thử nghiệm, thì các thuộc tính này sẽ được đưa vào lặp lại, có thể không phải là thứ bạn muốn.

Một số khung công tác JS, như Prototype sửa đổi nguyên mẫu Array. Các khung công tác khác như JQuery không, vì vậy với JQuery, bạn có thể sử dụng một cách an toàn.

Nếu bạn nghi ngờ, có thể bạn không nên sử dụng cho.

Một cách khác để lặp qua một mảng là sử dụng vòng lặp for:

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

Tuy nhiên, điều này có một vấn đề khác. Vấn đề là một mảng JavaScript có thể có "lỗ hổng". Nếu bạn xác định arr như:

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

Sau đó, mảng có hai mục, nhưng chiều dài là 101. Sử dụng for-in sẽ mang lại hai chỉ mục, trong khi vòng lặp for sẽ mang lại 101 chỉ mục, trong đó 99 có giá trị undefined.


37
2018-02-01 10:34





Ngoài những lý do được đưa ra trong các câu trả lời khác, bạn có thể không muốn sử dụng cấu trúc "for ... in" nếu bạn cần làm toán với biến truy cập vì vòng lặp lặp qua tên của các thuộc tính của đối tượng và do đó biến là một chuỗi.

Ví dụ,

for (var i=0; i<a.length; i++) {
    document.write(i + ', ' + typeof i + ', ' + i+1);
}

se viêt

0, number, 1
1, number, 2
...

trong khi,

for (var ii in a) {
    document.write(i + ', ' + typeof i + ', ' + i+1);
}

se viêt

0, string, 01
1, string, 11
...

Tất nhiên, điều này có thể dễ dàng được khắc phục bằng cách bao gồm

ii = parseInt(ii);

trong vòng lặp, nhưng cấu trúc đầu tiên trực tiếp hơn.


28
2017-09-02 02:29



Bạn có thể sử dụng tiền tố + thay vì parseInt trừ khi bạn thực sự cần số nguyên hoặc bỏ qua các ký tự không hợp lệ. - Konrad Borowski
Ngoài ra, sử dụng parseInt() không được khuyến khích. Thử parseInt("025"); và nó sẽ Thất bại. - Derek 朕會功夫
@ Derek 朕 會 功夫 - bạn chắc chắn có thể sử dụng parseInt. Vấn đề là nếu bạn không bao gồm cơ số, các trình duyệt cũ hơn có thể cố gắng diễn giải số (do đó 025 trở thành bát phân). Điều này đã được sửa trong ECMAScript 5 nhưng nó vẫn xảy ra đối với các số bắt đầu bằng "0x" (nó diễn giải số dưới dạng hex). Để được ở bên an toàn, hãy sử dụng cơ số để xác định số lượng như vậy parseInt("025", 10) - chỉ định cơ sở 10. - IAmTimCorey


Tính đến năm 2016 (ES6), chúng tôi có thể sử dụng for…of để lặp lại mảng, như John Slegers đã nhận thấy.

Tôi chỉ muốn thêm mã trình diễn đơn giản này, để làm cho mọi việc rõ ràng hơn:

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++;
    }

Bảng điều khiển hiển thị:

for...of:

0: undefined
1: undefined
2: undefined
3: undefined
4: undefined
5: xyz

for...in:

0: 5
1: foo

Nói cách khác:

  • for...of đếm từ 0 đến 5 và cũng bỏ qua Array.prototype.foo. Nó cho thấy mảng giá trị.

  • for...in chỉ liệt kê 5, bỏ qua các chỉ mục mảng không xác định, nhưng thêm foo. Nó cho thấy mảng tên thuộc tính.


24
2018-03-10 04:29





Bên cạnh thực tế là for...in vòng lặp trên tất cả các thuộc tính đếm được (đó là không phải giống như "tất cả các phần tử mảng"!), xem http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf, mục 12.6.4 (ấn bản thứ 5) hoặc 13.7.5.15 (ấn bản thứ 7):

Cơ học và gọi món liệt kê các đặc tính ... không được chỉ định...

(Mỏ nhấn mạnh.)

Điều đó có nghĩa là nếu một trình duyệt muốn, nó có thể đi qua các thuộc tính theo thứ tự mà chúng được chèn vào. Hoặc theo thứ tự số. Hoặc theo thứ tự từ vựng (trong đó "30" xuất hiện trước "4"! Hãy ghi nhớ tất cả các khóa đối tượng - và do đó, tất cả các chỉ mục mảng - thực sự là các chuỗi, do đó có ý nghĩa tổng thể). Nó có thể đi qua chúng bằng thùng, nếu nó thực hiện các đối tượng dưới dạng bảng băm. Hoặc lấy bất kỳ thứ gì và thêm "ngược". Một trình duyệt thậm chí có thể lặp lại ngẫu nhiên và tuân thủ ECMA-262, miễn là nó truy cập từng thuộc tính một cách chính xác một lần.

Trong thực tế, hầu hết các trình duyệt hiện đang muốn lặp lại theo cùng một thứ tự. Nhưng không có gì nói họ phải làm. Đó là thực hiện cụ thể, và có thể thay đổi bất cứ lúc nào nếu một cách khác được tìm thấy là hiệu quả hơn rất nhiều.

Dù bằng cách nào, for...in mang theo nó không có ý nghĩa của trật tự. Nếu bạn quan tâm đến trật tự, hãy rõ ràng về nó và sử dụng thường xuyên for vòng lặp với chỉ mục.


21
2018-05-14 16:26





Câu trả lời ngắn gọn: Nó không đáng giá.


Câu trả lời dài hơn: Nó không chỉ đáng giá, ngay cả khi thứ tự phần tử tuần tự và hiệu suất tối ưu là không cần thiết.


Câu trả lời dài: Nó không đáng, vì những lý do sau:

  • Sử dụng for (var i in array) {} sẽ khiến 'mảng' được hiểu là bất kỳ mảng nào khác nguyên chất đối tượng, đi qua chuỗi thuộc tính đối tượng và cuối cùng hoạt động chậm hơn một chỉ mục dựa trên chỉ mục for vòng lặp.
  • Nó không đảm bảo trả về các thuộc tính đối tượng theo thứ tự tuần tự như người ta có thể mong đợi.
  • Sử dụng hasOwnProperty() hoặc là isNaN() kiểm tra để lọc các thuộc tính đối tượng là một chi phí bổ sung khiến nó thực hiện (thậm chí nhiều hơn) chậm hơn. Ngoài ra, việc giới thiệu logic bổ sung như vậy phủ nhận lý do chính để sử dụng nó ngay từ đầu, tức là vì định dạng ngắn gọn hơn.

Vì những lý do này, một sự cân bằng chấp nhận được giữa hiệu suất và sự thuận tiện thậm chí không tồn tại. Thực sự, không có lợi ích trừ khi mục đích là để xử lý các mảng như là một nguyên chất đối tượng và thực hiện các thao tác trên các thuộc tính của đối tượng mảng.


20
2018-03-14 07:12





Chủ yếu là hai lý do:

Một

Giống như những người khác đã nói, Bạn có thể nhận được các phím mà không có trong mảng của bạn hoặc được thừa hưởng từ nguyên mẫu. Vì vậy, nếu, giả sử, một thư viện thêm một thuộc tính vào các nguyên mẫu Array hoặc Object:

Array.prototype.someProperty = true

Bạn sẽ nhận được nó như là một phần của mỗi mảng:

for(var item in [1,2,3]){
  console.log(item) // will log 1,2,3 but also "someProperty"
}

bạn có thể giải quyết điều này bằng phương thức hasOwnProperty:

var ary = [1,2,3];
for(var item in ary){
   if(ary.hasOwnProperty(item)){
      console.log(item) // will log only 1,2,3
   }
}

nhưng điều này đúng cho việc lặp qua bất kỳ đối tượng nào với vòng lặp for-in.

Hai

Thông thường thứ tự của các mục trong một mảng là quan trọng, nhưng vòng lặp for-in không nhất thiết phải lặp lại theo thứ tự đúng, đó là bởi vì nó xử lý mảng như một đối tượng, đó là cách nó được thực hiện trong JS, và không như một mảng. Điều này có vẻ như một điều nhỏ, nhưng nó thực sự có thể làm hỏng các ứng dụng và khó gỡ lỗi.


15
2018-02-04 16:54



Object.keys(a).forEach( function(item) { console.log(item) } )lặp qua một mảng các khóa thuộc tính riêng, không phải là các khóa được thừa hưởng từ nguyên mẫu. - Qwerty
Đúng, nhưng giống như vòng lặp for-in, nó sẽ không nhất thiết phải theo đúng thứ tự chỉ mục. Ngoài ra, nó sẽ không hoạt động trên các trình duyệt cũ hơn không hỗ trợ ES5. - Lior
Bạn có thể dạy những trình duyệt đó array.forEach bằng cách chèn mã nhất định vào tập lệnh của bạn. Xem Polyfill developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Qwerty
Tất nhiên, nhưng sau đó bạn đang thao túng nguyên mẫu, và đó không phải lúc nào cũng là một ý tưởng tốt ... và vân vân, bạn có vấn đề về thứ tự ... - Lior
Và, tất nhiên, lý do thứ ba: Các mảng thưa thớt. - a better oliver