stefan
stefan

Reputation: 2477

"Uncaught TypeError: Illegal invocation" in Chrome

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?

Upvotes: 169

Views: 225308

Answers (5)

Honza P.
Honza P.

Reputation: 1242

In my case the proper hint to resolve this issue was proper error handling and logging of promises via XXX.catch() promise method. Then the core issue appeared and I could handle it.

Upvotes: 0

Yanjan. Kaf.
Yanjan. Kaf.

Reputation: 1725

This is a Quite Common Issue related to binding in JavaScript, and how this works —

Suppose I have an function like this document.write, if I call it directly, like following, write function is aware of this which is referring of document object, But in JavaScript there is a things called Implicity Loss, or at least thats how we refer it to

document.write("Hello"); // Write is aware of this

If I make a alias of the function (its is not copy ), document.element to a variable like x, you might expect that the function will keep working as it was working, right ?

let x = document.write // x is just a refrence to the write function

No, the function x is just an alias of write function which is a native function in most browsers, which means its implemented by the browser. x is not aware of document object, so it takes whatever the global scope this at time of invoation is to be the this for the function.

So if you call function x it will give you illigal Invocation error, and to be able to use it you have make x aware of this , this is what's called explicit binding and you can use it using call or bind like this

x.call(document, "Hello, World!");

// Or you can use bind, which will create a new function

x.bind(document)("Hello, World!");

Upvotes: 2

Nemoy
Nemoy

Reputation: 3427

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
};

Upvotes: 239

Michał Perłakowski
Michał Perłakowski

Reputation: 92521

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() {});.

Upvotes: 18

afmeva
afmeva

Reputation: 869

You can also use:

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

Upvotes: 23

Related Questions