Tại sao document.querySelectorAll trả về StaticNodeList thay vì Array thực?


103

Tôi thấy lỗi rằng tôi không thể làm document.querySelectorAll(...).map(...)ngay cả trong Firefox 3.6 và tôi vẫn không thể tìm thấy câu trả lời, vì vậy tôi nghĩ rằng tôi sẽ đăng chéo SO câu hỏi từ blog này:

http://blowery.org/2008/08/29/yay-for-queryselectorall-boo-for-staticnodelist/

Có ai biết lý do kỹ thuật khiến bạn không nhận được Mảng không? Hoặc tại sao một StaticNodeList không kế thừa từ một Array theo cách như vậy mà bạn có thể sử dụng map, concat, vv?

(BTW nếu đó chỉ là một chức năng bạn muốn, bạn có thể làm điều gì đó như NodeList.prototype.map = Array.prototype.map;... nhưng một lần nữa, tại sao chức năng này (cố ý?) Bị chặn ngay từ đầu?)


3
Trên thực tế, getElementsByTagName cũng không trả về Mảng mà là một tập hợp, và nếu bạn muốn sử dụng nó như Mảng (với các phương thức như concat, v.v.), bạn phải chuyển đổi bộ sưu tập đó thành Mảng bằng cách thực hiện một vòng lặp và sao chép từng phần tử của tập hợp vào một Mảng. Không ai phàn nàn về điều này.
Marco Demaio

Câu trả lời:


81

Tôi tin rằng đó là một quyết định triết học của W3C. Thiết kế của W3C DOM [spec] khá trực quan với thiết kế của JavaScript, vì DOM có nghĩa là nền tảng và ngôn ngữ trung lập.

Các quyết định như " getElementsByFoo()trả về một thứ tự NodeList" hoặc " querySelectorAll()trả về một StaticNodeList" là rất có chủ đích, vì vậy việc triển khai không phải lo lắng về việc căn chỉnh cấu trúc dữ liệu trả về của chúng dựa trên các triển khai phụ thuộc vào ngôn ngữ (như .mapcó sẵn trên Mảng trong JavaScript và Ruby, nhưng không có trên Danh sách trong C #).

Mục tiêu của W3C thấp: họ sẽ nói a NodeListnên chứa thuộc .lengthtính chỉ đọc thuộc loại unsigned long vì họ tin rằng mọi triển khai ít nhất có thể hỗ trợ điều đó , nhưng họ sẽ không nói rõ ràng rằng []toán tử chỉ mục nên được quá tải để hỗ trợ nhận các phần tử vị trí, bởi vì họ không muốn pha trộn một số ngôn ngữ nhỏ nghèo nàn đi kèm muốn triển khai getElementsByFoo()nhưng không thể hỗ trợ quá tải toán tử. Đó là một triết lý phổ biến hiện nay trong hầu hết các thông số kỹ thuật.

John Resig đã nói về một lựa chọn tương tự như của bạn, mà anh ấy nói thêm :

Đối số của tôi không quá nhiều, NodeIteratorkhông giống DOM mà không giống JavaScript. Nó không tận dụng các tính năng có trong ngôn ngữ JavaScript và sử dụng chúng với khả năng tốt nhất của nó ...

Tôi đồng cảm phần nào. Nếu DOM được viết riêng với các tính năng JavaScript, nó sẽ bớt khó sử dụng hơn và trực quan hơn rất nhiều. Đồng thời, tôi hiểu các quyết định thiết kế của W3C.


Cảm ơn, điều đó giúp tôi hiểu tình hình.
Kev

@Kev: Tôi đã thấy bình luận của bạn trên trang blog đó đặt câu hỏi về cách bạn sẽ chuyển đổi thành StaticNodeListmảng. Tôi sẽ xác nhận câu trả lời của @ mck89 là cách để chuyển đổi a NodeList/ StaticNodeListthành Mảng nguyên bản, nhưng điều đó sẽ không thành công trong IE (8 obv) với lỗi JScript, vì các đối tượng đó được lưu trữ / "đặc biệt".
Crescent Fresh

Đúng, đó là lý do tại sao tôi ủng hộ anh ấy. Ai đó khác đã hủy +1 của tôi. Bạn có nghĩa là gì bởi lưu trữ / đặc biệt?
Kev

1
@Kev: các biến được lưu trữ là bất kỳ biến nào được cung cấp bởi môi trường "máy chủ" (ví dụ: trình duyệt web). Ví dụ document, windowv.v. IE thường triển khai các "đặc biệt" này (ví dụ như các đối tượng COM) đôi khi không phù hợp với cách sử dụng thông thường, theo những cách nhỏ và tinh vi, chẳng hạn như Array.prototype.slice.callđánh bom khi được đưa ra StaticNodeList;)
Crescent Fresh

200

Bạn có thể sử dụng ES2015 (ES6) điều hành lây lan :

[...document.querySelectorAll('div')]

sẽ chuyển đổi StaticNodeList thành Mảng các mục.

Đây là một ví dụ về cách sử dụng nó.

[...document.querySelectorAll('div')].map(x => console.log(x.innerHTML))
<div>Text 1</div>
<div>Text 2</div>


24
Một cách khác là sử dụng Array.from () :Array.from(document.querySelectorAll('div')).map(x => console.log(x.innerHTML))
Michael Berdyshev Ngày

42

Tôi không biết tại sao nó trả về một danh sách nút thay vì một mảng, có thể vì giống như getElementsByTagName, nó sẽ cập nhật kết quả khi bạn cập nhật DOM. Dù sao một phương pháp rất đơn giản để biến đổi kết quả đó thành một mảng đơn giản là:

Array.prototype.slice.call(document.querySelectorAll(...));

và sau đó bạn có thể làm:

Array.prototype.slice.call(document.querySelectorAll(...)).map(...);

3
Trên thực tế, nó không cập nhật kết quả khi bạn cập nhật DOM - do đó 'tĩnh'. Bạn phải gọi lại qSA theo cách thủ công để cập nhật kết quả. +1 cho slicedòng mặc dù.
Kev

1
Vâng, như Kev đã nói: resultset qSA là tĩnh, getElementsByTagName () resultset là động.
joonas.fi

IE8 chỉ hỗ trợ querySelectorAll () trong chế độ tiêu chuẩn
mbokil

13

Chỉ để thêm vào những gì Crescent đã nói,

nếu đó chỉ là một chức năng bạn muốn, bạn có thể thực hiện một số việc như NodeList.prototype.map = Array.prototype.map

Đừng làm điều này! Nó hoàn toàn không đảm bảo hoạt động.

Không có tiêu chuẩn JavaScript hoặc DOM / BOM nào chỉ định rằng hàm NodeListtạo-hàm thậm chí tồn tại dưới dạng toàn cục / thuộc windowtính hoặc rằng hàm được NodeListtrả về querySelectorAllsẽ kế thừa từ nó, hoặc nguyên mẫu của nó có thể ghi hoặc hàm Array.prototype.mapsẽ thực sự hoạt động trên NodeList.

Một NodeList được phép là một 'đối tượng máy chủ' (và là một, trong IE và một số trình duyệt cũ hơn). Các Arrayphương thức được định nghĩa là được phép hoạt động trên bất kỳ 'đối tượng gốc' nào của JavaScript hiển thị số và lengththuộc tính, nhưng chúng không bắt buộc phải hoạt động trên các đối tượng máy chủ (và trong IE thì không).

Thật khó chịu khi bạn không nhận được tất cả các phương thức mảng trên danh sách DOM (tất cả chúng, không chỉ StaticNodeList), nhưng không có cách nào đáng tin cậy để làm tròn nó. Bạn sẽ phải chuyển đổi mọi danh sách DOM mà bạn nhận được trở lại Mảng theo cách thủ công:

Array.fromList= function(list) {
    var array= new Array(list.length);
    for (var i= 0, n= list.length; i<n; i++)
        array[i]= list[i];
    return array;
};

Array.fromList(element.childNodes).forEach(function() {
    ...
});

1
Bắn đi, tôi không nghĩ đến điều đó. Cảm ơn!
Kev

Tôi đồng ý +1. Chỉ là một nhận xét, tôi nghĩ rằng thực hiện "var array = []" thay vì "var array = new Array (list.length)" để làm cho mã ngắn hơn. Nhưng tôi quan tâm nếu bạn biết có thể có vấn đề khi làm điều này.
Marco Demaio

@MarcoDemaio: Không, không sao. new Array(n)chỉ cung cấp cho terp JS một gợi ý về thời gian mảng sẽ kết thúc. Điều đó có thể cho phép nó phân bổ trước lượng không gian đó, điều này có thể dẫn đến tăng tốc độ vì có thể tránh được một số phân bổ lại bộ nhớ khi mảng phát triển. Tôi không biết liệu nó có thực sự giúp ích trong các trình duyệt hiện đại hay không ... Tôi nghi ngờ là không thể đo lường được.
bobince

2
Bây giờ nó được triển khai trong Array.from ()
Michael Berdyshev vào

2

Tôi nghĩ bạn có thể đơn giản làm theo

Array.prototype.map.call(document.querySelectorAll(...), function(...){...});

Nó hoạt động hoàn hảo cho tôi


0

Đây là một tùy chọn mà tôi muốn thêm vào phạm vi các khả năng khác do những người khác đề xuất ở đây. Nó chỉ dành cho thú vui trí tuệ và không được khuyên dùng .


Nói cho vui thôi , đây là một cách để "ép" querySelectorAllphải quỳ xuống lạy bạn:

Element.prototype.querySelectorAll = (function(QSA){
    return function(){
        return [...QSA.call(this, arguments[0])]
    }
})(Element.prototype.querySelectorAll);

Bây giờ, thật tuyệt khi bước qua tất cả chức năng đó, cho nó thấy ai là ông chủ. Bây giờ tôi không biết điều gì tốt hơn, tạo một trình bao bọc hàm hoàn toàn mới được đặt tên và sau đó yêu cầu tất cả mã của bạn sử dụng tên kỳ lạ đó (khá nhiều kiểu jQuery) hoặc ghi đè hàm như trên một lần để phần còn lại của mã của bạn vẫn có thể để sử dụng tên phương thức DOM gốc querySelectorAll.

Tôi sẽ không giới thiệu điều này theo bất kỳ cách nào, trừ khi bạn thành thật không đưa ra [bạn biết không].

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.