«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: незаконный вызов

в хроме. Зачем?

Штефана
источник

Ответы:

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: Я использую Chrome 33, и он работает для меня.
Немой
1
Я просто скопировал ваш код и получил ошибку незаконного вызова. Вот скринкаст.
Дан Даскалеску
24
Вы обязательно получите ошибку незаконного вызова, потому что первый заговор myObj.myAlert('this is an alert');недопустим. Правильное использование есть myObj.myAlert.call(window, 'this is an alert'). Пожалуйста, прочитайте ответы правильно и постарайтесь понять это.
Немой
3
Если я здесь не единственный, кто застревает при попытке заставить console.log.apply работать таким же образом, «this» должно быть консолью, а не окном: stackoverflow.com/questions/8159233/…
Alex
17

Вы также можете использовать:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
afmeva
источник
2
Это не полностью отвечает на вопрос. Я думаю, что это должен быть комментарий, а не ответ.
Михал Перлаковский
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() {});.

Михал Перлаковский
источник