Подтвердить что ты не робот

JS глобальная переменная "let" не обновляется в функции?

Изменить: Я сообщил об этом как об ошибке Chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=668257

Я создаю небольшую игру в холсте в JS с врагами, которые могут стрелять. Для тестирования я создал флаг, объявленный глобально как let fancy = true;, чтобы определить, следует ли использовать "фантастический" алгоритм таргетинга. Я сделал это так, чтобы нажатие P переключило этот флаг. Моя основная функция frame, вызывает другую функцию, autoShoot, пять раз в секунду. autoShoot использует флаг fancy.

Сегодня началось что-то странное; Я не помню, какие изменения внесли его. Иногда, когда я нажимаю P, autoShoot действует как fancy, не переключается. Я сделал некоторую отладку и обнаружил, что новое, измененное значение отражается внутри frame, но в autoShoot значение не обновляется. Это случается с перерывами, и иногда значение в autoShoot будет фиксироваться (без меня что-либо сделало).

Я сократил код до следующего, что все еще показывает проблему для меня. Попробуйте нажать P несколько раз. Для меня два значения получают "вне синхронизации" и отображаются по-разному после нажатия P только один или два раза:

Снимок экрана после нажатия P на моем компьютере

(Я запускаю Chrome "Версия 54.0.2840.99 м" в 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>
4b9b3361

Ответ 1

Как все прокомментировали это кажется проблемой Chrome.

Я попытался воспроизвести ту же самую проблему в Chrome version 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 и функцию hoisting.

Я не могу сказать, из какой версии Chrome мы можем видеть эту "ошибку", но могу предположить, что это может быть версия 52, потому что в этой версии произошло много изменений, как новый метод Garbage collection, встроенная поддержка es6 и es7 и т.д. Для получения дополнительной информации вы можете смотреть это видео из I/O 2016.

Возможно, новый метод Garbage collection вызывает эту проблему, потому что, как они сказали в вышеупомянутом видео, оно связано с рамами браузера, что-то вроде v8 делает GC, когда браузер не работает, чтобы не трогать рисование кадров и т.д. И поскольку мы знаем, что requestAnimationFrame - это метод, который вызывает callback на следующем рисунке кадра, возможно, в этом процессе эти странные вещи происходят. Но это всего лишь предположение, и я не имею права говорить что-то серьезное об этом:)

Ответ 2

Я нахожусь на Chrome 54.0.2840.98 на Mac, и это тоже происходит. Я думаю, что это проблема, потому что, если я завершу объявление, следующее за оператором let, в блок {…}, то сниппет отлично работает, и оба значения сразу меняются после нажатия клавиши.

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>