ES6

ECMAScript 6.0(以下简称 ES6)是2015 年 6 月正式发布的 JavaScript 语言标准。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。 下面就来介绍几个ES6的新特性。

1.块级作用域的letconst

let用来声明变量,用法类似于var,改变的是let声明的变量只在声明的代码块中有效。

{
    let a = 10;
    var b = 1;
}
a // error
b // 1

const是一个只读常量,一旦声明,常量的值就不能被改变,其作用域同let一样,也是块级作用域。

const PI = 3.1415;
PI // 3.1415

PI = 3; // error

修改一个常量的值会报错。

2.解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。

数组的结构赋值是这样的:

let [a, b, c] = [1, 2, 3];
a // 1
b // 2
c // 3

本质上,这是一种模式匹配。只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果结构不成功则变量的值为undefined

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [foo] = [];
foo // 结构不成功,值为undefined

对象的解构赋值:和数组稍有不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

解构的出现让很多操作变得容易起来。

交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

提取JSON数据

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

函数参数的默认值

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
} = {}) {
  // ... do stuff
};

3.默认参数

ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。

function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

这个方法在多数情况下是适用的,但是当传入的y是一个false值时,则会被认为没有传入参数。例如:

log('Hello', 0); //  Hello World,传入的参数0失效

ES6允许为函数的参数设置默认值,即直接写在参数定义后面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

4.箭头函数

简单使用:

var f = v => v;

等同于
var f = function(v) {
    return v;
}

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;
// 等同于
var f = function () { return 5 };

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => {
    return num1 + num2;
}

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

箭头函数体内的this对象,就是定义时所在的对象,不再是使用时所在的对象。

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

5.模板表达式和多行字符串

在ES5中,如果想要拼接字符串和变量需要这样写:

var age = 18;
var str = 'I am ' + age + ' years lod';

ES6提供了新语法-在反引号包裹的字符串中,使用${NAME}引入变量。那拼接字符串可以这样写:

var str = `I am ${age} years old`;

同时,反引号包裹的字符串还支持换行。

var str = `I am ${age} years old, 
and my name id Linda`;

// 等同于
var str = 'I am ' + age + ' years lod' +
'and my name id Linda';

6.Promise对象

Promise是异步编程的一种解决方案,最早由社区提出来并实现。简单来说,Promise是一个容器,里面保存着某个未来才会结束的事件结果,它具有以下两个特点:

  • 状态不收外界影响,只有异步操作的结果可以决定其状态,其他任何操作都无法改变这个状态。

  • 一旦改变状态,就不会再改变了。任何时候都可以得到这个结果。

Promise也有一些缺点:

  • 无法取消Promise,一旦新建就会立即执行,无法中途取消。

  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。

  • 当处于penging状态时无法得知目前进展到了哪一个阶段。

创建一个Promise实例:

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

// 也可写作下面这样(推荐写法)
promise.then(function(value) {
  // success
}).catch(function(err) {
    // error
});

7.Class定义类

在之前,生成实例对象的传统方法是通过构造函数,但这种写法跟传统的面向对象语言差异很大。ES6提供了更接近传统语言的写法,引入了class关键字,用来定义类。

//ES5写法
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

// ES6写法
class Point {
    constructor(x,y) {  // 构造函数
        this.x = x;
        this.y = y;
    }
    toString() { // 定义类的方法时,不需要加上function关键字,方法之间也不需要逗号分隔
        return `(${this.x},${this.y})`;
    }
}
var p = new Point(1,2);

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

class Point {
  constructor() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// 等同于

Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

Class可以通过extends关键字实现继承。

class ColorPoint extends Point { // ColorPoint继承Pointl类
    constructor(x, y, color) {
        super(x,y); // 调用父类的constructor方法
        this.color = color;
    }
    toString() {
        return this.color + '  ' + super.toString();
    }
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

8.模块(module)体系

ES6在语言标准的层面上实现了模块功能,设计思想是尽量地静态化,使得编译时就能确定模块的依赖关系以及输入和输出的变量。模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

export命令

一个模块是一个独立的文件,外部无法获取文件内部的变量。如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。基本语法如下:

// profile.js

// 输出变量
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

// 输出变量的另一种写法
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

// 输出函数和类
export function multiply(x, y) {
  return x * y;
};

最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,import命令也是如此。 这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

import命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

// main.js
import {firstName, lastName, year} from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

import { lastName as surname } from './profile.js';

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。 由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

不想指定模块名称时可使用export default命令,为模块指定默认输出。

// export-default.js
export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo'

Last updated