146

When I use requestAnimationFrame to do some native supported animation with below code:

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

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

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

Directly calling the support.animationFrame will give...

Uncaught TypeError: Illegal invocation

in Chrome. Why?

Michał Perłakowski
  • 70,955
  • 24
  • 137
  • 155
stefan
  • 2,217
  • 3
  • 13
  • 11

3 Answers3

210

In your code you are assigning a native method to a property of custom object. When you call support.animationFrame(function () {}) , it is executed in the context of current object (ie support). For the native requestAnimationFrame function to work properly, it must be executed in the context of window.

So the correct usage here is support.animationFrame.call(window, function() {});.

The same happens with alert too:

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 

Another option is to use Function.prototype.bind() which is part of ES5 standard and available in all modern browsers.

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

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};
Nemoy
  • 3,117
  • 1
  • 13
  • 14
  • 2
    As of Chrome 33, the second call fails as well with "Illegal invocation". Happy to remove the downvote once the answer is [updated](http://stackoverflow.com/a/13278323/1269037)! – Dan Dascalescu Mar 06 '14 at 13:19
  • @DanDascalescu: I am using chrome 33 and it is working for me. – Nemoy Mar 10 '14 at 02:30
  • 1
    I've just copy-pasted your code and get the Illegal invocation error. [Here's the screencast.](http://imgur.com/oTDsazG) – Dan Dascalescu Mar 10 '14 at 03:22
  • 24
    You will definitely get illegal invocation error, because the first stamtement `myObj.myAlert('this is an alert');` is illegal. Correct usage is `myObj.myAlert.call(window, 'this is an alert')`. Please read answers properly and try to understand it. – Nemoy Mar 11 '14 at 09:24
  • 3
    If I'm not the only one here getting stuck on trying to get console.log.apply to work in the same way, "this" should be the console, not window: http://stackoverflow.com/questions/8159233/typeerror-illegal-invocation-on-console-log-apply – Alex Dec 16 '15 at 11:24
  • As an alternative to `bind` , it's possible to create an anonymous function/lambda too -- `window.requestAnimationFrame.bind(window)`) can be replaced with `function(callback){ window.requestAnimationFrame(callback) }`. – user202729 Dec 30 '20 at 01:48
17

You can also use:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
Michał Perłakowski
  • 70,955
  • 24
  • 137
  • 155
afmeva
  • 699
  • 6
  • 11
  • 2
    This doesn't fully answer the question. I think it should rather be a comment, not an answer. – Michał Perłakowski Jan 19 '16 at 22:49
  • 2
    Also, it is important to bind to appropriate object, e.g. when working with history.replaceState, one should use: `var realReplaceState = history.replaceState.bind(history);` – DeeY Mar 12 '16 at 14:43
  • 1
    @DeeY: thanks for answering my question! For future people, localStorage.clear requires you to `.bind(localStorage)`, not `.bind(window)`. – Samyok Nepal Dec 08 '19 at 03:16
  • So, in the good ol days it was `let log = console.log` and `let create = document.createElement`, and now it is `let log = console.log.bind(console)` and `let create = document.createElement.bind(document)`. Ok ok ok. – Nils Lindemann Apr 02 '21 at 19:18
14

When you execute a method (i.e. function assigned to an object), inside it you can use this variable to refer to this object, for example:

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

If you assign a method from one object to another, its this variable refers to the new object, for example:

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

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

anotherObj.someMethod(); // logs false

The same thing happens when you assign requestAnimationFrame method of window to another object. Native functions, such as this, has build-in protection from executing it in other context.

There is a Function.prototype.call() function, which allows you to call a function in another context. You just have to pass it (the object which will be used as context) as a first parameter to this method. For example alert.call({}) gives TypeError: Illegal invocation. However, alert.call(window) works fine, because now alert is executed in its original scope.

If you use .call() with your object like that:

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

it works fine, because requestAnimationFrame is executed in scope of window instead of your object.

However, using .call() every time you want to call this method, isn't very elegant solution. Instead, you can use Function.prototype.bind(). It has similar effect to .call(), but instead of calling the function, it creates a new function which will always be called in specified context. For example:

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

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

The only downside of Function.prototype.bind() is that it's a part of ECMAScript 5, which is not supported in IE <= 8. Fortunately, there is a polyfill on MDN.

As you probably already figured out, you can use .bind() to always execute requestAnimationFrame in context of window. Your code could look like this:

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

Then you can simply use support.animationFrame(function() {});.

Michał Perłakowski
  • 70,955
  • 24
  • 137
  • 155