comment fonctionne Array.prototype.slice.call ()?


474

Je sais qu'il est utilisé pour faire des arguments un vrai tableau, mais je ne comprends pas ce qui se passe lors de l'utilisation Array.prototype.slice.call(arguments)


2
^ légèrement différent car ce lien pose des questions sur les nœuds DOM et non sur les arguments. Et je pense que la réponse ici est bien meilleure en décrivant ce que «ceci» est en interne.
sqram

Réponses:


870

Ce qui se passe sous le capot, c'est que lorsqu'il .slice()est appelé normalement, thisest un tableau, puis il itère simplement sur ce tableau et fait son travail.

Comment est thisdans la .slice()fonction un tableau? Parce que quand vous le faites:

object.method();

... le objectdevient automatiquement la valeur de thisdans le method(). Donc avec:

[1,2,3].slice()

... le [1,2,3]tableau est défini comme la valeur de thisin .slice().


Mais que se passe-t-il si vous pouvez remplacer quelque chose d'autre par la thisvaleur? Tant que tout ce que vous remplacez a une .lengthpropriété numérique et un tas de propriétés qui sont des indices numériques, cela devrait fonctionner. Ce type d'objet est souvent appelé objet de type tableau .

Les méthodes .call()et .apply()vous permettent de définir manuellement la valeur de thisdans une fonction. Donc, si nous définissons la valeur de thisin .slice()dans un objet de type tableau , supposerons.slice() simplement qu'il fonctionne avec un tableau et fera son travail.

Prenez cet objet simple comme exemple.

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

Ce n'est évidemment pas un tableau, mais si vous pouvez le définir comme thisvaleur de .slice(), cela fonctionnera simplement, car il ressemble suffisamment à un tableau pour .slice()fonctionner correctement.

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

Exemple: http://jsfiddle.net/wSvkv/

Comme vous pouvez le voir dans la console, le résultat est ce que nous attendons:

['three','four'];

C'est donc ce qui se produit lorsque vous définissez un argumentsobjet comme thisvaleur de .slice(). Parce qu'il argumentsa une .lengthpropriété et un tas d'index numériques, .slice()va juste sur son travail comme s'il travaillait sur un vrai tableau.


7
Très bonne réponse! Mais malheureusement, vous ne pouvez pas convertir n'importe quel objet de cette façon, si vos clés d'objet sont des valeurs de chaîne, comme dans les mots réels. :'valeur'.
joopmicroop

6
@Michael: La lecture du code source des implémentations open source JS est possible, mais il est plus simple de se référer simplement à la spécification du langage "ECMAScript". Voici un lien vers la Array.prototype.slicedescription de la méthode.

1
comme les clés d'objet n'ont pas d'ordre, cette démonstration spécifique peut échouer dans d'autres navigateurs. tous les fournisseurs ne trient pas les clés de leurs objets par ordre de création.
vsync

1
@vsync: c'est la for-indéclaration qui ne garantit pas l'ordre. L'algorithme utilisé par .slice()définit un ordre numérique commençant par 0et se terminant (non inclus) par l' .lengthobjet donné (ou Array ou autre). Il est donc garanti que l'ordre est cohérent dans toutes les implémentations.
cookie monster

7
@vsync: Ce n'est pas une hypothèse. Vous pouvez obtenir l'ordre de n'importe quel objet si vous le faites respecter. Disons que oui var obj = {2:"two", 0:"zero", 1: "one"}. Si nous utilisons for-inpour énumérer l'objet, il n'y a aucune garantie d'ordre. Mais si nous utilisons for, nous pouvons appliquer manuellement l'ordre: for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Nous savons maintenant que les propriétés de l'objet seront atteintes dans l'ordre numérique croissant que nous avons défini par notre forboucle. Voilà ce qui .slice()fait. Il ne se soucie pas d'avoir un véritable tableau. Il commence juste à 0et accède aux propriétés dans une boucle ascendante.
cookie monster

88

L' argumentsobjet n'est pas réellement une instance d'un tableau et n'a aucune des méthodes de tableau. Donc, arguments.slice(...)cela ne fonctionnera pas car l'objet arguments n'a pas la méthode slice.

Les tableaux ont cette méthode, et parce que l' argumentsobjet est très similaire à un tableau, les deux sont compatibles. Cela signifie que nous pouvons utiliser des méthodes de tableau avec l'objet arguments. Et puisque les méthodes de tableau ont été construites avec des tableaux à l'esprit, elles renverront des tableaux plutôt que d'autres objets d'argument.

Alors pourquoi l'utiliser Array.prototype? C'est Arrayl'objet à partir duquel nous créons de nouveaux tableaux à partir de ( new Array()), et ces nouveaux tableaux sont des méthodes et des propriétés transmises, comme slice. Ces méthodes sont stockées dans l' [Class].prototypeobjet. Donc, pour des raisons d'efficacité, au lieu d'accéder à la méthode de tranche par (new Array()).slice.call()ou [].slice.call(), nous l'obtenons directement à partir du prototype. C'est ainsi que nous n'avons pas à initialiser un nouveau tableau.

Mais pourquoi devons-nous le faire en premier lieu? Eh bien, comme vous l'avez dit, il convertit un objet arguments en une instance Array. Cependant, la raison pour laquelle nous utilisons la tranche est plus un "hack" qu'autre chose. La méthode slice prendra une, vous l'avez deviné, une tranche d'un tableau et retournera cette tranche comme un nouveau tableau. Le fait de ne lui passer aucun argument (en plus de l'objet arguments comme contexte) amène la méthode slice à prendre un morceau complet du "tableau" passé (dans ce cas, l'objet arguments) et à le renvoyer comme un nouveau tableau.


Vous pouvez utiliser une tranche pour les raisons décrites ici: jspatterns.com/arguments-considered-harmful
KooiInc

44

Normalement, appeler

var b = a.slice();

copiera le tableau adans b. Cependant, nous ne pouvons pas faire

var a = arguments.slice();

car argumentsn'est pas un vrai tableau et n'a pas slicede méthode. Array.prototype.sliceest la slicefonction des tableaux et callexécute la fonction avec la valeur thisset arguments.


2
merci mais pourquoi utiliser prototype? n'est pas sliceune Arrayméthode native ?
ilyo

2
Notez qu'il Arrays'agit d'une fonction constructeur et que la "classe" correspondante est Array.prototype. Vous pouvez également utiliser[].slice
user123444555621

4
IlyaD, sliceest une méthode de chaque Arrayinstance, mais pas la Arrayfonction constructeur. Vous utilisez prototypepour accéder aux méthodes des instances théoriques d'un constructeur.
Delan Azabani

23

Tout d'abord, vous devriez lire comment fonctionne l'invocation de fonctions en JavaScript . Je soupçonne que cela suffit à lui seul pour répondre à votre question. Mais voici un résumé de ce qui se passe:

Array.prototype.sliceextrait la méthode de de » prototype . Mais l'appeler directement ne fonctionnera pas, car c'est une méthode (pas une fonction) et nécessite donc un contexte (un objet appelant ), sinon il lancerait .slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

La call()méthode vous permet de spécifier le contexte d'une méthode, ce qui rend essentiellement ces deux appels équivalents:

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

Sauf que le premier nécessite que la sliceméthode existe dans someObjectla chaîne de prototypes de (comme il le fait pour Array), tandis que le second permet au contexte ( someObject) d'être passé manuellement à la méthode.

En outre, ce dernier est l'abréviation de:

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

C'est la même chose 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 (arguments) est la méthode à l'ancienne pour convertir un argument en tableau.

Dans ECMAScript 2015, vous pouvez utiliser Array.from ou l'opérateur de propagation:

let args = Array.from(arguments);

let args = [...arguments];

9

C'est parce que, comme le note MDN

L'objet arguments n'est pas un tableau. Il est similaire à un tableau, mais n'a aucune propriété de tableau à l'exception de la longueur. Par exemple, il n'a pas la méthode pop. Cependant, il peut être converti en un véritable tableau:

Ici, nous faisons appel sliceà l'objet natif Arrayet non à sa mise en œuvre et c'est pourquoi le supplément.prototype

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

4

N'oubliez pas, qu'une base basique de ce comportement est la fonte de type qui s'est entièrement intégrée au moteur JS.

Slice prend simplement l'objet (grâce à la propriété arguments.length existante) et retourne le tableau-objet casté après avoir fait toutes les opérations dessus.

Les mêmes logiques que vous pouvez tester si vous essayez de traiter la méthode String avec une valeur INT:

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

Et cela explique la déclaration ci-dessus.


1

Il utilise les slicetableaux méthode ont et appelle son thisêtre l' argumentsobjet. Cela signifie qu'il l'appelle comme si vous supposiez arguments.slice()qu'il argumentsavait une telle méthode.

La création d'une tranche sans argument prendra simplement tous les éléments - elle copie donc simplement les éléments de argumentsdans un tableau.


1

Supposons que vous ayez: function.apply(thisArg, argArray )

La méthode apply appelle une fonction, en passant l'objet qui sera lié à cela et un tableau optionnel d'arguments.

La méthode slice () sélectionne une partie d'un tableau et renvoie le nouveau tableau.

Ainsi, lorsque vous appelez Array.prototype.slice.apply(arguments, [0])la méthode de tranche de tableau est invoquée (bind) sur les arguments.


1

Peut-être un peu tard, mais la réponse à tout ce gâchis est que call () est utilisé dans JS pour l'héritage. Si nous comparons cela à Python ou PHP, par exemple, call est utilisé respectivement comme super (). init () ou parent :: _ construct ().

Ceci est un exemple de son utilisation qui clarifie tout:

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

  this.subject = subject;
}

Référence: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance


0

lorsque .slice () est appelé normalement, il s'agit d'un tableau, puis il itère simplement sur ce tableau et fait son travail.

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

J'écris juste ceci pour me rappeler ...

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

Ou utilisez simplement cette fonction pratique $ A pour transformer la plupart des choses en un tableau.

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

exemple d'utilisation ...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.