Bagaimana cara kerja Array.prototype.slice.call ()?

474

Saya tahu ini digunakan untuk membuat argumen array nyata, tapi saya tidak mengerti apa yang terjadi ketika menggunakan Array.prototype.slice.call(arguments)

ilyo
sumber
2
^ sedikit berbeda karena tautan itu menanyakan tentang simpul DOM dan bukan argumen. Dan saya pikir jawabannya di sini jauh lebih baik dengan menggambarkan apa 'ini' secara internal.
sqram

Jawaban:

870

Apa yang terjadi di bawah tenda adalah bahwa ketika .slice()dipanggil secara normal, thisadalah Array, dan kemudian hanya beralih ke Array itu, dan melakukan tugasnya.

Bagaimana thisdi .slice()fungsi Array? Karena ketika Anda melakukannya:

object.method();

... objectsecara otomatis menjadi nilai thisdalam method(). Demikian dengan:

[1,2,3].slice()

... [1,2,3]Array ditetapkan sebagai nilai thisin .slice().


Tetapi bagaimana jika Anda bisa mengganti sesuatu yang lain sebagai thisnilainya? Selama apapun yang Anda gantikan memiliki .lengthproperti numerik , dan banyak properti yang merupakan indeks numerik, itu harus berfungsi. Jenis objek ini sering disebut objek seperti array .

Metode .call()dan .apply()membiarkan Anda secara manual mengatur nilai thisdalam suatu fungsi. Jadi jika kita menyetel nilai thisin .slice()ke objek mirip array , anggap.slice() saja itu berfungsi dengan Array, dan akan melakukan hal tersebut.

Ambil objek sederhana ini sebagai contoh.

var my_object = {
    '0': 'zero',
    '1': 'one',
    '2': 'two',
    '3': 'three',
    '4': 'four',
    length: 5
};

Ini jelas bukan Array, tetapi jika Anda dapat mengaturnya sebagai thisnilai .slice(), maka itu hanya akan berfungsi, karena terlihat cukup seperti Array untuk .slice()dapat bekerja dengan baik.

var sliced = Array.prototype.slice.call( my_object, 3 );

Contoh: http://jsfiddle.net/wSvkv/

Seperti yang dapat Anda lihat di konsol, hasilnya adalah apa yang kami harapkan:

['three','four'];

Jadi inilah yang terjadi ketika Anda menetapkan argumentsobjek sebagai thisnilai .slice(). Karena argumentsmemiliki .lengthproperti dan banyak indeks numerik, .slice()cukup berfungsi seperti bekerja pada Array nyata.

pengguna113716
sumber
7
Jawaban bagus! Namun sayangnya Anda tidak dapat mengonversi objek apa pun dengan cara ini, jika kunci objek Anda adalah nilai string, seperti dalam kata-kata aktual .. Ini akan gagal, jadi simpan konten objek Anda sebagai '0': 'nilai' dan tidak seperti 'stringName' :'nilai'.
joopmicroop
6
@Michael: Membaca kode sumber dari implementasi JS sumber terbuka dimungkinkan, tetapi lebih mudah untuk hanya merujuk ke spesifikasi bahasa "ECMAScript". Berikut tautan ke Array.prototype.slicedeskripsi metode.
1
karena kunci objek tidak memiliki urutan, demonstrasi khusus ini mungkin gagal di browser lain. tidak semua vendor mengurutkan kunci objek mereka berdasarkan urutan pembuatan.
vsync
1
@vsync: for-inPernyataan yang tidak menjamin pesanan. Algoritma yang digunakan oleh .slice()mendefinisikan urutan numerik dimulai dengan 0dan diakhiri (non-inklusif) dengan .lengthobjek yang diberikan (atau Array atau apa pun). Jadi pesanan dijamin konsisten di semua implementasi.
cookie monster
7
@ vsync: Itu bukan asumsi. Anda bisa mendapatkan pesanan dari objek apa pun jika Anda menegakkannya. Katakanlah saya punya var obj = {2:"two", 0:"zero", 1: "one"}. Jika kita gunakan for-inuntuk menyebutkan objek, tidak ada jaminan pesanan. Tetapi jika kita menggunakan for, kita secara manual dapat menegakkan perintah: for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Sekarang kita tahu bahwa properti objek akan tercapai dalam urutan numerik naik yang kita tetapkan oleh forloop kita . Itu yang .slice()dilakukannya. Tidak peduli jika ia memiliki Array yang sebenarnya. Itu baru saja dimulai 0dan mengakses properti dalam loop menanjak.
kue monster
88

The argumentsobjek adalah tidak benar-benar sebuah contoh dari Array, dan tidak memiliki metode Array. Jadi, arguments.slice(...)tidak akan berfungsi karena objek argumen tidak memiliki metode slice.

Array memiliki metode ini, dan karena argumentsobjeknya sangat mirip dengan array, keduanya kompatibel. Ini berarti bahwa kita dapat menggunakan metode array dengan objek argumen. Dan karena metode array dibangun dengan mempertimbangkan array, mereka akan mengembalikan array daripada objek argumen lainnya.

Jadi mengapa digunakan Array.prototype? The Arrayadalah objek yang kita buat array baru dari ( new Array()), dan ini array baru lulus metode dan properti, seperti slice. Metode-metode ini disimpan dalam [Class].prototypeobjek. Jadi, demi efisiensi, alih-alih mengakses metode slice dengan (new Array()).slice.call()atau [].slice.call(), kami hanya mendapatkannya langsung dari prototipe. Ini agar kita tidak perlu menginisialisasi array baru.

Tetapi mengapa kita harus melakukan ini sejak awal? Nah, seperti yang Anda katakan, itu mengubah objek argumen menjadi contoh Array. Namun, alasan mengapa kita menggunakan slice lebih merupakan "retasan" daripada apa pun. Metode slice akan mengambil irisan array dan mengembalikan irisan itu sebagai array baru. Melewati tidak ada argumen untuk itu (selain objek argumen sebagai konteksnya) menyebabkan metode slice untuk mengambil potongan lengkap dari "array" yang diteruskan (dalam hal ini, objek argumen) dan mengembalikannya sebagai array baru.

Ben Fleming
sumber
Anda mungkin ingin menggunakan irisan untuk alasan yang dijelaskan di sini: jspatterns.com/arguments-considered-harmful
KooiInc
44

Biasanya, menelepon

var b = a.slice();

akan menyalin array ake b. Namun, kami tidak bisa melakukannya

var a = arguments.slice();

karena argumentsbukan array nyata, dan tidak memiliki slicesebagai metode. Array.prototype.sliceadalah slicefungsi untuk array, dan callmenjalankan fungsi dengan thisset ke arguments.

Delan Azabani
sumber
2
thanx tapi mengapa digunakan prototype? bukan metode sliceasli Array?
ilyo
2
Catatan itu Arrayadalah fungsi konstruktor, dan "kelas" yang sesuai adalah Array.prototype. Anda juga dapat menggunakan[].slice
user123444555621
4
IlyaD, sliceadalah metode dari setiap Arrayinstance, tetapi bukan Arrayfungsi konstruktor. Anda gunakan prototypeuntuk mengakses metode dari contoh teoritis konstruktor.
Delan Azabani
23

Pertama, Anda harus membaca cara fungsi doa berfungsi dalam JavaScript . Saya kira itu saja sudah cukup untuk menjawab pertanyaan Anda. Tetapi inilah ringkasan dari apa yang terjadi:

Array.prototype.sliceekstrak metode dari 's prototipe . Tetapi memanggilnya secara langsung tidak akan berhasil, karena ini adalah metode (bukan fungsi) dan karena itu memerlukan konteks (objek panggilan, ), jika tidak maka akan dibuang .slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

The call()Metode memungkinkan Anda untuk menentukan konteks metode ini, pada dasarnya membuat dua panggilan ini setara:

someObject.slice(1, 2);
slice.call(someObject, 1, 2);

Kecuali yang pertama membutuhkan slicemetode untuk ada dalam someObjectrantai prototipe (seperti halnya untuk Array), sedangkan yang terakhir memungkinkan konteks ( someObject) untuk secara manual diteruskan ke metode.

Juga, yang terakhir adalah kependekan dari:

var slice = Array.prototype.slice;
slice.call(someObject, 1, 2);

Yang sama dengan:

Array.prototype.slice.call(someObject, 1, 2);
Marco Roy
sumber
22
// We can apply `slice` from  `Array.prototype`:
Array.prototype.slice.call([]); //-> []

// Since `slice` is available on an array's prototype chain,
'slice' in []; //-> true
[].slice === Array.prototype.slice; //-> true

// … we can just invoke it directly:
[].slice(); //-> []

// `arguments` has no `slice` method
'slice' in arguments; //-> false

// … but we can apply it the same way:
Array.prototype.slice.call(arguments); //-> […]

// In fact, though `slice` belongs to `Array.prototype`,
// it can operate on any array-like object:
Array.prototype.slice.call({0: 1, length: 1}); //-> [1]
sam
sumber
16

Array.prototype.slice.call (argumen) adalah cara kuno untuk mengubah argumen menjadi array.

Di ECMAScript 2015, Anda dapat menggunakan Array.from atau operator spread:

let args = Array.from(arguments);

let args = [...arguments];
Alexander Moiseyev
sumber
9

Itu karena, seperti catatan MDN

Objek argumen bukan array. Ini mirip dengan array, tetapi tidak memiliki properti array apa pun kecuali panjang. Misalnya, tidak memiliki metode pop. Namun itu dapat dikonversi menjadi array nyata:

Di sini kita memanggil sliceobjek asli Arraydan bukan pada implementasinya dan itu sebabnya tambahan.prototype

var args = Array.prototype.slice.call(arguments);
naveen
sumber
4

Jangan lupa, bahwa dasar-dasar tingkat rendah dari perilaku ini adalah tipe-casting yang terintegrasi dalam mesin-JS sepenuhnya.

Slice hanya mengambil objek (berkat properti arguments.length yang ada) dan mengembalikan array-objek yang dilemparkan setelah melakukan semua operasi.

Logika yang sama dengan yang Anda dapat uji jika Anda mencoba memperlakukan metode-String dengan nilai INT:

String.prototype.bold.call(11);  // returns "<b>11</b>"

Dan itu menjelaskan pernyataan di atas.

Andrey
sumber
1

Ia menggunakan slicemetode array memiliki dan menyebutnya dengan yang thismenjadi argumentsobjek. Ini berarti ia memanggilnya seolah-olah Anda arguments.slice()berasumsi argumentsmemiliki metode seperti itu.

Membuat slice tanpa argumen apa pun hanya akan mengambil semua elemen - jadi itu hanya menyalin elemen dari argumentske array.

Pencuri
sumber
1

Anggap Anda memiliki: function.apply(thisArg, argArray )

Metode terapkan memanggil fungsi, meneruskan objek yang akan terikat dengan ini dan array argumen opsional.

Metode slice () memilih bagian dari array, dan mengembalikan array baru.

Jadi ketika Anda memanggil Array.prototype.slice.apply(arguments, [0])metode slice array dipanggil (ikat) pada argumen.

pengguna278064
sumber
1

Mungkin agak terlambat, tetapi jawaban untuk semua kekacauan ini adalah bahwa panggilan () digunakan dalam JS untuk warisan. Jika kita membandingkan ini dengan Python atau PHP, misalnya, panggilan digunakan masing-masing sebagai super (). init () atau parent :: _ construct ().

Ini adalah contoh penggunaannya yang menjelaskan semua:

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

Referensi: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance

Davide Pugliese
sumber
0

ketika .slice () dipanggil secara normal, ini adalah Array, dan kemudian hanya mengulangi Array itu, dan melakukan tugasnya.

 //ARGUMENTS
function func(){
  console.log(arguments);//[1, 2, 3, 4]

  //var arrArguments = arguments.slice();//Uncaught TypeError: undefined is not a function
  var arrArguments = [].slice.call(arguments);//cp array with explicity THIS  
  arrArguments.push('new');
  console.log(arrArguments)
}
func(1,2,3,4)//[1, 2, 3, 4, "new"]
Pablo
sumber
-1

Saya hanya menulis ini untuk mengingatkan diri saya sendiri ...

    Array.prototype.slice.call(arguments);
==  Array.prototype.slice(arguments[1], arguments[2], arguments[3], ...)
==  [ arguments[1], arguments[2], arguments[3], ... ]

Atau cukup gunakan fungsi praktis ini $ A untuk mengubah sebagian besar hal menjadi sebuah array.

function hasArrayNature(a) {
    return !!a && (typeof a == "object" || typeof a == "function") && "length" in a && !("setInterval" in a) && (Object.prototype.toString.call(a) === "[object Array]" || "callee" in a || "item" in a);
}

function $A(b) {
    if (!hasArrayNature(b)) return [ b ];
    if (b.item) {
        var a = b.length, c = new Array(a);
        while (a--) c[a] = b[a];
        return c;
    }
    return Array.prototype.slice.call(b);
}

contoh penggunaan ...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
Orwellophile
sumber