Làm cách nào để chuyển đổi danh sách nút DOM thành một mảng trong Javascript?


96

Tôi có một hàm Javascript chấp nhận danh sách các nút HTML, nhưng nó yêu cầu một mảng Javascript (nó chạy một số phương thức Mảng trên đó) và tôi muốn cung cấp cho nó kết quả đầu ra của Document.getElementsByTagNamenó trả về một danh sách nút DOM.

Ban đầu tôi nghĩ về việc sử dụng một cái gì đó đơn giản như:

Array.prototype.slice.call(list,0)

Và điều đó hoạt động tốt trong tất cả các trình duyệt, tất nhiên là ngoại trừ Internet Explorer trả về lỗi "đối tượng JScript được mong đợi", vì rõ ràng danh sách nút DOM được trả về bởi Document.getElement*các phương thức không phải là đối tượng JScript đủ để trở thành mục tiêu của một lệnh gọi hàm.

Lưu ý: Tôi không ngại viết mã cụ thể cho Internet Explorer, nhưng tôi không được phép sử dụng bất kỳ thư viện Javascript nào như JQuery vì tôi đang viết một tiện ích con để nhúng vào trang web của bên thứ ba và tôi không thể tải các thư viện bên ngoài. sẽ tạo ra xung đột cho các khách hàng.

Nỗ lực cuối cùng của tôi là lặp lại danh sách nút DOM và tự tạo một mảng, nhưng có cách nào tốt hơn để làm điều đó không?


Tốt hơn, hãy tạo một hàm để chuyển đổi từ danh sách nút DOM, nhưng đó thực sự sẽ là giải pháp của tôi, tôi nghĩ bạn đã hiểu đúng.
Kristoffer Sall-Storgaard

> for (i = 0; i & lt; x.length; i ++) Tại sao lại lấy độ dài của NodeList ở mỗi lần lặp? Nó không chỉ lãng phí thời gian, mà vì NodeLists là tập hợp trực tiếp, nếu bất kỳ thứ gì trong phần thân của vòng lặp thay đổi độ dài của nó, bạn có thể lặp liên tục hoặc đạt một chỉ mục nằm ngoài giới hạn. Sau đó là điều tồi tệ nhất có thể xảy ra nếu bạn gán độ dài cho một biến và một lỗi tốt hơn nhiều so với một vòng lặp vô tận.

Đây là một câu hỏi thực sự cũ, nhưng jQuery được xây dựng bằng phương thức .noConflict đặc biệt nên nó sẽ không gây ra xung đột với các thư viện khác (ngay cả chính nó), có nghĩa là nhiều phiên bản jQuery có thể được tải trên một trang. Điều đó nói rằng, tốt nhất là tránh sử dụng / tải một thư viện trừ khi bạn hoàn toàn phải làm vậy.
vol7ron

@ vol7ron: tua nhanh đến năm 2016, và mọi người vẫn chưa chắc chắn về kích thước mà các thư viện javascript thêm vào trang. Đã được cấp phép, JQuery được rút gọn và gzipped là 30KB, vẫn là 30KB quá nhiều chỉ để biến đổi danh sách nút :-)
Guss

Câu trả lời:


64

NodeLists là các đối tượng máy chủ lưu trữ , việc sử dụng Array.prototype.slicephương thức trên các đối tượng máy chủ không được đảm bảo hoạt động, Đặc tả ECMAScript nêu rõ:

Việc hàm Slice có thể được áp dụng thành công cho một đối tượng chủ hay không là tùy thuộc vào việc triển khai.

Tôi khuyên bạn nên tạo một hàm đơn giản để lặp lại NodeListvà thêm từng phần tử hiện có vào một mảng:

function toArray(obj) {
  var array = [];
  // iterate backwards ensuring that length is an UInt32
  for (var i = obj.length >>> 0; i--;) { 
    array[i] = obj[i];
  }
  return array;
}

CẬP NHẬT:

Như các câu trả lời khác đề xuất, bây giờ bạn có thể sử dụng trong môi trường hiện đại cú pháp lây lan hoặc Array.fromphương pháp:

const array = [ ...nodeList ] // or Array.from(nodeList)

Nhưng nghĩ về nó, tôi đoán trường hợp sử dụng phổ biến nhất để chuyển đổi NodeList thành Mảng là lặp lại nó và bây giờ NodeList.prototypeđối tượng có forEachphương thức nguyên bản , vì vậy nếu bạn đang ở trong một môi trường hiện đại, bạn có thể sử dụng nó trực tiếp hoặc có một pollyfill.


2
Đây là tạo một mảng với thứ tự ban đầu của danh sách bị đảo ngược, mà tôi không cho rằng đó là những gì OP muốn. Bạn có ý định làm array[i] = obj[i]thay vì array.push(obj[i])?
Tim Down

@Tim, phải, tôi đã có nó như vậy trước đây nhưng đã chỉnh sửa vào đêm hôm qua mà không nhận thấy nó (3 giờ sáng giờ địa phương :), Cảm ơn !.
CMS

9
Trong những trường hợp nào sẽ obj.lengthlà bất kỳ giá trị nào khác sau đó là một số nguyên?
Peter

1
Tôi không thể tin rằng nó phức tạp như vậy. Xấu xí. Đó là một nhu cầu rất phổ biến trong lập trình Web / JS. Một phương pháp mới cho phiên bản tiếp theo của ngôn ngữ?
Andrew Koper,

1
@AlbertoPerez, không có chi !. Saludos hasta Madrid!
CMS

125

Trong es6, bạn chỉ có thể sử dụng như sau:

  • Toán tử Spread

     var elements = [... nodelist]
  • Sử dụng Array.from

     var elements = Array.from(nodelist)

tham khảo thêm tại https://developer.mozilla.org/en-US/docs/Web/API/NodeList


4
dễ dàng như vậy với Array.from(): D
Josan Iracheta

4
trong trường hợp ai đó đang sử dụng phương pháp này với Typecript (sang ES5), chỉ Array.fromhoạt động, vì TS chuyển điều này sang nodelist.slice- không được hỗ trợ.
Peter Albert

Tôi đã trả lời như vậy một năm trước khi bạn và bạn vượt qua tôi về số phiếu bầu? Tôi không thể giải thích điều này ..
vsync

3
@vsync, câu trả lời của bạn không đề cậpArray.from
ESR

@EdmundReed - vậy? làm thế nào để điều đó biện minh cho nó. nó dài hơn để viết vì vậy trong tình huống thực tế nó sẽ không bao giờ được sử dụng, chỉ spreadđược sử dụng.
vsync

16

Sử dụng spread (ES2015) , thật dễ dàng như:[...document.querySelectorAll('p')]

(tùy chọn: sử dụng Babel để chuyển mã ES6 ở trên sang cú pháp ES5)


Hãy thử nó trong bảng điều khiển của trình duyệt của bạn và xem điều kỳ diệu:

for( links of [...document.links] )
  console.log(links);

Ít nhất là chrome mới nhất, 44 tuổi, tôi nhận được điều này: Uncaught TypeError: document.querySelectorAll không phải là một hàm (…)
Nick

@OmidHezaveh - Như tôi đã nói, đây là mã ES6. Tôi không biết Chrome 44 có hỗ trợ ES6 hay không và nếu có thì ở mức độ phù hợp nào. Đó là trình duyệt gần một năm tuổi và rõ ràng bạn sẽ phải chạy mã này trên trình duyệt hỗ trợ lây lan ES6.
vsync

Hoặc chuyển nó sang es5 trước khi thực thi
HelloWorld

8

Sử dụng thủ thuật đơn giản này

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})

Bạn có thể vui lòng giải thích tại sao bạn nghĩ điều này có cơ hội thành công hơn là sử dụng Array.prototype.slice(hoặc [].slicenhư bạn nói)? Như một lưu ý, tôi muốn nhận xét rằng lỗi cụ thể của IE mà tôi đã ghi lại trong Q xảy ra trong IE 8 trở xuống, nơi mapvẫn chưa được triển khai. Trong IE 9 ("chế độ tiêu chuẩn") hoặc cao hơn, cả hai slicemapthành công theo cùng một cách.
Guss

6

Mặc dù nó không thực sự là một miếng đệm thích hợp, vì không có thông số kỹ thuật yêu cầu làm việc với các phần tử DOM, tôi đã tạo một cái để cho phép bạn sử dụng slice()theo cách này: https://gist.github.com/brettz9/6093105

CẬP NHẬT : Khi tôi nêu điều này với trình soạn thảo của đặc tả DOM4 (hỏi liệu họ có thể thêm các hạn chế của riêng mình vào các đối tượng lưu trữ hay không (để thông số này yêu cầu người triển khai chuyển đổi đúng các đối tượng này khi được sử dụng với các phương thức mảng) ngoài thông số ECMAScript đã có được phép độc lập với việc triển khai), ông trả lời rằng "Các đối tượng máy chủ lưu trữ ít nhiều đã lỗi thời theo ES6 / IDL." Tôi thấy theo http://www.w3.org/TR/WebIDL/#es-array rằng thông số kỹ thuật có thể sử dụng IDL này để xác định "các đối tượng mảng nền tảng" nhưng http://www.w3.org/TR/domcore/ không dường như không sử dụng IDL mới cho HTMLCollection(mặc dù có vẻ như nó có thể đang làm như vậy Element.attributesmặc dù nó chỉ tuyên bố rõ ràng rằng nó đang sử dụng WebIDL cho DOMString và DOMTimeStamp). Tôi thấy[ArrayClass](kế thừa từ Array.prototype) được sử dụng cho NodeList(và NamedNodeMaphiện không được dùng nữa vì mục duy nhất vẫn sẽ sử dụng nó, Element.attributes). Trong mọi trường hợp, có vẻ như nó sẽ trở thành tiêu chuẩn. ES6 Array.fromcũng có thể thuận tiện hơn cho các chuyển đổi như vậy so với việc phải chỉ định Array.prototype.slicevà rõ ràng hơn về mặt ngữ nghĩa so với [].slice()(và dạng ngắn hơn, Array.slice()(một "mảng chung"), theo như tôi biết, không trở thành hành vi tiêu chuẩn).


Tôi đã cập nhật để chỉ ra rằng các thông số kỹ thuật có thể đang di chuyển theo hướng yêu cầu hành vi này.
Brett Zamir

5

Ngày nay, vào năm 2018, chúng ta có thể sử dụng ECMAScript 2015 (Phiên bản thứ 6) hoặc ES6, nhưng không phải tất cả các trình duyệt đều có thể hiểu được nó (ví dụ: IE không hiểu tất cả). Nếu bạn muốn, bạn có thể sử dụng ES6 như sau: var array = [... NodeList];( như toán tử spread ) hoặcvar array = Array.from(NodeList); .

Trong trường hợp khác (nếu bạn không thể sử dụng ES6), bạn có thể sử dụng cách ngắn nhất để chuyển đổi NodeList thành Array:

var array = [].slice.call(NodeList, 0);.

Ví dụ:

var nodeList = document.querySelectorAll('input');
//we use "{}.toString.call(Object).slice(8, -1)" to find the class name of object
console.log({}.toString.call(nodeList).slice(8, -1)); //NodeList

var array = [].slice.call(nodeList, 0);
console.log({}.toString.call(array).slice(8, -1)); //Array

var result = array.filter(function(item){return item.value.length > 5});

for(var i in result)
  console.log(result[i].value); //credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Nhưng nếu bạn chỉ muốn lặp lại DOMdanh sách nút một cách dễ dàng, thì bạn không cần phải chuyển đổi a NodeListthành an Array. Có thể lặp lại các mục NodeListbằng cách sử dụng:

var nodeList = document.querySelectorAll('input');
// Calling nodeList.item(i) isn't necessary in JavaScript
for(var i = 0; i < nodeList.length; i++)
    console.log(nodeList[i].value); //trust, credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Đừng muốn sử dụng for...inhoặc for each...inliệt kê các mục trong danh sách, vì điều đó cũng sẽ liệt kê độ dài và thuộc tính mục của NodeListvà gây ra lỗi nếu tập lệnh của bạn giả định rằng nó chỉ phải xử lý các đối tượng phần tử. Ngoài ra, for..inkhông được đảm bảo để thăm các tài sản theo bất kỳ thứ tự cụ thể nào. for...ofvòng lặp sẽ lặp qua các đối tượng NodeList một cách chính xác.

Xem quá:


3
var arr = new Array();
var x= ... get your nodes;

for (i=0;i<x.length;i++)
{
  if (x.item(i).nodeType==1)
  {
    arr.push(x.item(i));
  }
}

Điều này sẽ hoạt động, trình duyệt chéo và giúp bạn có được tất cả các nút "phần tử".


1
Về cơ bản, điều này giống với câu trả lời của @ CMS, ngoại trừ việc nó giả định rằng tôi chỉ muốn các nút phần tử - mà tôi không muốn.
Guss
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.