como o Array.prototype.slice.call () funciona?

474

Eu sei que é usado para transformar argumentos em uma matriz real, mas não entendo o que acontece ao usar Array.prototype.slice.call(arguments)

ilyo
fonte
2
^ um pouco diferente, pois esse link pergunta sobre nós DOM e não argumentos. E acho que a resposta aqui é muito melhor descrevendo o que 'isso' é internamente.
Sqram

Respostas:

870

O que acontece sob o capô é que, quando .slice()é chamado normalmente, thisé uma matriz e, em seguida, itera sobre essa matriz e faz seu trabalho.

Como está thisa .slice()função uma matriz? Porque quando você faz:

object.method();

... objectautomaticamente se torna o valor de thisem method(). Então com:

[1,2,3].slice()

... a [1,2,3]matriz é definida como o valor de thisin .slice().


Mas e se você pudesse substituir outra coisa como thisvalor? Desde que o que você substitua tenha uma .lengthpropriedade numérica e várias propriedades que são índices numéricos, ele deve funcionar. Esse tipo de objeto geralmente é chamado de objeto de matriz .

Os métodos .call()e .apply()permitem definir manualmente o valor de thisem uma função. Então, se nós definir o valor thisno .slice()a um array como objeto , .slice()só vai assumir que está trabalhando com uma matriz, e vai fazer a sua coisa.

Tome esse objeto simples como exemplo.

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

Obviamente, isso não é uma matriz, mas se você pode defini-la como o thisvalor de .slice(), funcionará apenas porque parece suficiente com uma matriz para .slice()funcionar corretamente.

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

Exemplo: http://jsfiddle.net/wSvkv/

Como você pode ver no console, o resultado é o que esperamos:

['three','four'];

Então é isso que acontece quando você define um argumentsobjeto como o thisvalor de .slice(). Como argumentspossui uma .lengthpropriedade e vários índices numéricos, .slice()o trabalho é realizado como se estivesse trabalhando em uma matriz real.

user113716
fonte
7
Ótima resposta! Mas, infelizmente, você não pode converter nenhum objeto dessa maneira, se as chaves do objeto forem valores de sequência, como em palavras reais. Isso falhará, portanto, mantenha o conteúdo dos objetos como '0': 'value' e não como 'stringName' :'valor'.
Joopmicroop 09/04
6
@ Michael: É possível ler o código-fonte das implementações de código-fonte aberto em JS, mas é mais simples referir-se à especificação da linguagem "ECMAScript". Aqui está um link para a Array.prototype.slicedescrição do método.
1
como as chaves do objeto não têm ordem, essa demonstração específica pode falhar em outros navegadores. nem todos os fornecedores classificam as chaves de seus objetos por ordem de criação.
vsync 21/02
1
@ vsync: É a for-inafirmação que não garante a ordem. O algoritmo usado por .slice()define uma ordem numérica começando 0e terminando (não inclusivo) com o .lengthobjeto especificado (ou Matriz ou o que for). Portanto, é garantido que o pedido seja consistente em todas as implementações.
cookie monster
7
@ vsync: Não é uma suposição. Você pode obter pedidos de qualquer objeto se aplicá-lo. Digamos que sim var obj = {2:"two", 0:"zero", 1: "one"}. Se usarmos for-inpara enumerar o objeto, não há garantia de ordem. Mas se usarmos for, podemos aplicar manualmente a ordem: for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Agora sabemos que as propriedades do objeto serão alcançadas na ordem numérica crescente que definimos pelo nosso forloop. Isso é o que .slice()faz. Não importa se ele tem uma matriz real. Apenas inicia em 0e acessa propriedades em um loop ascendente.
cookie monster
88

O argumentsobjeto não é realmente uma instância de uma matriz e não possui nenhum dos métodos de matriz. Portanto, arguments.slice(...)não funcionará porque o objeto de argumentos não possui o método de fatia.

As matrizes têm esse método e, como o argumentsobjeto é muito semelhante a uma matriz, as duas são compatíveis. Isso significa que podemos usar métodos de matriz com o objeto de argumentos. E como os métodos de matriz foram criados com matrizes em mente, eles retornarão matrizes em vez de outros objetos de argumento.

Então, por que usar Array.prototype? O Arrayé o objeto do qual criamos novas matrizes a partir de ( new Array()), e essas novas matrizes passam métodos e propriedades, como fatia. Esses métodos são armazenados no [Class].prototypeobjeto. Portanto, por questões de eficiência, em vez de acessar o método de fatia por (new Array()).slice.call()ou [].slice.call(), apenas obtemos isso diretamente do protótipo. Isso é para que não tenhamos que inicializar uma nova matriz.

Mas por que temos que fazer isso em primeiro lugar? Bem, como você disse, ele converte um objeto de argumentos em uma instância Array. A razão pela qual usamos fatia, no entanto, é mais um "hack" do que qualquer coisa. O método de fatia terá uma fatia de uma matriz, você adivinhou, e retornará essa fatia como uma nova matriz. Não passar argumentos para ele (além do objeto de argumentos como seu contexto) faz com que o método de fatia pegue uma parte completa da "matriz" passada (nesse caso, o objeto de argumentos) e a retorne como uma nova matriz.

Ben Fleming
fonte
Você pode querer usar uma fatia por razões descritas aqui: jspatterns.com/arguments-considered-harmful
KooiInc
44

Normalmente, chamando

var b = a.slice();

copiará a matriz apara b. No entanto, não podemos fazer

var a = arguments.slice();

porque argumentsnão é uma matriz real e não tem slicecomo método. Array.prototype.sliceé a slicefunção para matrizes e callexecuta a função com thisdefinido como arguments.

Delan Azabani
fonte
2
thanx mas por que usar prototype? não é sliceum Arraymétodo nativo ?
Ilyo 14/08
2
Observe que Arrayé uma função construtora e a "classe" correspondente é Array.prototype. Você também pode usar[].slice
user123444555621
4
IlyaD, sliceé um método de cada Arrayinstância, mas não a Arrayfunção construtora. Você usa prototypepara acessar métodos das instâncias teóricas de um construtor.
Delan Azabani
23

Primeiro, você deve ler como a chamada de função funciona em JavaScript . Eu suspeito que só isso é suficiente para responder à sua pergunta. Mas aqui está um resumo do que está acontecendo:

Array.prototype.sliceextrai o método a partir do protótipo . Mas chamá-lo diretamente não funcionará, pois é um método (não uma função) e, portanto, requer um contexto (um objeto que chama ), caso contrário, seria possível .slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

O call()método permite especificar o contexto de um método, basicamente tornando essas duas chamadas equivalentes:

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

Exceto que o primeiro requer que o slicemétodo exista na someObjectcadeia de protótipos do (como faz para Array), enquanto o último permite que o contexto ( someObject) seja passado manualmente para o método.

Além disso, o último é a abreviação de:

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

Qual é o mesmo que:

Array.prototype.slice.call(someObject, 1, 2);
Marco Roy
fonte
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
fonte
16

Array.prototype.slice.call (argumentos) é a maneira antiquada de converter argumentos em uma matriz.

No ECMAScript 2015, você pode usar Array.from ou o operador de spread:

let args = Array.from(arguments);

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

É porque, como observa o MDN

O objeto de argumentos não é uma matriz. É semelhante a uma matriz, mas não possui nenhuma propriedade da matriz, exceto o comprimento. Por exemplo, ele não possui o método pop. No entanto, pode ser convertido em uma matriz real:

Aqui estamos chamando sliceo objeto nativo Arraye não sua implementação e é por isso que o extra.prototype

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

Não se esqueça, que um básico de baixo nível desse comportamento é a conversão de tipo que foi totalmente integrada ao mecanismo JS.

A fatia apenas pega o objeto (graças à propriedade argument.length existente) e retorna o objeto de matriz convertido após fazer todas as operações nele.

A mesma lógica que você pode testar se tentar tratar o método String com um valor INT:

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

E isso explica a afirmação acima.

Andrey
fonte
1

Ele usa o slicemétodo que as matrizes têm e o chama thissendo o argumentsobjeto. Isso significa que ele é chamado como se você arguments.slice()assumisse que argumentstinha esse método.

Criar uma fatia sem argumentos simplesmente usará todos os elementos - portanto, basta copiar os elementos argumentspara uma matriz.

ThiefMaster
fonte
1

Vamos supor que você tenha: function.apply(thisArg, argArray )

O método apply chama uma função, transmitindo o objeto que será vinculado a isso e a uma matriz opcional de argumentos.

O método slice () seleciona uma parte de uma matriz e retorna a nova matriz.

Portanto, quando você chama Array.prototype.slice.apply(arguments, [0])o método de fatia da matriz, é invocado (bind) nos argumentos.

user278064
fonte
1

Talvez um pouco tarde, mas a resposta para toda essa bagunça é que call () é usado em JS para herança. Se compararmos isso com Python ou PHP, por exemplo, call é usado respectivamente como super (). init () ou parent :: _ construct ().

Este é um exemplo de seu uso que esclarece tudo:

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

  this.subject = subject;
}

Referência: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance

Davide Pugliese
fonte
0

quando .slice () é chamado normalmente, isso é uma matriz e, em seguida, itera sobre essa matriz e faz seu trabalho.

 //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
fonte
-1

Só estou escrevendo isso para me lembrar ...

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

Ou apenas use esta útil função $ A para transformar a maioria das coisas em uma matriz.

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

exemplo de uso ...

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