见见

通用 ES6 特性简介

见见 · 2017-06-04翻译 · 755阅读 原文链接

JavaScript 近年来一直在进步。如果你在 2017 年学习 JavaScript,并且还没有接触 ES6,那么你会错过更简单的阅读和编写 JavaScript 的方法。

不用担心你还不是 JavaScrip 大师。你不需要在 JavaScript 上特别厉害就可以更好的利用 ES6 给你带来的附加值。在本文中,我想与大家分享一下八个 ES6 特性,我作为开发者每天都会使用到的,来帮助你轻松融入新的语法。

ES6 特性列表

首先,ES6 是对 JavaScript 的巨大更新。如果你对新功能感到好奇,这里是大特性列表,感谢Luke Hoban

  • Arrows

  • Classes

  • Enhanced object literals

  • Template strings

  • Destructuring

  • Default + rest + spread

  • Let + const

  • Iterators + for..of

  • Generators

  • Unicode

  • Modules

  • Module loaders

  • Map + set + weakmap + weakset

  • Proxies

  • Symbols

  • Subclassable built-ins

  • Promises

  • Math + number + string + array + object apis

  • Binary and octal literals

  • Reflect api

  • Tail calls

不要让这么长的特性列表给吓到。你不需要马上知道一切。我将和你分享我每天使用的这些特性中的八个,它们是:

  1. Let and const

  2. Arrow functions

  3. Default parameters

  4. Destructuring

  5. Rest parameter and spread operator

  6. Enhanced object literals

  7. Template literals

  8. Promises

以下部分将介绍这八个特性。现在,我将介绍前五个特性。在接下来的几周里,我会补充一下。

顺便说一下,浏览器对于 ES6 的支持是惊人的。如果你的浏览器是最新版本(Edge,最新版 FF,Chrome 和 Safari),那么几乎所有都本地支持

如果你想写 ES6 而不想使用如 Webpack 那样的工具。如果在浏览器不支持的情况下,你可以依靠 polyfills 创建的社区。只需要搜索它们 :)

这样,让我们进入第一个特性

Let 和 const

在 ES5(旧的 JavaScript)中,我们习惯用 var 关键字声明变量。在 ES6 中,var 关键字可以被 letconst 替代,两个强大的关键字使开发更简单。

我们先看看 letvar 之间的区别,以了解为什么 letconst 更好。

Let vs var

让我们先来谈谈熟悉的 var

首先,我们可以使用 var 关键字声明变量。一旦声明,该变量可以在当前作用域的任意位置使用。

var me = 'Zell'
console.log(me) // Zell

在上面的例子中,我已经将 me 声明为全局变量。该全局变量 me 也可以在一个函数中使用,像这样:

var me = 'Zell'
function sayMe () {
  console.log(me)
}

sayMe() // Zell

然而,反之亦然。如果我在函数中声明一个变量,我不能在函数之外使用它。

function sayMe() {
  var me = 'Zell'
  console.log(me)
}

sayMe() // Zell
console.log(me) // Uncaught ReferenceError: me is not defined

所以我们可以说 var函数作用域的。这意味着每当在函数中使用 var 创建变量时, 它将仅存在函数中

如果变量在函数外部创建,它将存在于外部作用域中。

var me = 'Zell' // global scope

function sayMe () {
  var me = 'Sleepy head' // local scope
  console.log(me)
}

sayMe() // Sleepy head
console.log(me) // Zell

另一方面,let块作用域。 这意味着每当使用 let 创建变量时,它将仅存在其块中。

等等,什么是块(block)?

JavaScript 中的一个块是一对花括号内的任何东西。以下是块的示例。

{
  // new scope block
}

if (true) {
  // new scope block
}

while (true) {
  // new scope block
}

function () {
  // new block scope
}

块作用域和函数作用域之间的差异是巨大的。当你使用函数作用域的变量时,你可能会意外的覆盖变量,以下是一个例子:

var me = 'Zell'

if (true) {
  var me = 'Sleepy head'
}

console.log(me) // 'Sleepy head'

在这个例子中,你可以看到,me 变成了 Sleepy head 在执行 if 块后。这个例子可能不会导致任何问题,因为可能不会声明具有相同名称的变量。

但是在 for 循环的情况下使用 var 的任何人都可能会因为变量的范围而变得有些奇怪。考虑以下代码,将变量 i 记录四次,然后使用 setTimeout 函数记录 i

for (var i = 1; i < 5; i++) {
  console.log(i)
  setTimeout(function () {
    console.log(i)
  }, 1000)
};

你期望这段代码能做什么?这是实际发生的事情

i was logged as 5 four times in the timeout function

itimeout 函数中被记录四次 5

为何会在 timeout 函数中 i 变成四次 5 呢?恩,事实证明,因为 var 是函数作用域,即使在 timeout 函数运行之前,i 的值也变成了 4

为了在 setTimeout 中得到正确的 i 值,我们需要创建另一个函数 logLater,并在稍后执行,来确保在 setTimeout 执行之前,i 值不会被 for 循环所改变:

function logLater (i) {
  setTimeout(function () {
    console.log(i)
  })
}

for (var i = 1; i < 5; i++) {
  console.log(i)
  logLater(i)
};

i was correctly logged as 1, 2 3 and 4

i 被正确记录为 1, 2 3 和 4

(顺便说一下,这被称为闭包)。

好消息是,函数作用域很奇怪,像 for 循环示例,它并不会在 let 中发生。我们之前写的相同的 timeout 函数实例可以这样写,它会正常工作,而不需要编写额外的功能:

for (let i = 1; i < 5; i++) {
  console.log(i)
  setTimeout(function () {
    console.log(i)
  }, 1000)
};

i was correctly logged as 1, 2 3 and 4

i 被正确记录为 1, 2 3 和 4

正如你所看到的,块作用域变量通过使用函数作用域变量删除常见的错误使开发变得更简单。为了使生活变得更简单,我建议你从现在开始声明 JavaScript 变量时,使用 let 而不是 var(ES6 已经是新的 JavaScript 😎)。

现在我们知道 let 做了什么,让我们继续谈谈 letconst 之间的区别。

Let vs const

letconst 一样是阻塞作用域。不同之处在于 const 声明之后无法重新分配。

const name = 'Zell'
name = 'Sleepy head' // TypeError: Assignment to constant variable.

let name1 = 'Zell'
name1 = 'Sleepy head'
console.log(name1) // 'Sleepy head'

由于 const 不能被重新分配,它们对变量不会改变

假设在我们的网站上有一个按钮用于启动一个模态。我知道只有一个按钮,它不会改变。在这种情况下,我们可以使用 const

const modalLauncher = document.querySelector('.jsModalLauncher')

当声明变量时,只要有可能,我总是喜欢 const 超过 let, 因为我收到额外的提示,变量不会被重新分配。然而,在其它情况下,我使用 let

接下来,我们继续谈谈箭头函数。

Arrow Functions

箭头函数由你在 ES6 代码中看到的宽箭头 (=>) 表示。这是一个简写的匿名函数。它们可以在使用 function 关键字的任何地方使用。例如:

let array = [1,7,98,5,4,2]

// ES5 way
var moreThan20 = array.filter(function (num) {
  return num > 20
})

// ES6 way
let moreThan20 = array.filter(num => num > 20)

箭头功能非常酷。它们有助于缩短代码,从而减少错误隐藏的空间。它们还可以帮助你编写代码,这更容易理解,一旦你习惯了语法。

让我们深入了解箭头功能的细节,以便你学会识别和使用它们。

箭头功能的本质

首先,让我们来谈谈创建功能。在 JavaScript 中,你可能习惯通过以下方式创建函数:

function namedFunction() {
  // Do something
}

// using the function
namedFunction()

有第二种创建函数的方法。你可以创建一个匿名函数并将其分配给一个变量。要创建一个匿名函数,我们将其名称从函数声明中删除。

var namedFunction = function() {
  // Do something
}

创建函数的第三种方法是直接创建它们作为另一个函数或方法的参数。第三个用例是匿名函数最常用的例子。以下是一个例子:

// Using an anonymous function in a callback
button.addEventListener('click', function() {
  // Do something
})

由于 ES6 箭头函数是匿名函数的简称,你可以用箭头函数创建一个匿名函数。

看起来如下:

// Normal Function
const namedFunction = function (arg1, arg2) { /* do your stuff */}

// Arrow Function
const namedFunction2 = (arg1, arg2) => {/* do your stuff */}

// Normal function in a callback
button.addEventListener('click', function () {
  // Do something
})

// Arrow function in a callback
button.addEventListener('click', () => {
  // Do something
})

看到这里的相似了么?基本上,你删除了 function 关键字,并在稍微不同的位置用 => 替换它。

但是箭头函数有什么不同呢?我们不只是用 => 替代 function

那么事实证明,我们不只是用 => 替代 function。箭头函数的语法可以根据两个因素而改变:

  1. 所需参数数量

  2. 是否需要一个隐式返回

第一因素是提供给箭头函数的参数数量。如果你只提供一个参数,可以删除参数周围的括号。如果不需要参数,可以用括号(())来替换下划线(_)。

以下均是有效的箭头函数。

const zeroArgs = () => {/* do something */}
const zeroWithUnderscore = _ => {/* do something */}
const oneArg = arg1 => {/* do something */}
const oneArgWithParenthesis = (arg1) => {/* do something */}
const manyArgs = (arg1, arg2) => {/* do something */}

箭头函数的第二因素是你是否想要一个隐式返回。如果代码只占用一行,并且不包含在块中,则默认情况下, 箭头函数会自动创建一个 return关键字。

所以,这两个是等效的:

const sum1 = (num1, num2) => num1 + num2
const sum2 = (num1, num2) => { return num1 + num2 }

这两个因素是你可以编写较短代码的原因,如上所述的 moreThan20

let array = [1,7,98,5,4,2]

// ES5 way
var moreThan20 = array.filter(function (num) {
  return num > 20
})

// ES6 way
let moreThan20 = array.filter(num => num > 20)

总而言之,箭头函数非常酷。他们花点时间习惯,所以试试看,你会很快在任何地方使用它。

但是,在你跳转到 FTW 频道的箭头函数之前,我想让你了解 ES6 箭头函数的另一个特征,导致很多混乱 - this 词汇。

The lexical this

this 是一个唯一的关键字,它的值根据它的调用方式而改变。当它被称为在任何函数之外this 默认为浏览器中的 Window 对象。

console.log(this) // Window

This defaults to window object in browsers

在浏览器中默认为 window 对象。

this 在一个简单的函数调用中被调用时,this 被设置为全局对象。在浏览器的情况下,this 始终是 Window 对象。

function hello () {
  console.log(this)
}

hello() // Window

JavaScript 在一个简单的函数调用中总是将 this 设置为 window 对象。这就解释了为什么像 setTimeout 这样的函数中的 this 值总是Window

this对象方法中被调用时,this 将是对象本身:

let o = {
  sayThis: function() {
    console.log(this)
  }
}

o.sayThis() // o

This refers to the object when the function is called in an object method.

当在对象方法中调用函数时,这是对象。

当函数被调用为构造函数时,this 指的是新建的对象

function Person (age) {
  this.age = age
}

let greg = new Person(22)
let thomas = new Person(24)

console.log(greg) // this.age = 22
console.log(thomas) // this.age = 24

This refers to the constructed object called with the new keyword or Object.create().

这是指使用 new 关键字或 Object.create() 调用的构造对象。

当在事件监听器中使用,this 设置为触发事件的元素。

let button = document.querySelector('button')

button.addEventListener('click', function() {
  console.log(this) // button
})

正如你在上述情况中可以看到,this 的值由调用它的函数设置。每个函数定义它自己的 this 值。

宽箭头函数中,this 永远不会被绑定到一个新的值,无论函数怎么调用。this 将永远是与它的周围代码相同的 this 值。(顺便说一下,我猜这个词汇的意思是词汇 this 的名字)。

好了,这听起来很混乱,所有我们来看几个真实的例子。

首先,你不要使用箭头函数声明对象方法,因为你不能再用 this 引用对象了。

let o = {
  // Don't do this
  notThis: () => {
    console.log(this) // Window
    this.objectThis() // Uncaught TypeError: this.objectThis is not a function
  },
  // Do this
  objectThis: function () {
    console.log(this) // o
  }
  // Or this, which is a new shorthand
  objectThis2 () {
    console.log(this) // o
  }
}

其次,你可能不想使用箭头函数来创建事件监听器,因为 this 不再绑定到附加到事件监听器的元素。

然而,你可以随时使用 event.currentTarget 获得正确的 this 上下文。这就是为什么我说不可能

button.addEventListener('click', function () {
  console.log(this) // button
})

button.addEventListener('click', e => {
  console.log(this) // Window
  console.log(event.currentTarget) // button
})

第三, 你可能要在这个地方使用词汇 this绑定更改而不需要它。一个例子是 timeout 函数,所以你永远不必处理 thisthatself

let o = {
  // Old way
  oldDoSthAfterThree: function () {
    let that = this
    setTimeout(function () {
      console.log(this) // Window
      console.log(that) // o
    })
  },
  // Arrow function way
  doSthAfterThree: function () {
    setTimeout(() => {
      console.log(this) // o
    }, 3000)
  }
}

如果你需要在一段时间后添加或删除类,此用例特别有用:

let o = {
  button: document.querySelector('button')
  endAnimation: function () {
    this.button.classList.add('is-closing')
    setTimeout(() => {
      this.button.classList.remove('is-closing')
      this.button.classList.remove('is-open')
    }, 3000)
  }
}

最后,在其他地方随便使用宽箭头函数来帮助你的代码变得更加简洁,就像我们上面的 moreThan20一样:

let array = [1,7,98,5,4,2]
let moreThan20 = array.filter(num => num > 20)

让我们继续。

默认参数

ES6 中的默认参数…好吧,当我们定义函数时,给我们一个指定默认参数的方法。我们来看一个例子,你会看到它有多大的帮助。

假设我们正在创建一个从团队中宣布一个球员的名字。如果你在 ES5 中写这个函数,它将类似于以下内容:

function announcePlayer (firstName, lastName, teamName) {
  console.log(firstName + ' ' + lastName + ', ' + teamName)
}

announcePlayer('Stephen', 'Curry', 'Golden State Warriors')
// Stephen Curry, Golden State Warriors

乍一看,这段代码看起来不错。但是如果我们不得不宣布一个与任何球队无关的球员呢?

如果我们没有 teamName,目前的代码有点尴尬:

announcePlayer('Zell', 'Liew')
// Zell Liew, undefined

我很确定 undefined 不是一个团队 😉。

如果玩家未关联,那么宣布 Zell Liew, unaffiliated 会更有意义。而不是 Zell Liew, undefined。你同意么?

要让 announcePlayer 宣布 Zell Liew, unaffiliated,我们的一种方式是将 unaffiliated 字符串传递给 teamName

announcePlayer('Zell', 'Liew', 'unaffiliated')
// Zell Liew, unaffiliated

虽然这样做,我们可以通过将 unaffiliated 重构为 announcePlayer 来检查 teamName 是否定义,可以做的更好。

在 ES5 版本中,你可以将代码重构如下:

function announcePlayer (firstName, lastName, teamName) {
  if (!teamName) {
    teamName = 'unaffiliated'
  }
  console.log(firstName + ' ' + lastName + ', ' + teamName)
}

announcePlayer('Zell', 'Liew')
// Zell Liew, unaffiliated

announcePlayer('Stephen', 'Curry', 'Golden State Warriors')
// Stephen Curry, Golden State Warriors

或者,如果你使用三元表达式, 你可以选择更简单的版本:

function announcePlayer (firstName, lastName, teamName) {
  var team = teamName ? teamName : 'unaffiliated'
  console.log(firstName + ' ' + lastName + ', ' + team)
}

在 ES6 中,使用默认参数,我们可以在定义参数时添加等号 (=) 。如果我们这样做,当参数未定义时,ES6 会自动默认为该值。

所以,在下面的代码中,当 teamName 未定义时,它默认为 unaffiliated

const announcePlayer = (firstName, lastName, teamName = 'unaffiliated') => {
  console.log(firstName + ' ' + lastName + ', ' + teamName)
}

announcePlayer('Zell', 'Liew')
// Zell Liew, unaffiliated

announcePlayer('Stephen', 'Curry', 'Golden State Warriors')
// Stephen Curry, Golden State Warriors

很酷,不是么? :)

还有一件事,如果要调用默认值,你可以手动传递 undefined。当你的默认参数不是函数的最后一个参数时,此手册传递的 undefined 对你有帮助。

announcePlayer('Zell', 'Liew', undefined)
// Zell Liew, unaffiliated

这就是你需要了解的默认参数。这很简单,非常有用 :)

Destructuring

解构(Destructuring)是一种从数组和对象中获取值的便捷方式。在解构数组和对象之间存在细微差别,所以我们分开讨论它们。

解构对象

假设你有以下对象:

const Zell = {
  firstName: 'Zell',
  lastName: 'Liew'
}

要从 Zell 获取 firstNamelastName ,你需要创建两个变量,然后将每个变量分配一个值,如下所示:

let firstName = Zell.firstName // Zell
let lastName = Zell.lastName // Liew

通过解构,你可以使用单行代码和分配这些变量。以下是你拆除对象的方式:

let { firstName, lastName } = Zell

console.log(firstName) // Zell
console.log(lastName) // Liew

看看这里发生了什么?通过在声明变量时添加大括号 ({}),我们告诉 JavaScript 创建上述变量,然后将 Zell.firstName 分配给 firstNameZell.lastNamelastName

这是什么发生的事情:

// What you write
let { firstName, lastName } = Zell

// ES6 does this automatically
let firstName = Zell.firstName
let lastName = Zell.lastName

现在,如果已经使用了变量名,我们不能再次声明变量(特别是如果你使用letconst)。

以下操作失败:

let name = 'Zell Liew'
let course = {
  name: 'JS Fundamentals for Frontend Developers'
  // ... other properties
}

let { name } = course // Uncaught SyntaxError: Identifier 'name' has already been declared

如果遇到上述情况,你可以重命名变量,同时使用冒号 (:) 进行解构。

在下面的这个例子中,我们创建一个 courseName 变量并将 course.name 赋予它。

let { name: courseName } = course

console.log(courseName) // JS Fundamentals for Frontend Developers

// What ES6 does under the hood:
let courseName = course.name

还有一件事。

如果你尝试重构不包含在对象中的变量,那就别担心。它将只返回 undefined

let course = {
  name: 'JS Fundamentals for Frontend Developers'
}

let { package } = course

console.log(package) // undefined

但等等,这不是全部。记住 默认参数

你也可以为解构变量编写默认参数。语法与定义函数时的语法相同。

let course = {
  name: 'JS Fundamentals for Frontend Developers'
}

let { package = 'full course' } = course

console.log(package) // full course

你甚至可以重命名变量,同时提供默认值。只有结合这两个,一开始看起来有点滑稽,但是如果你经常使用它,你会习惯的。

在下面的这个例子中,我将 course.name 分配给 packageName

let course = {
  name: 'JS Fundamentals for Frontend Developers'
}

let { package: packageName = 'full course' } = course

console.log(packageName) // full course

那就是解构对象。我们继续谈解构数组 😄。

解构数组

解构数组和解构对象是类似的,我们使用方括号 ([]) 而不是大括号 ({})。

当你重构数组时:

  • 你的第一个变量是数组中的第一个项

  • 你的第二个变量是数组中的第二个项

  • 等等…

let [one, two] = [1, 2, 3, 4, 5]
console.log(one) // 1
console.log(two) // 2

它可能解构很多变量,会超过给定数组中的项目数。当这种情况发生时,额外的解构变量会 undefined

let [one, two, three] = [1, 2]
console.log(one) // 1
console.log(two) // 2
console.log(three) // undefined

当解构数组时,我们通常只会重构我们需要的变量。如果需要数组的其余部分,可以使用 rest 运算符(...),如下所示:

let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third, ...rest] = scores

console.log(first) // 98
console.log(second) // 95
console.log(third) // 93
console.log(rest) // [90, 87, 85]

我们将在以下部分中讨论下节中的 rest 运算符。但是现在,我们来谈谈一个独特的能力,你可以使用解构数组 - 交换变量。

交换具有解构数组的变量

假设你有两个变量ab

let a = 2
let b = 3

你想交换这些变量。所以 a = 3b = 2。在 ES5 中,你需要使用临时第三个变量来完成交换:

let a = 2
let b = 3
let temp

// swapping
temp = a // temp is now 2
a = b // a is now 3
b = temp // b is now 2

尽管如此,逻辑可以模糊和混乱,特别是引入第三个变量。

现在看看你将如何使用解构数组的 ES6 方法:

let a = 2
let b = 3; // semicolon required because next line begins with a square bracket

// Swapping with destructured arrays
[a, b] = [b, a]

console.log(a) // 3
console.log(b) // 2

💥💥💥。这比以前交换变量的方法要简单多了! :)

接下来,我们来讨论一个函数中的数组和对象的解构。

声明函数时解构数组和对象

关于解构的最酷的事情是你可以在任何地方使用它们。甚至可以在函数中重构对象和数组。

假设我们有一个函数可以获取一组分数,并返回一个具有前三个分数的对象。这个功能类似于我们在解构数组时所做的。

// Note: You don't need arrow functions to use any other ES6 features
function topThree (scores) {
  let [first, second, third] = scores
  return {
    first: first,
    second: second,
    third: third
  }
}

编写此函数的另一种方法是在声明函数时重构 scores 。在这种情况下,写一行代码要少一些。同时,我们知道我们正在采取一个阵列。

function topThree ([first, second, third]) {
  return {
    first: first,
    second: second,
    third: third
  }
}

很酷,不是么? 😄。

现在,这是一个快速的小测试。既然我们可以在声明函数的时候结合默认参数和解构,那么下面说的是什么呢?

function sayMyName ({
  firstName = 'Zell',
  lastName = 'Liew'
} = {}) {
 console.log(firstName + ' ' + lastName)
}

这是一个棘手的问题。我们将几个功能结合在一起。

首先,我们可以看到这个函数接收一个参数,一个对象。这个对象是可选的,未定义时默认{}

第二,我们尝试从给定对象中重构 firstNamelastName 变量。如果找到这些属性,请使用它们。

最后,如果在给定对象中 firstNamelastName 未定义,那么我们分别将他们设置为 ZellLiew

因此,此功能产生以下结果:

sayMyName() // Zell Liew
sayMyName({firstName: 'Zell'}) // Zell Liew
sayMyName({firstName: 'Vincy', lastName: 'Zhang'}) // Vincy Zhang

在函数声明中组合解构和默认参数很酷好吗?😄。我喜欢这个。

接下来,让我们来看看 restspread

rest 参数和 spread 运算符

rest 参数和 spread 操作符看起来一样。它们都用三个点(...) 表示。

我们所做的是不同的,取决于它们的用途。这就是为什么它们的命名不同。所以,我们分别来看看 rest 参数和 spread 运算符。

rest 参数

大致解释 rest 参数 意味着把剩下的东西包装成一个数组。它将一个逗号分隔的参数列表转换成一个数组

让我们来看看在行动中的 rest 参数。想象一下,我们有一个函数 add,它总结了其下参数:

sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 55

在 ES5 中,我们依赖与 arguments 变量,只要我们不得不处理一个占用未知数量的变量的函数。这个 arguments 变量是一个类数组 Symbol

function sum () {
  console.log(arguments)
}

sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Arguments is a Symbol, not an array

参数是一个 Symbol 对象,而不是数组

计算这个参数总和的一种方法是将其转换成具有 Array.prototype.slice.call(arguments) 的数组,然后用数组方法循环遍历每个数字,如 forEachreduce

我相信你可以自己实现 forEach ,所以这里是 reduce 的例子:

// ES5 way
function sum () {
  let argsArray = Array.prototype.slice.call(arguments)
  return argsArray.reduce(function(sum, current) {
    return sum + current
  }, 0)
}

使用 ES6 rest 参数,我们可以将所有逗号分隔的参数直接打包到数组中

// ES6 way
const sum = (...args) => args.reduce((sum, current) => sum + current, 0)

// ES6 way if we didn't shortcut it with so many arrow functions
function sum (...args) {
  return args.reduce((sum, current) => sum + current, 0)
}

很清晰? 🙂。

现在,我们在解构 中简要地遇到了 rest 参数。在那里,我们尝试将一系列分数重新排序列成前三名:

let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third] = scores

console.log(first) // 98
console.log(second) // 95
console.log(third) // 93

如果我们想要 rest 的分数,我们可以通过将剩余的分数打包成一个数组

let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third, ...restOfScores] = scores

console.log(restOfScores) // [90, 97, 95]

如果你感到困难,只有记住这一点 - rest 参数将所有内容都包含到数组中。它出现在功能参数和解构数组中。

接下来,让我们继续看看 spread

spread 运算符

spread 运算符和 rest 参数相反。它需要一个数组,并将其(如 jam)传播成逗号分隔的参数列表

let array = ['one', 'two', 'three']

// These two are exactly the same
console.log(...array) // one two three
console.log('one', 'two', 'three') // one two three

spread 运算符通常用于更易于阅读和理解的方式帮助连接数组。

例如,你想连接以下数组:

let array1 = ['one', 'two']
let array2 = ['three', 'four']
let array3 = ['five', 'six']

连接这两个数组的 ES5 方法是使用 Array.concat。你可以使用多个 Array.concat 来连接任意数量的数组,如下所示:

// ES5 way
let combinedArray = array1.concat(array2).concat(array3)
console.log(combinedArray) // ['one', 'two', 'three', 'four', 'five', 'six']

使用 ES6 spread 运算符,你可以将数组扩展到一个新的数组,就像这样,一旦你习惯了,它将稍微更容易阅读:

// ES6 way
let combinedArray = [...array1, ...array2, ...array3]
console.log(combinedArray) // ['one', 'two', 'three', 'four', 'five', 'six']

spread 运算符也可以用于从数组中删除项,而不会使数组发生变化。Redux 中常用这种方法。我强烈建议你观看 Dan Abramov 的视频,如果你有兴趣看它如何运作的。

这就是 spread :)

增强的对象文字

自从编写 JavaScript 以后,对象应该是一件很熟悉的事情。为了防止你不了解它们,它们看起来像这样:

const anObject = {
  property1: 'value1',
  property2: 'value2',
  property3: 'value3',
}

ES6 增强的对象文字(enhanced object literals)为你认识和喜爱的对象带来了三个甜蜜的升级,它们是:

  1. 属性值的速写

  2. 方法的速写

  3. 使用计算属性名称的能力

让我们来看看它们中的每一个。我会保证很快的 :)

属性值的速写

你有没有注意到你有时候会分配与对象属性具有相同名称的变量?你知道,如下:

const fullName = 'Zell Liew'

const Zell = {
  fullName: fullName
}

那么,你不希望你能以较短的方式写这个,因为属性 (fullName) 和值 (fullName)?

这是个好消息,你可以的! :)

ES6 增强了具有属性值的速写。这意味着,如果你的变量名称与你的属性名称匹配,则只能写入该变量。ES6 照顾其它剩下的。

这是它的样子:

const fullName = 'Zell Liew'

// ES6 way
const Zell = {
  fullName
}

// Underneath the hood, ES6 does this:
const Zell = {
  fullName: fullName
}

很整洁不是么?现在我们写的更少了。

Happy, dancing star wars figure

很开心吧。:)

让我们继续看更多的速记。

方法速记

方法是与属性相关联的函数。它们只是特别命名,因为它们是功能 :)

这是一个方法的例子:

const anObject = {
  aMethod: function () { console.log("I'm a method!~~")}
}

使用 ES6,我们可以用速记编写方法。我们可以从方法声明中删除 : function ,它将像以前一样工作:

const anObject = {
  // ES6 way
  aShorthandMethod (arg1, arg2) {},

  // ES5 way
  aLonghandMethod: function (arg1, arg2) {},
}

这一升级,对象已经获得了速写方法,所以请在定义对象时请不要使用箭头函数。你会打破 this 的上下文(如果你不记得为什么,参见[箭头函数])(#arrow-functions)

const dontDoThis = {
  // Noooo. Don't do this
  arrowFunction: () => {}
}

这就是对象方法速记。我们继续进行我们得到的对象的最终版本。

使用计算属性名称的能力

又是,你创建对象时需要动态属性名称,以旧的 JavaScript 方式,你必须创建对象,然后将其属性分配给其中,如下所示:

// ES5
const newPropertyName = 'smile'

// Create an object first
const anObject = { aProperty: 'a value' }

// Then assign the property
anObject[newPropertyName] = ':D'

// Adding a slightly different property and assigning it
anObject['bigger ' + newPropertyName] = 'XD'

// Result
// {
//   aProperty: 'a value',
//   'bigger smile': 'XD'
//   smile: ':D',
// }

在 ES6 中,你不再需要这样做。你可以在创建时直接分配动态属性名,关键是放方括号扩起动态属性:

const newPropertyName = 'smile'

// ES6 way.
const anObject = {
  aProperty: 'a value',
  // Dynamic property names!
  [newPropertyName]: ':D',
  ['bigger ' + newPropertyName]: 'XD',
}

// Result
// {
//   aProperty: 'a value',
//   'bigger smile': 'XD'
//   smile: ':D',
// }

不是么? :)

这就是增强的对象文字。我没有说会很快吗?

让我们转到另一个我绝对喜欢的功能:模板字符串

模板字符串

在 JavaScript 中处理字符串是一个非常笨拙的体验。以前在 默认参数中创建了 announcePlayer 函数的时候,你已经体验过了。在那里,我们创建了空字符串的空格,并加入了它们:

function announcePlayer (firstName, lastName, teamName) {
  console.log(firstName + ' ' + lastName + ', ' + teamName)
}

在 ES6 中这个问题由于模板字符串而消失(在规范中,它们以前称为模板文字)。

要在 ES6 中创建一个模板字符串,你将使用反引号 (``)来包含字符串。在反引号中,你可以访问的占有符 (${}`) ,你可以在其中正常使用 JavaScript。

行动如下:

const firstName = 'Zell'
const lastName = 'Liew'
const teamName = 'unaffiliated'

const theString = ${firstName} ${lastName}, ${teamName}

console.log(theString)
// Zell Liew, unaffiliated

看到了么?我们可以将所有内容与模板字符串分组!在模板字符串中,正常是英文。几乎就像我们使用 模板引擎 :)

关于模板字符串的最好的部分是你将轻松写多行字符串:

const multi = One upon a time,
In a land far far away,
there lived a witich,
who could change night into day

Multi-line strings! Multi-line strings works! Woot!

一种好办法就是使用这些字符串来创建 HTML 元素,如果你需要它们。(注意,这可能不是最好的方法来制作 HTML 元素),但它仍然比一个接一个地创建它们更好)。

const container = document.createElement('div')
const aListOfItems =

Point number one
Point number two
Point number three
Point number four


container.innerHTML = aListOfItems

document.body.append(container)

[Zell Liew](https://codepen.io/zellwk)CodePen上使用[多行字符串创建更复杂的 HTML ]。

模板字符串的另一个特征是tags。tags 是允许你操作模板字符串的函数,如果要替换任何字符串。

看起来如下:

const animal = 'lamb'

// This a tag
const tagFunction = () => {
// Do something here
}

// This tagFunction allows you to manipulate the template literal.
const string = tagFunction 'Mary had a little ${animal}'

老实来说,即使模板标签看起来很酷,我还没有一个用例。如果你想了解有关模板标签的更多信息,建议你阅读 MDN 上的 文章

这就是模板字符串。

结尾

这几乎是我定期使用的所有 ES6 特性。ES6 很棒,这绝对值得你花点时间了解它们,所以你可以了解其他人在写什么。

相关文章