用箭头函数重写回调:现代 JavaScript 教程函数表达式精简实战
用箭头函数重写回调:现代 JavaScript 教程函数表达式精简实战
在 1-js/02-first-steps/17-arrow-functions-basics/ 章节中,有一道经典入门练习题 "Rewrite with arrow functions":把 ask 函数调用处两个匿名的 Function Expression 回调改写为箭头函数,以体会箭头语法在回调场景下的简洁性。本文以该练习及其官方解答(1-rewrite-arrow/solution.md)为主线,结合同章节 article.md 与后续深入章节,系统讲解箭头函数的基础语法、回调中的应用方式及其背后的运行机制,读完你能够熟练地在回调、单行动作等场景中用箭头函数替换冗长的函数表达式,并理解其边界。
任务回顾:等待重写的 ask 回调代码
练习题(1-rewrite-arrow/task.md)给出了如下代码,要求把两个匿名函数表达式替换为箭头函数:
function ask(question, yes, no) {
if (confirm(question)) yes();
else no();
}
ask(
"Do you agree?",
function() { alert("You agreed."); },
function() { alert("You canceled the execution."); }
);
这段代码的背景来自前一章 16-function-expressions/article.md 的"回调函数"小节:ask 接收三个参数——question 是问题文本,yes 与 no 分别是用户确认/取消后要执行的函数。ask 内部根据 confirm(question) 的结果调用 yes() 或 no(),这种"把函数作为值传入、稍后再被调用"的函数就被称为回调函数(callback)。
当前版本的问题在于:每个回调都要写成完整的 function() { ... },占用了两行且略显冗长。这正是箭头函数最典型的应用场景——为单行动作与回调而生的精简语法。
参考解答:一行一个回调
官方解答(1-rewrite-arrow/solution.md)如下:
function ask(question, yes, no) {
if (confirm(question)) yes();
else no();
}
ask(
"Do you agree?",
() => alert("You agreed."),
() => alert("You canceled the execution.")
);
改动点与效果:
- 两个回调参数由
function() { ... }变为() => alert(...); - 因为箭头函数体是一个单一表达式
alert(...),它会被自动求值并作为返回值(此处返回值无实际用途,但表达式本身会被执行),所以可以省去花括号与return; - 因为回调没有参数,所以空括号
()必须保留(这是箭头函数对零参数的硬性要求); - 整体从 4 行缩到 2 行,语义不变:"看起来又短又干净,对吧?"(原解答的结语)。
箭头函数基础语法:三种形态
掌握这道题的前提,是先理解箭头函数的基本形态。该章节的 article.md 给出了完整的入门语法,共三种写法:
1. 多参数 + 表达式体
let func = (arg1, arg2, ..., argN) => expression;
这等价于:
let func = function(arg1, arg2, ..., argN) {
return expression;
};
实际例子:
let sum = (a, b) => a + b;
alert( sum(1, 2) ); // 3
(a, b) => a + b 表示一个接收两个参数的函数,执行时求值右侧表达式 a + b 并返回结果——不需要显式 return。
2. 单参数:可省略括号
如果只有一个参数,圆括号可以省掉,使写法更短:
let double = n => n * 2;
// 大致等价于:let double = function(n) { return n * 2 }
alert( double(3) ); // 6
3. 零参数:必须保留空括号
let sayHi = () => alert("Hello!");
sayHi();
这正是本练习中两个回调的写法——回调不需要任何参数,因此写成 () => ...,空括号不可省略。
多行箭头函数:花括号 + 显式 return
当函数体需要多条语句时,用花括号包起来。此时箭头函数与普通函数一样,必须有显式 return 才能返回值:
let sum = (a, b) => { // 花括号开启多行函数体
let result = a + b;
return result; // 使用花括号时必须显式 return
};
alert( sum(1, 2) ); // 3
两条形态规则可以这样记忆:
- 无花括号:
(...args) => expression——右侧是表达式,函数求值并返回其结果;单参数时括号可省略,如n => n*2; - 有花括号:
(...args) => { body }——可在体内写多条语句,但要返回内容必须显式写return。
为什么回调场景最适合箭头函数
本练习展示了箭头函数的核心理念:JavaScript 充满"写一个小函数并交给别处执行"的场景——arr.forEach(func) 由 forEach 对每个数组元素调用 func,setTimeout(func) 由内置调度器调用 func。而箭头函数正是为这类"简短代码块"设计的,它不在当前上下文之外另立门户。
以本练习的 ask 为例,将 16-function-expressions/article.md 中同名示例的回调改写后,代码的意图("同意"或"取消"时各弹一条提示)变得更加一目了然,这是箭头函数在回调与单行动作上被广泛使用的原因。同章节 article.md 还展示了箭头函数作为函数值参与三元运算的动态创建:
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
() => alert('Hello!') :
() => alert("Greetings!");
welcome();
可以看到,箭头函数可以像 Function Expression 一样在任意表达式中被创建、赋值与传递。
深入机制:箭头函数没有自己的 this、arguments 与 super
入门章节 article.md 明确提示"箭头函数还有更多特性,留待后续深入",对应的进阶章节是 06-advanced-functions/12-arrow-functions/article.md。理解这些特性,才能判断"何时该用、何时不该用"箭头函数。
没有自己的 this:箭头函数内部访问 this 时,取值来自外层词法环境。例如在对象方法里用箭头函数遍历数组,this.title 与外层方法中的 this 完全相同;若改用普通匿名函数,forEach 默认以 this=undefined 调用它,访问 this.title 会直接报错(Cannot read property 'title' of undefined)。这也意味着箭头函数不能用作构造函数,不能以 new 调用。
没有自己的 arguments:这反而让装饰器(decorator)写法更简洁——内部箭头函数可以直接沿用外层函数的 this 和 arguments,无需像普通函数那样先用 args、ctx 两个变量暂存:
function defer(f, ms) {
return function() {
setTimeout(() => f.apply(this, arguments), ms);
};
}
没有 super:这与后续 09-classes 章节中的类继承相关,属于进阶内容。
把这些特性与本练习联系起来,可以得出一个实用判断标准:
- 适合箭头函数:短小的回调、单行动作、需要在当前上下文(当前
this)中工作的代码; - 不适合箭头函数:需要自己的
this(如对象方法)、需要作为构造函数使用、需要访问自身arguments的场景。
常见误区与自查清单
结合本练习与章节内容,重写时最容易踩的坑有四个:
- 零参数忘写空括号:
() => alert(...)的()不能省略,写成=> alert(...)是语法错误; - 表达式体误加花括号后忘记
return:(a, b) => { a + b }不会返回a + b,需要return; - 以为箭头函数完全等同于普通函数:它没有自己的
this、arguments、super,也不能new; - 回调语义未变但作用域变了:在需要"保留外层
this"的回调中,箭头函数反而更安全(参考上面showList的例子)。
小结
本练习 (1-rewrite-arrow/solution.md) 用两行改动说明了箭头函数的核心价值:把 ask 的回调从 function() { ... } 压缩为 () => ...,代码更短、意图更清晰。结合入门章节 article.md 的语法体系与进阶章节 12-arrow-functions/article.md 的运行机制,可以归纳出完整的实践结论:
- 箭头函数有两种形态:无花括号的表达式体(自动返回)与带花括号的多行体(需显式
return); - 单参数可省略括号,零参数必须保留空括号;
- 箭头函数天然适合回调、单行动作等"在当前上下文工作"的场景;
- 它没有自己的
this、arguments和super,不能用new调用,因此在需要独立上下文的场合应改用普通函数。