JavaScript中的箭头函数——如何使用简洁的语法
了解关于JavaScript箭头函数的所有信息。我们将告诉你如何使用ES6箭头语法,以及在代码中利用箭头函数时需要注意的一些常见错误。你会看到很多例子来说明它们是如何工作的。
JavaScript的箭头函数是随着ECMAScript 2015的发布而出现的,也被称为ES6。由于其简洁的语法和对*this*关键字的处理,箭头函数很快成为开发者最喜欢的功能。
箭头函数语法:重写一个常规函数
函数就像食谱一样,你在其中存储有用的指令,以完成你在程序中需要发生的事情,比如执行一个动作或返回一个值。通过调用你的函数,你执行你的食谱中包括的步骤。你可以在每次调用该函数时这样做,而不需要一次又一次地重写配方。
这里有一个标准的方法来声明一个函数,然后在JavaScript中调用它:
// function declaration
function sayHiStranger() {
return 'Hi, stranger!'
}
// call the function
sayHiStranger()
你也可以把同一个函数写成一个函数表达式,像这样:
const sayHiStranger = function () {
return 'Hi, stranger!'
}
JavaScript的箭头函数总是表达式。下面是你如何使用胖箭头符号重写上面的函数:
const sayHiStranger = () => 'Hi, stranger'
这样做的好处包括:
- 只有一行代码
- 没有
function关键字 - 没有
return关键字 - 也没有大括号{}
在JavaScript中,函数是 "一等公民"。你可以将函数存储在变量中,将它们作为参数传递给其他函数,并将它们作为值从其他函数中返回。你可以用JavaScript的箭头函数来做所有这些事情。
无帕累斯句法
在上面的例子中,该函数没有参数。在这种情况下,你必须在胖箭头 (=>) 符号前添加一组空括号() 。当你有一个以上的参数时也是如此:
const getNetflixSeries = (seriesName, releaseDate) => `The ${seriesName} series was released in ${releaseDate}`
// call the function
console.log(getNetflixSeries('Bridgerton', '2020') )
// output: The Bridgerton series was released in 2020
然而,如果只有一个参数,你可以继续下去,不加括号(你不一定要这样做,但你可以):
const favoriteSeries = seriesName => seriesName === "Bridgerton" ? "Let's watch it" : "Let's go out"
// call the function
console.log(favoriteSeries("Bridgerton"))
// output: "Let's watch it"
不过要小心。例如,如果你决定使用一个默认参数,你必须把它包在圆括号里:
// with parentheses: correct
const bestNetflixSeries = (seriesName = "Bridgerton") => `${seriesName} is the best`
// outputs: "Bridgerton is the best"
console.log(bestNetflixSeries())
// no parentheses: error
const bestNetflixSeries = seriesName = "Bridgerton" => `${seriesName} is the best`
// Uncaught SyntaxError: invalid arrow-function arguments (parentheses around the arrow-function may help)
仅仅因为你可以,并不意味着你应该。Kyle Simpson(《你不知道的JS》的作者)把他对省略小括号的想法写进了这个流程图中,其中夹杂着一点轻松、善意的讽刺。
隐式返回
当你的函数体中只有一个表达式时,你可以使ES6的箭头语法更加简洁。你可以把所有东西放在一行,去掉大括号,并取消return 关键字。
你刚刚在上面的例子中看到了这些漂亮的单行代码是如何工作的。这里还有一个例子,只是为了更好地衡量。orderByLikes() 函数做了它所描述的事情:也就是说,它返回一个Netflix系列对象的数组,按喜欢的最高数量排序:
// using the JS sort() function to sort the titles in descending order
// according to the number of likes (more likes at the top, fewer at the bottom
const orderByLikes = netflixSeries.sort( (a, b) => b.likes - a.likes )
// call the function
// output:the titles and the n. of likes in descending order
console.log(orderByLikes)
这很酷,但要注意你的代码的可读性--特别是当使用单行和无括号的ES6箭头语法对一堆箭头函数进行排序时,比如在这个例子中:
const greeter = greeting => name => `${greeting}, ${name}!`
那里发生了什么?试试使用常规的函数语法:
function greeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`
}
}
现在,你可以很快看到外层函数greeter 有一个参数,greeting ,并返回一个匿名函数。这个内部函数又有一个叫name 的参数,并使用greeting 和name 的值返回一个字符串。 这里是你如何调用这个函数的:
const myGreet = greeter('Good morning')
console.log( myGreet('Mary') )
// output:
"Good morning, Mary!"
注意这些隐式返回的错误
当你的JavaScript箭头函数包含一个以上的语句时,你需要用大括号把所有的语句包起来,并使用return 关键字。
在下面的代码中,该函数建立了一个包含几个Netflix系列的标题和摘要的对象(Netflix的评论来自烂番茄网站):
const seriesList = netflixSeries.map( series => {
const container = {}
container.title = series.name
container.summary = series.summary
// explicit return
return container
} )
.map() 函数内的箭头函数在一系列的语句中发展,在结束时返回一个对象。这使得在函数的主体周围使用大括号是不可避免的。
此外,由于你使用了大括号,隐式返回不是一个选项。你必须使用return 关键字。
如果你的函数使用隐式返回返回一个对象字面,你需要将该对象包裹在圆括号内。不这样做会导致错误,因为JavaScript引擎会错误地将对象字面的大括号解析为函数的大括号。正如你刚刚注意到的,当你在一个箭头函数中使用大括号时,你不能省略返回关键字。
前面的代码的较短版本演示了这种语法:
// Uncaught SyntaxError: unexpected token: ':'
const seriesList = netflixSeries.map(series => { title: series.name });
// Works fine
const seriesList = netflixSeries.map(series => ({ title: series.name }));
你不能为箭头函数命名
在function 关键字和参数列表之间没有名称标识的函数被称为匿名函数。下面是一个正规的匿名函数表达式的样子:
const anonymous = function() {
return 'You can\'t identify me!'
}
箭头函数都是匿名函数。
const anonymousArrowFunc = () => 'You can\'t identify me!'
从ES6开始,变量和方法可以从其语法位置推断出匿名函数的名称,使用其name 属性。这使得在检查函数的值或报告错误时,有可能识别该函数:
使用anonymousArrowFunc 来检查这一点。
console.log(anonymousArrowFunc.name)
// output: "anonymousArrowFunc"
请注意,这个推断的name 属性只在匿名函数被分配给一个变量时才存在,如上面的例子。如果你使用匿名函数作为回调,你就会失去这个有用的功能。这在下面的演示中得到了体现,.setInterval() 方法中的匿名函数不能利用name 属性:
let counter = 5
let countDown = setInterval(() => {
console.log(counter)
counter--
if (counter === 0) {
console.log("I have no name!!")
clearInterval(countDown)
}
}, 1000)
而这还不是全部。这个推断出来的name 属性仍然不能作为一个适当的标识符,你可以用它来指代函数本身--比如递归、解除绑定事件等等。
箭头函数的内在匿名性使得Kyle Simpson对其表达了如下观点:
因为我认为匿名函数在你的程序中经常使用并不是一个好主意,所以我不喜欢使用
=>箭头函数的形式。-你不知道的JS
箭头函数如何处理this 关键字
关于箭头函数,最重要的一点是它们处理this 关键字的方式。特别是,箭头函数内的this 关键字不会被反弹。
为了说明这意味着什么,请看下面的演示。
这里有一个按钮。点击按钮会触发一个从5到1的反向计数器,它显示在按钮本身:
<button class="start-btn">Start Counter</button>
...
const startBtn = document.querySelector(".start-btn");
startBtn.addEventListener('click', function() {
this.classList.add('counting')
let counter = 5;
const timer = setInterval(() => {
this.textContent = counter
counter --
if(counter < 0) {
this.textContent = 'THE END!'
this.classList.remove('counting')
clearInterval(timer)
}
}, 1000)
})
注意.addEventListener() 方法里面的事件处理程序是一个普通的匿名函数表达式,而不是一个箭头函数。为什么呢?如果你在函数内部记录this ,你会看到它引用了监听器所连接的按钮元素,这正是预期的,也是程序按计划工作所需要的。
startBtn.addEventListener('click', function() {
console.log(this)
...
})
下面是它在Firefox开发者工具控制台中的样子:

然而,试着用一个箭头函数来代替普通函数,像这样:
startBtn.addEventListener('click', () => {
console.log(this)
...
})
现在,this 不再引用这个按钮了。相反,它引用了Window 对象:

这意味着,如果你想用this ,在按钮被点击后给它添加一个类,例如,你的代码将无法工作:
// change button's border's appearance
this.classList.add('counting')
下面是控制台中的错误信息:

当你在JavaScript中使用一个箭头函数时,this 关键字的值不会被反弹。它是从父作用域中继承的(这叫做词法范围)。在这个特殊的例子中,有关的箭头函数被作为参数传递给startBtn.addEventListener() 方法,该方法在全局范围内。因此,函数处理程序中的this 也被绑定到全局作用域,也就是Window 对象。
因此,如果你想让this 来引用程序中的启动按钮,正确的做法是使用一个普通的函数,而不是一个箭头函数。
匿名的箭头函数
在上面的演示中,接下来要注意的是.setInterval() 方法中的代码。在这里,你也会发现一个匿名函数,但这一次是一个箭头函数。为什么呢?
注意如果你使用一个普通的函数,this 的值会是什么?
const timer = setInterval(function() {
console.log(this)
...
}, 1000)
会是button 这个元素吗?根本不是。它将是Window 对象!

事实上,上下文已经改变了,因为this 现在是在一个非绑定的或全局的函数里面,它被作为一个参数传递给.setInterval() 。因此,this 关键字的值也改变了,因为它现在被绑定到全局范围。
在这种情况下,一个常见的黑客方法是包括另一个变量来存储this 关键字的值,这样它就会一直指向预期的元素--在这种情况下,就是button 元素:
const that = this
const timer = setInterval(function() {
console.log(that)
...
}, 1000)
你也可以使用.bind() 来解决这个问题:
const timer = setInterval(function() {
console.log(this)
...
}.bind(this), 1000)
使用箭头函数,这个问题就完全消失了。下面是当你使用箭头函数时,this 的值:
const timer = setInterval( () => {
console.log(this)
...
}, 1000)

这一次,控制台记录了这个按钮,这是我们想要的。事实上,程序要改变按钮文本,所以它需要this ,以引用button 元素:
const timer = setInterval( () => {
console.log(this)
// the button's text displays the timer value
this.textContent = counter
}, 1000)
箭头函数没有自己的this 上下文。它们从父级继承了this 的值,正是因为这个特点,它们在像上面这种情况下是一个很好的选择。
JavaScript 箭头函数并不总是适合工作的工具
箭头函数并不只是在JavaScript中编写函数的一种花哨的新方法。它们有自己的局限性,这意味着在某些情况下你不想使用它们。前面演示中的点击处理程序就是一个例子,但它不是唯一的例子。让我们再来看看几个例子。
作为对象方法的箭头函数
箭头函数作为对象的方法并不好用。这里有一个例子。
考虑一下这个netflixSeries 对象,它有一些属性和几个方法。调用console.log(netflixSeries.getLikes()) ,应该打印出一条当前喜欢数的信息,而调用console.log(netflixSeries.addLike()) ,应该将喜欢数增加一个,然后在控制台打印出新的值和感谢信息:
const netflixSeries = {
title: 'After Life',
firstRealease: 2019,
likes: 5,
getLikes: () => `${this.title} has ${this.likes} likes`,
addLike: () => {
this.likes++
return `Thank you for liking ${this.title}, which now has ${this.likes} likes`
}
}
相反,调用.getLikes() 方法返回 "undefined有NaN个喜欢",而调用.addLike() 方法返回 "谢谢你喜欢undefined,它现在有NaN个喜欢"。所以,this.title 和this.likes 分别未能引用对象的属性title 和likes 。
再次,问题出在箭头函数的词法范围上。对象方法中的this 是在引用父对象的范围,在这种情况下是Window 对象,而不是父对象本身--也就是说,不是netflixSeries 对象。
当然,解决方案是使用普通函数:
const netflixSeries = {
title: 'After Life',
firstRealease: 2019,
likes: 5,
getLikes() {
return `${this.title} has ${this.likes} likes`
},
addLike() {
this.likes++
return `Thank you for liking ${this.title}, which now has ${this.likes} likes`
}
}
// call the methods
console.log(netflixSeries.getLikes())
console.log(netflixSeries.addLike())
// output:
After Life has 5 likes
Thank you for liking After Life, which now has 6 likes
使用第三方库的箭头函数
另一个需要注意的问题是,第三方库通常会绑定方法调用,使this 值指向有用的东西。
例如,在jQuery的事件处理程序中,this 会让你访问处理程序所绑定的DOM元素:
$('body').on('click', function() {
console.log(this)
})
// <body>
但是如果我们使用一个箭头函数--正如我们所看到的,它没有自己的this 上下文--我们会得到意想不到的结果:
$('body').on('click', () =>{
console.log(this)
})
// Window
下面是一个使用Vue的进一步例子:
new Vue({
el: app,
data: {
message: 'Hello, World!'
},
created: function() {
console.log(this.message);
}
})
// Hello, World!
在created 钩子里面,this 被绑定到Vue实例上,所以显示了 "Hello, World!"信息。
然而,如果我们使用一个箭头函数,this 将指向父范围,而父范围没有message 属性:
new Vue({
el: app,
data: {
message: 'Hello, World!'
},
created: function() {
console.log(this.message);
}
})
// undefined
箭头函数没有arguments 对象
有时,你可能需要创建一个具有无限数量参数的函数。例如,假设你想创建一个函数,列出你最喜欢的Netflix系列,并按喜好排序。然而,你还不知道你要包括多少个系列。JavaScript提供了*arguments*对象。这是一个类似数组的对象(不是一个完整的数组),在调用时存储传递给函数的值。
试着用一个箭头函数来实现这个功能:
const listYourFavNetflixSeries = () => {
// we need to turn the arguments into a real array
// so we can use .map()
const favSeries = Array.from(arguments)
return favSeries.map( (series, i) => {
return `${series} is my #${i +1} favorite Netflix series`
} )
console.log(arguments)
}
console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))
当你调用这个函数时,你会得到以下错误信息。Uncaught ReferenceError: arguments is not defined.这意味着arguments 对象在箭头函数中是不可用的。事实上,将箭头函数替换成普通函数就可以了:
const listYourFavNetflixSeries = function() {
const favSeries = Array.from(arguments)
return favSeries.map( (series, i) => {
return `${series} is my #${i +1} favorite Netflix series`
} )
console.log(arguments)
}
console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))
// output:
["Bridgerton is my #1 favorite Netflix series", "Ozark is my #2 favorite Netflix series", "After Life is my #3 favorite Netflix series"]
因此,如果你需要arguments 对象,你就不能使用箭头函数。
但如果你真的想用箭头函数来复制同样的功能呢?你可以做的一件事是使用ES6休息参数(...)。下面是你如何重写你的函数:
const listYourFavNetflixSeries = (...seriesList) => {
return seriesList.map( (series, i) => {
return `${series} is my #${i +1} favorite Netflix series`
} )
}
结论
通过使用箭头函数,你可以编写带有隐式返回的简明单行代码,并最终忘记旧时代的黑客,以解决JavaScript中this 关键字的绑定问题。箭头函数对数组方法也有很好的作用,如.map(),.sort(),.forEach(),.filter(), 和.reduce() 。但是请记住:箭头函数并不能取代常规的JavaScript函数。记住,只有在箭形函数是正确的工具时,才使用它。