【问题标题】:JS global "let" variable not updating in function?JS全局“让”变量没有在函数中更新?
【发布时间】:2017-04-05 13:31:47
【问题描述】:

编辑:我已将此报告为 Chromium 错误:https://bugs.chromium.org/p/chromium/issues/detail?id=668257

我正在用 JS 创建一个带有可以射击的敌人的小画布游戏。为了测试,我创建了一个标志,全局声明为let fancy = true;,以确定是否使用“花式”定位算法。我这样做是为了按 P 将切换此标志。我的主函数 frame 每秒调用 5 次另一个函数 autoShootautoShoot 使用 fancy 标志。

今天,开始发生一些奇怪的事情;我不记得是什么变化引入了它。有时,当我按下 P 时,autoShoot 的行为就像 fancy 没有被切换。我进行了一些调试,发现新的切换值 反映在 frame 中,但在 autoShoot 中,该值没有更新。它间歇性地发生,有时autoShoot 中的值会自行修复(我没有做任何事情)。

我已将代码简化为以下代码,但对我来说仍然存在问题。尝试按 P 几次。对我来说,这两个值在按 P 一次或两次后“不同步”并显示不同:

(我在 Windows 10 上运行 Chrome“版本 54.0.2840.99 m”。)

const canvas = document.getElementById("c");
const width = 0;
const height = 0;
const ctx = canvas.getContext("2d");
const ratio =1;// (window.devicePixelyRatio||1)/(ctxFOOOOOOOOFOOOOOOOOOFOOOOO||1);
canvas.width = width*ratio;
canvas.height = height*ratio;
canvas.style.width = width+"px";
canvas.style.height = height+"px";
ctx.scale(ratio, ratio);

function testSet(id, val) {
  console.log(id+": "+val);
  document.getElementById(id).innerText = val;
}


let fancy = true;
document.body.addEventListener("keydown", function(e) {
  if (e.keyCode == 80) {
    fancy = !fancy;
    console.log("Set fancy to: "+fancy);
  }
});

let bullets = Array(2000);
let lastTime = 0, shotTimer = 0;
function frame(time) {
  const dt = (time - lastTime)/1000;
  lastTime = time;
  
  if ((shotTimer -= dt) <= 0) {
    testSet("frame", fancy);
    autoShoot();
    shotTimer = 0.2;
  }
  for (let b of bullets) {}
  
  requestAnimationFrame(frame);
}
function autoShoot() {
  testSet("autoShoot", fancy);
}

requestAnimationFrame(frame);
<code>
  fancy (frame)     = <span id="frame"></span><br>
  fancy (autoShoot) = <span id="autoShoot"></span>
</code>
<canvas id="c"></canvas>

玩玩,这里有一些观察:

  • 删除以下任何内容都会导致问题消失:
    • 顶部代码中处理画布的任何行,甚至只是const ratio之后的注释
    • 空的 for...of 循环:for (let b of bullets) {}
    • let fancy = 更改为var fancy = 或只是fancy =
    • 将整个事物置于全局范围之外(通过使用 IIFE、onload 处理程序或块范围)
  • 增加bullets 数组的大小会增加问题发生的频率。我认为这是因为它使frame 执行时间更长;最初,bullets.length 只有 20 个,但每次循环迭代都会做一些事情来更新项目符号等。

这是否发生在您的计算机上?这有什么合乎逻辑的解释吗?我试过重启浏览器,没有任何变化。

【问题讨论】:

  • 不,在 Firefox 中永远不会不同步
  • this fiddle 中对我来说坚如磐石,在 Firefox 和 Chrome 中。
  • 我可以复制。 autoShoot 需要一些时间来关注更新。很奇怪。
  • @Ultimater 我不再担心让它工作;我刚刚将let 换成了var。现在我只是好奇为什么会发生这种情况。

标签: javascript google-chrome ecmascript-6 requestanimationframe let


【解决方案1】:

我在 Mac 上使用 Chrome 54.0.2840.98,它也发生了。我认为这是一个范围界定问题,因为如果我将 let 语句之后的声明包装到 {…} 块中,那么 sn-p 工作正常,并且两个值在按键后立即更改。

const canvas = document.getElementById("c");
const width = 0;
const height = 0;
const ctx = canvas.getContext("2d");
const ratio =1;// (window.devicePixelyRatio||1)/(ctxFOOOOOOOOFOOOOOOOOOFOOOOO||1);
canvas.width = width*ratio;
canvas.height = height*ratio;
canvas.style.width = width+"px";
canvas.style.height = height+"px";
ctx.scale(ratio, ratio);

function testSet(id, val) {
  console.log(id+": "+val);
  document.getElementById(id).innerText = val;
}


let fancy = true;
{
  document.body.addEventListener("keydown", function(e) {
    if (e.keyCode == 80) {
      fancy = !fancy;
      console.log("Set fancy to: "+fancy);
    }
  });

  let bullets = Array(2000);
  let lastTime = 0, shotTimer = 0;
  function frame(time) {
    const dt = (time - lastTime)/1000;
    lastTime = time;
  
    if ((shotTimer -= dt) <= 0) {
      testSet("frame", fancy);
      autoShoot();
      shotTimer = 0.2;
    }
    for (let b of bullets) {}
  
    requestAnimationFrame(frame);
  }
  function autoShoot() {
    testSet("autoShoot", fancy);
  }

  requestAnimationFrame(frame);
}
<code>
  fancy (frame)     = <span id="frame"></span><br>
  fancy (autoShoot) = <span id="autoShoot"></span>
</code>
<canvas id="c"></canvas>

【讨论】:

  • 为什么不将let fancy 也放在块范围内?
  • 没有块范围,它仅限于 keydown 侦听器。”到底是什么意思?有什么限制?
  • @Bergi 好问题 ;) 我不是 ES6 专家,但我知道与块作用域相关的 let 语句。而我的方法就是基于这个,所以我玩弄它。
  • 是的,OP 确实找到了各种解决方案,可以在玩耍时避免该问题,但他要求的是对观察到的行为的解释。
  • 即使这个答案没有解释它,有趣的是,即使let 仍在全球范围内,问题也会消失......
【解决方案2】:

正如大家评论的那样,这似乎是一个Chrome 问题。

我尝试在Chrome 版本45.0.2454.85 m (64-bit)44.0.2403.107 m (32-bit)(当然启用严格模式)上重现相同的问题,但我没有成功。但是在版本54.0.2840.99 m (64-bit) 上它就在那里。

我注意到将requestAnimationFrame 更改为setInterval 之类的东西也会使问题完全消失。

所以,我认为这种奇怪的行为与较新版本的 Chrome's requestAnimationFrame 有关,并且可能会阻止 let 的作用域性质和函数提升。

我不能说从Chrome的哪个版本我们可以看到这种“错误”,但我可以假设它可能是52版本,因为在这个版本中发生了很多变化,比如新方法的Garbage collection,原生支持es6es7等。更多信息可以看this video from I/O 2016

也许,新的Garbage collection 方法导致了这个问题,因为正如他们在上面提到的视频中所说的那样,它与浏览器框架相连,就像v8 在浏览器空闲时执行 GC ,为了不接触帧的绘制等。而且我们知道requestAnimationFrame 是一个在下一个帧绘制时调用callback 的方法,也许在这个过程中这些奇怪的事情正在发生。但这只是一个假设,我没有能力对此说些严肃的话:)

【讨论】:

  • 问题中的脚本不应该产生任何垃圾,一切都可以静态分配(或在堆栈上),所以没有什么可收集的。
猜你喜欢
  • 2020-06-13
  • 1970-01-01
  • 2019-10-03
  • 2020-09-09
  • 2014-09-06
  • 1970-01-01
  • 2014-08-06
  • 1970-01-01
  • 2013-02-09
相关资源
最近更新 更多