开发者问题收集

Chrome 中的“未捕获类型错误:非法调用”

2012-03-13
218586

当我使用 requestAnimationFrame 来制作一些本机支持的动画时,代码如下:

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

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

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

在 Chrome 中直接调用 support.animationFrame 会给出...

Uncaught TypeError: Illegal invocation

为什么?

3个回答

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

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

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

var support = {
   animationFrame: _raf ? _raf.bind(window) : null
};
Nemoy
2012-03-13

您还可以使用:

var obj = {
    alert: alert.bind(window)
};
obj.alert('I´m an alert!!');
afmeva
2015-05-04

执行方法(即分配给对象的函数)时,您可以在其中使用 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() {});

它可以正常工作,因为 requestAnimationFramewindow 的范围内执行,而不是在您的对象中执行。

但是,每次要调用此方法时都使用 .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 的一部分, IE <= 8 不支持 ECMAScript 5。幸运的是, MDN 上有一个 polyfill

您可能已经发现,您可以使用 .bind() 始终在 window 上下文中执行 requestAnimationFrame 。您的代码可能如下所示:

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

然后您只需使用 support.animationFrame(function() {});

Michał Perłakowski
2016-01-19