เหตุใด document.querySelectorAll จึงส่งคืน StaticNodeList แทนที่จะเป็น Array จริง


103

มันทำให้ฉันไม่สามารถทำได้document.querySelectorAll(...).map(...)แม้กระทั่งใน Firefox 3.6 และฉันก็ยังไม่สามารถหาคำตอบได้ดังนั้นฉันจึงคิดว่าฉันจะโพสต์ข้ามคำถามจากบล็อกนี้:

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

มีใครรู้เหตุผลทางเทคนิคว่าทำไมคุณไม่ได้รับ Array? หรือทำไม StaticNodeList ไม่ได้รับมรดกจากอาร์เรย์ในลักษณะดังกล่าวที่คุณสามารถใช้map, concatetc?

(BTW ถ้าเป็นเพียงฟังก์ชั่นเดียวที่คุณต้องการคุณสามารถทำบางอย่างเช่นNodeList.prototype.map = Array.prototype.map;... แต่อีกครั้งทำไมฟังก์ชันนี้ (โดยเจตนา?) ถูกบล็อกตั้งแต่แรก?)

คำตอบ:


81

ฉันเชื่อว่าเป็นการตัดสินใจเชิงปรัชญาของ W3C การออกแบบ W3C DOM [ข้อมูลจำเพาะ] ค่อนข้างตั้งฉากกับการออกแบบของ JavaScript เนื่องจาก DOM มีจุดมุ่งหมายเพื่อให้แพลตฟอร์มและภาษาเป็นกลาง

การตัดสินใจเช่น " getElementsByFoo()ส่งคืนคำสั่งซื้อNodeList" หรือ " querySelectorAll()ส่งคืน a StaticNodeList" มีความตั้งใจเป็นอย่างมากดังนั้นการใช้งานจึงไม่ต้องกังวลเกี่ยวกับการจัดโครงสร้างข้อมูลที่ส่งคืนตามการใช้งานที่ขึ้นอยู่กับภาษา (เช่น.mapมีอยู่ในอาร์เรย์ใน JavaScript และ Ruby แต่ไม่อยู่ในรายการใน C #)

W3C มีเป้าหมายต่ำ: พวกเขาจะบอกว่าNodeListควรมีคุณสมบัติอ่านอย่างเดียว.lengthประเภทที่ไม่ได้ลงนามยาวเพราะพวกเขาเชื่อว่าอย่างน้อยการใช้งานทุกครั้งสามารถรองรับสิ่งนั้นได้ แต่พวกเขาจะไม่พูดอย่างชัดเจนว่าตัว[]ดำเนินการดัชนีควรมีมากเกินไปเพื่อรองรับการรับองค์ประกอบตำแหน่ง เพราะพวกเขาไม่ต้องการขัดขวางภาษาเล็ก ๆ น้อย ๆ ที่น่าสงสารซึ่งมาพร้อมกับที่ต้องการใช้งานgetElementsByFoo()แต่ไม่สามารถรองรับตัวดำเนินการมากเกินไป เป็นปรัชญาที่แพร่หลายอยู่ตลอดทั้งข้อมูลจำเพาะ

John Resig ได้เปล่งเสียงตัวเลือกที่คล้ายกันกับคุณซึ่งเขากล่าวเสริม :

ข้อโต้แย้งของฉันไม่มากนักที่ NodeIteratorไม่เหมือน DOM มากนักซึ่งมันไม่เหมือน JavaScript มากนัก มันไม่ได้ใช้ประโยชน์จากคุณสมบัติที่มีอยู่ในภาษา JavaScript และใช้มันอย่างสุดความสามารถ ...

ฉันค่อนข้างเห็นอกเห็นใจ หาก DOM ถูกเขียนขึ้นโดยเฉพาะโดยคำนึงถึงคุณลักษณะของ JavaScript การใช้งานจะไม่ยุ่งยากและใช้งานง่ายกว่ามาก ในขณะเดียวกันฉันก็เข้าใจการตัดสินใจออกแบบของ W3C


201

คุณสามารถใช้ตัวดำเนินการกระจาย ES2015 (ES6) :

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

จะแปลง StaticNodeList เป็น Array ของรายการ

นี่คือตัวอย่างวิธีการใช้งาน

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


42

ฉันไม่รู้ว่าทำไมมันจึงส่งคืนรายการโหนดแทนที่จะเป็นอาร์เรย์อาจเป็นเพราะเช่น getElementsByTagName มันจะอัปเดตผลลัพธ์เมื่อคุณอัปเดต DOM อย่างไรก็ตามวิธีง่ายๆในการแปลงผลลัพธ์ในอาร์เรย์ง่ายๆคือ:

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

จากนั้นคุณสามารถทำได้:

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

13

เพียงเพื่อเพิ่มสิ่งที่ Crescent พูด

หากเป็นเพียงฟังก์ชันเดียวที่คุณต้องการคุณสามารถทำบางอย่างเช่น NodeList.prototype.map = Array.prototype.map

อย่าทำแบบนี้! ไม่รับประกันเลยว่าจะได้ผล

ไม่มีมาตรฐาน JavaScript หรือ DOM / BOM ระบุว่าคอนNodeListสตรัคเตอร์ - ฟังก์ชั่นยังมีอยู่เป็น global / windowproperty หรือที่NodeListส่งคืนโดยquerySelectorAllจะสืบทอดจากมันหรือต้นแบบนั้นสามารถเขียนได้หรือฟังก์ชันArray.prototype.mapนั้นจะทำงานบน NodeList ได้จริง

NodeList ได้รับอนุญาตให้เป็น 'โฮสต์วัตถุ' (และเป็นหนึ่งใน IE และเบราว์เซอร์รุ่นเก่าบางรุ่น) Arrayวิธีการจะถูกกำหนดเป็นได้รับอนุญาตให้ทำงานใน 'วัตถุพื้นเมือง' ใด ๆ JavaScript ที่ exposes ตัวเลขและlengthคุณสมบัติ แต่พวกเขากำลังไม่จำเป็นต้องทำงานเกี่ยวกับวัตถุโฮสต์ (และใน IE ที่พวกเขาทำไม่ได้)

มันน่ารำคาญที่คุณไม่ได้รับเมธอดอาร์เรย์ทั้งหมดในรายการ DOM (ทั้งหมดไม่ใช่แค่ StaticNodeList) แต่ไม่มีวิธีที่เชื่อถือได้ คุณจะต้องแปลงรายการ DOM ทุกรายการที่คุณกลับไปเป็น Array ด้วยตนเอง:

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() {
    ...
});

2

ฉันคิดว่าคุณสามารถทำสิ่งต่อไปนี้ได้

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

มันทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน


0

นี่เป็นตัวเลือกที่ฉันต้องการเพิ่มลงในช่วงของความเป็นไปได้อื่น ๆ ที่แนะนำโดยผู้อื่นที่นี่ มันหมายเพื่อความสนุกสนานทางปัญญาเท่านั้นและไม่ควร


เพื่อความสนุกสนานนี่คือวิธี "บังคับ" querySelectorAllให้คุกเข่าลงและคำนับคุณ:

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

ตอนนี้รู้สึกดีที่ได้ก้าวข้ามหน้าที่นั้นไปทั่วแสดงให้เห็นว่าใครคือหัวหน้า ตอนนี้ฉันไม่รู้ว่ามีอะไรดีไปกว่าการสร้างฟังก์ชัน wrapper ที่มีชื่อใหม่ทั้งหมดจากนั้นให้โค้ดทั้งหมดของคุณใช้ชื่อแปลก ๆ (สไตล์ jQuery ค่อนข้างมาก) หรือแทนที่ฟังก์ชันเหมือนข้างบนหนึ่งครั้งเพื่อให้โค้ดที่เหลือของคุณยังคงสามารถใช้ได้ querySelectorAllจะใช้ชื่อวิธี

ฉันจะไม่แนะนำสิ่งนี้ แต่อย่างใดเว้นแต่คุณจะไม่ให้ [คุณรู้อะไร] โดยสุจริต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.