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>
Ответы
Ответ 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>