ES6特性归纳

ES6特性归纳

ES的全称是ECMAScript,它是JavaScript的规格,JS是ES的一种实现。ES还有JScript(IE中实际使用的是这种脚本语言),ActionScript(Flash中使用的脚本语言)等实现形式。本篇文章是对阮一峰-《ECMAScript 6入门》中JS新增特性的归纳。

let和const

let只在块级作用域内有效,const声明的变量的值不能被改变

for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。let在父作用域和子作用域互不影响。

let与const都不允许重复声明。

变量的解构赋值

用于将值从数组或属性从对象提取到不同的变量中,一次给多个变量赋值的一种语法

数组解构赋值

完全解构赋值(左边与右边的量相等,或者大于右边的量。即右边的值被使用完全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

不完全解构赋值(即等号左边的模式,只匹配一部分的等号右边的数组)

1
2
3
4
5
6
7
8
let [x, y] = [1, 2, 3];
x // 1
y // 2

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

默认值

1
2
3
4
5
let [foo = true] = [];
foo // true
//必须为空或者undefined默认值才生效
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x, y = 'b'] = ['a', null]; // x='a', y=null

等号右边的值,需要对象或转为对象以后具备 Iterator 接口

对象的解构赋值

根据左右两边的属性名进行赋值,变量名默认为属性名,如果属性有值则变量名为属性值

1
2
3
4
5
6
7
8
9
10
11
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

嵌套解构的对象解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//loc,start是模式不是变量不会被赋值
var node = {
loc: {
start: {
line: 1,
column: 5
}
}
};

var { loc: { start: { line }} } = node;
line // 1
loc // error: loc is undefined
start // error: start is undefined

// 报错,因为foo为undefined无法取得bar
let {foo: {bar}} = {baz: 'baz'};

对数组进行对象属性的解构

1
2
3
4
5
let arr = [1, 2, 3];
//方括号这种写法,属于“属性名表达式”
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

等号右边的值,需要对象或转为对象后进行结构赋值

函数解构赋值

1
2
3
4
5
function add([x, y]){
return x + y;
}

add([1, 2]); // 3

为函数变量指定默认值,当x或y没有值时,就会启用默认值

1
2
3
4
5
6
7
8
function move({x = 0, y = 0} = {}) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

为函数参数指定默认值,默认值是整体{x,y}的,所以只要有参数就不启用默认值

1
2
3
4
5
6
7
8
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

Symbol

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//不能有new,因为Symbol也是一种基础类型
//参数字符串只用与描述,同参情况下也不相等
var s1 = Symbol('foo');
var s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

Symbol作为属性名

Symbol值作为对象属性名时,不能用点运算符。使用symbol保证属性不会被覆盖,symbol作为属性名不会被常规的方法遍历到,可以使用该特性为对象定义内部方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
var a = {
[mySymbol]: 'Hello!'
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

获取Symbol属性

通过Object.getOwnPropertySymbols方法获取指定对象的所有Symbol属性名。

1
2
3
4
5
6
7
8
9
10
11
var obj = {};
var a = Symbol('a');
var b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

var objectSymbols = Object.getOwnPropertySymbols(obj);

objectSymbols
// [Symbol(a), Symbol(b)]

使用Reflect.ownKeys返回类型所有键名

1
2
3
4
5
6
7
8
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};

Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]

Symbol API

Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

1
2
3
4
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');

s1 === s2 // true

Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key

1
2
3
4
5
var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

Symbol.for为Symbol值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。

对象有些内置的Symbol值,如:对象的Symbol.hasInstance属性,指向一个内部方法。

1
2
3
4
5
6
7
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}

[1, 2, 3] instanceof new MyClass() // true

其他还有:Symbol.iterator,Symbol.replace,Symbol.search,Symbol.split,Symbol.match指向对象默认的同名方法

Set和Map数据结构

Set与数组

1
2
3
4
5
6
7
8
//set与数组互换
var set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
set.size//4

// 去除数组的重复成员
[...new Set(array)]

Set中NaN可以等于自身

求交、并和差集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。

1
2
3
4
5
6
7
8
9
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6

// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6

Set实例的属性和方法

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。
  • add(value):添加某个值,返回Set结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。

WeakSet

WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//只能添加对象
var ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set

//数组的成员只能是对象
var a = [[1,2], [3,4]];
var ws = new WeakSet(a);
//非对象时会报错
var b = [3, 4];
var ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
  • WeakSet.prototype.add(value):向WeakSet实例添加一个新成员。
  • WeakSet.prototype.delete(value):清除WeakSet实例的指定成员。
  • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在

WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

Map

map与数组

数组

1
2
3
4
5
6
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}

WeakMap

它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。

典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

weakmap除了像weakset一样可以用作存放dom节点外,还能够用于部署私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let _counter = new WeakMap();
let _action = new WeakMap();

class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}

let c = new Countdown(2, () => console.log('DONE'));

c.dec()
c.dec()
// DONE

遍历方法

Set和Map都支持keys(),values(),entries(),forEach()等遍历方式,对于set,keys()、values()、entries()三种遍历方式一模一样。forEach()遍历方式接受一个回调函数其中的两个参数分别为集合中的元素以及序号(表示当前是第几项)。

Proxy

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

1
2
3
4
5
6
7
8
9
10
11
12
13
var proxy = new Proxy({}, {
/*
target:目标对象
property:访问的属性
*/
get: function(target, property) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

可拦截操作

  • get(target, propKey, receiver) 拦截取值操作
  • set(target, propKey, value, receiver) 拦截设值操作
  • has(target, propKey) 拦截propKey in proxy操作
  • deleteProperty(target, propKey) 拦截delete proxy[propKey]的操作
  • ownKeys(target) 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。
  • getOwnPropertyDescriptor(target, propKey) 拦截Object.getOwnPropertyDescriptor(proxy, propKey)
  • defineProperty(target, propKey, propDesc) 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs)
  • preventExtensions(target) 拦截Object.preventExtensions(proxy)
  • getPrototypeOf(target) 拦截Object.getPrototypeOf(proxy)
  • isExtensible(target) 拦截Object.isExtensible(proxy)
  • setPrototypeOf(target, proto) 拦截Object.setPrototypeOf(proxy, proto)
  • apply(target, object, args) 拦截 Proxy 实例(即target函数的调用)作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)、Reflect.apply(proxy, null,args)
  • construct(target, args) 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。必须返回一个对象否则会报错

Reflect

让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。

1
2
3
4
5
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true

Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

Promise对象

Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

Promise特点:

  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//两个函数resolve和reject由JS引擎提供不用自己部署。
var promise = new Promise(function(resolve, reject) {
// ... some code

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

//可以用then方法分别指定Resolved状态和Reject状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

1
var p = Promise.all([p1, p2, p3]);

Promise.race只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

1
var p = Promise.race([p1, p2, p3]);

Promise.resolve将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。

1
2
3
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

两个有用的方法

done()Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。

1
2
3
4
5
6
7
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};

finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

1
2
3
4
5
6
7
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};

Generator函数

Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

基本使用

每次执行函数,会从yield返回对应的值

1
2
3
4
5
6
7
8
9
10
11
12
13
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
//next如果带参数,则yield会返回相应的参数
g.next(true) // { value: 0, done: false }

Generator.prototype.throw()

Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。捕获后会顺带执行下一条yield,即执行一次next方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};

var i = g();
i.next();

try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b

一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。

Generator.prototype.return()

return方法,可以返回给定的值,并且终结遍历Generator函数。如果Generator函数内部有try…finally代码块,那么return方法会推迟到finally代码块执行完再执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers()
g.next() // { done: false, value: 1 }
g.next() // { done: false, value: 2 }
g.return(7) // { done: false, value: 4 }
g.next() // { done: false, value: 5 }
g.next() // { done: true, value: 7 }

yield* 语句

yield*语句,用来在一个 Generator 函数里面执行另一个 Generator 函数。yield后面的Generator函数(没有return语句时),不过是for…of的一种简写形式,完全可以用后者替代前者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function* foo() {
yield 'a';
yield 'b';
}

function* bar() {
yield 'x';
yield* foo();
yield 'y';
}

// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}

任何数据结构只要有Iterator接口,就可以被yield*遍历。

注意点

Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,也继承了Generator函数的prototype对象上的方法。

作为对象属性时generator的写法

1
2
3
4
5
6
7
8
9
10
11
let obj = {
* myGeneratorMethod() {
···
}
};
//等价于
let obj = {
myGeneratorMethod: function* () {
// ···
}
};

使得Generator能够使用this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

使得Generator能够使用new:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}

function F() {
return gen.call(gen.prototype);
}

var f = new F();

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

Generator函数被称为“半协程”(semi-coroutine),意思是只有Generator函数的调用者,才能将程序的执行权还给Generator函数。

除了for…of循环以外,扩展运算符(…)、解构赋值和Array.from方法内部调用的,都是遍历器接口。

async

async 函数就是 Generator 函数的语法糖。async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//使用Generator函数实现异步
var fs = require('fs');

var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) reject(error);
resolve(data);
});
});
};

var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

//使用async实现异步
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
(2)更好的语义。
(3)更广的适用性。
(4)返回值是 Promise。

异步遍历的接口

异步遍历器的最大的语法特点,就是调用遍历器的next方法,返回的是一个 Promise 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();

asyncIterator
.next()
.then(iterResult1 => {
console.log(iterResult1); // { value: 'a', done: false }
return asyncIterator.next();
})
.then(iterResult2 => {
console.log(iterResult2); // { value: 'b', done: false }
return asyncIterator.next();
})
.then(iterResult3 => {
console.log(iterResult3); // { value: undefined, done: true }
});

for await…of

for…of循环用于遍历同步的 Iterator 接口。新引入的for await…of循环,则是用于遍历异步的 Iterator 接口。

1
2
3
4
5
6
7
8
async function f() {
//createAsyncIterable返回一个异步遍历器
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// a
// b

Class

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let methodName = "getArea";
//定义类
class Point {
//constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
constructor(x, y) {
this.x = x;
this.y = y;
}
//定义在类的prototype上
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
//可以使用表达式
[methodName]() {
// ...
}
//定义在构造器上的方法
static myMethod(msg) {
console.log('static', msg);
}
//在实例属性前加上static关键字
static myStaticProp = 42;
//getter、setter方法
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}

class定义的类不存在变量提升
类和模块的内部,默认就是严格模式

new.target返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined

Class的继承

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。

super虽然代表了父类的构造函数,但是返回的是子类的实例,即super内部的this指的是ColorPoint,因此super()在这里相当于Point.prototype.constructor.call(this)

1
2
3
4
5
6
7
8
9
10
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}

toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}

prototype和__proto__

对象具有__proto__,函数同时拥有prototype和__proto__

1
2
3
4
5
6
7
8
class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

修饰器

修饰器(Decorator)是一个函数,用来修改类的行为。这是ES7的一个提案,目前Babel转码器已经支持。

修饰器本质就是编译时执行的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function testable(isTestable) {
//target指代目标类
return function(target) {
target.isTestable = isTestable;
}
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

除了修饰类还能修饰类的成员,修饰函数时,参数为target:对象原型、name:类的某个成员名、descriptor:该成员的描述符。返回的描述符成为成员的修饰符。

由于存在函数提升,使得修饰器不能用于函数。而类不会提升

Module语法

export

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载。

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上”use strict”。

ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。

export

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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 {
firstName as fN,
lastName as lN,
year};

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

// 错误
export default var a = 1;
// 正确
export default 42;

export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

import

1
2
3
4
5
6
7
8
//导入接口
import {firstName, lastName, year} from './profile';
//设置别名
import { lastName as surname } from './profile';
//需要有配置文件,告诉JS引擎该模块的位置
import {myMethod} from 'util';
//不导入任何值,只执行lodash模块代码
import 'lodash';

import命令具有提升效果,会提升到整个模块的头部,首先执行。

有一个提案,建议引入import()函数,完成动态加载。

1
2
3
4
5
6
const main = document.querySelector('main');

import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})

export与import混合写法

1
2
3
4
5
export { foo, bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';
export { foo, bar };

Module加载实现

浏览器加载 ES6 模块,也使用<script>标签,但是要加入type=”module”属性。

同一个模块如果加载多次,将只执行一次。

CommonJS 加载机制

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS加载模块是阻塞式加载会暂停执行直到获取模块的值,循环引用时a调用b模块处在阻塞状态,b调用a模块只能返回其阻塞前的export值
由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。

CommonJS 模块的输出缓存机制,在 ES6 加载方式下依然有效。

1
2
3
4
// foo.js
module.exports = 123;
//导入的module.exports将一直是123,而不会变成null。
setTimeout(_ => module.exports = null)

ES6模块转码

目前浏览器并不直接支持模块,可以使用ES6 module transpiler转为ES5代码,或者使用SystemJS调用模块

ES6 module transpiler

1
2
3
4
5
6
7
8
9
# 首先,安装这个转码器。

$ npm install -g es6-module-transpiler
# 然后,使用compile-modules convert命令,将 ES6 模块文件转码。

$ compile-modules convert file1.js file2.js
# -o参数可以指定转码后的文件名。

$ compile-modules convert -o out.js file1.js

SystemJS

1
2
3
4
5
6
<script src="system.js"></script>
<script>
System.import('app/es6-file').then(function(m) {
console.log(new m.q().es6); // hello
});
</script>

参考文献:
ECMAScript 6 入门

坚持原创技术分享,您的支持将鼓励我继续创作!