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


137

当我用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:非法调用

在Chrome中。为什么?

Answers:


195

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

1
从Chrome 33开始,第二次调用也会因“非法调用”而失败。答案更新后,很高兴删除下注!
Dan Dascalescu 2014年

@DanDascalescu:我正在使用chrome 33,它正在为我工​​作。
Nemoy 2014年

1
我刚刚复制粘贴了您的代码,并收到了非法调用错误。这是截屏视频。
Dan Dascalescu 2014年

24
您肯定会得到非法调用错误,因为第一次驻扎myObj.myAlert('this is an alert');是非法的。正确用法是myObj.myAlert.call(window, 'this is an alert')。请正确阅读答案并尝试理解它。
Nemoy 2014年

3
如果我不是唯一一个在这里试图console.log.apply去工作以同样的方式被卡住,“这”应该是控制台,而不是窗口:stackoverflow.com/questions/8159233/...
亚历克斯·

17

您还可以使用:

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

2
这不能完全回答问题。我认为这应该是评论,而不是答案。
米哈尔Perłakowski

2
同样,绑定到适当的对象也很重要,例如,在使用history.replaceState时,应使用: var realReplaceState = history.replaceState.bind(history);
DeeY

@DeeY:感谢您回答我的问题!对于未来的人,localStorage.clear要求您提供.bind(localStorage),而不是.bind(window)
Samyok Nepal

13

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

当您分配的requestAnimationFrame方法相同时,也会发生window给另一个对象。诸如此类的本机功能具有内置保护,无法在其他上下文中执行它。

有一个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不支持它。幸运的是,MDN上一个polyfill

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.