Tại sao gật đầu không có forEach?


91

Tôi đang làm việc trên một tập lệnh ngắn để thay đổi <abbr>văn bản bên trong của các phần tử, nhưng nhận thấy rằng nodelistkhông có forEachphương pháp. Tôi biết rằng điều nodelistđó không kế thừa từ Array, nhưng có vẻ như nó không phải forEachlà một phương pháp hữu ích để có? Có vấn đề triển khai cụ thể nào mà tôi không biết ngăn cản việc thêm forEachvào nodelistkhông?

Lưu ý: Tôi biết rằng Dojo và jQuery đều có forEachở một số dạng cho các danh sách nút của họ. Tôi không thể sử dụng do giới hạn.


6
Xin chào từ tương lai! nodeList có forEach kể từ ES6 .
Blaise

Câu trả lời:


94

NodeList hiện có forEach () trong tất cả các trình duyệt chính

Xem nodeList forEach () trên MDN .

Câu trả lời ban đầu

Không có câu trả lời nào trong số này giải thích tại sao NodeList không kế thừa từ Array, do đó cho phép nó có forEachvà tất cả phần còn lại.

Câu trả lời được tìm thấy trên chủ đề es-thảo luận này . Tóm lại, nó phá vỡ trang web:

Vấn đề là mã giả định không chính xác instanceof có nghĩa là phiên bản là một Mảng kết hợp với Array.prototype.concat.

Có một lỗi trong Thư viện đóng cửa của Google khiến hầu hết các ứng dụng của Google bị lỗi do lỗi này. Thư viện đã được cập nhật ngay sau khi điều này được tìm thấy nhưng vẫn có thể có mã ngoài đó tạo ra giả định không chính xác tương tự kết hợp với concat.

Đó là, một số mã đã làm một cái gì đó như

if (x instanceof Array) {
  otherArray.concat(x);
} else {
  doSomethingElseWith(x);
}

Tuy nhiên, concatsẽ xử lý mảng "thực" (không phải mảng instanceof) khác với các đối tượng khác:

[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]

vì vậy điều đó có nghĩa là đoạn mã trên xđã bị hỏng khi còn là NodeList, bởi vì trước khi nó đi xuống doSomethingElseWith(x)đường dẫn, trong khi sau đó nó đi xuống otherArray.concat(x)đường dẫn, điều này đã gây ra một điều gì đó kỳ lạ vì xkhông phải là một mảng thực.

Trong một thời gian, có một đề xuất cho một Elementslớp là lớp con thực sự của Array và sẽ được sử dụng làm "NodeList mới". Tuy nhiên, điều đó đã bị xóa khỏi Tiêu chuẩn DOM , ít nhất là hiện tại, vì nó không khả thi để triển khai vì nhiều lý do liên quan đến kỹ thuật và đặc điểm kỹ thuật.


11
Có vẻ như một cuộc gọi xấu cho tôi. Nghiêm túc mà nói, tôi nghĩ rằng đó là quyết định đúng đắn khi thỉnh thoảng phá vỡ mọi thứ, đặc biệt nếu điều đó có nghĩa là chúng ta có các API lành mạnh cho tương lai. Bên cạnh đó, nó không giống như web thậm chí gần như là một nền tảng ổn định, mọi người đã quen với việc javascript 2 năm tuổi của họ không còn hoạt động như mong đợi ..
JoyalToTheWorld

3
"Breaks the web"! = "Phá vỡ nội dung của Google"
Matt.

Tại sao không chỉ mượn phương thức forEach từ Array.prototype? Ví dụ: thay vì thêm Array làm nguyên mẫu, chỉ cần thực hiện this.forEach = Array.prototype.forEach trong constructor hoặc thậm chí chỉ triển khai forEach duy nhất cho NodeList?
PopKernel

1
Ghi chú; bản cập nhật này NodeList now has forEach() in all major browsersdường như ngụ ý rằng IE không phải là một trình duyệt chính. Hy vọng rằng điều đó đúng với một số người, nhưng nó chưa đúng với tôi (chưa).
Graham P Heath

58

Bạn có thể làm

Array.prototype.forEach.call (nodeList, function (node) {

    // Your code here.

} );

3
Array.prototype.forEach.callcó thể được rút ngắn đáng để[].forEach.call
CodeBrauer

7
@CodeBrauer: đó không chỉ là rút ngắn Array.prototype.forEach.callmà còn tạo ra một mảng trống và sử dụng forEachphương thức của nó .
Paul D. Waite,

34

Bạn có thể xem xét việc tạo một mảng các nút mới.

  var nodeList = document.getElementsByTagName('div'),

      nodes = Array.prototype.slice.call(nodeList,0); 

  // nodes is an array now.
  nodes.forEach(function(node){ 

       // do your stuff here.  

  });

Lưu ý: Đây chỉ là danh sách / mảng tham chiếu nút mà chúng tôi đang tạo ở đây, không có nút trùng lặp.

  nodes[0] === nodeList[0] // will be true

22
Hoặc chỉ cần làm Array.prototype.forEach.call(nodeList, fun).
akuhn

Tôi cũng xin đề nghị aliasing chức năng foreach như vậy: var forEach = Array.prototype.forEach.call(nodeList, callback);. Giờ đây, bạn có thể chỉ cần gọiforEach(nodeList, callback);
Andrew Craswell

19

Đừng bao giờ nói không bao giờ, đó là năm 2016 và NodeListđối tượng đã triển khai một forEachphương thức trong chrome mới nhất (v52.0.2743.116).

Còn quá sớm để sử dụng nó trong sản xuất vì các trình duyệt khác chưa hỗ trợ điều này (FF 49 đã thử nghiệm) nhưng tôi đoán rằng điều này sẽ sớm được chuẩn hóa.


2
Opera cũng hỗ trợ nó và hỗ trợ sẽ được thêm vào v50 của Firefox, dự kiến ​​phát hành vào ngày 15/11/16.
Shaggy

1
Mặc dù được triển khai nhưng nó không phải là một phần của bất kỳ tiêu chuẩn nào. Tốt nhất vẫn Array.prototype.slice.call(nodelist).forEach(…)là làm theo tiêu chuẩn và hoạt động trên các trình duyệt cũ.
Nate

17

Trong ngắn hạn, nó là một xung đột thiết kế để thực hiện phương pháp đó.

Từ MDN:

Tại sao tôi không thể sử dụng forEach hoặc bản đồ trên NodeList?

NodeList được sử dụng rất giống các mảng và sẽ rất hấp dẫn nếu sử dụng các phương thức Array.prototype trên chúng. Tuy nhiên, điều này là không thể.

JavaScript có cơ chế kế thừa dựa trên các nguyên mẫu. Các cá thể mảng kế thừa các phương thức mảng (chẳng hạn như forEach hoặc map) vì chuỗi nguyên mẫu của chúng trông giống như sau:

myArray --> Array.prototype --> Object.prototype --> null (chuỗi nguyên mẫu của một đối tượng có thể được lấy bằng cách gọi nhiều lần Object.getPrototypeOf)

forEach, map và những thứ tương tự là các thuộc tính riêng của đối tượng Array.prototype.

Không giống như mảng, chuỗi nguyên mẫu NodeList trông giống như sau:

myNodeList --> NodeList.prototype --> Object.prototype --> null

NodeList.prototype chứa phương thức item, nhưng không có phương thức Array.prototype nào, vì vậy chúng không thể được sử dụng trên NodeLists.

Nguồn: https://developer.mozilla.org/en-US/docs/DOM/NodeList (cuộn xuống Tại sao tôi không thể sử dụng forEach hoặc bản đồ trên NodeList? )


8
Vì vậy, vì nó là một danh sách, tại sao nó được thiết kế theo cách đó? Điều gì đã ngăn họ tạo chuỗi myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null:?
Igor Pantović

14

Nếu bạn muốn sử dụng forEach trên NodeList, chỉ cần sao chép hàm đó từ Array:

NodeList.prototype.forEach = Array.prototype.forEach;

Đó là tất cả, bây giờ bạn có thể sử dụng nó theo cách giống như bạn làm cho Array:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});

4
Ngoại trừ việc thay đổi các lớp cơ sở là không rõ ràng cho người đọc bình thường. Nói cách khác, sâu trong một số mã, bạn phải nhớ mọi tùy chỉnh bạn thực hiện đối với các đối tượng cơ sở của trình duyệt. Việc dựa vào tài liệu MDN không còn hữu ích nữa vì các đối tượng đã thay đổi hành vi so với quy chuẩn. Tốt hơn là áp dụng nguyên mẫu một cách rõ ràng tại thời điểm gọi để người đọc có thể dễ dàng nhận ra rằng forEach là một ý tưởng vay mượn chứ không phải một thứ gì đó nằm trong định nghĩa ngôn ngữ. Hãy xem câu trả lời mà @akuhn có ở trên.
Sukima

@Sukima đã gây hiểu lầm bởi cơ sở sai. Trong trường hợp cụ thể này, cách tiếp cận đã cho sẽ sửa NodeList để hoạt động như mong đợi của nhà phát triển. Đây là cách thích hợp nhất để khắc phục sự cố Lớp hệ thống. (NodeList có vẻ là chưa hoàn thành và cần được cố định trong các phiên bản tương lai của ngôn ngữ.)
Daniel Garmoshka

1
bây giờ NodeList.forEach tồn tại, đây trở thành một polyfill đơn giản vui nhộn!
Damon,

7

Trong ES2015, bây giờ bạn có thể sử dụng forEachphương thức cho nodeList.

document.querySelectorAll('abbr').forEach( el => console.log(el));

Xem Liên kết MDN

Tuy nhiên, nếu bạn muốn sử dụng Bộ sưu tập HTML hoặc các đối tượng giống mảng khác, trong es2015, bạn có thể sử dụng Array.from()phương thức. Phương thức này nhận một đối tượng giống mảng hoặc có thể lặp lại (bao gồm nodeList, HTML Collections, chuỗi, v.v.) và trả về một thể hiện Array mới. Bạn có thể sử dụng nó như thế này:

const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));

Array.from()phương thức có thể che giấu được, bạn có thể sử dụng nó trong mã es5 như thế này

var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
    console.log(el);
});

Để biết chi tiết, hãy xem trang MDN .

Để kiểm tra hỗ trợ trình duyệt hiện tại .

HOẶC LÀ

một cách khác của es2015 là sử dụng toán tử spread.

[...document.querySelectorAll('abbr')].forEach( el => console.log(el));

Toán tử spread MDN

Nhà điều hành Spread - Hỗ trợ trình duyệt


2

Giải pháp của tôi:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;

1
Thường không nên mở rộng chức năng của DOM thông qua các nguyên mẫu, đặc biệt là trong các phiên bản IE cũ hơn ( bài viết ).
KFE

0

NodeList là một phần của API DOM. Xem xét các ràng buộc ECMAScript áp dụng cho JavaScript. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html . NodeList và thuộc tính độ dài chỉ đọc và mục (chỉ mục) có chức năng trả về một nút.

Câu trả lời là, bạn phải lặp lại. Không có cách thay thế. Foreach sẽ không hoạt động. Tôi làm việc với liên kết Java DOM API và gặp vấn đề tương tự.


Nhưng có một lý do cụ thể nào tại sao nó không nên được triển khai? Cả hai jQuery và Dojo đã thực hiện nó trong thư viện riêng của họ
Snakes and Coffee

2
nhưng nó là một xung đột thiết kế như thế nào?
Snakes and Coffee

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.