JavaScript 函数与迷你游戏对象模型:this、call、apply 和清理
本文目录
早期贪吃蛇练习把随机数、食物元素、坐标和初始化逻辑放进多个构造函数。这个例子仍适合学习对象协作,但实际项目要明确状态所有者、坐标单位和销毁时机;不需要为了面向对象而增加全局单例。
把地图和食物职责分开
地图提供网格尺寸与占用信息,Food 管理食物模型和自身 DOM 节点,游戏控制器决定何时移动、重新生成或结束。随机区间采用左闭右开,宽高必须能被格子尺寸整除,生成结果还要避开已有蛇身。
function randomIndex(length) {
if (!Number.isInteger(length) || length <= 0) throw new RangeError('无可用格子');
return Math.floor(Math.random() * length);
}
class Food {
constructor(element, gridSize) {
this.element = element;
this.gridSize = gridSize;
}
place(cell, mapElement) {
this.element.style.transform =
'translate(' + cell.x * this.gridSize + 'px, ' + cell.y * this.gridSize + 'px)';
mapElement.append(this.element);
}
remove() {
this.element.remove();
}
}
真实实现还要从可用格集合里挑位置,处理棋盘填满、地图 resize、键盘输入与动画帧同步。重复调用初始化应是幂等的,卸载时清理监听器和 requestAnimationFrame;用 CSS Grid 或 transform 时明确元素定位上下文,避免把视口坐标误当网格坐标。
函数声明、表达式与函数值
函数声明与函数表达式的差异包括初始化时机、作用域和调试名称,不改变函数可作为值传递的事实。函数可以存入数组、对象、Map,作为回调传给事件监听器。数组中保存多个函数时,执行前要确认函数来源可信,并捕获单项失败以免整个批处理意外终止。
const actions = [
() => '开始',
() => '暂停',
];
const labels = actions.map((action) => action());
this 是调用约定,不是函数定义属性
普通函数的 this 通常由调用方式决定:obj.method() 以 obj 为接收者;构造调用创建实例;call/apply/bind 可显式绑定。严格模式下独立调用的 this 是 undefined;非严格脚本中可能被替换为全局对象。箭头函数没有自己的 this,适合词法捕获,但不能作为构造函数。
function formatScore(prefix, value) {
return prefix + this.name + ': ' + value;
}
const player = { name: '玩家一' };
formatScore.call(player, '得分 ', 8);
formatScore.apply(player, ['得分 ', 8]);
const boundFormat = formatScore.bind(player, '得分 ');
boundFormat(9);
call 按参数列表传值,apply 接收数组或类数组,bind 返回新函数并预设接收者/参数。不要把三者仅当作“继承工具”;现代 class 使用 extends/super,组合通常更直观。定时器回调的 this 也受回调形式与 API 约定影响,不能笼统记成 window;用箭头捕获或显式参数,并读对应 API 文档。
小游戏的工程边界
- 随机位置使用网格索引而不是直接随机 CSS 像素。
- 游戏状态与渲染分离,渲染从状态推导。
- 事件监听、定时器和动画帧在重新开始前先清理。
- 暂停、结束、重新开始都要覆盖重复点击与资源释放。
- 不把玩家昵称或外部数据拼进 innerHTML。
参考:MDN Functions、MDN this、MDN Function.call、MDN Function.apply、MDN Function.bind、MDN requestAnimationFrame。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。