Javascript篇 | 箭头函数
前言
在前面的章节里,我们从闭包与作用域聊到了this 指向与执行上下文的五种绑定规则。如果你一路读下来,应该已经对”变量怎么查找、this 怎么绑定”有了一套完整的认知框架。
但你可能也发现了:在 this 那一章里,有一个角色总在”特例”的位置上出现——箭头函数。当时我们只用一句话概括了它的行为:
箭头函数没有自己的 this,它的 this 继承自定义时所在的外层执行上下文。
这句话虽然正确,但如果面试官继续追问:
- “继承外层”到底是什么意思?为什么对象字面量不算”外层”?
- 箭头函数为什么不能 new?从规范角度怎么解释?
- 没有 arguments、没有 prototype,这些限制背后的设计逻辑是什么?
- 在 React 类组件、事件回调、定时器里,箭头函数和普通函数分别该怎么选?
很多人就开始支支吾吾了。
本章,我们就把箭头函数从语法、语义、规范、实战四个层面彻底讲清楚。读完之后,你不仅能准确回答上面这些问题,还能在代码里做出更合理的选择。
诊断自测
在开始正文之前,先用几道题测测你目前对箭头函数的理解程度。答不上来也没关系,读完全文再回来看,效果更好。
Q1:下面代码输出什么?为什么?
const team = {
members: ['Alice', 'Bob'],
teamName: 'Alpha',
listMembers: () => {
return this.members?.map(m => `${this.teamName}: ${m}`);
}
};
console.log(team.listMembers());
点击查看答案
输出 undefined。listMembers 是箭头函数,而对象字面量 {} 不会创建新的执行上下文,所以 this 不是 team,而是外层的全局/模块上下文。this.members 为 undefined,调用 ?.map 短路返回 undefined。
Q2:箭头函数能不能通过 call 或 bind 改变 this 指向?说说原因。
点击查看答案
不能。箭头函数压根没有自己的 this 绑定——它的 this 是从外层作用域”捕获”过来的,存在闭包中。call/apply/bind 修改的是函数自身的 this 绑定,但箭头函数没有这个绑定可供修改,所以这些方法对箭头函数的 this 无效(虽然不报错,但 this 不会变)。
Q3:以下两种写法有什么区别?在什么场景下选择哪种?
class Logger {
// 写法 A
log() { console.log(this); }
// 写法 B
log = () => { console.log(this); };
}
点击查看答案
写法 A 是原型方法,所有实例共享同一份函数,但单独提取出来调用时 this 会丢失。写法 B 是实例属性(类字段),每个实例各有一份,this 永远指向实例。如果要把方法作为回调传递(如 onClick={this.log}),选写法 B 更安全;如果实例数量多、且不需要提取为回调,选写法 A 更节省内存。
一、箭头函数解决了什么问题?
在 ES6 之前,JavaScript 里最让人头疼的场景之一就是回调中的 this 丢失。
看一个经典的例子:
function Timer() {
this.seconds = 0;
setInterval(function () {
this.seconds++; // ❌ this 不是 Timer 实例
console.log(this.seconds);
}, 1000);
}
const t = new Timer();
// 输出 NaN,因为回调里的 this 指向了全局对象(非严格模式)
setInterval 的回调是一个普通函数,它被引擎独立调用,根据我们在 this 那章讲过的默认绑定规则,this 会指向全局对象(非严格模式)或 undefined(严格模式)。
为了绕过这个问题,ES6 之前常见的做法有两种:
方法一:var self = this
function Timer() {
this.seconds = 0;
var self = this;
setInterval(function () {
self.seconds++; // ✅ 通过闭包访问外层的 self
}, 1000);
}
方法二:bind
function Timer() {
this.seconds = 0;
setInterval(
function () {
this.seconds++; // ✅ bind 绑定了 this
}.bind(this),
1000
);
}
这两种方法都能解决问题,但都不够优雅——要么多一个变量,要么多一层 .bind(this)。
ES6 的箭头函数,就是为了从语言层面彻底消除这类样板代码而设计的:
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // ✅ 箭头函数的 this 就是 Timer 实例
}, 1000);
}
一行 => 同时解决了两个问题:语法更短、this 更稳。
但要注意,箭头函数不仅仅是”语法糖”。它和普通函数在语言层面有本质的差异,下面我们就逐一拆解。
二、基础语法:几种写法和常见坑
在深入语义之前,先快速过一遍语法。箭头函数的语法比普通函数灵活得多,但也因此有几个容易踩的坑。
最基本的形式
const add = (a, b) => {
return a + b;
};
简写:省略大括号和 return
如果函数体只有一个表达式,可以省略 {} 和 return,表达式的值就是返回值:
const add = (a, b) => a + b;
简写:单参数省略括号
如果只有一个参数,可以省略参数列表的括号:
const double = x => x * 2;
但注意:无参数或多参数时,括号不能省。
const greet = () => 'hello';
const add = (a, b) => a + b;
坑一:返回对象字面量要加小括号
这是笔试和面试中出场率极高的一个坑:
// ❌ 错误:大括号被解析成代码块,name 被当成了 label 语法
const buildUser = () => { name: 'Alice' };
console.log(buildUser()); // undefined
// ✅ 正确:用小括号包裹,告诉引擎这是一个表达式
const buildUser = () => ({ name: 'Alice' });
console.log(buildUser()); // { name: 'Alice' }
为什么会这样? 因为 { 在 JavaScript 里有两种含义:代码块和对象字面量。当 => 后面紧跟 { 时,引擎会优先把它解析为代码块。加上 () 后,引擎就知道这是一个需要求值的表达式。
坑二:换行问题
箭头符号 => 和参数之间不能换行:
// ❌ SyntaxError
const add = (a, b)
=> a + b;
// ✅ 正确
const add = (a, b) =>
a + b;
与解构、默认参数的组合
箭头函数的参数列表支持所有 ES6 参数特性:
// 解构 + 默认值
const greet = ({ name = 'Anonymous' } = {}) => `Hi, ${name}`;
// rest 参数
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
三、核心差异:箭头函数和普通函数到底差在哪?
语法只是表面,真正让箭头函数”特殊”的,是它在语言规范层面和普通函数的本质不同。
3.1 没有自己的 this:从”运行时绑定”到”定义时捕获”
这是箭头函数最核心、也是面试中最常考的特性。
我们在 this 那章讲过,普通函数的 this 是在调用时根据调用方式动态绑定的。但箭头函数完全不同:
箭头函数没有自己的
this绑定。它的this和普通变量一样,是从外层作用域中一层一层往上找的。
说人话:箭头函数在写出来那一刻,就”偷看”了外层的 this 并记住了,之后无论怎么调用(隐式绑定、显式绑定、甚至 new),this 都不会变。
从规范角度看,普通函数在被调用时会创建一个新的执行上下文,其中包含一个新的 this 绑定。而箭头函数在被调用时,不会创建新的 this 绑定——它直接沿着作用域链,使用外层执行上下文的 this。
这就是为什么我们说箭头函数的 this 是”定义时确定”的——因为外层执行上下文的 this 在箭头函数定义的那一刻就已经存在了。
来看一个对比:
const obj = {
name: 'obj',
regular() {
return function () {
return this.name;
};
},
arrow() {
return () => this.name;
}
};
const f1 = obj.regular();
const f2 = obj.arrow();
console.log(f1()); // undefined 或 window.name(默认绑定)
console.log(f2()); // 'obj'(继承了 arrow() 执行时的 this)
f1 是一个普通函数,独立调用时触发默认绑定。而 f2 是箭头函数,它”记住”了 obj.arrow() 执行时的 this(即 obj),所以无论后续怎么调用,都返回 'obj'。
更关键的是:call / apply / bind 对箭头函数的 this 无效。
console.log(f2.call({ name: 'hijack' })); // 仍然是 'obj'
这不是说 call “没生效”,而是箭头函数压根就没有自己的 this 可以被替换。call 改的是函数自己的 this 绑定,但箭头函数没有这个绑定,所以改了个寂寞。
3.2 一个高频误区:对象字面量不是”外层作用域”
这个问题在面试中出现频率极高:
const obj = {
name: 'obj',
sayName: () => {
console.log(this.name);
}
};
obj.sayName(); // ❌ 不是 'obj',而是 undefined(全局)
很多人直觉上觉得”箭头函数写在 obj 里面,外层就是 obj 啊”。但这是错的。
关键概念:对象字面量 {} 不会创建新的执行上下文。
在 JavaScript 中,能创建新执行上下文(也就是能产生新 this 的)只有:
- 函数调用(包括普通函数、方法调用、构造调用)
eval(直接调用)- 模块顶层
对象字面量只是一个表达式,写 { key: value } 的过程中,执行上下文并没有发生任何切换。所以箭头函数 sayName 的”外层”不是 obj,而是包含这个对象字面量的那个执行上下文——通常是全局上下文或模块上下文。
正确写法:
const obj = {
name: 'obj',
sayName() {
// 普通方法,this 由调用方式决定
console.log(this.name);
}
};
3.3 不能作为构造函数:没有 [[Construct]] 内部方法
const Person = (name) => {
this.name = name;
};
const p = new Person('Alice'); // ❌ TypeError: Person is not a constructor
在 ECMAScript 规范中,一个函数要能被 new 调用,必须拥有 [[Construct]] 内部方法。我们在 new 那章讲过,new 调用会触发 [[Construct]],执行”创建空对象 → 绑定原型 → 执行函数 → 返回”这四步。
箭头函数在规范层面没有 [[Construct]] 内部方法,只有 [[Call]]。所以用 new 调用时,引擎会直接抛出 TypeError。
这也是为什么箭头函数没有 prototype 属性——既然不能当构造函数,也就不需要 prototype 来给实例建立原型链了:
const fn = () => {};
console.log(fn.prototype); // undefined
function normalFn() {}
console.log(normalFn.prototype); // { constructor: normalFn }
3.4 没有 arguments 对象
普通函数在被调用时,会在执行上下文中自动创建一个 arguments 对象,包含所有传入的参数。但箭头函数不会创建自己的 arguments。
function outer() {
const inner = () => {
console.log(arguments); // 这里的 arguments 是 outer 的!
};
inner();
}
outer(1, 2, 3); // Arguments(3) [1, 2, 3]
如果箭头函数在全局作用域中使用 arguments,会直接报 ReferenceError(严格模式)或拿到全局的 arguments(非严格模式,如果存在的话)。
替代方案:rest 参数
const sum = (...args) => args.reduce((a, b) => a + b, 0);
console.log(sum(1, 2, 3)); // 6
rest 参数不仅可以替代 arguments,而且它是一个真正的数组,不需要像 arguments 那样用 Array.from 或 [].slice.call 做转换。在现代代码中,即使是普通函数,也推荐用 rest 参数替代 arguments。
3.5 不能用作 Generator
箭头函数不能使用 yield 关键字,也不能用 function* 的箭头版本(不存在 *=> 这种语法)。
// ❌ SyntaxError
const gen = *() => {
yield 1;
};
如果需要生成器,必须使用普通的 function* 声明。
3.6 没有 super 绑定
和 this、arguments 类似,箭头函数也没有自己的 super 绑定。如果在箭头函数中使用 super,它会沿作用域链查找外层的 super。
这在 class 的方法中偶尔会用到,比如在回调里调用父类方法:
class Child extends Parent {
start() {
setTimeout(() => {
super.doSomething(); // ✅ 箭头函数继承了 start() 的 super
}, 1000);
}
}
完整对比表
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
| this 绑定 | 调用时动态绑定 | 定义时继承外层,无法改变 |
| arguments | 有自己的 arguments 对象 | 没有,沿作用域链查找 |
| prototype | 有 | 没有 |
| 能否 new | 可以(有 [[Construct]]) | 不可以 |
| 能否用作 Generator | 可以 | 不可以 |
| super 绑定 | 有自己的 | 继承外层 |
| call/apply/bind 改 this | 有效 | 无效 |
四、什么时候该用箭头函数?什么时候不该用?
知道差异还不够,更重要的是在实际写代码时做出正确的选择。这部分也是面试官考察”工程素养”的常见角度。
✅ 推荐:回调函数
这是箭头函数的”主战场”。任何需要在回调里保留外层 this 的场景,箭头函数都比普通函数更合适:
// 定时器
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++;
}, 1000);
}
// DOM 事件(注意:如果回调里需要通过 this 访问 DOM 元素本身,就不能用箭头函数)
button.addEventListener('click', () => {
console.log('clicked'); // 如果不需要 this 指向 button,箭头函数更简洁
});
// Promise 链
fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data));
✅ 推荐:数组的函数式方法
map、filter、reduce、forEach、find、some、every 等方法的回调,用箭头函数既简洁又不会有 this 问题:
const names = users.map(u => u.name);
const adults = users.filter(u => u.age >= 18);
const total = prices.reduce((sum, p) => sum + p, 0);
⚠️ 不推荐:对象方法
前面 3.2 已经详细解释过了。如果你需要在方法里通过 this 访问对象本身,必须用普通函数或方法简写:
// ❌ 箭头函数:this 不是 counter
const counter = {
count: 0,
inc: () => { this.count++; }
};
// ✅ 方法简写:this 由调用方式决定
const counter = {
count: 0,
inc() { this.count++; }
};
⚠️ 不推荐:原型方法
原型方法需要通过 this 访问实例,箭头函数会导致 this 指向错误:
// ❌
Person.prototype.sayHi = () => {
return `Hi, I am ${this.name}`; // this 不是实例
};
// ✅
Person.prototype.sayHi = function () {
return `Hi, I am ${this.name}`;
};
⚠️ 不推荐:需要动态 this 的 DOM 事件处理器
有时候事件处理器需要通过 this 拿到触发事件的 DOM 元素:
// ❌ 箭头函数里 this 不是 button 元素
button.addEventListener('click', () => {
this.classList.toggle('active'); // this 是全局对象
});
// ✅ 普通函数里 this 指向 button
button.addEventListener('click', function () {
this.classList.toggle('active');
});
// ✅ 或者用 event.currentTarget 替代 this,这样也能用箭头函数
button.addEventListener('click', (e) => {
e.currentTarget.classList.toggle('active');
});
五、实战场景深入:箭头函数 + class
在现代前端开发中,箭头函数和 class 的组合使用非常常见。这里有一个细节值得深入。
class 中的箭头函数属性
class Button {
constructor(text) {
this.text = text;
}
// 方法一:原型方法(普通函数)
handleClick() {
console.log(this.text);
}
// 方法二:实例属性(箭头函数)
handleClickArrow = () => {
console.log(this.text);
};
}
这两种写法在使用上的关键区别是:
const btn = new Button('Submit');
// 原型方法:单独拿出来调用时,this 丢失
const fn1 = btn.handleClick;
fn1(); // ❌ undefined(this 不是 btn)
// 箭头函数属性:单独拿出来调用时,this 仍然是实例
const fn2 = btn.handleClickArrow;
fn2(); // ✅ 'Submit'
为什么?
handleClickArrow = () => { ... } 是一个类字段(class field),它的初始化等价于在 constructor 中写:
constructor(text) {
this.text = text;
this.handleClickArrow = () => {
console.log(this.text);
};
}
根据我们在 new 那章讲的,constructor 执行时 this 指向新创建的实例。箭头函数定义在 constructor 的执行上下文中,所以它”记住”的 this 就是这个实例。
但要注意权衡:
- 箭头函数属性是每个实例各一份(挂在实例上),而原型方法是所有实例共享一份(挂在
prototype上) - 如果创建大量实例,箭头函数属性会占用更多内存
- 箭头函数属性不能被子类通过
super调用(因为它不在原型上)
在 React 类组件中,这种写法曾经非常流行(避免在 constructor 里写一堆 this.handleClick = this.handleClick.bind(this))。但在函数组件 + Hooks 时代,这个问题已经不太常见了。
六、回到开头:热身题逐行分析
现在我们有了完整的知识体系,回来看开头的热身题:
const obj = {
name: 'obj',
fn1() {
console.log('fn1 this:', this.name);
},
fn2: () => {
console.log('fn2 this:', this.name);
},
fn3() {
setTimeout(function () {
console.log('fn3 timeout this:', this?.name);
}, 0);
},
fn4() {
setTimeout(() => {
console.log('fn4 timeout this:', this.name);
}, 0);
}
};
obj.fn1()
obj.fn1(); // 输出: 'fn1 this: obj'
fn1 是普通方法,通过 obj.fn1() 调用,触发隐式绑定,this 指向 obj。
obj.fn2()
obj.fn2(); // 输出: 'fn2 this: undefined'
fn2 是箭头函数。对象字面量 {} 不创建执行上下文,所以 fn2 的外层是全局/模块上下文。非严格模式下可能是 window.name(通常是空字符串),模块环境下是 undefined。
obj.fn3()
obj.fn3(); // 输出: 'fn3 timeout this: undefined'
fn3 是普通方法,但内部 setTimeout 的回调是普通函数。这个回调被引擎独立调用,触发默认绑定,this 指向全局对象(非严格模式)或 undefined(严格模式)。
注意:即使 fn3 本身的 this 是 obj,也不影响 setTimeout 回调里的 this——因为回调是一个独立的普通函数,它有自己的 this 绑定规则。
obj.fn4()
obj.fn4(); // 输出: 'fn4 timeout this: obj'
fn4 是普通方法,内部 setTimeout 的回调是箭头函数。箭头函数没有自己的 this,它继承了外层 fn4 执行时的 this。而 obj.fn4() 通过隐式绑定,fn4 的 this 是 obj,所以箭头函数里的 this 也是 obj。
这道题的精髓在于:fn3 和 fn4 唯一的区别就是 setTimeout 回调用的是普通函数还是箭头函数,但结果完全不同。 这恰恰体现了箭头函数设计的初衷——在回调中保留外层 this。
Bonus:箭头函数的 name 属性
一个很少有人提到但面试偶尔会问到的细节:箭头函数是匿名的,但它仍然有 name 属性。
const foo = () => {};
console.log(foo.name); // 'foo'
const obj = {
bar: () => {}
};
console.log(obj.bar.name); // 'bar'
// 但直接传入的箭头函数没有名字
console.log((() => {}).name); // ''
这是因为 ES6 引入了函数名推断(name inference):当一个匿名函数被赋值给变量或属性时,引擎会自动推断出一个名字。这对调试很有帮助——在堆栈追踪中,有名字的函数比 <anonymous> 更容易定位问题。
常见误区
在面试和实际开发中,关于箭头函数有几个非常顽固的误解。这里逐一澄清,帮你避免踩坑。
误区一:“箭头函数就是普通函数的简写语法糖”
不是。语法糖意味着”换了个写法,本质完全一样”,比如 class 之于构造函数 + 原型。但箭头函数和普通函数在规范层面有本质差异:没有自己的 this、没有 arguments、没有 [[Construct]]、没有 prototype、不能做 Generator。这些不是语法层面的省略,而是语义层面的删减。把箭头函数当成”短一点的 function”来用,迟早会掉进 this 的坑里。
误区二:“箭头函数的 this 指向定义时所在的对象”
这句话看起来”差不多对”,但差的那一点恰好是面试重灾区。准确的说法是:箭头函数的 this 继承自定义时所在的外层执行上下文。“对象”和”执行上下文”是两个完全不同的概念——对象字面量 {} 不会创建执行上下文。看这个例子:
const config = {
env: 'production',
getEnv: () => this.env
};
console.log(config.getEnv()); // undefined,不是 'production'
getEnv 写在 config 对象里,但 config 不产生执行上下文,所以 this 是外层的全局/模块上下文,而不是 config。
误区三:“箭头函数没有 this”
严格说,箭头函数不是”没有 this”,而是没有自己的 this 绑定。在箭头函数内部,this 这个关键字依然可以使用,只不过它引用的是外层作用域的 this,就像访问一个闭包变量一样。说”没有 this”容易让人以为箭头函数里写 this 会报错,但实际上不会——它只是不归箭头函数自己管。
function Outer() {
this.value = 42;
const inner = () => {
console.log(this.value); // 42,this 可以正常使用,只不过是 Outer 的
};
inner();
}
new Outer();
误区四:“用了箭头函数就不用考虑 this 问题了”
恰恰相反,用箭头函数时更需要清楚 this 的来源。箭头函数只是把 this 的决定时机从”调用时”提前到了”定义时”,但如果你搞不清楚定义时外层的 this 是谁,照样会出 bug。比如在 forEach 的回调里用箭头函数,你得确认 forEach 所在的那个函数的 this 是你期望的值。箭头函数不是银弹,它只是把 this 的问题从”动态”变成了”静态”,让你在写代码的时候就能确定,而不是等到运行时才发现。
小结
本章我们从 ES6 之前”回调中 this 丢失”的痛点出发,完整拆解了箭头函数的设计动机、语法细节、和普通函数的核心差异、以及实战中的选择策略。
核心要点
- 箭头函数没有自己的 this:它的 this 从外层作用域继承,在定义时就确定了,之后 call / apply / bind 都无法改变
- 对象字面量不是”外层作用域”:
{}不创建执行上下文,箭头函数会继续往上找 - 不能 new:没有
[[Construct]]内部方法,也没有prototype - 没有 arguments:用 rest 参数
...args替代 - 不能做 Generator:不支持
yield - 最佳使用场景:回调函数、数组方法、需要保留外层 this 的地方
- 谨慎使用场景:对象方法、原型方法、需要动态 this 的 DOM 事件处理器
记忆口诀
箭头函数四个”没有”:没有 this、没有 arguments、没有 prototype、没有 constructor。
一个”不能”:不能做 Generator。
箭头函数 语法简洁,但更重要的是 词法 this
一个”无效”:call / apply / bind 改不了它的 this。
本章思维导图
- 为什么会出现
- ES6 之前回调 this 丢失(var self = this / bind)
- 箭头函数从语言层面解决这个问题
- 语法
- 基本形式:(params) => expression
- 省略规则:单参数省括号、单表达式省 return
- 坑:返回对象要加小括号
- 核心差异(vs 普通函数)
- this:定义时继承外层,无法被 call/apply/bind 改变
- 对象字面量不创建执行上下文(高频误区)
- 没有 [[Construct]],不能 new
- 没有 prototype
- 没有 arguments,用 rest 参数替代
- 没有 super 绑定(继承外层)
- 不能做 Generator
- 使用场景
- ✅ 回调(定时器、Promise、事件)
- ✅ 数组方法(map/filter/reduce)
- ⚠️ 不推荐:对象方法、原型方法
- ⚠️ 不推荐:需要动态 this 的 DOM 处理器
- class 中的箭头函数
- 类字段箭头函数 vs 原型方法
- 权衡:this 稳定 vs 内存开销
- 与 this 章的关系
- 箭头函数不参与 this 优先级比较
- 它是"五种绑定"之外的特例
练习挑战
从易到难,三道题帮你巩固本章内容。建议先自己写出答案,再展开对照。
第一题(基础):预测输出
const calculator = {
value: 100,
add: function(nums) {
return nums.map(n => this.value + n);
},
subtract: function(nums) {
return nums.map(function(n) {
return this.value - n;
});
}
};
console.log(calculator.add([1, 2, 3]));
console.log(calculator.subtract([1, 2, 3]));
点击查看答案与解析
[101, 102, 103]
[NaN, NaN, NaN] // 非严格模式下,如果全局没有 value 属性
add 方法中,map 的回调是箭头函数,this 继承自 add 执行时的上下文,即 calculator,所以 this.value 是 100。
subtract 方法中,map 的回调是普通函数,被 map 内部独立调用,触发默认绑定,this 是全局对象(非严格模式)或 undefined(严格模式)。全局对象上通常没有 value 属性,所以 undefined - n 得到 NaN。
第二题(进阶):修复 Bug
下面的代码期望点击按钮后,延迟 500ms 打印出 "Clicked: submitBtn",但实际运行时打印的是 "Clicked: undefined"。请找出 Bug 并用最少的改动修复。
class Widget {
constructor(name) {
this.name = name;
}
bindEvents(button) {
button.addEventListener('click', function() {
setTimeout(function() {
console.log('Clicked: ' + this.name);
}, 500);
});
}
}
const w = new Widget('submitBtn');
w.bindEvents(document.querySelector('#submit'));
点击查看答案与解析
问题出在两层嵌套的普通函数回调上,this 在每一层都会被重新绑定。最简洁的修复方式是把两个回调都改成箭头函数:
bindEvents(button) {
button.addEventListener('click', () => {
setTimeout(() => {
console.log('Clicked: ' + this.name);
}, 500);
});
}
外层箭头函数继承 bindEvents 的 this(即 Widget 实例),内层箭头函数继承外层箭头函数的 this(同样是 Widget 实例)。两层箭头函数层层传递,最终 this.name 就是 'submitBtn'。
注意:如果你只改内层的 setTimeout 回调为箭头函数,还是不够——外层的 addEventListener 回调仍然是普通函数,它的 this 会被绑定为触发事件的 DOM 元素(button),而不是 Widget 实例。所以两层都要改。
第三题(综合):实现一个 pipeline 工具函数
请用箭头函数实现一个 pipeline 函数,接收一个初始值和任意数量的变换函数,依次将值传入每个函数并返回最终结果。要求:
- 使用 rest 参数收集变换函数
- 使用数组方法(
reduce)实现管道 - 不能出现
function关键字
// 用法示例:
const result = pipeline(
' Hello, World! ',
s => s.trim(),
s => s.toLowerCase(),
s => s.replace(/\s+/g, '-'),
s => s.split('-'),
arr => arr.length
);
console.log(result); // 2
点击查看参考实现
const pipeline = (initial, ...fns) => fns.reduce((val, fn) => fn(val), initial);
一行搞定。rest 参数 ...fns 收集所有变换函数为数组,reduce 依次将上一步的结果传入下一个函数。整个实现没有用到 this,所以箭头函数在这里既简洁又安全——这正是箭头函数最舒服的使用场景。
自我检测
读完本章后,对照下面的清单检验一下自己的掌握程度。每一项都能自信地打勾,说明你对箭头函数的理解已经足够扎实了。
- 能说清楚 ES6 之前处理回调 this 丢失的两种常见方法(
var self = this和.bind(this)),以及箭头函数如何从语言层面解决这个问题 - 能准确列出箭头函数和普通函数的至少 5 个核心差异,并解释每个差异背后的规范原因
- 能解释”箭头函数的 this 继承自外层执行上下文”这句话的准确含义,特别是为什么对象字面量不算外层
- 能区分 class 中的原型方法和类字段箭头函数的差异,并说出各自的适用场景和内存影响
- 能在给定的代码片段中准确判断箭头函数的
this指向谁 - 能说出箭头函数返回对象字面量时需要加小括号的原因
- 能在实际代码中合理选择使用箭头函数还是普通函数,并说出选择的理由
- 能解释为什么
call/apply/bind无法改变箭头函数的this(不是”规定如此”,而是从”箭头函数没有自己的 this 绑定”这个角度解释)
购买课程解锁全部内容
大厂前端面试通关:71 篇构建完整知识体系
¥89.90