为什么JavaScript函数别名不起作用?

21

我有一些Firebug控制台函数调用,当Firebug未启用时,我希望将其禁用,例如console没有定义。这在IE6和FF3中运行良好,但在Chrome中不起作用:

var log;

if(console){
  log = console.log;
}else{
  log = function(){ return; }
}

我在Chrome中遇到了"Uncaught TypeError: Illegal Invocation"的错误 =/

我在这里阅读到了这个问题的解答,其中提到必须应用上下文,这对我来说有点新奇......而且我似乎无法弄清楚如何在所有浏览器中实现上述操作...


2
我应该指出,这个问题增强了我的对JavaScript的理解! - qodeninja
5个回答

27

是的,你应该持久化上下文:

var log;

if (window.console && typeof console.log === "function"){
  // use apply to preserve context and invocations with multiple arguments
  log = function () { console.log.apply(console, arguments); };
} else {
  log = function(){ return; }
}

发生的情况是当你调用一个函数时,上下文(即this值)会隐式设置,例如:

var obj = {
  method: function () { return this; }
};

obj.method() === obj; // true
在这种情况下,您正在调用作为对象属性定义的函数,当调用该函数时,this值将设置为该对象。
现在,就像在您的示例中一样,如果将该方法的引用复制到变量中:
var method = obj.method;
method() === window; // global object

正如你所看到的,this 值指的是全局对象。

因此,为了避免这种隐式行为,你可以使用 callapply 函数来显式地设置上下文。


1
如果(console)未为浏览器定义或已禁用,则会导致“console未定义”错误。最好检查(window.console)甚至(typeof console != "undefined")。 - Ates Goral
@Ates,是的,如果console未定义,将会产生一个ReferenceError错误。if (window.console && typeof console.log === "function")更安全,已编辑。 - Christian C. Salvadó
是的,我确实检查了 typeof,只是为了简化问题,因为那并不是问题所在。 - qodeninja
值得一提的是,控制台不仅由FireBug定义,而且在IE8和Chrome中也存在。 - kim3er
有个有趣的故事,我现在完全理解了这些有趣的东西。 - qodeninja
我认为值得指出的是,在这个答案中,apply()在闭包内被调用,从而变得不必要,因为闭包会将console捕获到它自己的执行上下文中。在这个答案中,只需在匿名函数内部调用console.log(arguments);就足够了。我认为一个更清晰的替代方案是省略闭包,使用bind()代替apply()log = console.bind(console); - sethro

7

将函数(如console.log)包装在另一个函数中的问题是它会失去上下文,即它将无法显示我们在其中放置“log”快捷方式的文件的正确行号。

相反,我建议使用以下方法:

 window.log = ((window.console && window.console.log) ?
              console.log.bind(console) : 
              function(){});

这适用于Firebug和Chrome开发工具,在没有控制台可用时不会抛出错误。最重要的是,它显示正确的文件和行号。


1
太棒了!唯一保留行号的答案。 - Daniel Ribeiro
1
我已经搜索了数小时,试图找到一种方法来执行此操作并保留控制台的文件名和行号。这个方法完美地解决了我的问题! - Justin

2

这行代码不起作用:

log("hi");

虽然这样做会:

log.call(console, "hi");

很明显,你需要使用正确的上下文调用别名函数--正如你自己所提到的。
我认为你需要使用一个函数包装器(一个具有对原始上下文的引用的闭包),而不是别名...
更新
还要注意,如果直接检查console,当变量不存在时可能会出现运行时错误。最好明确地检查它作为window.console。这是一种实现条件log包装器的方法:
var log = (function (console) {
    return console
        ? function () { console.log.apply(console, arguments); }
        : function () {}
})(window.console);

1

这个解决方案修改了之前CMS的优秀答案,以使其与IE8兼容。在执行此操作之前,您需要打开IE8控制台(按F12)。 (如果您忘记了,您需要完全退出IE8并重新启动,因为即使控制台存在,IE8也不会随后创建控制台对象。)

请注意,我们没有设置上下文,这是最初的问题,但事实证明,IE8不需要该上下文。(好事,因为IE8还没有提供console.log对象的apply方法!)

此代码适用于最新版本的Chrome,FireFox和MSIE。(它与MSIE6兼容且不会抛出错误。)

if((typeof console !== "undefined") && ((typeof console.log) !== "undefined"))
{
  if ((typeof console.log.apply !== "undefined"))
  {
    log = function() { console.log.apply(console,arguments) };
  }
  else
  {
    log = console.log;
  }
}
else
{
  log = function() {};
  // alert("No debug console");
}

0

我做了这个

var log;

log = function() {
  if ((window.console != null) && (window.console.log.apply != null)) {
    return console.log.apply(console, arguments);
  } else {
    return function() {};
  }
};

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