Как работает Array.prototype.slice.call()?

Я знаю, что он используется для создания аргументов реального массива, но я не понимаю, что происходит при использовании Array.prototype.slice.call(arguments)

Ответ 1

Что происходит под капотом, так это то, что когда .slice() вызывается нормально, this является массивом, а затем он просто выполняет итерацию над этим массивом и выполняет свою работу.

Как this в функции .slice() находится массив? Потому что когда вы делаете:

object.method();

... object автоматически становится значением this в method(). Итак:

[1,2,3].slice()

... [1,2,3] Массив задается как значение this в .slice().


Но что, если вы могли бы заменить что-то еще как значение this? До тех пор, пока все, что вы замените, имеет числовое свойство .length и набор свойств, которые являются числовыми индексами, он должен работать. Этот тип объекта часто называют массивом.

Методы .call() и .apply() позволяют вручную установить значение this в функции. Поэтому, если мы установим значение this в .slice() для объекта, подобного массиву, .slice() просто предположим, что он работает с массивом и выполнит его.

Возьмите этот простой объект в качестве примера.

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

Это, очевидно, не массив, но если вы можете установить его как значение this .slice(), тогда он будет работать, потому что он выглядит достаточно, как Array для .slice() для правильной работы.

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

Пример: http://jsfiddle.net/wSvkv/

Как вы можете видеть на консоли, результат - это то, что мы ожидаем:

['three','four'];

Так вот что происходит, когда вы устанавливаете объект arguments как значение this .slice(). Поскольку arguments имеет свойство .length и набор числовых индексов, .slice() просто выполняет свою работу, как если бы он работал над реальным массивом.

Ответ 2

Объект arguments фактически не является экземпляром массива и не имеет каких-либо методов Array. Таким образом, arguments.slice(...) не будет работать, потому что объект arguments не имеет метода среза.

Массивы имеют этот метод, и поскольку объект arguments очень похож на массив, эти два являются совместимыми. Это означает, что мы можем использовать методы массива с объектом arguments. И поскольку методы массива были построены с учетом массивов, они возвращают массивы, а не другие объекты аргументов.

Так зачем использовать Array.prototype? Array - это объект, из которого мы создаем новые массивы из (new Array()), и этим новым массивам передаются методы и свойства, такие как срез. Эти методы хранятся в объекте [Class].prototype. Итак, ради эффективности, вместо доступа к методу среза (new Array()).slice.call() или [].slice.call(), мы просто получаем его прямо из прототипа. Это значит, что нам не нужно инициализировать новый массив.

Но почему мы должны это делать в первую очередь? Ну, как вы сказали, он преобразует объект аргументов в экземпляр Array. Однако причина, по которой мы используем срез, скорее всего является "взломом", чем чем-либо. Метод slice примет, как вы догадались, срез массива и возвращает этот срез как новый массив. Передача ему никаких аргументов (помимо объекта arguments в качестве его контекста) заставляет метод slice принимать полный фрагмент переданного "массива" (в данном случае объекта arguments) и возвращать его как новый массив.

Ответ 3

Обычно вызов

var b = a.slice();

скопирует массив a в b. Однако мы не можем делать

var a = arguments.slice();

потому что arguments не является реальным массивом и не имеет slice в качестве метода. Array.prototype.slice - это функция slice для массивов, а call запускает функцию с this, установленную на arguments.

Ответ 4

// We can apply `slice` from  `Array.prototype`:
Array.prototype.slice.call([]); //-> []

// Since `slice` is available on an array 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]

Ответ 5

Во-первых, вы должны прочитать как работает функция вызова в JavaScript. Я подозреваю, что одного достаточно, чтобы ответить на ваш вопрос. Но вот резюме того, что происходит:

Array.prototype.slice извлекает slice method from Array prototype. Но вызов его напрямую не будет работать, как метод (а не функция) и поэтому требует контекста (вызывающего объекта, this), иначе он throw Uncaught TypeError: Array.prototype.slice called on null or undefined.

Метод call() позволяет указать контекст метода, в основном делая эти два вызова эквивалентными:

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

За исключением первого, требуется, чтобы метод slice существовал в цепочке прототипов someObject (как и для Array), в то время как последний позволяет переносить контекст (someObject) в метод.

Кроме того, последнее сокращается для:

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

Это то же самое, что:

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

Ответ 6

Потому что, поскольку примечания MDN

Объект arguments не является массивом. Он похож на массив, но не имеет никаких свойств массива, кроме длины. Например, это не имеет метода pop. Однако он может быть преобразован в реальный массив:

Здесь мы вызываем slice на нативный объект Array, а не на его реализацию, и поэтому дополнительный .prototype

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

Ответ 7

Не забывайте, что низкоуровневые основы этого поведения - это тип-литье, полностью интегрированное в JS-движок.

Slice просто берет объект (благодаря существующему свойству arguments.length) и возвращает массив-объект, литый после выполнения всех операций над ним.

Те же логики, которые вы можете проверить, если попытаетесь обработать String-метод с INT-значением:

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

И это объясняет вышесказанное выше.

Ответ 8

Array.prototype.slice.call(arguments) - это старомодный способ преобразования аргументов в массив.

В ECMAScript 2015 вы можете использовать Array.from или оператор распространения:

let args = Array.from(arguments);

let args = [...arguments];

Ответ 9

Он использует массив методов slice, и он вызывает его с this объектом arguments. Это означает, что он называет его так, как если бы вы arguments.slice() предполагали, что arguments имел такой метод.

Создание среза без каких-либо аргументов будет просто принимать все элементы - поэтому он просто копирует элементы из arguments в массив.

Ответ 10

Предположим, что у вас есть: function.apply(thisArg, argArray )

Метод apply вызывает функцию, проходящую в объекте, который будет привязан к этому и необязательный массив аргументов.

Метод slice() выбирает часть массива и возвращает новый массив.

Поэтому, когда вы вызываете Array.prototype.slice.apply(arguments, [0]), метод среза массива вызывается (привязывается) к аргументам.

Ответ 11

Возможно, немного поздно, но ответ на весь этот беспорядок заключается в том, что call() используется в JS для наследования. Если сравнить это с Python или PHP, например, вызов используется соответственно как super(). init() или parent:: _ construct().

Это пример его использования, который разъясняет все:

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

  this.subject = subject;
}

Ссылка: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance

Ответ 12

когда .slice() вызывается нормально, это Array, а затем он просто выполняет итерацию над этим массивом и выполняет свою работу.

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

Ответ 13

Я просто пишу это, чтобы напомнить себе...

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

Или просто используйте эту удобную функцию $A, чтобы превратить большинство вещей в массив.

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

пример использования...

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