come funziona Array.prototype.slice.call ()?


474

So che è usato per rendere gli argomenti un vero array, ma non capisco cosa succede quando si usa Array.prototype.slice.call(arguments)


2
^ leggermente diverso dal momento che quel link chiede sui nodi DOM e non sugli argomenti. E penso che la risposta qui sia molto meglio descrivendo cosa sia "questo" internamente.
sqram

Risposte:


870

Quello che succede sotto il cofano è che quando .slice()viene chiamato normalmente, thisè un array e quindi scorre solo su quell'array e fa il suo lavoro.

Come è thisnella .slice()funzione un array? Perché quando lo fai:

object.method();

... objectdiventa automaticamente il valore di thisin method(). Quindi con:

[1,2,3].slice()

... l' [1,2,3]array è impostato come valore di thisin .slice().


E se potessi sostituire qualcos'altro come thisvalore? Finché tutto ciò che sostituisci ha una .lengthproprietà numerica e un gruppo di proprietà che sono indici numerici, dovrebbe funzionare. Questo tipo di oggetto viene spesso chiamato un oggetto simile a un array .

I metodi .call()e .apply()consentono di impostare manualmente il valore di thisin una funzione. Quindi, se si imposta il valore di thisin .slice()un array come oggetto , .slice()sarà solo supporre che sta lavorando con un array, e farà il suo dovere.

Prendi questo semplice oggetto come esempio.

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

Ovviamente questo non è un array, ma se è possibile impostarlo come thisvalore di .slice(), allora funzionerà, perché sembra abbastanza un array per .slice()funzionare correttamente.

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

Esempio: http://jsfiddle.net/wSvkv/

Come puoi vedere nella console, il risultato è quello che ci aspettiamo:

['three','four'];

Questo è ciò che accade quando si imposta un argumentsoggetto come thisvalore di .slice(). Perché argumentsha una .lengthproprietà e un mucchio di indici numerici, .slice()fa il suo lavoro come se stesse lavorando su un array reale.


7
Bella risposta! Ma purtroppo non puoi convertire qualsiasi oggetto in questo modo, se le chiavi degli oggetti sono valori stringa, come in parole reali. Ciò non funzionerà, quindi mantieni i tuoi oggetti contenuti come '0': 'valore' e non come 'nome stringa' :'valore'.
joopmicroop,

6
@Michael: è possibile leggere il codice sorgente delle implementazioni JS open source, ma è più semplice fare riferimento alla specifica del linguaggio "ECMAScript". Ecco un link alla Array.prototype.slicedescrizione del metodo.

1
poiché le chiavi dell'oggetto non hanno alcun ordine, questa dimostrazione specifica potrebbe non riuscire in altri browser. non tutti i fornitori ordinano le chiavi dei loro oggetti in ordine di creazione.
vsync,

1
@vsync: è l' for-inaffermazione che non garantisce l'ordine. L'algoritmo utilizzato da .slice()definisce un ordine numerico che inizia con 0e termina (non inclusivo) con l' .lengthoggetto specificato (o Array o altro). Pertanto, l'ordine è garantito per essere coerente in tutte le implementazioni.
cookie monster

7
@vsync: non è un presupposto. Puoi ottenere l'ordine da qualsiasi oggetto se lo imponi. Diciamo che ho var obj = {2:"two", 0:"zero", 1: "one"}. Se utilizziamo for-inper enumerare l'oggetto, non vi è alcuna garanzia di ordine. Ma se usiamo for, siamo in grado di far rispettare manualmente l'ordine: for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Ora sappiamo che le proprietà dell'oggetto saranno raggiunte nell'ordine numerico crescente che abbiamo definito dal nostro forciclo. Questo è quello che .slice()fa. Non importa se ha un array reale. Inizia da 0e accede alle proprietà in un ciclo crescente.
cookie monster

88

L' argumentsoggetto non è in realtà un'istanza di un array e non ha alcun metodo dell'array. Quindi, arguments.slice(...)non funzionerà perché l'oggetto argomenti non ha il metodo slice.

Gli array hanno questo metodo e poiché l' argumentsoggetto è molto simile a un array, i due sono compatibili. Ciò significa che possiamo usare metodi array con l'oggetto argomenti. E poiché i metodi array sono stati creati pensando alle matrici, restituiranno matrici anziché altri oggetti argomento.

Quindi perché usare Array.prototype? Il Arrayè l'oggetto di creiamo nuovi array da ( new Array()), e questi nuovi array sono passati metodi e proprietà, come fetta. Questi metodi sono memorizzati [Class].prototypenell'oggetto. Quindi, per motivi di efficienza, invece di accedere al metodo slice da (new Array()).slice.call()o [].slice.call(), lo otteniamo direttamente dal prototipo. Questo è quindi non è necessario inizializzare un nuovo array.

Ma perché dobbiamo farlo prima di tutto? Bene, come hai detto, converte un oggetto argomenti in un'istanza di matrice. Il motivo per cui usiamo slice, tuttavia, è più un "hack" che altro. Il metodo slice prenderà una sezione, hai indovinato, di una matrice e restituirà quella fetta come nuova matrice. Passandogli nessun argomento (oltre all'oggetto argomenti come suo contesto), il metodo slice prende un pezzo completo della "matrice" passata (in questo caso, l'oggetto argomenti) e lo restituisce come una nuova matrice.


Potresti voler utilizzare una sezione per i motivi descritti qui: jspatterns.com/arguments-considered-harmful
KooiInc

44

Normalmente, chiamando

var b = a.slice();

copierà l'array a in b. Tuttavia, non possiamo farlo

var a = arguments.slice();

perché argumentsnon è un array reale e non ha slicecome metodo. Array.prototype.sliceè la slicefunzione per le matrici ed callesegue la funzione con thisset su arguments.


2
grazie ma perché usare prototype? non è sliceun Arraymetodo nativo ?
ilyo,

2
Si noti che Arrayè una funzione di costruzione e la "classe" corrispondente è Array.prototype. Puoi anche usare[].slice
user123444555621

4
IlyaD, sliceè un metodo di ogni Arrayistanza, ma non la Arrayfunzione di costruzione. Utilizzi prototypeper accedere ai metodi delle istanze teoriche di un costruttore.
Delan Azabani,

23

Innanzitutto, dovresti leggere come funziona l'invocazione della funzione in JavaScript . Sospetto che da solo sia sufficiente rispondere alla tua domanda. Ma ecco un riassunto di ciò che sta accadendo:

Array.prototype.sliceestrae il metodo da 's prototipo . Ma chiamarlo direttamente non funzionerà, poiché è un metodo (non una funzione) e quindi richiede un contesto (un oggetto chiamante ), altrimenti verrebbe lanciato .slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

Il call()metodo consente di specificare il contesto di un metodo, fondamentalmente rendendo equivalenti queste due chiamate:

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

Tranne il primo richiede che il slicemetodo esista nella someObjectcatena di prototipi (come fa per Array), mentre il secondo consente il contesto (someObject ) di essere passato manualmente al metodo.

Inoltre, quest'ultimo è l'abbreviazione di:

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

Che è lo stesso di:

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

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]

16

Array.prototype.slice.call (argomenti) è il vecchio modo di convertire un argomento in un array.

In ECMAScript 2015, è possibile utilizzare Array.from o l'operatore di diffusione:

let args = Array.from(arguments);

let args = [...arguments];

9

È perché, come osserva MDN

L'oggetto argomenti non è un array. È simile a un array, ma non ha proprietà dell'array tranne la lunghezza. Ad esempio, non ha il metodo pop. Tuttavia, può essere convertito in un array reale:

Qui stiamo invocando slicel'oggetto nativo Arraye non la sua implementazione e questo è il motivo per cui extra.prototype

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

4

Non dimenticare che una base di basso livello di questo comportamento è il casting del tipo integrato nel motore JS.

Slice prende semplicemente l'oggetto (grazie alla proprietà argomenti.length esistente) e restituisce un oggetto array lanciato dopo aver eseguito tutte le operazioni su di esso.

Le stesse logiche che puoi provare se provi a trattare il metodo String con un valore INT:

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

E questo spiega la dichiarazione sopra.


1

Usa il slicemetodo che hanno gli array e lo chiama con il thisfatto che è l' argumentsoggetto. Ciò significa che lo chiama come se avessi arguments.slice()assunto argumentsun simile metodo.

La creazione di una sezione senza argomenti richiederà semplicemente tutti gli elementi, quindi copia semplicemente gli elementi argumentsin un array.


1

Supponiamo che tu abbia: function.apply(thisArg, argArray )

Il metodo apply invoca una funzione, passando l'oggetto che sarà associato a questo e un array opzionale di argomenti.

Il metodo slice () seleziona una parte di un array e restituisce il nuovo array.

Pertanto, quando si chiama Array.prototype.slice.apply(arguments, [0])il metodo slice dell'array, viene richiamato (bind) sugli argomenti.


1

Forse un po 'in ritardo, ma la risposta a tutto questo casino è che call () è usato in JS per ereditarietà. Se confrontiamo questo con Python o PHP, ad esempio, call viene usato rispettivamente come super (). init () o parent :: _ construct ().

Questo è un esempio del suo utilizzo che chiarisce tutto:

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

  this.subject = subject;
}

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


0

quando .slice () viene chiamato normalmente, si tratta di un array, quindi esegue l'iterazione su tale array e fa il suo lavoro.

 //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"]

-1

Sto solo scrivendo questo per ricordare a me stesso ...

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

O semplicemente usa questa pratica funzione $ A per trasformare la maggior parte delle cose in un 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);
}

esempio di utilizzo ...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.