"Uncaught TypeError: Незаконне виклик" у Chrome


137

Коли я використовую requestAnimationFrameвбудовану підтримувану анімацію із наведеним нижче кодом:

var support = {
    animationFrame: window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame
};

support.animationFrame(function() {}); //error

support.animationFrame.call(window, function() {}); //right

Безпосередньо заклик до support.animationFrameволі дасть ...

Uncaught TypeError: Незаконне виклик

в Chrome. Чому?

Відповіді:


195

У своєму коді ви призначаєте нативний метод властивості власного об'єкта. Коли ви телефонуєте support.animationFrame(function () {}), він виконується в контексті поточного об'єкта (тобто підтримки). Щоб нативна функція requestAnimationFrame працювала належним чином, вона повинна бути виконана в контексті window.

Тож правильне використання тут є support.animationFrame.call(window, function() {});.

Те саме відбувається і з попередженням:

var myObj = {
  myAlert : alert //copying native alert to an object
};

myObj.myAlert('this is an alert'); //is illegal
myObj.myAlert.call(window, 'this is an alert'); // executing in context of window 

Іншим варіантом є використання Function.prototype.bind (), яка є частиною стандарту ES5 і доступна у всіх сучасних браузерах.

var _raf = window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame;

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};

1
Що стосується Chrome 33, другий дзвінок закінчується також "Незаконне виклик". Раді вилучити протокол, коли відповідь буде оновлена !
Дан Даскалеску

@DanDascalescu: Я використовую хром 33, і він працює для мене.
Немой

1
Я щойно скопіював ваш код і отримав помилку виклику. Ось скріншот.
Дан Даскалеску

24
Ви неодмінно отримаєте помилку виклику, оскільки перша заява myObj.myAlert('this is an alert');є незаконною. Правильне використання є myObj.myAlert.call(window, 'this is an alert'). Будь ласка, прочитайте відповіді належним чином і спробуйте їх зрозуміти.
Немой

3
Якщо я не єдиний, хто зациклюється на спробі отримати console.log.apply, щоб працювати так само, "це" має бути консоль, а не вікно: stackoverflow.com/questions/8159233/…
Alex

17

Ви також можете використовувати:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');

2
Це не повністю відповідає на запитання. Я думаю, що це швидше повинен бути коментар, а не відповідь.
Michał Perłakowski

2
Також важливо прив’язати до відповідного об'єкта, наприклад, під час роботи з history.replaceState, слід використовувати: var realReplaceState = history.replaceState.bind(history);
DeeY

@DeeY: дякую за відповідь на моє запитання! Для майбутніх людей, localStorage.clear вимагає від вас .bind(localStorage), ні .bind(window).
Самйок Непал

13

Коли ви виконуєте метод (тобто функцію, призначену об'єкту), всередині нього ви можете використовувати thisзмінну для позначення цього об'єкта, наприклад:

var obj = {
  someProperty: true,
  someMethod: function() {
    console.log(this.someProperty);
  }
};
obj.someMethod(); // logs true

Якщо ви призначаєте метод від одного об'єкта до іншого, його thisзмінна посилається на новий об'єкт, наприклад:

var obj = {
  someProperty: true,
  someMethod: function() {
    console.log(this.someProperty);
  }
};

var anotherObj = {
  someProperty: false,
  someMethod: obj.someMethod
};

anotherObj.someMethod(); // logs false

Те ж саме відбувається, коли ви призначаєте requestAnimationFrameметод windowіншого об'єкта. Рідні функції, такі як ця, мають вбудований захист від її виконання в іншому контексті.

Існує Function.prototype.call()функція, яка дозволяє викликати функцію в іншому контексті. Вам просто потрібно передати його (об’єкт, який буде використовуватися як контекст) як перший параметр цього методу. Наприклад alert.call({})дає TypeError: Illegal invocation. Однак alert.call(window)працює чудово, адже зараз alertвін виконаний у своєму первісному обсязі.

Якщо ви використовуєте такий .call()об'єкт, як:

support.animationFrame.call(window, function() {});

він працює чудово, оскільки requestAnimationFrameвиконується в межах області windowзамість вашого об'єкта.

Однак використання .call()кожного разу, коли ви хочете викликати цей метод, не дуже елегантне рішення. Натомість можна використовувати Function.prototype.bind(). Він має подібний ефект .call(), але замість виклику функції, він створює нову функцію, яка завжди буде викликатися у визначеному контексті. Наприклад:

window.someProperty = true;
var obj = {
  someProperty: false,
  someMethod: function() {
    console.log(this.someProperty);
  }
};

var someMethodInWindowContext = obj.someMethod.bind(window);
someMethodInWindowContext(); // logs true

Єдиним недоліком Function.prototype.bind()є те, що це частина ECMAScript 5, яка не підтримується в IE <= 8 . На щастя, на MDN є поліфіл .

Як ви, напевно, вже зрозуміли, ви можете .bind()завжди виконувати requestAnimationFrameв контексті window. Ваш код може виглядати так:

var support = {
    animationFrame: (window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame).bind(window)
};

Тоді ви можете просто використовувати support.animationFrame(function() {});.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.