前言
ES6(or ECMAScript 6)规定了新一代的 JavaScript 语法,其中含有许多必要且实用的新特性。尤其是加入了一些语法糖使我们代码更简洁明了。本文所有代码都在浏览器的 JavaScript 控制台或者 Node 环境下运行过,输出结果在// result:
注释后。
Arrows
箭头函数是使用 => 语法缩写的一个函数(相当于匿名函数),左边是参数,右边可以是表达式(expression)或者声明体(statement bodies)。
=>
右边接表达式:
1 | console.log([1,2,4].map(v => v + 1)) |
=>
右边接声明体:
1 | console.log([1,2,4].forEach(v => { |
箭头函数可以和不定参数配合使用,下面代码定义了一个求和方法:
1 | const sum = (...nums) => { |
需要特别注意的是,箭头函数绑定this,这是和 function 定义是不同的:
1 | let std = { |
传统的写法我需要特地定义个_this
让它指向当前这个 std 对象,而箭头函数绑定了上下文的 this,即调用该 function 的对象 std:
1 | std = { |
Let + Const
ES6 新增了两个绑定了块作用域的声明变量的关键字 let 和 const。
let
用法和var
一样,但是限定了作用域。因为在 JavaScript 中,var
定义全局变量,所以能不用var
的地方就不用:
1 | { |
const
声明一个只读的常量,并且必须初始化。一旦声明,常量的值就不能改变:
1 | const a // Error: Missing initializer in const declaration |
阮一峰老师的ES6编程风格中明确指出在let
和const
之间,建议优先使用const
,尤其是在全局环境,不应该设置变量,只应设置常量。const优于let原因如下:
- const可以提醒阅读程序的人,这个变量不应该改变。
- const比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算。
- JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率,也就是说let和const的本质区别,其实是编译器内部的处理不同。所有的函数都应该设置为常量。
Template Strings
ES6 引入了构造字符串的语法糖:模版字符串 `…`,使用反引号表示。它有两个作用,一是表示多行字符串,二是替换为绑定的变量值。
多行字符串:
1 | console.log(`第一行 |
在模版字符串中使用${property}
替换为属性的值:
1 | const name = 'Bob', time = 'today' |
Default + Rest + Spread
参数的默认值、不定参数以及参数数组的展开。
函数的参数可以指定默认值,调用时如果未指定将使用默认值:
1 | function f(x, y = 12) { |
不定参数使用...
表示,它取代了之前的 arguments,适用于更常见的需求。如下例中 y 是一个参数数组:
1 | function f(x, ...y) { |
同时...
还可以在函数调用时将数组转换成连续的参数:
1 | function f(x, y, z) { |
for…of
for…of 是 ES6 新加入的遍历循环的操作,而传统的 for…in 循环由于历史遗留问题,它遍历的实际上是对象的属性名。
我们用for...in
遍历对象的属性名,需要用o[prop]
来获取属性的值:
1 | const o = { name: 'Jack', age: 11} |
同样的,Array 实际上也是一个对象,它的每个元素的索引被视为一个属性:
1 | const arr = [1,4,9] |
我们使用for...in
去遍历数组,实际上for...in
遍历的是数组的下标索引:
1 | for (let index in arr) { |
ES6 引入for...of
循环,遍历的是集合本身的元素,可以用于遍历 Array、Set、Map 等:
1 | for (let value of arr) { |
可以看到name: "Jack"
被舍弃了,实际上也不推荐这种向数组添加属性的操作。
forEach
更好的方式是直接使用iterable
内置的forEach
方法,它接收一个函数,每次迭代就自动回调该函数:
1 | arr.forEach((value,index) => console.log(`${index} : ${value}`)) |
遍历一个 Map:
1 | var m = new Map([['Jack', 11], ['Tom', 13], ['John', 10]]) |
Destructuring
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为**解构(Destructuring)**。
可以利用数组和解构,一次性给多个变量赋值。同时可以很方便的交换两个变量值:
1 | let [a, b] = [1, 2] |
也可以使用嵌套数组进行解构,左右两边的模版应该相同,不对应则变量值为undefined
:
1 | let [x, [[y], z]] = [1, [[2], 3]] |
语法糖 ...
被解构成一个数组:
1 | let [a,...rest] = [1,2,3,4] |
解构还可以用于对象,需要注意: 数组的解构是按次序排列的,而对象的属性没有次序,所以变量必须与属性同名:
1 | let { bar, foo } = { foo: "aaa", bar: "bbb" } |
当不知道参数传入顺序时,可以将一组参数与变量名对应起来:
1 | function f({x, y, z}) { |
非常适合提取 JSON 对象中的属性值:
1 | let jsonData = { |
以及加载模块时:
1 | import { getTable, postLoginForm } from 'api' |
Classes
ES6 引入 class 关键字,是的原先对象原型(prototype)的写法变得更加直观、更像面向对象编程的语法,用法类似于 Java 中的 class。
定义一个 Point 类,其中有个构造函数constructor
,需要注意类中的函数不需要function
关键字:
1 | class Point { |
Modules
模块的引入是 ES6 的一大重要特性,我们可以将一个大程序拆成几个小文件,需要的时候再导入。ES6 之前是没有模块加载的功能的,社区制定了一些方案,比如 CommonJS的 require、module.exports。而 ES6 规定了模块加载的标准语法,并且 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。
下面代码我在b.js
中导出了一个常量和一个函数,在a.js
中导入:
1 | // b.js |
在 ES6 中规定了模块导入导出的关键字import、export
,使用方法如下:
1 | // b.js |
import
大括号中变量名,必须与导出时声明的对外接口的名称相同。
export default
命令用于指定模块的默认输出,一个模块只能有一个默认输出,因此export default
只能出现一次,所以import
导入时可以不加大括号,名称也可以随意取。其实就想当于导出了一个 default 变量,将开平方的箭头函数赋值给它。
1 | // b.js |
因此我们才可以这样引入第三方库:
1 | import _ from 'lodash' |
需要特别注意的是,目前 Node 和浏览器还未对 import、export 做支持,因为V8引擎暂不支持,但是不久的将来肯定会加入该语法。Node官方文档 也明确说明了不建议 require 而推荐使用标准的 import 并且等待最新的V8引擎的支持。可以使用 Babel 进行转码。