From bf75bab72a57893f1da4b4ff534e79d75a693113 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 2 Nov 2015 21:30:07 +0800 Subject: [PATCH] edit module --- docs/module.md | 157 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 109 insertions(+), 48 deletions(-) diff --git a/docs/module.md b/docs/module.md index face8ae..294433a 100644 --- a/docs/module.md +++ b/docs/module.md @@ -22,6 +22,12 @@ import { stat, exists, readFile } from 'fs'; 上面代码的实质是从`fs`模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”,即ES6可以在编译时就完成模块编译,效率要比CommonJS模块的加载方式高。 +除了静态加载带来的各种好处,ES6模块还有以下好处。 + +- 不再需要UMD模块格式了,将来服务器和浏览器都会支持ES6模块格式。目前,通过各种工具库,其实已经做到了这一点。 +- 将来浏览器的新API就能用模块格式提供,不再必要做成全局变量或者`navigator`对象的属性。 +- 不再需要对象作为命名空间(比如`Math`对象),未来这些功能可以通过模块提供。 + ## 严格模式 ES6的模块自动采用严格模式,不管你有没有在模块头部加上`"use strict"`。 @@ -141,22 +147,6 @@ function setName(element) { import { lastName as surname } from './profile'; ``` -ES6支持多重加载,即所加载的模块中又加载其他模块。 - -```javascript -import { Vehicle } from './Vehicle'; - -class Car extends Vehicle { - move () { - console.log(this.name + ' is spinning wheels...') - } -} - -export { Car } -``` - -上面的模块先加载`Vehicle`模块,然后在其基础上添加了`move`方法,再作为一个新模块输出。 - 注意,`import`命令具有提升效果,会提升到整个模块的头部,首先执行。 ```javascript @@ -356,7 +346,7 @@ let o = new MyClass(); 模块之间也可以继承。 -假设有一个circleplus模块,继承了circle模块。 +假设有一个`circleplus`块,继承了`circle`模块。 ```javascript // circleplus.js @@ -364,11 +354,11 @@ let o = new MyClass(); export * from 'circle'; export var e = 2.71828182846; export default function(x) { - return Math.exp(x); + return Math.exp(x); } ``` -上面代码中的`export *`,表示输出`circle`模块的所有属性和方法,`export default`命令定义模块的默认方法。 +上面代码中的`export *`,表示再输出`circle`模块的所有属性和方法。注意,`export *`命令会忽略`circle`模块的`default`方法。然后,上面代码又输出了自定义的`e`变量和默认方法。 这时,也可以将`circle`的属性或方法,改名后再输出。 @@ -392,6 +382,103 @@ console.log(exp(math.E)); 上面代码中的`import exp`表示,将`circleplus`模块的默认方法加载为`exp`方法。 +## ES6模块加载的实质 + +ES6模块加载的机制,与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。 + +CommonJS模块输入的是被输出值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个例子。 + +下面是一个模块文件`lib.js`。 + +```javascript +// lib.js +var counter = 3; +function incCounter() { + counter++; +} +module.exports = { + counter: counter, + incCounter: incCounter, +}; +``` + +上面代码输出内部变量`counter`和改写这个变量的内部方法`incCounter`。 + +然后,加载上面的模块。 + +```javascript +// main.js +var counter = require('./lib').counter; +var incCounter = require('./lib').incCounter; + +console.log(counter); // 3 +incCounter(); +console.log(counter); // 3 +``` + +上面代码说明,`counter`输出以后,`lib.js`模块内部的变化就影响不到`counter`了。 + +ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令`import`时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,换句话说,ES6的输入有点像Unix系统的”符号连接“,原始值变了,输入值也会跟着变。因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。 + +还是举上面的例子。 + +```javascript +// lib.js +export let counter = 3; +export function incCounter() { + counter++; +} + +// main1.js +import { counter, incCounter } from './lib'; +console.log(counter); // 3 +incCounter(); +console.log(counter); // 4 +``` + +上面代码说明,ES6模块输入的变量`counter`是活的,完全反映其所在模块`lib.js`内部的变化。 + +还是举本章开头时的例子。 + +```javascript +// m1.js +export var foo = 'bar'; +setTimeout(() => foo = 'baz', 500); + +// m2.js +import {foo} from './m1.js'; +console.log(foo); +setTimeout(() => console.log(foo), 500); +``` + +上面代码中,`m1.js`的变量`foo`,在刚加载时等于`bar`,过了500毫秒,又变为等于`baz`。 + +让我们看看,`m2.js`能否正确读取这个变化。 + +```bash +$ babel-node m2.js + +bar +baz +``` + +上面代码表明,ES6模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。 + +由于ES6输入的模块变量,只是一个”符号连接“,所以这个变量是只读的,对它进行重新赋值会报错。 + +```javascript +// lib.js +export let obj = {}; + +// main.js +import { obj } from './lib'; + +obj.prop = 123; // OK +obj = {}; // TypeError +``` + +上面代码中,`main.js`从`lib.js`输入变量`obj`,可以对`obj`添加属性,但是重新赋值就会报错。因为变量`obj`指向的地址是只读的,不能重新赋值,这就好比`main.js`创造了一个名为`obj`的const变量。 + ## 循环加载 “循环加载”(circular dependency)指的是,`a`脚本的执行依赖`b`脚本,而`b`脚本的执行又依赖`a`脚本。 @@ -472,37 +559,11 @@ a.js 执行完毕 exports.done = true; ``` +总之,CommonJS输入的是被输出值的拷贝。 + ### ES6模块 -ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令`import`时,不会去执行模块,而是只生成一个引用。等到真的需要用到时,再到模块里面去取值。 - -因此,ES6模块是动态引用,不存在缓存值的问题,而且模块里面的变量,绑定其所在的模块。还是举本章开头时的例子。 - -```javascript -// m1.js -export var foo = 'bar'; -setTimeout(() => foo = 'baz', 500); - -// m2.js -import {foo} from './m1.js'; -console.log(foo); -setTimeout(() => console.log(foo), 500); -``` - -上面代码中,`m1.js`的变量`foo`,在刚加载时等于`bar`,过了500毫秒,又变为等于`baz`。 - -让我们看看,`m2.js`能否正确读取这个变化。 - -```bash -$ babel-node m2.js - -bar -baz -``` - -上面代码表明,ES6模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。 - -这导致ES6处理“循环加载”与CommonJS有本质的不同。ES6根本不会检查是否发生了“循环加载”,只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。 +ES6处理“循环加载”与CommonJS有本质的不同。ES6模块是动态引用,根本不会检查是否发生了“循环加载”,只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。 请看下面的例子(摘自 Dr. Axel Rauschmayer 的[《Exploring ES6》](http://exploringjs.com/es6/ch_modules.html))。