¿Cómo funciona `Array.prototype.slice.call`?
Sé que se usa para hacer arguments
un real Array
, pero no entiendo qué pasa al usarlo Array.prototype.slice.call(arguments);
.
Lo que sucede bajo el capó es que cuando .slice()
se llama normalmente, this
es una matriz, y luego simplemente itera sobre esa matriz y hace su trabajo.
¿Cómo está this
en la .slice()
función un Array? Porque cuando lo haces:
object.method();
... el object
automáticamente se convierte en el valor de this
en el method()
. Entonces con:
[1,2,3].slice()
...la [1,2,3]
matriz se establece como el valor de this
in .slice()
.
Pero, ¿y si pudieras sustituir el this
valor por otra cosa? Siempre que lo que sustituya tenga una .length
propiedad numérica y un montón de propiedades que sean índices numéricos, debería funcionar. Este tipo de objeto a menudo se denomina objeto similar a una matriz .
Los métodos .call()
y .apply()
le permiten establecer manualmente el valor de this
en una función. Entonces , si establecemos el valor de this
in .slice()
en un objeto similar a una matriz , .slice()
asumiremos que está funcionando con una matriz y hará lo suyo.
Tome este objeto simple como ejemplo.
var my_object = {
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three',
'4': 'four',
length: 5
};
Obviamente, esto no es una matriz, pero si puede establecerlo como el this
valor de .slice()
, entonces simplemente funcionará, porque se parece lo suficiente a una matriz para .slice()
funcionar correctamente.
var sliced = Array.prototype.slice.call( my_object, 3 );
Ejemplo: http://jsfiddle.net/wSvkv/
Como puedes ver en la consola, el resultado es el que esperábamos:
['three','four'];
Entonces esto es lo que sucede cuando estableces un arguments
objeto como el this
valor de .slice()
. Debido a que arguments
tiene una .length
propiedad y un montón de índices numéricos, .slice()
simplemente realiza su trabajo como si estuviera trabajando en una matriz real.
El arguments
objeto no es en realidad una instancia de un Array y no tiene ninguno de los métodos del Array. Por lo tanto, arguments.slice(...)
no funcionará porque el objeto de argumentos no tiene el método de corte.
Las matrices tienen este método y, debido a que el arguments
objeto es muy similar a una matriz, los dos son compatibles. Esto significa que podemos usar métodos de matriz con el objeto de argumentos. Y dado que los métodos de matriz se crearon teniendo en cuenta las matrices, devolverán matrices en lugar de otros objetos de argumento.
Entonces, ¿por qué utilizar Array.prototype
? Es Array
el objeto a partir del cual creamos nuevas matrices ( new Array()
), y a estas nuevas matrices se les pasan métodos y propiedades, como segmento. Estos métodos se almacenan en el [Class].prototype
objeto. Entonces, por motivos de eficiencia, en lugar de acceder al método de corte mediante (new Array()).slice.call()
o [].slice.call()
, simplemente lo obtenemos directamente del prototipo. Esto es para que no tengamos que inicializar una nueva matriz.
Pero, ¿por qué tenemos que hacer esto en primer lugar? Bueno, como dijiste, convierte un objeto de argumentos en una instancia de Array. Sin embargo, la razón por la que usamos slice es más un "truco" que otra cosa. El método de corte tomará, como habrá adivinado, un segmento de una matriz y devolverá ese segmento como una nueva matriz. No pasarle ningún argumento (aparte del objeto de argumentos como contexto) hace que el método de corte tome una parte completa de la "matriz" pasada (en este caso, el objeto de argumentos) y la devuelva como una nueva matriz.
Normalmente llamando
var b = a.slice();
Copiará la matriz a
en b
. Sin embargo, no podemos hacer
var a = arguments.slice();
porque arguments
no tiene slice
como método (no es una matriz real).
Array.prototype.slice
es la slice
función para matrices. .call
ejecuta esta slice
función, con el this
valor establecido en arguments
.
Array.prototype.slice.call(arguments) es la forma antigua de convertir argumentos en una matriz.
En ECMAScript 2015, puede utilizar Array.from o el operador de extensión:
let args = Array.from(arguments);
let args = [...arguments];
Primero, deberías leer cómo funciona la invocación de funciones en JavaScript . Sospecho que eso por sí solo es suficiente para responder a tu pregunta. Pero he aquí un resumen de lo que está pasando:
Array.prototype.slice
extrae el método del prototipo . Pero llamarlo directamente no funcionará, ya que es un método (no una función) y por lo tanto requiere un contexto (un objeto de llamada ), de lo contrario arrojaría .slice
Array
this
Uncaught TypeError: Array.prototype.slice called on null or undefined
El call()
método le permite especificar el contexto de un método, básicamente haciendo que estas dos llamadas sean equivalentes:
someObject.slice(1, 2);
slice.call(someObject, 1, 2);
Excepto que el primero requiere que el slice
método exista en someObject
la cadena de prototipos (como ocurre con Array
), mientras que el segundo permite que el contexto ( someObject
) se pase manualmente al método.
Además, este último es la abreviatura de:
var slice = Array.prototype.slice;
slice.call(someObject, 1, 2);
Que es lo mismo que:
Array.prototype.slice.call(someObject, 1, 2);
// 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]