Chrome 中的“未捕获的 TypeError: 非法调用”

当我使用 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将会给出..。

未捕获的 TypeError: 非法调用

铬合金的,怎么了?

196798 次浏览

在代码中,将本机方法分配给自定义对象的属性。 当您调用 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

另一种选择是使用 Bind (),它是 ES5标准的一部分,可在所有现代浏览器中使用。

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


var support = {
animationFrame: _raf ? _raf.bind(window) : null
};

你亦可使用:

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

当你执行一个方法(例如分配给一个对象的函数) ,在它里面你可以使用 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

windowrequestAnimationFrame方法分配给另一个对象时也会发生同样的情况。像这样的本机函数具有内置保护,不会在其他上下文中执行它。

有一个 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的一部分,而 ECMAScript 5是 IE < = 8不支持

正如您可能已经指出的,您可以使用 .bind()始终在 window的上下文中执行 requestAnimationFrame。您的代码可以是这样的:

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

然后您可以简单地使用 support.animationFrame(function() {});

这是一个与 JavaScript 中的绑定以及 this如何工作有关的常见问题ー

假设我有一个像 document.write这样的函数,如果我直接调用它,比如 following,write 函数知道 thisdocument对象的引用,但是在 JavaScript 中有一个叫做 隐性损失的东西,或者至少我们是这样引用它的

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

如果我把函数的别名(它不是拷贝) document.element写成像 x这样的变量,你可能会期望函数在工作时继续工作,对吗?

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

不,函数 x只是 write函数的一个别名,而 write函数在大多数浏览器中是一个本机函数,这意味着它是由浏览器实现的。x不知道 document对象,因此它在调用时采用的全局作用域 this是函数的 this

所以如果你调用函数 x它会给你一个非法的调用错误为了能够使用它你必须让 x知道 this这就是所谓的 explicit binding你可以像这样使用 call或者 bind

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


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


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