关于javascript:JavaScript中的箭头函数

34次阅读

共计 8764 个字符,预计需要花费 22 分钟才能阅读完成。

前言

本文能够让你理解所有无关 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)

隐式返回

在函数体内只有一个表达式时,你能够让 ES6 的箭头语法更加简洁。你能够把所有内容放在一行,去掉大括号,并移除 return 关键字。

你曾经在下面的示例中看到了这些丑陋的一行代码是如何工作的。上面的 orderByLikes() 函数返回奈飞剧集对象的数组,依照最高点赞数排序:

// 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 的参数,并应用 greetingname的值返回一个字符串。上面是调用函数的形式:

const myGreet = greeter('Good morning')
console.log(myGreet('Mary') )   

// output: 
"Good morning, Mary!"

留神隐式返回谬误

当你的 JavaScript 箭头函数蕴含不止一个语句,你须要在大括号内包裹所有语句,并应用 return 关键字。

在上面的代码中,该函数建设了一个蕴含几个 Netflix 剧集的题目和摘要的对象:

const seriesList = netflixSeries.map( series => {const container = {}
  container.title = series.name 
  container.summary = series.summary

  // explicit return
  return container
} )

.map()函数中的箭头函数在一系列的语句中开展,在语句的最初返回一个对象。这使得在函数主体四周应用大括号是不可避免的。

另外,因为正在应用花括号,隐式返回便不是一个选项。你必须显式应用 return 关键字。

如果你的函数应用隐式返回来返回一个对象字面量,你须要应用圆括号来包裹该对象字面量。不这样做将导致谬误,因为 JavaScript 引擎将对象字面量的大括号谬误地解析为函数的大括号。正如你方才留神到的,当你在一个箭头函数中应用大括号时,你不能省略 return 关键字。

后面代码的较短版本演示了这种语法:

// 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 属性依然不能作为一个适当的标识符,你能够用它来指代函数自身 – 比方递归、解除绑定事件等。

如何解决 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 来为按钮增加class,你的代码就无奈失常工作:

// 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 关键字的值也产生了变动,因为它当初被绑定到全局作用域。

在这种状况下,一个常见的 hack 伎俩是包含另一个变量来存储 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)

这次,控制台打印了 button,这就是咱们想要的。事实上,程序要扭转按钮的文本,所以它须要this 来指代 button 元素:

const timer = setInterval(() => {console.log(this)
 // the button's text displays the timer value
  this.textContent = counter
}, 1000)

箭头函数没有本人的 this 上下文。它们从父级继承 this 的值,正是因为这个特点,在下面这种状况下就是很好的抉择。

不失常工作的状况

箭头函数并不只是在 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 has NaN likes',调用.addLike() 办法返回 'Thank you for liking undefined, which now has NaN likes'。因而,this.titlethis.likes未能别离援用对象的属性 titlelikes

这次,问题出在箭头函数的词法作用域上。对象办法中的 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: () => {console.log(this.message);
  }
})
// undefined

箭头函数没有 arguments 对象

有时,你可能须要创立一个具备有限参数个数的函数。比方,假如你想创立一个函数,列出你最喜爱的奈飞剧集,并依照偏好排序。然而,你还不晓得你要包含多少个剧集。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 函数。记住,只有当箭形函数是正确的工具时,能力应用它。

以上就是本文的所有内容,如果对你有所帮忙,欢送点赞珍藏转发~

正文完
 0