【问题标题】:Javascript, odd behaviour in a closureJavascript,闭包中的奇怪行为
【发布时间】:2010-02-19 14:15:15
【问题描述】:

我正在玩弄(阅读:学习)Javascript,发现了一些我理解的东西,看起来很奇怪。它与闭包和似乎“失去”其对浏览器重要性的引用有关。

我使用的浏览器是 Chromium 5.0.307.7。

不管怎样,这里有一些代码:

HTMLElement.prototype.writeInSteps = function() {
  var i = 0;
  var elem = this;
  var args = arguments;

  function step() {
    elem.innerHTML += args[i];

    if(i < args.length) {
      i += 1;
    } else {
      elem.innerHTML = "";
      i = 0;
    }


    setTimeout(step, 500);
  }

  step();
}

这里发生的是第一个参数被写入正确的 HTMLElement,但后面的所有参数都没有。似乎发生的情况是,在第一个参数之后,以下参数被写入到现在被 'elem' 引用的某个其他元素。

我还要提一下,这似乎只有在我调用这个函数后直接写东西时才会发生,像这样:

div.writeInSteps("This", " is", " not", " working");
$id("body").innerHTML += "Doh!";

如果我在调用这个函数后不写任何东西,它似乎工作正常。

如果我将上面的代码改为:

HTMLElement.prototype.writeInSteps = function() {
  var i = 0;
  var e = this.id;
  var args = arguments;

  function step() {
    var elem = $id(e);
    elem.innerHTML += args[i];

    if(i < args.length) {
      i += 1;
    } else {
      elem.innerHTML = "";
      i = 0;
    }


    setTimeout(step, 500);
  }

  step();
}

一切都是花花公子。我的问题是,第一个版本的幕后究竟发生了什么?

编辑:根据ntownsend 的要求,更新了有关“...直接在...之后写一些东西”和浏览器使用情况的请求详细信息。 Bryan Matthews,我不知道如何提供一个测试页面而不会使这个问题过于混乱。

【问题讨论】:

  • 你应该在修改后的版本中将elem设为局部变量;现在它是一个隐式全局变量,如果你不小心,很容易导致奇怪的错误(如果你不必担心,为什么要担心呢?)。
  • 您能否提供一个示例页面来演示此行为?我试用了您的第一个版本,它似乎工作得很好,包括同时执行多个。
  • 杰夫是对的。虽然我确信这只是一个重构错误。
  • 你能澄清一下“这似乎只发生在我调用函数后直接写东西时”的意思吗?您的意思是,如果在调用writeInSteps 之后,您尝试更改您正在调用该函数的 HTMLElement 的内容,那么您只会看到奇怪的行为?另外,你是怎么调用函数的?
  • 另外,这发生在什么浏览器中?我似乎无法重现它。

标签: javascript reference closures


【解决方案1】:

我怀疑这是 DOM 问题,而不是 JavaScript 问题。

我的猜测是某些东西正在改变您尝试逐步写入的元素的祖先。例如,如果设置了元素父级的innerHTML(我认为甚至设置为完全相同的字符串),那么您拥有的元素引用将指向不再位于 DOM 中的元素。每次通过 ID 重新获取元素将解决该问题。

【讨论】:

  • +1 我也猜。设置 innerHTML 可能会“优化”以重新创建元素,从而使引用无效。
  • 没有任何优化,设置innerHTML 将总是销毁每个后代节点并用标记中的新节点替换它们,丢失 JavaScript 引用、属性和事件处理程序过程。这就是为什么你应该从不使用innerHTML+=来添加内容。
  • 那我应该用什么来添加内容呢?
  • 这确实是在修改 DOM 中的元素及其后代之一时所固有的基本问题,几乎同时(至少以非同步方式)。要么你不应该这样做,要么如果你认为有必要这样做(我倾向于认为应该可以避免这种情况,但这样做可能并不总是更容易),你不应该依赖预先计算引用,因为它们可能已经过时并且不再引用 DOM 中的预期节点。
  • 杰夫,虽然我理解这个问题,但你能否更深入地解释一下当你写到 innerHTML 时会发生什么,也许建议附加文本节点而不是直接编辑 innerHTML 属性?
【解决方案2】:

如果您要替换 elem 的祖先的 innerHTML(根据您的示例,body 元素),则 elem 不再存在。当step 是在原来的elem 被破坏之后,elem 所引用的将是别的东西。

浏览器应该做的正确事情应该是删除elem引用,但它看起来不像这样做。

【讨论】:

    【解决方案3】:

    我的猜测是,setTimeout(step, 500); 回调的执行几乎不知道this 是谁——你是在调用当前元素的step 还是HTMLElement.prototype.writeInSteps.step()?

    同时在两个不同的元素上尝试您的第二个代码(以便第二个 writeInSteps 在第一个超时之前出现)。我很确定它不会完全符合您的预期。

    当然,杰夫也可能是对的。 innerHTML 按规范是只读的,写入它可能会或可能不会重建整个树(并杀死所有引用)。

    【讨论】:

    • 不,step 明确指的是当前调用writeInSteps 中定义的函数——它是每次调用writeInSteps 时重新创建的本地绑定。由于step 从不引用this,所以将step 直接传递给setTimeout 应该没有问题。
    • -1 这不是问题。使用 elem 闭包的整个想法是保留对 this 的引用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-30
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    相关资源
    最近更新 更多