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


474

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


2
^ немного отличается, так как эта ссылка спрашивает об узлах DOM, а не об аргументах. И я думаю, что ответ здесь намного лучше, если описать, что «это» внутри.
sqram

Ответы:


870

Что происходит под капотом, так это то, что, когда .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(), то он просто будет работать, потому что он достаточно похож на массив для .slice()правильной работы.

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

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

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

['three','four'];

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


7
Отличный ответ! Но, к сожалению, вы не можете преобразовать просто любой объект таким образом, если ваши ключи объекта являются строковыми значениями, как в реальных словах. Это не удастся, поэтому сохраните содержимое ваших объектов как «0»: «значение», а не как «stringName» :'ценность'.
joopmicroop

6
@Michael: чтение исходного кода реализаций JS с открытым исходным кодом возможно, но проще просто обратиться к спецификации языка "ECMAScript". Вот ссылка на Array.prototype.sliceописание метода.

1
поскольку ключи объектов не имеют порядка, эта конкретная демонстрация может завершиться неудачей в других браузерах. не все поставщики сортируют ключи своих объектов по порядку создания.
vsync

1
@vsync: это for-inутверждение не гарантирует порядок. Используемый алгоритм .slice()определяет числовой порядок, начинающийся с 0и заканчивающийся (не включительно) с .lengthуказанным объектом (или массивом или чем-то еще). Таким образом, порядок гарантированно будет согласованным во всех реализациях.
печенье монстр

7
@vsync: это не предположение. Вы можете получить заказ от любого объекта, если вы его обеспечите. Допустим, у меня есть var obj = {2:"two", 0:"zero", 1: "one"}. Если мы используем for-inдля перечисления объекта, нет гарантии порядка. Но если мы используем for, мы можем вручную следить за соблюдением порядка: for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Теперь мы знаем, что свойства объекта будут достигнуты в порядке возрастания чисел, который мы определили в нашем forцикле. Вот что .slice()делает. Это не волнует, если у него есть фактический массив. Это только начинается в 0и обращается к свойствам в восходящем цикле.
печенье монстр

88

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

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

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

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


Вы можете использовать срез по причинам, описанным здесь: jspatterns.com/arguments-considered-harmful
KooiInc

44

Обычно звонит

var b = a.slice();

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

var a = arguments.slice();

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


2
спасибо, но зачем использовать prototype? не sliceнативный Arrayметод?
ilyo

2
Обратите внимание, что Arrayэто функция конструктора, и соответствующий «класс» это Array.prototype. Вы также можете использовать[].slice
user123444555621

4
IlyaD, sliceэто метод каждого Arrayэкземпляра, но не Arrayфункция конструктора. Вы используете prototypeдля доступа к методам теоретических экземпляров конструктора.
Делан Азабани

23

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

Array.prototype.sliceизвлекает метод из «s прототипа . Но вызов его напрямую не сработает, так как это метод (а не функция)slice Array и поэтому ему необходим контекст (вызывающий объект this), в противном случае он будет выброшен 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);

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) - это старомодный способ преобразования аргументов в массив.

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

let args = Array.from(arguments);

let args = [...arguments];

9

Это потому, что, как отмечает MDN

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

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

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

4

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

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

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

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

И это объясняет утверждение выше.


1

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

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


1

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

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

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

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


1

Может быть, немного поздно, но ответ на весь этот беспорядок в том, что call () используется в JS для наследования. Если мы сравним это, например, с Python или PHP, то call используется соответственно как 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


0

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

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

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

    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);
    });
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.