本文参考了以下文章/PPT:
- Use ECMAScript 6 today
- Ecmascript 6 Whats next for Javascript
- ECMAScript 6: classes
- ECMAScript 6 modules: the final syntax
- es6features
- Javascript Modules
之前的文章:
面对对象
1.关键字 Class
class Artist {
constructor(name) {
this.name = name;
}
perform() {
return this.name + " performs ";
}
}
class Singer extends Artist {
constructor(name, song) {
super.constructor(name);
this.song = song;
}
perform() {
return super.perform() + "[" + this.song + "]";
}
}
let james = new Singer("Etta James", "At last");
james instanceof Artist; // true
james instanceof Singer; // true
james.perform(); // "Etta James performs [At last]"
看上面例子就能明白。注意几个关键字extends
,super
。
虽然ES6的Class
本质上还是语法糖,但这么设计有它的目的。
在ES5中, function
关键字承担着三个职责:
- 定义函数。
- 定义方法属性。
- 定义类的
constructor
,配合new
创建新对象。
在ES6中,第2点被属性方法定义(Method definitions)替代,第3点被Class关键字替代。一个关键字只承担一个职责,不再是满屏function
,足够清晰了吧?
有几点要注意的:
- 类的body只能包含属性方法,不能包含属性值。属性值放到constructor方法里。
- 属性方法可以是生成器,在方法名前家
*
就可以。 - 声明类(Class Declaration)并不会被提升(hoisted)。
- 如果没有指定
constructor
,那会有个默认调用super的。
2.继承 Extending
继承的几种情况和规则:
-
不要继承空类,
class Foo {}
,因为:- Foo的原型(prototype)是
Function.prototype
(所有函数的原型都是这个)。 - 而Foo.prototype的原型是
Object.prototype
。 - 这种继承就和函数一样了。
- Foo的原型(prototype)是
-
继承null:
class Foo extends null {}
- Foo的原型是
Function.prototype
。 - Foo.prototype的原型是
null
。 - 这样Object.prototype的属性方法都不会继承到Foo中。
- Foo的原型是
-
继承构造器:
class Foo extends SomeClass
- Foo的原型是
SomeClass
。 - Foo.prototype的
SomeClass.prototype
。 - 这样,类方法属性也会被继承。
- Foo的原型是
-
继承非构造器(对象):
class Foo extends SomeObject
- Foo的原型是
Function.prototype
。 - Foo.prototype的
SomeClass
。
- Foo的原型是
错误检查:继承的目标一定要是个对象或者null。如果是继承构造器,那么构造器的原型一定要是个对象或者null。
-
类声明其实创建的是可变let绑定(binding,函数式编程会比较熟悉这个概念)。对于一个类Foo:
- Foo的原型是不可改写,且不可枚举的。
- Foo的构造器是可改写,但不可枚举。
- Foo的原型函数(Foo.prototype.*)是可改写,但不可枚举。
模块
ES6的内置模块系统借鉴了CommonJS和AMD各自的优点:
- 具有CommonJS的精简语法、唯一导出出口(single exports)和循环依赖(cyclic dependencies)的特点。
- 类似AMD,支持异步加载和可配置的模块加载。
不仅仅借鉴,ES6还对这些功能进行了优化:
- 语法比CommonJS更精简。
- 支持结构的静态分析(静态检查,优化等等)。
- 循环依赖的支持比CommonJS更好。
1.语法
ES6模块的导出出口形式有两种:命名导出(一个模块多个导出)、标准导出(一个模块一个导出)。
命名导出 Named exports
//-------lib.js----------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//-------main1.js--------
import { sqaure, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(3,4)); // 5
//或者这样,命名导入的名称:
//-------main2.js--------
import * as lib from 'lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(3,4)); // 5
标准导出 Default exports
//-------MyClass.js-----
// 注意,`export`的操作对象是表达式,一般是没有名字的。
export default class { ... };
//-------main3.js--------
// 通过模块名字识别
import MyClass from 'MyClass';
let inst = new MyClass();
当然,这两种导出也可以混合使用。本质上,标准导出只是指名导出的一种,名称是「default」而已。
就目前了解的来看,ES6的模块导出貌似有些繁琐,还不如CommonJS直接通过object
导出利索。
2.设计初衷 Design goals
TC39在讨论ES6的模块化问题时,主要考虑了下面几点:
- 直接导出优先
- 静态模块结构
- 同步/异步载入都要支持
- 支持循环依赖
第一点就是要简单已用。而静态模块结构更多是出于性能优化、静态类型检查(没错,就是这个,动态语言为什么要有这个,其实还是为了性能)和以后的可能会加入的「宏模板」功能。
3.更多导入/导出写法举例
导入:
// 标准导入,命名导入
import theDefault, { named1, named2 } from 'src/mylib';
import theDefault from from 'src/mylib';
import { named1, named2 } from 'src/mylib';
// 重命名
import { named1 as myNamed1, named2 } from 'src/mylib';
// 将导入的模块定义为一个对象
// 模块的每个属性都是该对象的同名方法属性
import * as mylib from 'src/mylib';
// 仅读取模块,不导入任何功能
import 'src/mylib';
导出:
// 使用关键字**export**导出
export let myVar1 = ...;
export function MyFunc() {...}
export function* myGeneratorFunc() {...}
// 也可以以对象形式导出
const MY_CONST = ...;
function myFunc() { ... }
export { MY_CONST, myFunc }
// 当然,名字不一定要相同
export { MY_CONST as THE_CONST, myFunc as theFunc };
// 支持重导出,即在当前模块导出其他模块的功能 方便hack
export * from 'src/other_module';
export { foo, bar } from 'src/other_module';
上面说的这些语法,普通的<script>
标签是不支持的。ES6引入了一个<module>
标签,负责载入模块。<script>
标签只能通过下面将要介绍的模块API进行模块载入。
4.模块元数据
不仅仅是导入别的数据,ES6还能通过导入当前模块拿到当前模块的信息:
import { url } from this module;
console.log(url);
这就像Ruby里的____FILENAME____
。
5.模块载入接口 Module loader API
API自然是为了通过代码控制模块载入的,算是多少弥补了静态结构灵活上上的缺陷。
每个浏览器平台都会有一个名为System
的全局变量,通过这个变量调用相应接口,异步载入模块(结合ES6的promises
):
System.import('some_module')
.then(some_module => {
...
})
.catch(error => {
...
})
//当然也可以一次载入多个模块
Promise.all(
['module1', 'module2', 'module3']
).map(x => Symtem.import(x)))
.then(function([module1, module2, module3]) {
...
});
其他接口:
-
System.module(source, options?)
将source中的代码求值成一个模块。 -
System.set(name, module)
注册一个通过System.module
生成的模块。 -
System.define(name, source, options?)
前两个接口的结合。
更多关于模块的说明
请看看Yehuda Katz的这一篇:Javascript Modules。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。