Lỗi cơ thể của một lỗi trong JSLint nên được gói trong một câu lệnh if 'nghĩa là gì?


242

Tôi đã sử dụng JSLint trên một tệp JavaScript của tôi. Nó đã ném lỗi:

for( ind in evtListeners ) {

Vấn đề ở dòng 41 ký tự 9: Phần thân của một for in phải được gói trong một câu lệnh if để lọc các thuộc tính không mong muốn từ nguyên mẫu.

Điều đó có nghĩa là gì?


5
Theo mặc định, 'in' cũng lặp lại các thuộc tính được kế thừa. Thông thường, cơ thể được bọc trong if (evtListeners.hasOwnProperty(ind))để hạn chế xử lý chỉ sở hữu các thuộc tính (không được kế thừa). Tuy nhiên, trong một số trường hợp, bạn thực sự muốn lặp lại tất cả các thuộc tính, bao gồm cả các thuộc tính được kế thừa. Trong trường hợp đó, JSLint buộc bạn phải bọc thân vòng lặp trong một câu lệnh if để quyết định các thuộc tính nào bạn thực sự muốn. Điều này sẽ hoạt động và làm cho JSlint hạnh phúc: if (evtListeners[ind] !== undefined)
xorcus

1
Hầu hết các câu trả lời đã lỗi thời. một giải pháp cập nhật có thể được tìm thấy tại stackoverflow.com/a/10167931/3138375
eli-bd

Câu trả lời:


430

Trước hết, không bao giờ sử dụng một for invòng lặp để liệt kê trên một mảng. Không bao giờ. Dùng cũ tốt for(var i = 0; i<arr.length; i++).

Lý do đằng sau điều này là như sau: mỗi đối tượng trong JavaScript có một trường đặc biệt được gọi prototype. Mọi thứ bạn thêm vào trường đó sẽ có thể truy cập được trên mọi đối tượng thuộc loại đó. Giả sử bạn muốn tất cả các mảng có một hàm mới thú vị được gọi là filter_0sẽ lọc các số không.

Array.prototype.filter_0 = function() {
    var res = [];
    for (var i = 0; i < this.length; i++) {
        if (this[i] != 0) {
            res.push(this[i]);
        }
    }
    return res;
};

console.log([0, 5, 0, 3, 0, 1, 0].filter_0());
//prints [5,3,1]

Đây là một cách tiêu chuẩn để mở rộng các đối tượng và thêm các phương thức mới. Rất nhiều thư viện làm điều này. Tuy nhiên, hãy xem cách làm for inviệc bây giờ:

var listeners = ["a", "b", "c"];
for (o in listeners) {
    console.log(o);
}
//prints:
//  0
//  1
//  2
//  filter_0

Bạn có thấy? Nó đột nhiên nghĩ rằng bộ lọc_0 là một chỉ mục mảng khác. Tất nhiên, nó không thực sự là một chỉ mục số, nhưng for inliệt kê thông qua các trường đối tượng, không chỉ các chỉ mục số. Vì vậy, bây giờ chúng tôi liệt kê thông qua mọi chỉ số filter_0 . Nhưng filter_0không phải là một trường của bất kỳ đối tượng mảng cụ thể nào, mọi đối tượng mảng đều có thuộc tính này.

May mắn thay, tất cả các đối tượng đều có một hasOwnPropertyphương thức, kiểm tra xem trường này có thực sự thuộc về chính đối tượng đó hay không nếu nó được kế thừa từ chuỗi nguyên mẫu và do đó thuộc về tất cả các đối tượng thuộc loại đó.

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}
 //prints:
 //  0
 //  1
 //  2

Lưu ý, mặc dù mã này hoạt động như mong đợi cho mảng, bạn không bao giờ, không bao giờ , sử dụng for infor each incho mảng. Hãy nhớ rằng for inliệt kê các trường của một đối tượng, không phải các chỉ mục hoặc giá trị mảng.

var listeners = ["a", "b", "c"];
listeners.happy = "Happy debugging";

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}

 //prints:
 //  0
 //  1
 //  2
 //  happy

43
Bạn không nên sử dụng for inđể lặp lại trên các mảng vì ngôn ngữ không thể hiện thứ tự for insẽ liệt kê trên một mảng. Nó có thể không theo thứ tự số. Ngoài ra, nếu bạn sử dụng cấu trúc kiểu `for (i = 0; i <Array.length; i ++), bạn có thể chắc chắn rằng bạn chỉ lặp lại các chỉ mục số theo thứ tự và không có thuộc tính chữ và số.
Breton

Cảm ơn! Tôi sẽ lưu nó làm tài liệu tham khảo.
nyuszika7h

Tôi thấy mình đang nhìn vào câu trả lời này một lần nữa bởi vì tôi tin rằng bit này của JSLint đã bị hỏng. Tôi đã có mã đại khái: for (o in lắng nghe) {if (lắng nghe.hasOwnProperty (i)) {console.log (o); }} Vấn đề là tôi đã gặp lỗi, tôi đã chuyển tên biến i thành o và bỏ lỡ một tham chiếu. JSLint đủ thông minh để đảm bảo rằng bạn đang kiểm tra hasOwnProperty cho đúng thuộc tính trên đúng đối tượng.
lôi cuốn

12
tuy nhiên, trong đó là tốt để lặp lại trên tài sản của một đối tượng. OP không bao giờ nói for in được áp dụng cho một Array. HasOwnProperty là cách thực hành tốt nhất, tuy nhiên, có những trường hợp bạn không muốn nó - ví dụ nếu một đối tượng mở rộng một đối tượng khác và bạn muốn liệt kê cả các đối tượng và thuộc tính của cha mẹ.
gotofritz

3
Tôi nghĩ rằng thay vì khiến mọi người sợ hãi khỏi for-incác vòng lặp (điều này thật tuyệt vời), chúng ta nên giáo dục họ cách họ làm việc (thực hiện đúng trong câu trả lời này) và giới thiệu cho họ để Object.defineProperty()họ có thể mở rộng nguyên mẫu một cách an toàn mà không phá vỡ bất cứ điều gì. Ngẫu nhiên, việc mở rộng nguyên mẫu của các đối tượng bản địa không nên được thực hiện mà không có Object.defineProperty.
Robert Rossmann

87

Douglas Crockford, tác giả của jslint đã viết (và nói) về vấn đề này nhiều lần. Có một phần này trang của trang web của mình trong đó bao gồm này:

cho tuyên bố

A cho lớp các câu lệnh nên có dạng sau:

for (initialization; condition; update) {
    statements
}

for (variable in object) {
    if (filter) {
        statements
    } 
}

Hình thức đầu tiên nên được sử dụng với các mảng và với các vòng lặp có số lần lặp được xác định trước.

Hình thức thứ hai nên được sử dụng với các đối tượng. Xin lưu ý rằng các thành viên được thêm vào nguyên mẫu của đối tượng sẽ được đưa vào bảng liệt kê. Sẽ là khôn ngoan khi lập trình phòng thủ bằng cách sử dụng phương thức hasOwnProperty để phân biệt các thành viên thực sự của đối tượng:

for (variable in object) {
    if (object.hasOwnProperty(variable)) {
        statements
    } 
}

Crockford cũng có một loạt video về nhà hát YUI nơi anh nói về điều này. Một loạt các video / bài nói về javascript của Crockford là phải xem nếu bạn thậm chí hơi nghiêm túc về javascript.


21

Xấu: (jsHint sẽ đưa ra lỗi)

for (var name in item) {
    console.log(item[name]);
}

Tốt

for (var name in item) {
  if (item.hasOwnProperty(name)) {
    console.log(item[name]);
  }
}

8

Câu trả lời của Vava là trên nhãn hiệu. Nếu bạn sử dụng jQuery, thì $.each()hàm này sẽ đảm nhiệm việc này, do đó sẽ an toàn hơn khi sử dụng.

$.each(evtListeners, function(index, elem) {
    // your code
});

5
Nếu hiệu suất là bất kỳ cân nhắc nào ở đây, tôi sẽ không khuyên bạn nên sử dụng $.each(hoặc underscore.js _.each) nếu bạn có thể thoát khỏi forvòng lặp thô . jsperf có một vài bài kiểm tra so sánh mở mắt đáng để chạy.
nickb

3
Điều này ( jsperf.com/each-vs-each-vs-for-in/3 ) thực tế hơn vì nó sử dụng bộ lọc proto cơ bản
dvdrtrgn

7

@all - mọi thứ trong JavaScript là một đối tượng (), vì vậy các câu như "chỉ sử dụng điều này trên các đối tượng" là một chút sai lệch. Ngoài ra, JavaScript không được gõ mạnh để 1 == "1" là đúng (mặc dù 1 === "1" thì không, Crockford rất quan tâm đến vấn đề này). Khi nói đến khái niệm progromatic của mảng trong JS, việc gõ rất quan trọng trong định nghĩa.

@Brenton - Không cần phải là một nhà độc tài thuật ngữ; "mảng kết hợp", "từ điển", "hàm băm", "đối tượng", tất cả các khái niệm lập trình này đều áp dụng cho một cấu trúc trong JS. Đó là cặp giá trị tên (khóa, chỉ mục), trong đó giá trị có thể là bất kỳ đối tượng nào khác (chuỗi cũng là đối tượng)

Vì vậy, new Array()giống như[]

new Object() gần giống với {}

var myarray = [];

Tạo một cấu trúc là một mảng với hạn chế là tất cả các chỉ mục (còn gọi là khóa) phải là một số nguyên. Nó cũng cho phép tự động gán các chỉ mục mới thông qua .push ()

var myarray = ["one","two","three"];

Thực sự là giải quyết tốt nhất thông qua for(initialization;condition;update){

Nhưng những gì về:

var myarray = [];
myarray[100] = "foo";
myarray.push("bar");

Thử cái này:

var myarray = [], i;
myarray[100] = "foo";
myarray.push("bar");
myarray[150] = "baz";
myarray.push("qux");
alert(myarray.length);
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}

Có lẽ không phải là cách sử dụng tốt nhất của một mảng, nhưng chỉ là một minh họa rằng mọi thứ không phải lúc nào cũng rõ ràng.

Nếu bạn biết các khóa của mình và chắc chắn nếu chúng không phải là số nguyên thì tùy chọn cấu trúc giống như mảng duy nhất của bạn là đối tượng.

var i, myarray= {
   "first":"john",
   "last":"doe",
   100:"foo",
   150:"baz"
};
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}

"Chỉ sử dụng cái này cho các đối tượng" có nghĩa là không sử dụng nó trên Mảng hoặc bất cứ thứ gì mở rộng Object, nếu không, như bạn chỉ ra, nó sẽ rất ngớ ngẩn vì mọi thứ đều mở rộng Object
Juan Mendes

'"mảng kết hợp", "từ điển", "hàm băm", "đối tượng", tất cả các khái niệm lập trình này đều áp dụng cho một cấu trúc trong JS.' Không. Chúng là những khái niệm khác nhau từ các ngôn ngữ khác nhau, có điểm tương đồng với nhau. Nhưng nếu bạn cứ cho rằng chúng giống hệt nhau / và được sử dụng theo cùng một cách, cho cùng một mục đích, bạn sẽ tự đặt ra cho mình một số sai lầm thực sự ngu ngốc mà bạn có thể tránh bằng cách bớt thờ ơ về cách ngôn ngữ bạn đang sử dụng công trình.
Breton

2

Chắc chắn đó là một chút cực đoan để nói

... không bao giờ sử dụng vòng lặp for để liệt kê một mảng. Không bao giờ. Sử dụng cũ tốt cho (var i = 0; i <Array.length; i ++)

?

Đó là giá trị làm nổi bật phần trong trích xuất Douglas Crockford

... Nên sử dụng hình thức thứ hai với các đối tượng ...

Nếu bạn yêu cầu một mảng kết hợp (còn gọi là hashtable / dictionary) trong đó các khóa được đặt tên thay vì được lập chỉ mục bằng số, bạn sẽ phải thực hiện điều này như một đối tượng, ví dụ var myAssocArray = {key1: "value1", key2: "value2"...};.

Trong trường hợp myAssocArray.lengthnày sẽ xuất hiện null (vì đối tượng này không có thuộc tính 'độ dài') và bạn i < myAssocArray.lengthsẽ không đưa bạn đi rất xa. Ngoài việc mang lại sự thuận tiện hơn, tôi mong muốn các mảng kết hợp sẽ mang lại lợi thế về hiệu suất trong nhiều tình huống, vì các khóa mảng có thể là các thuộc tính hữu ích (nghĩa là tên hoặc thuộc tính ID của thành viên mảng), nghĩa là bạn không phải lặp đi lặp lại trong một thời gian dài mảng liên tục đánh giá nếu các câu lệnh để tìm mục nhập mảng bạn đang theo sau.

Dù sao, cũng cảm ơn vì đã giải thích các thông báo lỗi của JSLint, tôi sẽ sử dụng kiểm tra 'isOwnProperty' ngay bây giờ khi liên kết qua vô số mảng kết hợp của tôi!


1
Bạn vô cùng bối rối. Không có thứ gọi là "mảng kết hợp" trong javascript. Đó đúng là một khái niệm php.
Breton

Đúng là những đồ vật này không có lengthtài sản, nhưng bạn có thể làm theo cách khác:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
nyuszika7h

3
@ Nyuszika7H Đó là cách sai. Nếu bạn không cần Mảng được lập chỉ mục số nguyên, bạn không nên sử dụng var myArr = [], thì đó là var myArr = {}trong PHP chúng là cùng một thứ, nhưng không phải trong JS.
Juan Mendes

"Mảng" kết hợp không phải là một mảng.
Vincent McNabb


0

Chỉ để thêm vào chủ đề của in / for / $. Mỗi cái, tôi đã thêm một trường hợp thử nghiệm jsperf để sử dụng $ .each vs for in: http://jsperf.com/each-vs-for-in/2

Các trình duyệt / phiên bản khác nhau xử lý nó khác nhau, nhưng có vẻ như $ .each và nói thẳng ra là những lựa chọn rẻ nhất về hiệu năng.

Nếu bạn đang sử dụng để lặp lại thông qua một mảng / đối tượng kết hợp, biết bạn đang theo đuổi điều gì và bỏ qua mọi thứ khác, hãy sử dụng $ .each nếu bạn sử dụng jQuery hoặc chỉ trong (và sau đó là nghỉ; đạt được những gì bạn biết nên là yếu tố cuối cùng)

Nếu bạn đang lặp qua một mảng để thực hiện một cái gì đó với từng cặp khóa trong đó, nên sử dụng phương thức hasOwnProperty nếu bạn KHÔNG sử dụng jQuery và sử dụng $ .each nếu bạn sử dụng jQuery.

Luôn luôn sử dụng for(i=0;i<o.length;i++)nếu bạn không cần một mảng kết hợp mặc dù ... lol chrome đã thực hiện nhanh hơn 97% so với một trong hoặc$.each

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.