JavaScript中的箭头函数
<h2 id="前言">前言</h2><p>本文可以让你了解所有有关JavaScript箭头函数的信息。我们将告诉你如何使用ES6的箭头语法,以及在代码中使用箭头函数时需要注意的一些常见错误。你会看到很多例子来说明它们是如何工作的。</p>
<p>JavaScript的箭头函数随着ECMAScript 2015的发布而到来,也被称为ES6。由于其简洁的语法和对this关键字的处理,箭头函数迅速成为开发者们最喜爱的功能。</p>
<h2 id="箭头函数语法">箭头函数语法</h2>
<p>函数就像食谱一样,你在其中存储有用的指令,以完成你需要在程序中发生的事情,比如执行一个动作或返回一个值。通过调用函数,来执行食谱中包含的步骤。你可以在每次调用该函数时都这样做,而不需要一次又一次地重写菜谱。</p>
<p>下面是在JavaScript中声明函数并调用它的标准方法:</p>
<pre><code class="language-jsx">// function declaration
function sayHiStranger() {
return 'Hi, stranger!'
}
// call the function
sayHiStranger()
</code></pre>
<p>你也可以编写同样的函数作为函数表达式,就行这样:</p>
<pre><code class="language-jsx">const sayHiStranger = function () {
return 'Hi, stranger!'
}
</code></pre>
<p>JavaScript箭头函数始终是表达式。下面是如何使用箭头符号重写上面的函数:</p>
<pre><code class="language-jsx">const sayHiStranger = () => 'Hi, stranger'
</code></pre>
<p>这样做的好处包括:</p>
<ul>
<li>代码只有一行</li>
<li>没有<code>function</code>关键字</li>
<li>没有<code>return</code>关键字</li>
<li>没有大括号<code>{}</code></li>
</ul>
<p>在JavaScript中,函数是一等公民。你可以把函数存储在变量中,把它们作为参数传递给其他函数,并从其他函数中把它们作为值返回。你可以使用JavaScript箭头函数来做所有这些事情。</p>
<h2 id="无圆括号语法">无圆括号语法</h2>
<p>在上述示例中,函数是没有参数的。在本例中,你必须在胖箭头符号(<code>=></code>)之前添加一对空的圆括号<code>()</code>。当有多个参数时同理:</p>
<pre><code class="language-jsx">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
</code></pre>
<p>如果只有一个参数,你可以省略圆括号(你不必如此,但你可以这么做):</p>
<pre><code class="language-jsx">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"
</code></pre>
<p>当你这么做的时候要小心一点。比如说,你决定使用默认参数,你必须将其包裹在圆括号中:</p>
<pre><code class="language-jsx">// 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)
</code></pre>
<h2 id="隐式返回">隐式返回</h2>
<p>在函数体内只有一个表达式时,你可以让ES6的箭头语法更加简洁。你可以把所有内容放在一行,去掉大括号,并移除<code>return</code>关键字。</p>
<p>你已经在上面的示例中看到了这些漂亮的一行代码是如何工作的。下面的<code>orderByLikes()</code>函数返回奈飞剧集对象的数组,按照最高点赞数排序:</p>
<pre><code class="language-jsx">// 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)
</code></pre>
<p>这种写法很酷,但是要注意代码的可读性。特别是在使用单行和无括号的ES6箭头语法对一堆箭头函数进行排序时。就像这个例子:</p>
<pre><code class="language-jsx">const greeter = greeting => name => `${greeting}, ${name}!`
</code></pre>
<p>那里发生了什么?尝试使用常规的函数语法:</p>
<pre><code class="language-jsx">function greeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`
}
}
</code></pre>
<p>现在,你可以快速看到外部函数<code>greeter</code>如何具有参数<code>greeting</code>,并返回一个匿名函数。这个内部函数又有一个叫做<code>name</code>的参数,并使用<code>greeting</code>和<code>name</code>的值返回一个字符串。下面是调用函数的方式:</p>
<pre><code class="language-jsx">const myGreet = greeter('Good morning')
console.log( myGreet('Mary') )
// output:
"Good morning, Mary!"
</code></pre>
<h3 id="注意隐式返回错误">注意隐式返回错误</h3>
<p>当你的JavaScript箭头函数包含不止一个语句,你需要在大括号内包裹所有语句,并使用<code>return</code>关键字。</p>
<p>在下面的代码中,该函数建立了一个包含几个Netflix剧集的标题和摘要的对象:</p>
<pre><code class="language-jsx">const seriesList = netflixSeries.map( series => {
const container = {}
container.title = series.name
container.summary = series.summary
// explicit return
return container
} )
</code></pre>
<p><code>.map()</code>函数中的箭头函数在一系列的语句中展开,在语句的最后返回一个对象。这使得在函数主体周围使用大括号是不可避免的。</p>
<p>另外,由于正在使用花括号,隐式返回便不是一个选项。你必须显式使用<code>return</code>关键字。</p>
<p>如果你的函数使用隐式返回来返回一个对象字面量,你需要使用圆括号来包裹该对象字面量。不这样做将导致错误,因为JavaScript引擎将对象字面量的大括号错误地解析为函数的大括号。正如你刚才注意到的,当你在一个箭头函数中使用大括号时,你不能省略<code>return</code>关键字。</p>
<p>前面代码的较短版本演示了这种语法:</p>
<pre><code class="language-jsx">// Uncaught SyntaxError: unexpected token: ':'
const seriesList = netflixSeries.map(series => { title: series.name });
// Works fine
const seriesList = netflixSeries.map(series => ({ title: series.name }));
</code></pre>
<h2 id="无法命名箭头函数">无法命名箭头函数</h2>
<p>在<code>function</code>关键字和参数列表之间没有名称标识的函数被称为匿名函数。下面是常规匿名函数表达式的样子:</p>
<pre><code class="language-jsx">const anonymous = function() {
return 'You can\'t identify me!'
}
</code></pre>
<p>箭头函数都是匿名函数:</p>
<pre><code class="language-jsx">const anonymousArrowFunc = () => 'You can\'t identify me!'
</code></pre>
<p>从ES6开始,变量和方法可以通过匿名函数的语法位置,使用<code>name</code>属性来推断其名称。这使得在检查函数值或报告错误时有可能识别该函数。</p>
<p>使用<code>anonymousArrowFunc</code>检查一下:</p>
<pre><code class="language-jsx">console.log(anonymousArrowFunc.name)
// output: "anonymousArrowFunc"
</code></pre>
<p>需要注意的是,只有当匿名函数被分配给一个变量时,这个可以推断的<code>name</code>属性才会存在,正如上面的例子。如果你使用匿名函数作为回调函数,你就会失去这个有用的功能。在下面的演示中,<code>.setInterval()</code>方法中的匿名函数无法利用<code>name</code>属性:</p>
<pre><code class="language-jsx">let counter = 5
let countDown = setInterval(() => {
console.log(counter)
counter--
if (counter === 0) {
console.log("I have no name!!")
clearInterval(countDown)
}
}, 1000)
</code></pre>
<p>这还不是全部。这个推断的<code>name</code>属性仍然不能作为一个适当的标识符,你可以用它来指代函数本身--比如递归、解除绑定事件等。</p>
<h2 id="如何处理this关键字">如何处理this关键字</h2>
<p>关于箭头函数,最重要的一点是它们处理<code>this</code>关键字的方式。特别是,箭头函数内的<code>this</code>关键字不会重新绑定。</p>
<p>为了说明这意味着什么,请查看下面的演示。</p>
<p>这里有一个按钮。点击按钮会触发一个从5到1的反向计数器,它显示在按钮本身。</p>
<pre><code class="language-jsx"><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)
})
</code></pre>
<p>注意到<code>.addEventListener()</code>方法里面的事件处理器是一个常规的匿名函数表达式,而不是一个箭头函数。为什么呢?如果在函数内部打印<code>this</code>的值,你会看到它引用了监听器所连接的按钮元素,这正是我们所期望的,也是程序按计划工作所需要的:</p>
<pre><code class="language-jsx">startBtn.addEventListener('click', function() {
console.log(this)
...
})
</code></pre>
<p>下面是它在Firefox开发人员工具控制台中的样子:</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d4dd165301e649ae9a3f1fa5f2a6859b~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>然后,尝试使用箭头函数来替代常规函数,就像这样:</p>
<pre><code class="language-jsx">startBtn.addEventListener('click', () => {
console.log(this)
...
})
</code></pre>
<p>现在,<code>this</code>不再引用按钮元素。相反,它引用<code>Window</code>对象:</p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fcf3be728d8848b9ab323385a977020d~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>这意味着,如果你想要在按钮被点击之后,使用<code>this</code>来为按钮添加<code>class</code>,你的代码就无法正常工作:</p>
<pre><code class="language-jsx">// change button's border's appearance
this.classList.add('counting')
</code></pre>
<p>下面是控制台中的错误信息:</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5fd739ba0d4a45d8bc4498d936a08e88~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>当你在JavaScript中使用箭头函数,<code>this</code>关键字的值不会被重新绑定。它继承自父作用域(也称为词法作用域)。在这种特殊情况下,箭头函数被作为参数传递给<code>startBtn.addEventListener()</code>方法,该方法位于全局作用域中。因此,函数处理器中的<code>this</code>也被绑定到全局作用域中--也就是<code>Window</code>对象。</p>
<p>因此,如果你想让<code>this</code>引用程序中的开始按钮,正确的做法是使用一个常规函数,而不是一个箭头函数。</p>
<h3 id="匿名箭头函数">匿名箭头函数</h3>
<p>在上面的演示中,接下来要注意的是<code>.setInterval()</code>方法中的代码。在这里,你也会发现一个匿名函数,但这次是一个箭头函数。为什么?</p>
<p>请注意,如果你使用常规函数,<code>this</code>值会是多少:</p>
<pre><code class="language-jsx">const timer = setInterval(function() {
console.log(this)
...
}, 1000)
</code></pre>
<p>是<code>button</code>元素吗?并不是。这个值将会是<code>Window</code>对象!</p>
<p>事实上,上下文已经发生了变化,因为现在<code>this</code>在一个非绑定的或全局的函数中,它被作为参数传递给<code>.setInterval()</code> 。因此,<code>this</code>关键字的值也发生了变化,因为它现在被绑定到全局作用域。</p>
<p>在这种情况下,一个常见的hack手段是包括另一个变量来存储<code>this</code>关键字的值,这样它就会一直指向预期的元素--在这种情况下,就是<code>button</code>元素:</p>
<pre><code class="language-jsx">const that = this
const timer = setInterval(function() {
console.log(that)
...
}, 1000)
</code></pre>
<p>你也可以使用<code>.bind()</code>来解决这个问题:</p>
<pre><code class="language-jsx">const timer = setInterval(function() {
console.log(this)
...
}.bind(this), 1000)
</code></pre>
<p>有了箭头函数,问题就彻底消失了。下面是使用箭头函数时<code>this</code>的值:</p>
<pre><code>const timer = setInterval( () => {
console.log(this)
...
}, 1000)
</code></pre>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6984ea57d8844d6bb0fd78c04111e429~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>这次,控制台打印了<code>button</code>,这就是我们想要的。事实上,程序要改变按钮的文本,所以它需要<code>this</code>来指代<code>button</code>元素:</p>
<pre><code class="language-jsx">const timer = setInterval( () => {
console.log(this)
// the button's text displays the timer value
this.textContent = counter
}, 1000)
</code></pre>
<p>箭头函数没有自己的<code>this</code>上下文。它们从父级继承<code>this</code>的值,正是因为这个特点,在上面这种情况下就是很好的选择。</p>
<h2 id="不正常工作的情况">不正常工作的情况</h2>
<p>箭头函数并不只是在JavaScript中编写函数的一种花里胡哨的新方法。它们有自己的局限性,这意味着在有些情况下你不想使用箭头函数。让我们看看更多的例子。</p>
<h3 id="箭头函数作为对象方法">箭头函数作为对象方法</h3>
<p>箭头函数作为对象上的方法不能很好地工作。</p>
<p>考虑这个<code>netflixSeries</code>对象,上面有一些属性和一系列方法。调用<code>console.log(netflixSeries.getLikes())</code> 应该会打印一条信息,说明当前喜欢的人数。<code>console.log(netflixSeries.addLike())</code>应该会增加一个喜欢的人数,然后在控制台上打印新值:</p>
<pre><code class="language-jsx">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`
}
}
</code></pre>
<p>相反,调用<code>.getLikes()</code>方法返回<code>'undefined has NaN likes'</code>,调用<code>.addLike()</code>方法返回<code>'Thank you for liking undefined, which now has NaN likes'</code>。因此,<code>this.title</code>和<code>this.likes</code>未能分别引用对象的属性<code>title</code>和<code>likes</code>。</p>
<p>这次,问题出在箭头函数的词法作用域上。对象方法中的<code>this</code>引用的是父对象的范围,在本例中是<code>Window</code>对象,而不是父对象本身--也就是说,不是<code>netflixSeries</code>对象。</p>
<p>当然,解决办法是使用常规函数:</p>
<pre><code class="language-jsx">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
</code></pre>
<h3 id="箭头函数与第三方库">箭头函数与第三方库</h3>
<p>另一个需要注意的问题是,第三方库通常会绑定方法调用,因此<code>this</code>值会指向一些有用的东西。</p>
<p>比如说,在Jquery事件处理器内部,<code>this</code>将使你能够访问处理器所绑定的DOM元素:</p>
<pre><code class="language-jsx">$('body').on('click', function() {
console.log(this)
})
// <body>
</code></pre>
<p>但是如果我们使用箭头函数,正如我们所看到的,它没有自己的<code>this</code>上下文,我们会得到意想不到的结果:</p>
<pre><code>$('body').on('click', () =>{
console.log(this)
})
// Window
</code></pre>
<p>下面是使用Vue的其他例子:</p>
<pre><code class="language-jsx">new Vue({
el: app,
data: {
message: 'Hello, World!'
},
created: function() {
console.log(this.message);
}
})
// Hello, World!
</code></pre>
<p>在<code>created</code>钩子内部,<code>this</code>被绑定到Vue实例上,因此会显示<code>'Hello, World!'</code>信息。</p>
<p>然而如果我们使用箭头函数,<code>this</code>将会指向父作用域,上面没有<code>message</code>属性:</p>
<pre><code class="language-jsx">new Vue({
el: app,
data: {
message: 'Hello, World!'
},
created: () => {
console.log(this.message);
}
})
// undefined
</code></pre>
<h3 id="箭头函数没有arguments对象">箭头函数没有<code>arguments</code>对象</h3>
<p>有时,你可能需要创建一个具有无限参数个数的函数。比如,假设你想创建一个函数,列出你最喜欢的奈飞剧集,并按照偏好排序。然而,你还不知道你要包括多少个剧集。JavaScript提供了<code>arguments</code>对象。这是一个类数组对象(不是完整的数组),在调用时存储传递给函数的值。</p>
<p>尝试使用箭头函数实现此功能:</p>
<pre><code class="language-jsx">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'))
</code></pre>
<p>当你调用该函数时,你会得到以下错误:<code>Uncaught ReferenceError: arguments is not defined</code>。这意味着<code>arguments</code>对象在箭头函数中是不可用的。事实上,将箭头函数替换成常规函数就可以了:</p>
<pre><code class="language-jsx">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"]
</code></pre>
<p>因此,如果你需要<code>arguments</code>对象,你不能使用箭头函数。</p>
<p>但如果你真的想用一个箭头函数来复制同样的功能呢?你可以使用ES6剩余参数(<code>...</code>)。下面是你该如何重写你的函数:</p>
<pre><code class="language-jsx">const listYourFavNetflixSeries = (...seriesList) => {
return seriesList.map( (series, i) => {
return `${series} is my #${i +1} favorite Netflix series`
} )
}
</code></pre>
<h2 id="总结">总结</h2>
<p>通过使用箭头函数,你可以编写带有隐式返回的单行代码,以解决JavaScript中<code>this</code>关键字的绑定问题。箭头函数在数组方法中也很好用,如<code>.map()</code>、<code>.sort()</code>、<code>.forEach()</code>、<code>.filter()</code>、和<code>.reduce()</code>。但请记住:箭头函数并不能取代常规的JavaScript函数。记住,只有当箭形函数是正确的工具时,才能使用它。</p>
<p>以上就是本文的所有内容,如果对你有所帮助,欢迎点赞收藏转发~</p><br><br>
来源:https://www.cnblogs.com/chuckQu/p/16871434.html
頁:
[1]