як працює 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": "value", а не як "stringName" : 'значення'.
joopmicroop

6
@Michael: Читання вихідного коду реалізацій JS з відкритим кодом можливо, але простіше просто звернутися до специфікації мови "ECMAScript". Ось посилання на Array.prototype.sliceопис методу.

1
Оскільки об'єктні ключі не мають порядку, ця специфічна демонстрація може не працювати в інших браузерах. не всі продавці сортують ключі об’єктів за порядком створення.
vsync

1
@vsync: це for-inтвердження, яке не гарантує замовлення. Використовуваний алгоритм .slice()визначає числовий порядок, починаючи з 0кінця (не включаючи) .lengthданого об'єкта (або масиву чи будь-якого іншого). Таким чином, замовлення гарантується послідовним у всіх реалізаціях.
монстр cookie

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та отримує доступ до властивостей у висхідному циклі.
монстр cookie

88

argumentsОб'єкт не є на самому ділі екземпляр масиву, і не має якогось - або з методів масиву. Отже, arguments.slice(...)не буде працювати, оскільки в об'єкта аргументів немає методу зрізу.

argumentsЦей масив має такий метод, і оскільки об’єкт дуже схожий на масив, вони сумісні. Це означає, що ми можемо використовувати методи масиву з об’єктом аргументів. А оскільки методи масиву були побудовані з урахуванням масивів, вони повертатимуть масиви, а не інші об’єкти аргументів.

То навіщо використовувати Array.prototype ? Це Arrayоб'єкт, з якого ми створюємо нові масиви з ( new Array()), і ці нові масиви передаються методами та властивостями, як фрагмент. Ці методи зберігаються в [Class].prototypeоб’єкті. Так, заради ефективності, замість того , щоб отримати доступ до методу зрізу шляхом (new Array()).slice.call()або [].slice.call()ми просто отримати його прямо від прототипу. Це так, що нам не доведеться ініціалізувати новий масив.

Але чому ми маємо це робити в першу чергу? Ну, як ви сказали, він перетворює об’єкт аргументів в екземпляр Array. Однак причина, по якій ми використовуємо фрагмент, є скоріше "злом", ніж будь-що. Метод зрізу візьме a, ви здогадалися, фрагмент масиву і поверне цей фрагмент як новий масив. Не передаючи йому жодних аргументів (окрім об'єкта аргументів як його контексту), змушує метод зрізу взяти повний фрагмент пройденого "масиву" (у цьому випадку об'єкта аргументів) і повернути його як новий масив.


Ви можете використовувати фрагмент з причин, описаних тут: 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витягує метод з російського прототипу . Але зателефонувати безпосередньо не вийде, оскільки це метод (а не функція), і тому потрібен контекст (викликуючий об'єкт, ), інакше він кинеться .slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

call()Метод дозволяє визначити контекст методу, в основному робить ці два виклики еквівалентні:

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

За винятком того, що перший вимагає, щоб sliceметод існував у someObjectланцюзі прототипів 's (як це відбувається 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 (аргументи) - це старомодний спосіб перетворення аргументів у масив.

У ECMAScript 2015 ви можете використовувати Array.from або оператор розповсюдження:

let args = Array.from(arguments);

let args = [...arguments];

9

Це тому, що, як зазначає MDN

Об'єкт аргументів не є масивом. Він схожий на масив, але не має властивостей масиву, крім довжини. Наприклад, у нього немає методу pop. Однак він може бути перетворений в реальний масив:

Тут ми закликаємося sliceдо рідного об’єкта, Arrayа не до його реалізації та ось чому зайвого.prototype

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

4

Не забувайте, що основи такої поведінки низького рівня - це типовий кастинг, який повністю інтегрований у JS-двигун.

Фрагмент просто приймає об'єкт (завдяки наявній властивості argument.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 )

Метод застосування викликає функцію, передаючи об'єкт, який буде прив'язаний до цього, і необов'язковий масив аргументів.

Метод slice () вибирає частину масиву і повертає новий масив.

Тому при виклику Array.prototype.slice.apply(arguments, [0])методу фрагмента масиву викликається (прив'язується) до аргументів.


1

Можливо, трохи пізно, але відповідь на весь цей безлад полягає в тому, що call () використовується в JS для успадкування. Якщо порівняти це з Python або PHP, наприклад, дзвінок використовується відповідно як super (). у цьому () або батько :: _ 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.