最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - JS global "let" variable not updating in function? - Stack Overflow

programmeradmin0浏览0评论

Edit: I've reported this as a Chromium bug:

I'm creating a little canvas game in JS with enemies that can shoot. For testing, I created a flag, declared globally as let fancy = true;, to determine whether or not to use a "fancy" targeting algorithm. I made it so that pressing P will toggle this flag. My main function, frame, calls another function, autoShoot, five times per second. autoShoot uses the fancy flag.

Today, something strange started happening; I don't remember what change introduced it. Sometimes, when I press P, autoShoot acts like fancy didn't get toggled. I did some debugging and discovered that the new, toggled value is reflected inside frame, but in autoShoot, the value isn't updated. It happens intermittently, and sometimes the value in autoShoot will fix itself (without me having done anything).

I've reduced the code to the following, which still exhibits the problem for me. Try pressing P a bunch of times. For me, the two values get "out of sync" and display differently after pressing P just once or twice:

(I'm running Chrome "Version 54.0.2840.99 m" on Windows 10.)

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>

Edit: I've reported this as a Chromium bug: https://bugs.chromium/p/chromium/issues/detail?id=668257

I'm creating a little canvas game in JS with enemies that can shoot. For testing, I created a flag, declared globally as let fancy = true;, to determine whether or not to use a "fancy" targeting algorithm. I made it so that pressing P will toggle this flag. My main function, frame, calls another function, autoShoot, five times per second. autoShoot uses the fancy flag.

Today, something strange started happening; I don't remember what change introduced it. Sometimes, when I press P, autoShoot acts like fancy didn't get toggled. I did some debugging and discovered that the new, toggled value is reflected inside frame, but in autoShoot, the value isn't updated. It happens intermittently, and sometimes the value in autoShoot will fix itself (without me having done anything).

I've reduced the code to the following, which still exhibits the problem for me. Try pressing P a bunch of times. For me, the two values get "out of sync" and display differently after pressing P just once or twice:

(I'm running Chrome "Version 54.0.2840.99 m" on Windows 10.)

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>

Playing around, here are some observations:

  • removing any of the following causes the issue to go away:
    • any line in the code at the top dealing with the canvas, even just the ment after const ratio
    • the empty for...of loop: for (let b of bullets) {}
    • changing let fancy = to var fancy = or just fancy =
    • putting the whole thing out of the global scope (by using IIFE, onload handler, or block scope)
  • Increasing the size of the bullets array increases the frequency that the issue occurs. I think it's because it makes frame take longer to execute; originally, bullets.length was only 20, but each loop iteration did some stuff to update the bullet, etc.

Does this happen on your puters? Is there any logical explanation for this? I've tried restarting my browser, no change.

Share edited Nov 24, 2016 at 21:10 qxz asked Nov 22, 2016 at 1:18 qxzqxz 3,8741 gold badge16 silver badges31 bronze badges 24
  • 2 nope, never gets out of synch in firefox – Jaromanda X Commented Nov 22, 2016 at 1:22
  • 2 Rock solid for me in this fiddle, in both Firefox and Chrome. – Pointy Commented Nov 22, 2016 at 1:25
  • 2 I can reproduce. autoShoot takes some time to follow the update. Weird. – Bergi Commented Nov 22, 2016 at 1:27
  • 2 @Ultimater I'm no longer worried about getting it working; I just swapped let for var. Now I'm just curious as to why the heck this is happening. – qxz Commented Nov 22, 2016 at 1:50
  • 2 Reported: bugs.chromium/p/chromium/issues/detail?id=668257 – qxz Commented Nov 23, 2016 at 21:01
 |  Show 19 more ments

2 Answers 2

Reset to default 2

As everybody mented out it seems to be a Chrome issue.

I've tried to reproduce the same issue on Chrome version 45.0.2454.85 m (64-bit) and 44.0.2403.107 m (32-bit) (enabling strict mode of course) but I've not succeded. But on version 54.0.2840.99 m (64-bit) it is there.

And I noticed that changing requestAnimationFrame to something like setInterval also makes the problem totally go away.

So, I assume that this strange behaviour has something to do with Chrome's requestAnimationFrame on newer versions, and maybe block scoping nature of let, and function hoisting.

I can't say from which version of Chrome we can see this kind of "bug", but I can assume that it can be the version 52, because in this version many changes occured, like new method of Garbage collection, native support for es6 and es7 etc. For more information you can watch this video from I/O 2016.

Maybe, new Garbage collection method is causing this issue, because as they told in the above mentioned video it is connected with browser frames, something like v8 does GC when the browser is idle, in order not to touch the drawing of frames etc. And as we know that requestAnimationFrame is a method which calls a callback on the next frame drawing, maybe in this process these weird thing's happening. But this is just an assumption and I've no petence to say something serious about this:)

I'm on Chrome 54.0.2840.98 on Mac and it happens too. I think it's a scoping issue, because if I wrap the declaration following the let statement into a {…} block, then the snippet works fine and both values change immediately after key press.

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>

发布评论

评论列表(0)

  1. 暂无评论