Apa yang dilakukan [] .forEach.call () dalam JavaScript?

135

Saya sedang melihat beberapa potongan kode, dan saya menemukan beberapa elemen memanggil fungsi dari daftar node dengan forEach diterapkan pada array kosong.

Misalnya saya punya sesuatu seperti:

[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

tapi saya tidak bisa mengerti cara kerjanya. Adakah yang bisa menjelaskan perilaku array kosong di depan forEach dan bagaimana cara callkerjanya?

Mimo
sumber

Jawaban:

203

[]adalah sebuah array.
Array ini tidak digunakan sama sekali.

Itu sedang diletakkan di halaman, karena menggunakan array memberi Anda akses ke prototipe array, seperti .forEach.

Ini lebih cepat daripada mengetik Array.prototype.forEach.call(...);

Berikutnya, forEachadalah fungsi yang mengambil fungsi sebagai input ...

[1,2,3].forEach(function (num) { console.log(num); });

... dan untuk setiap elemen di this( di mana thisseperti array, karena memiliki lengthdan Anda dapat mengakses bagian-bagiannya seperti this[1]) itu akan melewati tiga hal:

  1. elemen dalam array
  2. indeks elemen (elemen ketiga akan berlalu 2)
  3. referensi ke array

Terakhir, .calladalah prototipe yang memiliki fungsi (ini adalah fungsi yang dipanggil pada fungsi lain).
.callakan mengambil argumen pertamanya dan mengganti thisbagian dalam fungsi biasa dengan apa pun yang Anda lewati call, sebagai argumen pertama ( undefinedatau nullakan digunakan windowdalam JS sehari-hari, atau akan menjadi apa pun yang Anda berikan, jika dalam "mode ketat"). Sisa argumen akan diteruskan ke fungsi asli.

[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
    console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"

Oleh karena itu, Anda membuat cara cepat untuk memanggil forEachfungsi, dan Anda mengubah thisdari array kosong ke daftar semua <a>tag, dan untuk setiap <a>pesanan, Anda memanggil fungsi yang disediakan.

EDIT

Kesimpulan logis / Pembersihan

Di bawah ini, ada tautan ke artikel yang menyarankan agar kami membatalkan upaya pemrograman fungsional, dan tetap berpegang pada pengulangan inline manual, setiap saat, karena solusi ini bersifat hack-ish dan tidak sedap dipandang.

Saya akan mengatakan bahwa sementara .forEachkurang bermanfaat daripada rekan-rekan, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), masih melayani tujuan ketika semua Anda benar-benar ingin lakukan adalah memodifikasi dunia luar (bukan array), n-kali, sementara memiliki akses ke baik arr[i]atau i.

Jadi bagaimana kita menghadapi perbedaan ini, karena Motto jelas adalah orang yang berbakat dan berpengetahuan luas, dan saya ingin membayangkan bahwa saya tahu apa yang saya lakukan / ke mana saya akan pergi (sekarang dan kemudian ... ... lainnya kali ini adalah pembelajaran kepala-pertama)?

Jawabannya sebenarnya cukup sederhana, dan sesuatu Paman Bob dan Sir Crockford akan menghadapinya, karena pengawasan:

bersihkan .

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
  return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

Sekarang, jika Anda mempertanyakan apakah Anda perlu melakukan ini, sendiri, jawabannya mungkin tidak ...
Hal yang pasti ini dilakukan oleh ... ... setiap (?) Perpustakaan dengan fitur tingkat tinggi hari ini.
Jika Anda menggunakan lodash atau garis bawah atau bahkan jQuery, mereka semua akan memiliki cara untuk mengambil set elemen, dan melakukan aksi n-kali.
Jika Anda tidak menggunakan hal seperti itu, maka tentu saja, tulis sendiri.

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);
};

Pembaruan untuk ES6 (ES2015) dan Beyond

Tidak hanya metode pembantu slice( )/ array( )/ etc akan membuat hidup lebih mudah bagi orang-orang yang ingin menggunakan daftar seperti mereka menggunakan array (sebagaimana mestinya), tetapi untuk orang-orang yang memiliki kemewahan beroperasi di browser ES6 + yang relatif dekat di masa depan, atau "pengalihan" di Babel hari ini, Anda memiliki fitur bahasa bawaan, yang membuat hal semacam ini tidak perlu.

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 ];

Super bersih, dan sangat bermanfaat.
Cari operator Istirahat dan Sebarkan ; coba mereka di situs BabelJS; jika tumpukan teknologi Anda dalam urutan, gunakan mereka dalam produksi dengan Babel dan langkah membangun.


Tidak ada alasan bagus untuk tidak dapat menggunakan transformasi dari non-array menjadi array ... ... hanya saja jangan membuat kekacauan kode Anda melakukan apa-apa selain menempelkan garis jelek yang sama, di mana-mana.

Norguard
sumber
Sekarang saya agak bingung jika saya melakukannya: console.log (ini); Saya akan selalu mendapatkan objek jendela Dan saya pikir. Panggilan dapat mengubah nilai ini
Muhammad Saleh
.call tidak mengubah nilai this, itulah sebabnya Anda menggunakan calldengan forEach. Jika forEach terlihat seperti itu forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }maka dengan mengubah thisdengan mengatakan forEach.call( newArrLike )berarti bahwa ia akan menggunakan objek baru tersebut sebagai this.
Norguard
2
@MuhammadSaleh .calltidak mengubah nilai thisdi dalam apa yang Anda berikan forEach, .callmengubah nilai this di dalam forEach . Jika Anda bingung mengapa thistidak diteruskan forEachke fungsi yang ingin Anda panggil, Anda harus melihat perilaku thisdalam JavaScript. Sebagai tambahan, hanya berlaku di sini (dan memetakan / memfilter / mengurangi), ada argumen kedua forEach, yang menetapkan thisdi dalam fungsi arr.forEach(fn, thisInFn)atau [].forEach.call(arrLike, fn, thisInFn);atau hanya menggunakan .bind; arr.forEach(fn.bind(thisInFn));
Norguard
1
@thetrystero itulah intinya. []hanya ada sebagai sebuah array, sehingga Anda dapat .calldengan.forEach metode ini, dan ubah thiske set yang ingin Anda kerjakan. .callterlihat seperti ini: function (thisArg, a, b, c) { var method = this; method(a, b, c); }kecuali bahwa ada internal yang hitam-sihir untuk set thisdalam methodmenyamai apa yang Anda lulus sebagai thisArg.
Norguard
1
singkatnya ... Array.from ()?
zakius
53

The querySelectorAllMetode mengembalikan NodeList, yang mirip dengan array, tapi itu tidak cukup array. Oleh karena itu, ia tidak memiliki forEachmetode (yang mewarisi objek array melalui Array.prototype).

Karena a NodeListmirip dengan array, metode array akan benar-benar bekerja di atasnya, jadi dengan menggunakan [].forEach.callAnda memanggil Array.prototype.forEachmetode dalam konteks NodeList, seolah-olah Anda bisa melakukannya yourNodeList.forEach(/*...*/).

Perhatikan bahwa literal array kosong hanyalah jalan pintas ke versi yang diperluas, yang mungkin akan sering Anda lihat juga:

Array.prototype.forEach.call(/*...*/);
James Allardice
sumber
7
Jadi, untuk memperjelas, tidak ada keuntungan menggunakan [].forEach.call(['a','b'], cb)lebih ['a','b'].forEach(cb)dalam aplikasi sehari-hari dengan array standar, hanya ketika mencoba untuk beralih di atas struktur seperti array yang tidak forEachmemiliki prototipe mereka sendiri? Apakah itu benar?
Matt Fletcher
2
@MattFletcher: Ya itu benar. Keduanya akan bekerja tetapi mengapa hal-hal rumit terlalu rumit? Panggil saja metode langsung pada array itu sendiri.
James Allardice
Keren Terimakasih. Dan saya tidak tahu mengapa, mungkin hanya untuk kredit jalanan, mengesankan wanita tua di ALDI dan semacamnya. Saya akan tetap berpegang pada [].forEach():)
Matt Fletcher
var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) berfungsi dengan baik
daslicht
@daslicht bukan di versi IE yang lebih lama! (yang melakukan , bagaimanapun, mendukung forEachpada array, tetapi tidak objek NodeList)
Djave
21

Jawaban lain telah menjelaskan kode ini dengan sangat baik, jadi saya hanya akan menambahkan saran.

Ini adalah contoh kode yang baik yang harus di refactored untuk kesederhanaan dan kejelasan. Alih-alih menggunakan [].forEach.call()atau Array.prototype.forEach.call()setiap kali Anda melakukan ini, buatlah fungsi sederhana darinya:

function forEach( list, callback ) {
    Array.prototype.forEach.call( list, callback );
}

Sekarang Anda dapat memanggil fungsi ini alih-alih kode yang lebih rumit dan tidak jelas:

forEach( document.querySelectorAll('a'), function( el ) {
   // whatever with the current node
});
Michael Geary
sumber
5

Itu bisa lebih baik menggunakan tulisan

Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {

});

Apa yang dilakukan adalah document.querySelectorAll('a')mengembalikan objek yang mirip dengan array, tetapi tidak mewarisi dari Arraytipe. Jadi kita memanggil forEachmetode dari Array.prototypeobjek dengan konteks sebagai nilai yang dikembalikan olehdocument.querySelectorAll('a')

Arun P Johny
sumber
4
[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

Pada dasarnya sama dengan:

var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
   // whatever with the current node
});
vava
sumber
2

Ingin memperbarui pertanyaan lama ini:

Alasan untuk menggunakan [].foreach.call()untuk mengulang elemen di browser modern sebagian besar sudah berakhir. Kita bisa menggunakan document.querySelectorAll("a").foreach()secara langsung.

Objek NodeList adalah kumpulan node, biasanya dikembalikan oleh properti seperti Node.childNodes dan metode seperti document.querySelectorAll ().

Meskipun NodeList bukan Array, dimungkinkan untuk beralih di atasnya dengan forEach () . Itu juga dapat dikonversi ke Array nyata menggunakan Array.from ().

Namun, beberapa browser lama belum mengimplementasikan NodeList.forEach () atau Array.from (). Ini dapat dielakkan dengan menggunakan Array.prototype.forEach () - lihat Contoh dokumen ini.

František Heča
sumber
1

Array kosong memiliki properti forEachdalam prototipenya yang merupakan objek Function. (Array kosong hanyalah cara mudah untuk mendapatkan referensi ke forEachfungsi yang dimiliki semua Arrayobjek.) Objek fungsi, pada gilirannya, memiliki callproperti yang juga merupakan fungsi. Saat Anda menjalankan fungsi Function call, ia menjalankan function dengan argumen yang diberikan. Argumen pertama menjadithis fungsi yang disebut.

Anda dapat menemukan dokumentasi untuk callfungsi di sini . Dokumentasi forEachada di sini .

Ted Hopp
sumber
1

Cukup tambahkan satu baris:

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

Dan voila!

document.querySelectorAll('a').forEach(function(el) {
  // whatever with the current node
});

Nikmati :-)

Peringatan: NodeList adalah kelas global. Jangan gunakan rekomendasi ini jika Anda menulis perpustakaan umum. Namun itu cara yang sangat mudah untuk meningkatkan kemanjuran diri ketika Anda bekerja di situs web atau aplikasi node.js.

imos
sumber
1

Hanya solusi cepat dan kotor yang selalu saya gunakan. Saya tidak akan menyentuh prototipe, sama seperti praktik yang baik. Tentu saja, ada banyak cara untuk membuat ini lebih baik, tetapi Anda mendapatkan idenya.

const forEach = (array, callback) => {
  if (!array || !array.length || !callback) return
  for (var i = 0; i < array.length; i++) {
    callback(array[i], i);
  }
}

forEach(document.querySelectorAll('.a-class'), (item, index) => {
  console.log(`Item: ${item}, index: ${index}`);
});
Alfredo Maria Milano
sumber
0

[]selalu mengembalikan array baru, itu setara dengan new Array()tetapi dijamin untuk mengembalikan array karena Arraybisa ditimpa oleh pengguna sedangkan []tidak bisa. Jadi ini adalah cara yang aman untuk mendapatkan prototipe Array, kemudian seperti yang dijelaskan, calldigunakan untuk menjalankan fungsi pada nodelist seperti array (ini).

Memanggil fungsi dengan nilai ini dan argumen yang diberikan secara individual. mdn

Xotic750
sumber
Meskipun []sebenarnya akan selalu mengembalikan array, sementara window.Arraydapat ditimpa dengan apa pun (di semua browser lama), demikian juga dapat window.Array.prototype.forEachditimpa dengan apa pun. Tidak ada jaminan keselamatan dalam kedua kasus ini, di browser yang tidak mendukung getter / setter atau penyegelan / pembekuan objek (pembekuan menyebabkan masalah ekstensibilitasnya sendiri).
Norguard
Merasa bebas untuk mengedit jawaban untuk menambahkan informasi tambahan mengenai kemungkinan Timpa prototypeatau metode itu: forEach.
Xotic750
0

Norguard menjelaskan APA yang [].forEach.call() dilakukan dan James Allardice MENGAPA kita melakukannya: karena querySelectorAll mengembalikan aNodeList yang tidak memiliki metode forEach ...

Kecuali Anda memiliki browser modern seperti Chrome 51+, Firefox 50+, Opera 38, Safari 10.

Jika tidak, Anda dapat menambahkan Polyfill :

if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}
Mikel
sumber
0

Banyak info bagus di halaman ini (lihat answer + answer + comment ), tetapi saya baru-baru ini memiliki pertanyaan yang sama dengan OP, dan butuh beberapa penggalian untuk mendapatkan gambaran keseluruhan. Jadi, inilah versi singkatnya:

Tujuannya adalah menggunakan Arraymetode pada arrayNodeList yang tidak memiliki metode itu sendiri.

Pola yang lebih lama mengkooptasi metode Array melalui Function.call(), dan menggunakan array literal ( []) daripada Array.prototypekarena lebih pendek untuk mengetik:

[].forEach.call(document.querySelectorAll('a'), a => {})

Pola yang lebih baru (post ECMAScript 2015) akan digunakan Array.from():

Array.from(document.querySelectorAll('a')).forEach(a => {})
ellemenno
sumber