Reputation: 2477
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
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
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
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
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
Reputation: 869
You can also use:
var obj = {
alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
Upvotes: 23