为什么在JavaScript中某些函数调用被称为“非法调用”?

127

比如,如果我这样做:

var q = document.querySelectorAll;

q('body');

我在Chrome中遇到了“非法调用”错误。我想不出任何原因为什么这是必需的。首先,这并不适用于所有本地代码函数。事实上,我可以这样做:

var o = Object; // which is a native code function

var x = new o();

一切都运作得很好。但特别是在处理文档和控制台时,我发现了这个问题。你有什么想法吗?


在Chrome中出现“Uncaught TypeError: Illegal invocation”的完全重复问题 - Dan Dascalescu
它们相似,但不完全相同(只有答案相同)。在一个情况下,OP将函数分配给自由变量并调用它(全局上下文),在另一个情况下,OP将函数分配给对象属性并调用它(对象上下文)。来自搜索引擎的用户可能不知道这一点。 - user202729
3个回答

207

这是因为您已经失去了函数的“上下文”。

当您调用:

document.querySelectorAll()
函数的上下文是document对象,并且该方法的实现可以通过this来访问该上下文。
当只调用q时,就没有上下文了,它将变成"全局"window对象。 querySelectorAll方法的实现尝试使用this,但此时它不再是一个DOM元素,而是一个Window对象。该实现尝试调用某个在Window对象上不存在的DOM元素方法,解释器毫不意外地发出错误信息。
为了解决这个问题,在新版本的Javascript中使用.bind方法:
var q = document.querySelectorAll.bind(document);

这将确保所有后续对 q 的调用都有正确的上下文。如果你没有 .bind,可以使用以下代码:

function q() {
    return document.querySelectorAll.apply(document, arguments);
}

5
哦,说得对。你是对的,因为我可以这样做:q.apply(document, ['body']); 然后它就可以工作了。 - user1152187
请注意,这并不一定适用于IE中的内置函数。例如,console.log在那里没有apply方法。 - hugomg
@Alnitak:是的,它在除了IE之外的所有地方都可以工作,这就是为什么你通常应该正常传递参数,比如function q(x){ return document.querySelectorAll(x); }。我真的很喜欢IE浏览器对象的另一件事情是,其中一些对象如果你尝试从中读取属性,它们会抛出异常,因此你需要使用if( 'funcname' in browserobject)而不是通常的if(browserobject.funcname)来测试功能! - hugomg
非常好的答案,我真的对这个现象感到困惑,和楼主的情况完全一样。 - temporary_user_name
1
大开眼界,谢谢。 - rb-

-1
你可以这样使用:
let qsa = document.querySelectorAll;
qsa.apply(document,['body']);

-2
在我的情况下,由于将未声明的变量作为参数传递给函数而发生了非法调用。 确保在传递给函数之前声明变量。

1
在这种情况下,声明变量是没有意义的,因为非法调用发生在DOM依赖方法在DOM上下文之外被调用时,因为一旦你执行q = document.something,something方法就失去了document的上下文。 - Anshul Sahni

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接