"Uncaught TypeError: Illegal invocation" in Chrome
Asked Answered
F

5

166

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?

Finance answered 13/3, 2012 at 3:27 Comment(0)
S
235

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
};
Stalk answered 13/3, 2012 at 3:59 Comment(6)
As of Chrome 33, the second call fails as well with "Illegal invocation". Happy to remove the downvote once the answer is updated!Brotherinlaw
@DanDascalescu: I am using chrome 33 and it is working for me.Stalk
I've just copy-pasted your code and get the Illegal invocation error. Here's the screencast.Brotherinlaw
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.Stalk
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: #8159733Farber
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) }.Faustinafaustine
D
21

You can also use:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
Dietz answered 4/5, 2015 at 20:6 Comment(4)
This doesn't fully answer the question. I think it should rather be a comment, not an answer.Caren
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);Equatorial
@DeeY: thanks for answering my question! For future people, localStorage.clear requires you to .bind(localStorage), not .bind(window).Bloodandthunder
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.Bluish
I
17

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

Imbrue answered 19/1, 2016 at 22:47 Comment(0)
P
0

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!");
Pardue answered 27/9, 2022 at 5:23 Comment(0)
E
0

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.

Eisteddfod answered 21/4, 2023 at 13:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.