本书标签: 游戏同人 

第 37 节:箭头函数核心难点

原神第二课堂:胡桃讲编程之JS——ES6进阶实战

一、类比板块

(胡桃蹲在柜台摆弄招魂幡,手里两份账单对比,平板打开 MC 弹窗回调代码,晃着小辫子开口)

各位学徒、MC 脚本开发者,上一节我们掌握箭头函数各种简写格式,看着代码变清爽,但很多人写回调时会踩this指向大坑 —— 普通函数和箭头函数的this完全不一样,界面点击、定时器逻辑经常出现拿不到目标对象的 bug。本节彻底拆解箭头函数this规则,分清两种函数的核心区别,解决 MC GUI 回调、账单统计里最常见的 this 报错。

往生堂类比:普通 function 函数的 this 好比现场临时工,谁调用它,它就认谁做主;箭头函数的 this 是入职固定学徒,刚定义时绑定好归属,不管后面谁调用,永远认外层上下文,不会随便更换主人。如果用普通函数写台账统计,换调用场景 this 就乱了;箭头函数全程沿用外层账单对象,取值稳定不出错。

MC 基岩 GUI 类比:写道具弹窗点击事件、定时刷新背包脚本时,普通函数回调内 this 会指向全局 / 点击元素,拿不到道具配置;箭头函数直接继承外层 GUI 对象的 this,不用额外存_this/that做中转,界面逻辑更稳定。

二、正文板块

1. 前置:普通 function 函数的 this 规则(对比参照)

传统函数this由调用方式决定,执行时动态变更,基础 this 知识回看:https://www.ihuaben.com/book/14226653.html

全局直接调用:this === window(浏览器)

对象方法调用:this指向调用的对象

DOM/MC 界面事件回调:this指向触发元素

new 构造调用:this指向新建实例

示例(往生堂账单对象):

javascript

运行

const bill = {

price: 2000,

show: function(){

console.log(this.price); // this 是bill对象

}

}

bill.show(); // 输出2000

弊端:如果把方法单独取出执行,this 丢失上下文,直接变成全局:

javascript

运行

const fn = bill.show;

fn(); // undefined,this变成window

2. 箭头函数核心特性:无独立 this,继承外层作用域

箭头函数自身不创建 this,定义瞬间捕获外层最近一层普通函数 / 全局的 this,后续无论怎么调用,this 永远不变,不会随执行场景改变。

语法核心结论:箭头函数 this = 定义位置外层 this。

案例 1:对象内定时器(MC 高频踩坑场景)

ES2 老式写法,需要用let that = this中转保存上下文:

javascript

运行

// MC道具面板旧代码

const itemPanel = {

stack: 64,

refresh: function(){

let that = this; // 提前存储外层this

// 普通函数定时器,this指向全局

setTimeout(function(){

console.log(that.stack); // 必须靠that中转才能读取64

}, 1000)

}

}

itemPanel.refresh();

箭头函数优化,无需中转,直接继承外层 this:

javascript

运行

const itemPanel = {

stack: 64,

refresh: function(){

// 箭头函数直接继承refresh内的this(itemPanel)

setTimeout(() => {

console.log(this.stack); // 64,正常读取

}, 1000)

}

}

itemPanel.refresh();

案例 2:往生堂账单批量遍历回调

普通 function 回调 this 指向全局,读取不到账单基础价格:

javascript

运行

const hall = {

base: 1200,

priceList: [150, 800],

calc: function(){

// 普通function,this为window

const total = this.priceList.reduce(function(sum, p){

return sum + p + this.base; // this.base undefined

}, 0)

console.log(total);

}

}

hall.calc();

替换箭头函数,继承外层 hall 对象 this:

javascript

运行

const hall = {

base: 1200,

priceList: [150, 800],

calc: function(){

// 箭头函数捕获外层this(hall)

const total = this.priceList.reduce((sum, p) => {

return sum + p + this.base;

}, 0)

console.log(total);

}

}

hall.calc(); // 正常输出2150

3. 致命误区:不要用箭头函数作为对象方法

对象属性如果直接赋值箭头函数,外层 this 是全局 window,永远读取不到对象自身属性,属于高频错误写法:

javascript

运行

// 错误示范

const tool = {

name: "桃木符",

print: () => {

console.log(this.name); // this是全局,输出空/undefined

}

}

tool.print();

原因:箭头函数定义在全局层级,外层 this 为 window,和 tool 对象无关;对象方法必须使用普通 function。

4. 多层嵌套 this 传递演示

多层函数嵌套,箭头函数逐层向上继承最近普通函数的 this:

javascript

运行

const ui = {

width: 360,

run: function(){

// 第一层箭头,this=ui

const inner = () => {

// 第二层箭头,继续继承ui的this

setTimeout(() => {

console.log(this.width); // 360

}, 500)

}

inner();

}

}

ui.run();

5. 配套衍生限制(this 附带规则)

箭头函数无arguments,不定参数只能用...rest;

不能使用call/apply/bind修改 this,调用无效;

javascript

运行

const fn = () => console.log(this);

fn.call({name:"测试"}); // this依旧是全局,无法修改

不能作为构造函数 new,会直接报错;

6. 选型快速判断

定时器、数组 map/filter/reduce、GUI 异步回调 → 优先箭头函数,稳定继承外层 this;

对象方法、类方法、需要动态 this 的事件回调 → 必须普通 function。

三、课后练习

基础题

写出代码输出结果

javascript

运行

const bag = {

num: 64,

log: function(){

setTimeout(()=>{

console.log(this.num);

}, 100)

}

}

bag.log();

改错:指出下方代码错误原因

javascript

运行

const goods = {

price: 1200,

show: () => console.log(this.price)

}

goods.show();

进阶题

将下方需要 that 中转的老式代码,改写为箭头函数消除临时变量 that

javascript

运行

const panel = {

w: 400,

timer: function(){

let that = this;

setTimeout(function(){

console.log(that.w);

}, 200)

}

}

panel.timer();

编写 MC 道具对象:包含 stack=32,普通函数 calc,内部用 reduce + 箭头函数累加数组 [16,32] 并加上 this.stack,打印总和。

参考答案

基础题

输出 64

show 为箭头函数,定义外层 this 是全局 window,无法获取 goods 内部 price 属性;对象方法禁止使用箭头函数,改为普通 function。

进阶题

简化箭头版本

javascript

运行

const panel = {

w: 400,

timer: function(){

setTimeout(()=>{

console.log(this.w);

}, 200)

}

}

panel.timer();

MC 完整脚本

javascript

运行

const item = {

stack: 32,

numArr: [16, 32],

calc: function(){

const sum = this.numArr.reduce((s, n) => s + n + this.stack, 0);

console.log(sum);

}

}

item.calc();