¿Cómo funciona Array.prototype.slice.call ()?


474

Sé que se usa para convertir los argumentos en una matriz real, pero no entiendo qué sucede cuando se usa Array.prototype.slice.call(arguments)


2
^ ligeramente diferente ya que ese enlace pregunta sobre nodos DOM y no sobre argumentos. Y creo que la respuesta aquí es mucho mejor al describir qué es 'esto' internamente.
sqram

Respuestas:


870

Lo que sucede debajo del capó es que cuando .slice()se llama normalmente, thises un Array, y luego simplemente itera sobre ese Array, y hace su trabajo.

¿Cómo es thisen la .slice()función una matriz? Porque cuando lo haces:

object.method();

... el objectautomáticamente se convierte en el valor de thisen el method(). Entonces con:

[1,2,3].slice()

... la [1,2,3]matriz se establece como el valor de thisin .slice().


Pero, ¿qué pasaría si pudieras sustituir algo más como thisvalor? Siempre que lo que sustituya tenga una .lengthpropiedad numérica y un conjunto de propiedades que sean índices numéricos, debería funcionar. Este tipo de objeto a menudo se llama un objeto tipo matriz .

El .call()y .apply()métodos le permiten manualmente establece el valor de thisuna función. Así que si nos fijamos el valor de thisen .slice()un conjunto similar de objetos , .slice()sólo va a suponer que está trabajando con una matriz, y hará su cosa.

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 thisvalor de .slice(), entonces simplemente funcionará, porque se parece lo suficiente a una matriz para .slice()que funcione correctamente.

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

Ejemplo: http://jsfiddle.net/wSvkv/

Como puede ver en la consola, el resultado es lo que esperamos:

['three','four'];

Entonces, esto es lo que sucede cuando configura un argumentsobjeto como el thisvalor de .slice(). Debido a que argumentstiene una .lengthpropiedad y un montón de índices numéricos, .slice()simplemente realiza su trabajo como si estuviera trabajando en una matriz real.


77
¡Gran respuesta! Pero lamentablemente no puede convertir cualquier objeto de esta manera, si sus claves de objeto son valores de cadena, como en palabras reales ... Esto fallará, así que mantenga el contenido de sus objetos como '0': 'valor' y no como 'stringName' :'valor'.
joopmicroop

66
@Michael: es posible leer el código fuente de las implementaciones de JS de código abierto, pero es más simple simplemente referirse a la especificación del lenguaje "ECMAScript". Aquí hay un enlace a la Array.prototype.slicedescripción del método.

1
Como las claves de objeto no tienen orden, esta demostración específica puede fallar en otros navegadores. No todos los proveedores clasifican las claves de sus objetos por orden de creación.
vsync

1
@vsync: es la for-indeclaración que no garantiza el orden. El algoritmo utilizado por .slice()define un orden numérico que comienza 0y termina (no incluye) con el .lengthdel objeto dado (o Array o lo que sea). Por lo tanto, se garantiza que el orden será coherente en todas las implementaciones.
cookie monster

77
@vsync: no es una suposición. Puede obtener orden de cualquier objeto si lo aplica. Digamos que tengo var obj = {2:"two", 0:"zero", 1: "one"}. Si usamos for-inpara enumerar el objeto, no hay garantía de orden. Pero si utilizamos for, podemos cumplir manualmente el orden: for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Ahora sabemos que las propiedades del objeto se alcanzarán en el orden numérico ascendente que definimos por nuestro forbucle. Eso es lo que .slice()hace. No le importa si tiene una matriz real. Simplemente comienza en 0y accede a las propiedades en un bucle ascendente.
cookie monster

88

El argumentsobjeto no es en realidad una instancia de una matriz y no tiene ninguno de los métodos de matriz. Por lo tanto, arguments.slice(...)no funcionará porque el objeto de argumentos no tiene el método de división.

Las matrices tienen este método, y debido a que el argumentsobjeto 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 construyeron teniendo en cuenta las matrices, devolverán matrices en lugar de otros objetos de argumento.

Entonces, ¿por qué usar Array.prototype? El Arrayes el objeto desde el que creamos nuevas matrices ( new Array()), y estas nuevas matrices son métodos y propiedades pasados, como el corte. Estos métodos se almacenan en el [Class].prototypeobjeto. Entonces, en aras de la eficiencia, en lugar de acceder al método de corte por (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 "hack" que cualquier otra cosa. El método de división tomará una, usted lo adivinó, una división de una matriz y devolverá esa división como una nueva matriz. Al no pasarle argumentos (además del objeto de argumentos como contexto), el método de división toma una parte completa de la "matriz" pasada (en este caso, el objeto de argumentos) y la devuelve como una nueva matriz.


Es posible que desee utilizar un segmento por las razones que se describen aquí: jspatterns.com/arguments-considered-harmful
KooiInc

44

Normalmente, llamando

var b = a.slice();

copiará la matriz aen b. Sin embargo, no podemos hacer

var a = arguments.slice();

porque argumentsno es una matriz real, y no tiene slicecomo método. Array.prototype.slicees la slicefunción para matrices y callejecuta la función con thisset to arguments.


2
gracias, pero ¿por qué usar prototype? No es sliceun Arraymétodo nativo ?
ilyo

2
Tenga en cuenta que Arrayes una función constructora, y la "clase" correspondiente es Array.prototype. También puede usar[].slice
user123444555621

44
IlyaD, slicees un método de cada Arrayinstancia, pero no la Arrayfunción constructora. Se utiliza prototypepara acceder a métodos de instancias teóricas de un constructor.
Delan Azabani

23

Primero, debe leer cómo funciona la invocación de funciones en JavaScript . Sospecho que solo es suficiente para responder a su pregunta. Pero aquí hay un resumen de lo que está sucediendo:

Array.prototype.sliceextrae el método de 's 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 ArraythisUncaught 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 slicemétodo exista en someObjectla cadena de prototipos (como lo hace para 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);

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 (argumentos) es la forma tradicional de convertir un argumento en una matriz.

En ECMAScript 2015, puede usar Array.from o el operador de propagación:

let args = Array.from(arguments);

let args = [...arguments];

9

Es porque, como señala MDN

El objeto de argumentos no es una matriz. Es similar a una matriz, pero no tiene propiedades de matriz, excepto la longitud. Por ejemplo, no tiene el método pop. Sin embargo, se puede convertir en una matriz real:

Aquí estamos llamando sliceal objeto nativo Arrayy no a su implementación y es por eso que el extra.prototype

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

4

No olvide, que los conceptos básicos de bajo nivel de este comportamiento es la conversión de tipos que se integró completamente en el motor JS.

Slice solo toma el objeto (gracias a la propiedad argumentos.length existente) y devuelve el objeto matriz arrojado después de hacer todas las operaciones sobre eso.

Las mismas lógicas que puedes probar si intentas tratar el método String con un valor INT:

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

Y eso explica la declaración anterior.


1

Utiliza el slicemétodo que tienen las matrices y lo llama thissiendo su argumentsobjeto. Esto significa que lo llama como si arguments.slice()supusieras que tuvieras argumentstal método.

Crear un segmento sin argumentos simplemente tomará todos los elementos, por lo que simplemente copia los elementos de argumentsuna matriz.


1

Asumamos que tienes: function.apply(thisArg, argArray )

El método apply invoca una función, pasando el objeto que estará vinculado a esto y una matriz opcional de argumentos.

El método slice () selecciona una parte de una matriz y devuelve la nueva matriz.

Entonces, cuando llama Array.prototype.slice.apply(arguments, [0])al método de división de matriz, se invoca (enlaza) en los argumentos.


1

Tal vez un poco tarde, pero la respuesta a todo este desastre es que call () se usa en JS por herencia. Si comparamos esto con Python o PHP, por ejemplo, call se usa respectivamente como super (). init () o parent :: _ construct ().

Este es un ejemplo de su uso que aclara todo:

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

  this.subject = subject;
}

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


0

cuando .slice () se llama normalmente, este es un Array, y luego simplemente itera sobre ese Array, y hace su trabajo.

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

Solo estoy escribiendo esto para recordarme ...

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

O simplemente use esta práctica función $ A para convertir la mayoría de las cosas en una 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);
}

ejemplo de uso ...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.