ES6 带来的重大特性 – JavaScript 完全手册(2018版)

10年服务1亿前端开发工程师

注:本文为 《 JavaScript 完全手册(2018版) 》第2节,你可以查看该手册的完整目录。

ECMAScript 2015,也称为 ES6 或者 ES2015,是 ECMAScript 标准的基础版本。

在标准版本 ECMAScript 5.1 发布4年后发布,还有一个重要的变化是 ECMAScript 标准版本的命名从版本号命名改为了根据发布年号命名。

所以它不应该被命名为 ES6(尽管每个人都这样称呼它),更加准确的命名应该是 ES2015 。

从1999年到2009年,ES5 整整制定了 10 年,因此它也是该语言的基础和非常重要的修订版。 但现在已经过了很多时间,因此讨论 ES5 之前代码的工作原理并不值得。

由于 ES5.1 和 ES6 之间已经过了很长时间,因此 ES6 带来了很多重大的变化,一些重要的新功能以及在开发 JavaScript 程序时建议的最佳实践。 要了解 ES2015 的基本功能,请记住,在此版本中,规范文档从 250 页到 600 页,很长很长。

ES2015 最重要的变化包括:

  • Arrow functions(箭头函数)
  • Promises
  • Generators
  • let 和 const
  • Classes(类)
  • Modules(模块)
  • Multiline strings(多行字符串)
  • Template literals(模板字面量)
  • Default parameters(默认参数)
  • The spread operator(展开操作符)
  • Destructuring assignments(解构分配)
  • Enhanced object literals(增强的对象字面量)
  • for..of 循环
  • Map 和 Set

Arrow functions(箭头函数)

箭头函数改变了大多数 JavaScript 代码的外观(和工作方式)。 在视觉上,我们来看一下这种简单而受欢迎的变化:

const foo = function foo() {
  //...
}

变化为:

const foo = () => {
  //...
}

如果函数体内只有一行代码,只需:

const foo = () => doSomething()

此外,如果您只有一个参数,您可以写:

const foo = param => doSomething(param)

这不是一个破坏性的改变,因为常规函数可以继续像以前一样正常工作。

新的 this 作用域

箭头函数的 this 作用域继承自上下文。

对于常规函数,这总是指最近的函数,而使用箭头函数时,这个问题就不存在了,你不需要再次写 var that = this

如果你还没很多好的理解箭头函数中的 this 作用域,可以查看: ES2015 中的箭头函数和词法 this 以了解更多

Promises

Promise 允许我们消除 “回调地狱” ,虽然它们引入了更多的复杂性( ES2017 已经带来了 async,用更高级别的概念解决了复杂性问题)。

在 ES2015 之前,JavaScript 开发人员已经使用了 Promise ,它有许多不同的库实现(例如,jQuery,q,deferred.js,vow …)。 该标准为这些差异创造了共同点。

通过使用 promises,您可以重写以下代码:

setTimeout(function() {
  console.log('I promised to run after 1s')
  setTimeout(function() {
    console.log('I promised to run after 2s')
  }, 1000)
}, 1000)

使用 promises 重写为:

const wait = () => new Promise((resolve, reject) => {
  setTimeout(resolve, 1000)
})
wait().then(() => {
  console.log('I promised to run after 1s')
  return wait()
})
.then(() => console.log('I promised to run after 2s'))

更多关于 Promise 的信息,请查看 ES6 Promise 指南

Generator(生成器)函数

Generator(生成器)是一种特殊的函数,它能够暂停自身,并在稍后恢复,允许其他代码同时运行。

代码决定它必须等待,因此它允许其他代码“在队列中”运行,并保持 “当它正在等待的东西” 完成时恢复其操作的权利。

所有这些都是通过一个简单的关键字完成的:yield 。 当生成器包含该关键字时,将暂停执行。

生成器可以包含许多 yield 关键字,因此会多次自行停止,并且由 *function 关键字标识,不要与其他编程语言(如C,C ++或Go)中使用的指针解引用运算符混淆。

生成器在JavaScript中开启了全新的编程范例,允许:

  • Generator(生成器)运行时的双向通信
  • 持久的while循环,不会冻结你的程序

这是一个生成器的例子,它解释了生成器是如何工作的。

function *calculator(input) {
    var doubleThat = 2 * (yield (input / 2))
    var another = yield (doubleThat)
    return (input * doubleThat * another)
}

我们用下面的代码初始化这个 Generator(生成器)函数

const calc = calculator(10)

然后我们在我们的生成器上启动迭代器:

calc.next()

第一次迭代启动迭代器。代码返回 this 对象:

{
  done: false
  value: 5
}

会发生什么呢?代码运行函数,input = 10 传递给生成器构造函数。 它一直运行直到达到 yield,并返回 yield 的内容:input / 2 = 5。所以我们得到了一个值 5,并指示迭代没有完成(函数只是暂停)。

在第二次迭代中,我们传递值 7

calc.next(7)

我们得到的结果是:

{
  done: false
  value: 14
}

第二个 next 方法带有参数 7,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量 doubleThat 所在的表达式接收。

重要提示:您可能认为 input / 2 是参数,但这只是第一次迭代的返回值。我们现在跳过它,并使用新的输入值 7 ,并将其乘以 2

然后我们达到第二个 yield ,然后返回 doubleThat ,因此返回值为 14

在下一个和最后一个迭代中,我们传入 100

calc.next(100)

我们得到的结果是:

{
  done: true
  value: 14000
}

当迭代完成时(没有找到更多的 yield 关键字),我们只返回(input * doubleThat * another),其数量为 10 * 14 * 100

如果你还是没理解,可以看看 用最简单的方式理解 JavaScript 中的 Symbols,Iterators(迭代器),Generators(生成器),Async/Await(异步/等待) 和 Async Iterators(异步迭代器) 这篇文章了解更多详情

let 和 const

var 是传统上的函数作用域。

let 是一个新的变量声明方式,它是块作用域。

for 循环中、 if 中或普通块中声明 let 变量不会让该变量 “逃出” 该块,而 var 变量的作用域是函数定义中。

const 就像 let,但是他是一个固定不变的值。

在 JavaScript 向前发展中,你将很少,甚至不会看到 var 声明,只需要 letconst

特别是 const,也许令人惊讶的是,它现在被广泛使用,其中不可变性非常受欢迎。

更多信息请查看:ES6 中的块级作用域及变量声明 let

Classes(类)

传统上,JavaScript 是唯一一个基于原型继承的主流语言。 从基于类的语言切换到 JavaScript 的程序员,发现 JavaScript 很令人费解,但是 ES2015 引入了 Classes(类),这些类只是 JavaScript 内部工作的语法糖,但改变了我们构建 JavaScript 程序的方式。

现在,继承非常简单,类似于其他面向对象的编程语言:

class Person {
  constructor(name) {
    this.name = name
  }
  hello() {
    return 'Hello, I am ' + this.name + '.'
  }
}
class Actor extends Person {
  hello() {
    return super.hello() + ' I am an actor.'
  }
}
var tomCruise = new Actor('Tom Cruise')
tomCruise.hello()

以上代码将打印 : “Hello, I am Tom Cruise. I am an actor.”

Classes(类) 没有显式的类变量声明,但必须在 构造函数(constructor) 中初始化所有变量。

构造函数(constructor)

Classes(类) 有一个叫做 constructor 的特殊方法,当通过 new 初始化一个 class(类) 时会调用它。

超类(super)

可以使用 super() 引用父类。

getters 和 setters

可以将属性的 getter 声明为

class Person {
  get fullName() {
    return `${this.firstName} ${this.lastName}`
  }
}

setter 以相同的方式编写:

class Person {
  set age(years) {
    this.theAge = years
  }
}

关于 ES6 Classes(类) 的更多信息请查看 面向对象的 JavaScript – 深入了解 ES6 类

Modules(模块)

在 ES2015 之前,至少有三个主要的模块标准竞争,这些标准使社区分散:

  • AMD
  • RequireJS
  • CommonJS

ES2015 将这些标准化为通用格式。

import modules(导入模块)

导入模块是通过 import ... from ... 结构完成的:

import * from 'mymodule'
import React from 'react'
import { React, Component } from 'react'
import React as MyLibrary from 'react'

export modules(导出模块)

您可以编写模块,并使用 export 关键字将任何内容导出给其他模块使用:

export var foo = 2
export function bar() { /* ... */ }

你可以查看 JavaScript 模块简史ECMAScript 6 Modules(模块)系统及语法详解 来完整了解 JavaScript 模块化进程和 ES6 Modules(模块)的更多信息。

Template Literals(模板字面量)

Template Literals(模板字面量)是创建字符串的新语法:

const aString = `A string`

它提供了一种将表达式嵌入到字符串中的方法,通过使用 ${a_variable} 语法有效地进行插值:

const var = 'test'
const string = `something ${var}` //something test

您还可以执行更复杂的表达式:

const string = `something ${1 + 2 + 3}`
const string2 = `something ${foo() ? 'x' : 'y' }`

并且字符串可以跨越多行:

const string3 = `Hey
this
string
is awesome!`

比较一下,我们在 ES2015 之前 实现多行字符串的方式:

var str = 'One\n' +
'Two\n' +
'Three'

Default parameters(默认参数)

函数现在支持默认参数:

const foo = function(index = 0, testing = true) { /* ... */ }
foo()

了解更多关于默认参数的信息,请查看 JavaScript 函数中默认参数

spread operator(展开操作符)

您可以使用展开操作符 ... 展开数组,对象或字符串。

让我们从一个数组示例开始。查看以下内容:

const a = [1, 2, 3]

您可以使用创建一个新数组

const b = [...a, 4, 5, 6]

您还可以使用展开操作符创建一个数组的副本

const c = [...a]

spread operator(展开操作符)也适用于对象。用以下方法克隆对象:

const newObj = { ...oldObj }

使用字符串,spread operator(展开操作符)创建一个数组,其中包含字符串中的每个字符:

const hey = 'hey'
const arrayized = [...hey] // ['h', 'e', 'y']

这个运算符有一些非常有用的应用。最重要的是能够以非常简单的方式使用数组作为函数参数:

const f = (foo, bar) => {}
const a = [1, 2]
f(...a)

在过去,你可以使用 f.apply(null, a)来做到这一点,但可读性不是很好。

spread operator(展开操作符)对于函数参数的应用还有另一种叫法:Rest Parameters(剩余形参),你可以查看 JavaScript 函数中的参数:Parameters(形参) 和 Arguments(实参) 了解更多的说明。

Destructuring assignments(解构分配,解构赋值)

给定一个对象,您可以只提取一些值并将它们放入命名变量中:

const person = {
  firstName: 'Tom',
  lastName: 'Cruise',
  actor: true,
  age: 54, //made up
}
const {firstName: name, age} = person

console.log(name) // "Tom"
console.log(age)  // 54

nameage 包含了所需的值。

语法也适用于数组:

const a = [1,2,3,4,5]
let [first, second, , , fifth] = a;

console.log(first)   // 1
console.log(second)  // 2
console.log(fifth)   // 5

Enhanced object literals(增强的对象字面量)

在 ES2015 中,对象字面量获得了增强。

包含变量的更简单的语法

你不再需要这么做:

const something = 'y'
const x = {
  something: something
}

你只需:

const something = 'y'
const x = {
  something
}

Prototype(原型)

可以使用指定原型

const anObject = { y: 'y' }
const x = {
  __proto__: anObject
}

super()

const anObject = { y: 'y', test: () => 'zoo' }
const x = {
  __proto__: anObject,
  test() {
    return super.test() + 'x'
  }
}
x.test() //zoox

动态属性

const x = {
  ['a' + '_' + 'b']: 'z'
}
x.a_b //z

for..of 循环

ES5 早在 2009 年就推出了 forEach() 循环。虽然很好,但它们没有办法中断循环(比如使用 break 语句或使用 return 语句),就像 for 循环一样。

ES2015 引入了 for..of 循环,它结合了 forEach 的简洁性和中断循环的能力:

//iterate over the value
for (const v of ['a', 'b', 'c']) {
  console.log(v);
}
//get the index as well, using `entries()`
for (const [i, v] of ['a', 'b', 'c'].entries()) {
  console.log(i, v);
}

了解 JavaScript 中循环方法的比较,请查看 JavaScript里的循环方法:forEach,for…in,for…of 。关于 for..of 循环的更多解释和应用,请查看 理解 JavaScript 中的 for…of 循环

Map 和 Set

Map 和 Set(以及它们各自的垃圾收集 WeakMap 和 WeakSet )是两个非常流行的数据结构的官方实现(在本手册后续的章节中会着重介绍)。

推荐阅读最新关于 ECMAScript 2015 的文章

总之,ES6 一个重大的 ECMAScript 版本,增加了很多新特性,本手册基本涵盖了我们常用的特性,但是还不够深入,查看以下文章,更加深入的了解 ES6 的新特性。部分链接已经在上面的内容中推荐,你可以选择性的查看

赞(15) 打赏
未经允许不得转载:WEB前端开发 » ES6 带来的重大特性 – JavaScript 完全手册(2018版)

评论 1

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #-49

    沙发

    Ysom2个月前 (11-06)回复

前端开发相关广告投放 更专业 更精准

联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏