arrays - [].forEach.call() ทำอะไรใน JavaScript?
ฉันกำลังดูโค้ดบางส่วน และฉันพบหลายองค์ประกอบที่เรียกใช้ฟังก์ชันเหนือรายการโหนดโดยมี forEach ใช้กับอาเรย์ว่าง
ตัวอย่างเช่น ฉันมีบางอย่างเช่น:
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
แต่ฉันไม่เข้าใจว่ามันทำงานอย่างไร มีใครสามารถอธิบายพฤติกรรมของอาร์เรย์ว่างหน้าสำหรับ forEach และวิธีการทำงานของ call ได้ไหม?
คำตอบ #1
[] คืออาร์เรย์
อาร์เรย์นี้ไม่ได้ถูกใช้งานเลย
มันถูกใส่ลงในหน้า เพราะการใช้แถวให้คุณเข้าถึงโพรโทไทป์ของแถวได้ เช่น .forEach
นี่เร็วกว่าแค่การพิมพ์ Array.prototype.forEach.call(...);
ต่อไป, forEach เป็นฟังก์ชันที่รับฟังก์ชันเป็นข้อมูลนำเข้า...
[1,2,3].forEach(function (num) { console.log(num); });
...และสำหรับแต่ละองค์ประกอบใน this (ซึ่ง this คล้ายกับอาเรย์ เพราะมันมี length และคุณสามารถเข้าถึงส่วนต่าง ๆ ของมันได้เหมือน this[1]) มันจะส่งต่อสามสิ่ง:
- องค์ประกอบในอาเรย์
- ดัชนีขององค์ประกอบ (องค์ประกอบที่สามจะผ่าน
2) - การอ้างอิงถึงอาเรย์
สุดท้าย, .call เป็นพรอทไทป์ที่ฟังก์ชันมี (มันเป็นฟังก์ชันที่ถูกเรียกใช้กับฟังก์ชันอื่น ๆ)
.call จะเอาอาร์กิวเมนต์ตัวแรกและแทนที่ this ภายในฟังก์ชันปกติด้วยค่าที่คุณส่งให้ call เป็นอาร์กิวเมนต์ตัวแรก (undefined หรือ null จะใช้ window ใน JS ประจำวัน หรือจะเป็นค่าที่คุณส่งไปถ้าอยู่ใน "strict-mode") ส่วนอาร์กิวเมนต์ที่เหลือจะถูกส่งไปยังฟังก์ชันเดิม
[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"
ดังนั้น คุณกำลังสร้างวิธีการที่รวดเร็วในการเรียกฟังก์ชัน forEach และคุณกำลังเปลี่ยน this จากอาเรย์ว่างไปเป็นรายการของแท็ก <a> ทั้งหมด และสำหรับแต่ละ <a> ตามลำดับ คุณกำลังเรียกฟังก์ชันที่ให้ไว้
แก้ไข
ข้อสรุปเชิงตรรกะ/การทำความสะอาด
ด้านล่างมีลิงก์ไปยังบทความที่แนะนำให้เราทิ้งความพยายามในการเขียนโปรแกรมเชิงฟังก์ชัน และยึดติดกับการวนซ้ำด้วยมือ แบบฝังทุกครั้ง เพราะวิธีแก้ปัญหานี้ดูเหมือนแฮ็กและไม่น่ามอง
ผมจะบอกว่าแม้ว่า .forEach จะช่วยได้น้อยกว่าคู่แข่งของมันอย่าง .map(transformer), .filter(predicate), .reduce(combiner, initialValue) แต่ก็ยังมีประโยชน์เมื่อสิ่งที่คุณต้องการจริงๆ คือการแก้ไขโลกภายนอก (ไม่ใช่อาร์เรย์) n ครั้ง ในขณะที่ยังสามารถเข้าถึง arr[i] หรือ i ได้
แล้วเราจะจัดการกับความแตกต่างนี้อย่างไร เพราะชัดเจนว่าโมโต้เป็นคนที่มีความสามารถและมีความรู้ดี และผมอยากจะจินตนาการว่าผมรู้ว่าตัวเองกำลังทำอะไร/จะไปไหน (บางครั้ง... บางครั้งก็เป็นการเรียนรู้แบบลงไปก่อนเลย)?
คำตอบนั้นจริงๆ แล้วค่อนข้างง่าย และเป็นบางสิ่งที่ลุงบ็อบและเซอร์ครอคฟอร์ดทั้งคู่คงต้องผงกหน้าเพราะความพลาดนั้น:
ทำความสะอาดมัน.
function toArray (arrLike) { // or asArray(), or array(), or *whatever*
return [].slice.call(arrLike);
}
var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);
ตอนนี้ ถ้าคุณกำลังสงสัยว่าคุณจำเป็นต้องทำสิ่งนี้ด้วยตัวเองหรือไม่ คำตอบอาจจะไม่จำเป็น...
สิ่งนี้โดยตรงถูกทำโดย... ...ทุก(?) ไลบรารีที่มีฟีเจอร์สูงในปัจจุบัน
ถ้าคุณกำลังใช้ lodash หรือ underscore หรือแม้แต่ jQuery พวกมันทั้งหมดจะมีวิธีรับชุดขององค์ประกอบ และทำการกระทำซ้ำ n ครั้ง
ถ้าคุณไม่ได้ใช้สิ่งเหล่านี้ ก็แน่นอนว่าคุณสามารถเขียนเองได้
lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
var others = lib.array(arguments, 1);
return others.reduce(appendKeys, subject);
};
อัปเดตสำหรับ ES6(ES2015) และต่อไป
ไม่เพียงแต่เมธอดช่วยเหลือ slice( )/array( )/อื่น ๆ จะทำให้ชีวิตง่ายขึ้นสำหรับคนที่อยากใช้ลิสต์เหมือนกับที่ใช้แอเรย์ (ตามที่ควรจะเป็น) แต่สำหรับคนที่มีโอกาสใช้เบราว์เซอร์ ES6+ ในอนาคตอันใกล้ หรือทำการ "transpiling" ใน Babel วันนี้ คุณมีคุณสมบัติของภาษาในตัว ซึ่งทำให้สิ่งประเภทนี้ไม่จำเป็น
function countArgs (...allArgs) {
return allArgs.length;
}
function logArgs (...allArgs) {
return allArgs.forEach(arg => console.log(arg));
}
function extend (subject, ...others) { /* return ... */ }
var nodeArray = [ ...nodeList1, ...nodeList2 ];
สะอาดมาก และมีประโยชน์มาก
ลองดูตัวดำเนินการ Rest และ Spread; ทดลองใช้ที่เว็บไซต์ BabelJS; ถ้าชุดเทคโนโลยีของคุณพร้อม ใช้พวกมันในงานจริงกับ Babel และขั้นตอนการสร้าง
ไม่มีเหตุผลที่ดีที่จะไม่สามารถใช้การแปลงจากที่ไม่ใช่อาร์เรย์เป็นอาร์เรย์ได้… แค่ระวังอย่าให้โค้ดของคุณรกโดยการไม่ทำอะไรนอกจากการก็อปปี้บรรทัดที่น่าเกลียดเดียวกันนั้นไปทั่วทุกที่
คำตอบ #2
เมทอด querySelectorAll จะคืนค่า NodeList ซึ่งคล้ายกับอาร์เรย์ แต่ไม่ใช่อาร์เรย์โดยตรง ดังนั้นมันจึงไม่มีเมทอด forEach (ซึ่งออบเจ็กต์อาร์เรย์สืบทอดมาผ่าน Array.prototype)
เนื่องจาก NodeList มีลักษณะคล้ายกับอาเรย์ วิธีของอาเรย์จึงสามารถใช้งานกับมันได้ ดังนั้นโดยการใช้ [].forEach.call คุณกำลังเรียกใช้วิธี Array.prototype.forEach ในบริบทของ NodeList ราวกับว่าคุณสามารถทำได้โดยตรงอย่าง yourNodeList.forEach(/*...*/)
ทราบว่าลิเทอรัลอาร์เรย์ว่างเป็นเพียงทางลัดไปยังเวอร์ชันที่ขยายออก ซึ่งคุณน่าจะเห็นค่อนข้างบ่อยเช่นกัน:
Array.prototype.forEach.call(/*...*/);
คำตอบ #3
คำตอบอื่นได้อธิบายโค้ดนี้อย่างดีแล้ว ดังนั้นฉันจะเพียงแค่เพิ่มคำแนะนำเล็กน้อย
นี่คือตัวอย่างที่ดีของโค้ดที่ควรถูกปรับโครงสร้างใหม่เพื่อความเรียบง่ายและชัดเจน แทนที่จะใช้ [].forEach.call() หรือ Array.prototype.forEach.call() ทุกครั้งที่คุณทำแบบนี้ ให้สร้างฟังก์ชันง่ายๆ ออกมาจากมัน:
function forEach( list, callback ) {
Array.prototype.forEach.call( list, callback );
}
ตอนนี้คุณสามารถเรียกใช้ฟังก์ชันนี้แทนโค้ดที่ซับซ้อนและเข้าใจยากกว่าได้:
forEach( document.querySelectorAll('a'), function( el ) {
// whatever with the current node
});
