Node.js+Socket.io中for循环内setInterval仅触发一次问题排查
问题原因与解决方案
问题根源
你遇到的是JavaScript闭包与var作用域的经典问题:
- 用
var声明的i是函数级作用域,整个startGame函数里只有一个i变量。 setInterval的回调是异步执行的,等回调触发时,外层的for...in循环已经全部执行完毕,此时i的值已经变成了lobby.players里最后一个玩家的键,所以所有定时器都会去操作最后一个玩家,看起来就像只有第二个玩家的定时器生效。
解决办法
有三种简单的修复方式,任选其一即可:
1. 用let替代var
let是块级作用域,每次循环都会创建一个独立的i变量,确保回调能捕获到当前循环的玩家键:
function startGame(lobby) { for (let i in lobby.players) { // 把var改成let console.log(lobby.players[i].id) lobby.players[i].emit('startGame', { map: lobby.map, }); lobby.players[i].update = setInterval(function() { lobby.players[i].emit('update', getUpdatePackage(lobby)); console.log("update " + lobby.players[i].id) }, 1000 / gameConfig.FPS) console.log(lobby.players[i].update) } console.log("Game in lobby " + lobby.id + " is starting.") }
2. 用立即执行函数(IIFE)绑定当前i
通过IIFE把当前循环的i封装到独立作用域里:
function startGame(lobby) { for (var i in lobby.players) { console.log(lobby.players[i].id) lobby.players[i].emit('startGame', { map: lobby.map, }); // 用IIFE保存当前i的值 (function(currentI) { lobby.players[currentI].update = setInterval(function() { lobby.players[currentI].emit('update', getUpdatePackage(lobby)); console.log("update " + lobby.players[currentI].id) }, 1000 / gameConfig.FPS) })(i) console.log(lobby.players[i].update) } console.log("Game in lobby " + lobby.id + " is starting.") }
3. 遍历玩家对象的值(更直观)
把玩家对象转成数组后用forEach遍历,直接拿到每个玩家实例,避免键的问题:
function startGame(lobby) { // 把对象转成值数组,遍历每个玩家 Object.values(lobby.players).forEach(player => { console.log(player.id) player.emit('startGame', { map: lobby.map, }); player.update = setInterval(function() { player.emit('update', getUpdatePackage(lobby)); console.log("update " + player.id) }, 1000 / gameConfig.FPS) console.log(player.update) }) console.log("Game in lobby " + lobby.id + " is starting.") }
内容的提问来源于stack exchange,提问作者Martin Brůžek
相关产品推荐
相关产品推荐

