Elevator Saga循环内事件回调异常:原因分析与修复方案
Elevator Saga挑战2事件回调异常问题分析与修复
问题成因
这是JavaScript里典型的闭包变量引用陷阱:
- 循环是同步执行的,会快速遍历完所有楼层,而
up_button_pressed/down_button_pressed的回调是异步触发(用户点击后才执行)。 - 循环内定义的回调函数并没有绑定当前迭代的
floor实例,而是共享了同一个floor变量的引用。当回调触发时,循环已经结束,floor变量最后指向的是最后一个楼层对象,所以所有按钮回调都会操作最后一个楼层。 - 额外问题:循环里的
i没有用var/let/const声明,变成全局变量,进一步加剧了变量引用混乱,但核心原因还是闭包的共享变量问题。
修复方法
方法1:使用ES6的let声明循环变量
let在for循环中会为每个迭代创建独立的作用域,每个回调都会绑定当前迭代的floor实例,从根源上解决共享引用问题:
{ init: function(elevators, floors) { var elevator = elevators[0]; elevator.on("idle", function() { elevator.goToFloor(2); console.log("goToFloor 2 (because idle)") }); elevator.on("floor_button_pressed", function(floorNum) { elevator.goToFloor(floorNum); console.log("goToFloor " + floorNum + " (because floor_button_pressed)") }); console.log("floors: " + floors) // 用let声明i和floor,每个迭代生成独立作用域 for (let i = 0; i < floors.length; i++) { let floor = floors[i]; console.log(" floors["+i+"]: " + floor.floorNum()) floor.on("up_button_pressed", function() { elevator.goToFloor(floor.floorNum()); console.log("goToFloor " + floor.floorNum() + " (because up_button_pressed)") }); floor.on("down_button_pressed", function() { elevator.goToFloor(floor.floorNum()); console.log("goToFloor " + floor.floorNum() + " (because down_button_pressed)") }); } }, update: function(dt, elevators, floors) { } }
方法2:使用立即执行函数表达式(IIFE)
如果环境不支持ES6,可以用IIFE为每个迭代创建独立作用域,把当前floor作为参数传入,让回调捕获独立的变量值:
{ init: function(elevators, floors) { var elevator = elevators[0]; elevator.on("idle", function() { elevator.goToFloor(2); console.log("goToFloor 2 (because idle)") }); elevator.on("floor_button_pressed", function(floorNum) { elevator.goToFloor(floorNum); console.log("goToFloor " + floorNum + " (because floor_button_pressed)") }); console.log("floors: " + floors) for (var i = 0; i < floors.length; i++) { var floor = floors[i]; console.log(" floors["+i+"]: " + floor.floorNum()) // 用IIFE包裹,传入当前floor作为独立变量 (function(currentFloor) { currentFloor.on("up_button_pressed", function() { elevator.goToFloor(currentFloor.floorNum()); console.log("goToFloor " + currentFloor.floorNum() + " (because up_button_pressed)") }); currentFloor.on("down_button_pressed", function() { elevator.goToFloor(currentFloor.floorNum()); console.log("goToFloor " + currentFloor.floorNum() + " (because down_button_pressed)") }); })(floor); } }, update: function(dt, elevators, floors) { } }
内容的提问来源于stack exchange,提问作者WurmD
相关产品推荐
相关产品推荐

