将console.log分配给另一个对象(Webkit问题)

7

我希望在防止控制台不存在时,尽可能地保持我的日志记录语句简短。为此,我想出了以下解决方案:

var _ = {};
if (console) {
    _.log = console.debug;
} else {
    _.log = function() { }
}

在我看来,这似乎非常优雅,并且在Firefox 3.6中运行良好(包括保留行号,使console.debugconsole.log更有用)。但是它在Safari 4中不起作用。[更新:或者在Chrome中也不起作用。因此,问题似乎在于Firebug和Webkit控制台之间的差异。]如果我按照上述方法操作:

console.debug('A')
_.log('B');

第一条语句在两个浏览器中都可以正常工作,但第二条在Safari中会生成“TypeError:Type Error”。这只是Firebug和Safari Web Developer工具在实现控制台方面的差异吗?如果是这样,在苹果 Webkit方面非常令人恼火。将控制台函数绑定到原型上再实例化,而不是直接绑定到对象上,并不能解决问题。
当然,我也可以从分配给_.log的匿名函数中调用console.debug,但那样我就会失去我的行号。还有其他想法吗?

这是Webkit的一个特性,不是一个bug;-) https://bugs.webkit.org/show_bug.cgi?id=20141 - Will Moffat
相关链接:http://stackoverflow.com/questions/14146316/why-does-scope-reduction-in-safari-break-existing-code - MvG
2个回答

8

首先,如果console确实未定义(如在IE等浏览器中),您将会收到一个错误。相反,您应该将其作为全局对象的属性进行检查,浏览器中的全局对象是window。通常在使用功能之前测试它是个好主意,因此我已添加了对debug方法的测试。

可能Safari中console.debug的实现依赖于其this值引用console,如果您使用_.log调用它,则情况将不同(此时this将引用_)。经过快速测试,这似乎是正确的,以下是解决问题的方法:

var _ = {};
if (typeof window.console != "undefined"
       && typeof window.console.debug == "function") {
    _.log = function() {
        window.console.debug.apply(window.console, arguments);
    }
} else {
    _.log = function() { }
}

3
没错,这是传统方法,但它有一个严重的缺陷:会丢失行号。如果您在应用程序的任何地方调用_.log(),控制台将报告该输出是从_.log()函数内部生成的,而不是原始来源。因此,使用console.debug()而不是console.log()的好处就没有了。 - Trevor Burnham
2
在这种情况下,不仅仅是使用console而没有任何别名或包装会更容易吗?如果它不存在,只需定义带有存根方法的console即可。例如:if (typeof window.console != "undefined") { window.console = { debug: function() {} } }等等。 - Tim Down
我认为你是正确的,Tim(虽然你的意思是 typeof window.console == "undefined" 而不是 !=,对吗?)。也许我应该放弃追求简洁,而是在需要时分配一个虚拟控制台。 - Trevor Burnham
糟糕。是的,我确实是想用==而不是!= - Tim Down
这在IE中也会失败,因为console.debug没有apply方法。谁能想到呢? - HaxElit
@HaxElit:确实:console是一个宿主对象,在IE 9之前的宿主对象方法从未继承自Function.prototype - Tim Down

0

我一直在寻找解决方案(这也是我找到你的问题的原因)。

正如Tim所指出的,WebKit浏览器(Safari,Chrome)在这种情况下依赖于thisconsole。然而,Firefox不是这样的。因此,在FF中,您可以重新分配函数并保留行号(否则所有日志看起来都像起源于日志记录函数,这不是很有用)。检查您使用的浏览器的最佳方法是执行它并检查结果。以下是如何检查它(在Coffeescript中):

# Check to see if reassigning of functions work
f = console.log
assignSupported = true
try
  f('Initializing logging...')
catch e
  assignSupported = false

稍后,当您编写函数检查assignSupported并相应地执行时:

levels =
  ERROR: 1
  WARN:  2
  LOG:   3
  INFO:  4
  DEBUG: 6

log.setLevel = (newLevel) ->
  for label, level of levels
    if level > newLevel # Skip low levels
      continue

    name = label.toLowerCase()
    f = -> # Fallback - empty function. In Js: var f = function () {}
    if console?[name]
      if assignSupported
        f = console[name] # Wee, we'll have line numbers.
      else
        # Webkit need the this of console.log (namely, console)
        # preserved, so we use a wrapper.
        #
        # Calling console[name] within the returned wrapper
        # makes [name] a subject of the closure, meaning
        # that it's the last value in the iteration -
        # we need to preserve it.
        f = ((n) ->
          return (-> console[n].apply(console, arguments)))(name)
    log[name] = f

log.setLevel levels.DEBUG

这些行:

f = ((n) ->
  return (-> console[n].apply(console, arguments)))(name)

可能看起来有点奇怪。这是因为name是循环变量并且是词法绑定的,这意味着将使用执行时的值,这将始终是最后一个level。它编译成以下javascript代码(如果更容易阅读):

f = (function(n) {
  return (function() {
    return console[n].apply(console, arguments);
  });
})(name);

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