jak działa Array.prototype.slice.call ()?

474

Wiem, że służy on do przekształcenia argumentów w prawdziwą tablicę, ale nie rozumiem, co się dzieje podczas używania Array.prototype.slice.call(arguments)

ilyo
źródło
2
^ nieco inny, ponieważ ten link pyta o węzły DOM, a nie argumenty. I myślę, że odpowiedź tutaj jest znacznie lepsza, opisując wewnętrznie „to”.
sqram

Odpowiedzi:

870

To, co dzieje się pod maską, polega na tym, że gdy .slice()jest wywoływane normalnie, thisjest tablicą, a następnie po prostu iteruje po tej tablicy i wykonuje swoją pracę.

Jak thisw tej .slice()funkcji jest tablica? Ponieważ kiedy to zrobisz:

object.method();

... objectautomatycznie staje się wartością thisw method(). Więc z:

[1,2,3].slice()

... [1,2,3]tablica jest ustawiona jako wartość thisin .slice().


Ale co, jeśli możesz podstawić coś innego jako thiswartość? Tak długo, jak cokolwiek, co podstawisz, ma .lengthwłaściwość numeryczną i kilka właściwości, które są indeksami numerycznymi, powinno działać. Ten typ obiektu jest często nazywany obiektem tablicowym .

.call()I .apply()metody pozwala ręcznie ustawić wartość thisw funkcji. Więc jeśli ustawimy wartość thisin .slice()na obiekt podobny do tablicy , .slice()po prostu założymy, że działa on z tablicą i zrobi swoje.

Weź ten prosty obiekt jako przykład.

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

Oczywiście nie jest to tablica, ale jeśli możesz ustawić ją jako thiswartość .slice(), to po prostu będzie działać, ponieważ wygląda wystarczająco podobnie do tablicy, .slice()aby działała poprawnie.

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

Przykład: http://jsfiddle.net/wSvkv/

Jak widać w konsoli, wynik jest taki, jakiego oczekujemy:

['three','four'];

Tak dzieje się, gdy ustawisz argumentsobiekt jako thiswartość .slice(). Ponieważ argumentsma .lengthwłaściwość i kilka indeksów liczbowych, .slice()po prostu działa tak, jakby działał na prawdziwej macierzy.

użytkownik113716
źródło
7
Świetna odpowiedź! Niestety nie można w ten sposób przekonwertować żadnego obiektu, jeśli klucze obiektu są wartościami ciągów, tak jak w rzeczywistych słowach. To się nie powiedzie, więc zachowaj zawartość swoich obiektów jako „0”: „wartość”, a nie jak „stringName” :'wartość'.
joopmicroop
6
@ Michael: Odczytywanie kodu źródłowego implementacji JS typu open source jest możliwe, ale łatwiej jest po prostu odwołać się do specyfikacji języka „ECMAScript”. Oto link do Array.prototype.sliceopisu metody.
1
ponieważ klucze obiektów nie mają kolejności, ta konkretna demonstracja może się nie powieść w innych przeglądarkach. nie wszyscy dostawcy sortują klucze swoich obiektów według kolejności tworzenia.
vsync
1
@vsync: To for-instwierdzenie nie gwarantuje porządku. Algorytm używany przez .slice()definiuje porządek numeryczny rozpoczynający się 0i kończący (nie włącznie) z .lengthdanym obiektem (lub tablicą lub czymkolwiek). Dzięki temu kolejność jest gwarantowana we wszystkich implementacjach.
ciasteczkowy potwór
7
@vsync: To nie jest założenie. Możesz uzyskać zamówienie z dowolnego obiektu, jeśli go egzekwujesz. Powiedzmy, że mam var obj = {2:"two", 0:"zero", 1: "one"}. Jeśli użyjemy for-indo wyliczenia obiektu, nie ma gwarancji porządku. Ale jeśli używamy for, możemy ręcznie wymusić kolejność: for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Teraz wiemy, że właściwości obiektu zostaną osiągnięte w rosnącej kolejności numerycznej, którą zdefiniowaliśmy w naszej forpętli. Właśnie to .slice()robi. Nie ma znaczenia, czy ma on rzeczywistą macierz. Rozpoczyna się od 0i uzyskuje dostęp do właściwości w rosnącej pętli.
ciasteczkowy potwór
88

argumentsObiekt nie jest faktycznie instancja Array, a nie ma żadnej z metod Array. Więc arguments.slice(...)nie będzie działać, ponieważ obiekt argumentów nie ma metody plastra.

Tablice mają tę metodę, a ponieważ argumentsobiekt jest bardzo podobny do tablicy, dwie są kompatybilne. Oznacza to, że możemy korzystać z metod tablicowych z argumentami sprzeciwu. A ponieważ metody tablicowe zostały zbudowane z myślą o tablicach, zwracają tablice zamiast innych obiektów argumentów.

Dlaczego więc korzystać Array.prototype? Jest Arrayto obiekt, z którego tworzymy nowe tablice z ( new Array()), a te nowe tablice są przekazywane metodami i właściwościami, takimi jak plasterek. Te metody są przechowywane w [Class].prototypeobiekcie. Tak więc, ze względu na wydajność, zamiast uzyskiwać dostęp do metody wycinania za pomocą (new Array()).slice.call()lub [].slice.call(), po prostu pobieramy ją prosto z prototypu. Dzieje się tak, dlatego nie musimy inicjować nowej tablicy.

Ale dlaczego musimy to zrobić w pierwszej kolejności? Cóż, jak powiedziałeś, przekształca obiekt argumentów w instancję Array. Powodem, dla którego używamy wycinka, jest bardziej „hack” niż cokolwiek innego. Metoda wycinania weźmie, jak zgadłeś, wycinek tablicy i zwróci ten wycinek jako nową tablicę. Nie przekazanie do niego żadnych argumentów (oprócz obiektu argumentów jako kontekstu) powoduje, że metoda slice pobiera pełny fragment przekazanej „tablicy” (w tym przypadku obiekt argumentów) i zwraca ją jako nową tablicę.

Ben Fleming
źródło
Możesz użyć wycinka z powodów opisanych tutaj: jspatterns.com/arguments-consanted-harmful
KooiInc
44

Zwykle dzwonienie

var b = a.slice();

skopiuje tablicę ado b. Jednak nie możemy tego zrobić

var a = arguments.slice();

ponieważ argumentsnie jest prawdziwą tablicą i nie ma slicejako metody. Array.prototype.slicejest slicefunkcją dla tablic i calluruchamia funkcję z thisustawioną na arguments.

Delan Azabani
źródło
2
dzięki, ale po co korzystać prototype? nie slicejest rodzimą Arraymetodą?
ilyo
2
Zauważ, że Arrayjest to funkcja konstruktora, a odpowiednia „klasa” to Array.prototype. Możesz także użyć[].slice
user123444555621,
4
IlyaD, sliceto metoda każdej Arrayinstancji, ale nie Arrayfunkcja konstruktora. Służysz prototypedo uzyskiwania dostępu do metod teoretycznych wystąpień konstruktora.
Delan Azabani,
23

Najpierw powinieneś przeczytać, jak działa wywoływanie funkcji w JavaScript . Podejrzewam, że sam wystarczy, aby odpowiedzieć na twoje pytanie. Ale oto podsumowanie tego, co się dzieje:

Array.prototype.slicewyodrębnia sposób z „S prototypu . Ale bezpośrednie wywołanie nie zadziała, ponieważ jest to metoda (a nie funkcja) i dlatego wymaga kontekstu (obiektu wywołującego ), w przeciwnym razie zostałby rzucony .slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

call()Metoda pozwala określić kontekst sposobu, w zasadzie czyni te dwa połączenia równoważne:

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

Poza tym pierwszym wymaga, aby slicemetoda istniała w someObjectłańcuchu prototypów (tak jak ma to miejsce w przypadku Array), podczas gdy ten drugi umożliwia someObjectręczne przekazanie kontekstu ( ) do metody.

To drugie jest również skrótem od:

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

Który jest taki sam jak:

Array.prototype.slice.call(someObject, 1, 2);
Marco Roy
źródło
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
źródło
16

Array.prototype.slice.call (arguments) to staromodny sposób konwersji argumentów na tablicę.

W ECMAScript 2015 możesz użyć Array.from lub operatora rozprzestrzeniania:

let args = Array.from(arguments);

let args = [...arguments];
Alexander Moiseyev
źródło
9

Jest tak, ponieważ, jak zauważa MDN

Obiekt argumentów nie jest tablicą. Jest podobny do tablicy, ale nie ma żadnych właściwości tablicy poza długością. Na przykład nie ma metody pop. Można go jednak przekonwertować na prawdziwą tablicę:

Wzywamy tutaj slicenatywny obiekt, Arraya nie jego implementację, i dlatego dodatkowe.prototype

var args = Array.prototype.slice.call(arguments);
naveen
źródło
4

Nie zapominaj, że niskim poziomem podstaw tego zachowania jest rzutowanie typu, które całkowicie zintegrowało się z silnikiem JS.

Slice po prostu pobiera obiekt (dzięki istniejącej właściwości arguments.length) i zwraca rzutowany obiekt tablicowy po wykonaniu wszystkich operacji na tym.

Te same logiki, które możesz przetestować, jeśli spróbujesz traktować metodę String za pomocą wartości INT:

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

I to wyjaśnia powyższe stwierdzenie.

Andrey
źródło
1

Wykorzystuje slicetablice metod i wywołuje je, thisbędąc argumentsobiektem. Oznacza to, że nazywa go jako jeśli nie arguments.slice()zakładając argumentsmiał taką metodę.

Utworzenie wycinka bez żadnych argumentów po prostu zabierze wszystkie elementy - więc po prostu skopiuje je z argumentstablicy do tablicy.

ThiefMaster
źródło
1

Załóżmy, że masz: function.apply(thisArg, argArray )

Metoda Apply wywołuje funkcję, przekazując obiekt, który będzie z nią związany, oraz opcjonalną tablicę argumentów.

Metoda slice () wybiera część tablicy i zwraca nową tablicę.

Więc kiedy wywołujesz Array.prototype.slice.apply(arguments, [0])tablicę, wywoływana jest metoda argumentu (bind).

użytkownik278064
źródło
1

Może trochę późno, ale odpowiedzią na cały ten bałagan jest to, że call () jest używane w JS do dziedziczenia. Jeśli porównamy to na przykład do Pythona lub PHP, wywołanie zostanie użyte odpowiednio jako super (). init () lub parent :: _ construct ().

To jest przykład użycia, który wyjaśnia wszystkie:

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

  this.subject = subject;
}

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

Davide Pugliese
źródło
0

gdy .slice () jest wywoływane normalnie, jest to Array, a następnie po prostu iteruje tę tablicę i wykonuje swoją pracę.

 //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
źródło
-1

Piszę to, żeby sobie przypomnieć ...

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

Lub po prostu użyj tej przydatnej funkcji $ A, aby zamienić większość rzeczy w tablicę.

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

przykładowe użycie ...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
Orwellophile
źródło