mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 10:22:23 +00:00
docs(module): add Node 加载 ES6 模块
This commit is contained in:
parent
4d599e8785
commit
4bf719ef8f
226
docs/module.md
226
docs/module.md
@ -12,7 +12,9 @@ let { stat, exists, readFile } = require('fs');
|
|||||||
|
|
||||||
// 等同于
|
// 等同于
|
||||||
let _fs = require('fs');
|
let _fs = require('fs');
|
||||||
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;
|
let stat = _fs.stat;
|
||||||
|
let exists = _fs.exists;
|
||||||
|
let readfile = _fs.readfile;
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码的实质是整体加载`fs`模块(即加载`fs`的所有方法),生成一个对象(`_fs`),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
|
上面代码的实质是整体加载`fs`模块(即加载`fs`的所有方法),生成一个对象(`_fs`),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
|
||||||
@ -58,6 +60,8 @@ ES6 的模块自动采用严格模式,不管你有没有在模块头部加上`
|
|||||||
|
|
||||||
上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6,所以请参阅相关 ES5 书籍,本书不再详细介绍了。
|
上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6,所以请参阅相关 ES5 书籍,本书不再详细介绍了。
|
||||||
|
|
||||||
|
其中,尤其需要注意`this`的限制。ES6 模块之中,顶层的`this`指向`undefined`,即不应该在顶层代码使用`this`。
|
||||||
|
|
||||||
## export 命令
|
## export 命令
|
||||||
|
|
||||||
模块功能主要由两个命令构成:`export`和`import`。`export`命令用于规定模块的对外接口,`import`命令用于输入其他模块提供的功能。
|
模块功能主要由两个命令构成:`export`和`import`。`export`命令用于规定模块的对外接口,`import`命令用于输入其他模块提供的功能。
|
||||||
@ -86,7 +90,7 @@ export {firstName, lastName, year};
|
|||||||
|
|
||||||
上面代码在`export`命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在`var`语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。
|
上面代码在`export`命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在`var`语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。
|
||||||
|
|
||||||
export命令除了输出变量,还可以输出函数或类(class)。
|
`export`命令除了输出变量,还可以输出函数或类(class)。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
export function multiply(x, y) {
|
export function multiply(x, y) {
|
||||||
@ -399,6 +403,18 @@ export default var a = 1;
|
|||||||
|
|
||||||
上面代码中,`export default a`的含义是将变量`a`的值赋给变量`default`。所以,最后一种写法会报错。
|
上面代码中,`export default a`的含义是将变量`a`的值赋给变量`default`。所以,最后一种写法会报错。
|
||||||
|
|
||||||
|
同样地,因为`export default`本质是将该命令后面的值,赋给`default`变量以后再默认,所以直接将一个值写在`export default`之后。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 正确
|
||||||
|
export default 42;
|
||||||
|
|
||||||
|
// 报错
|
||||||
|
export 42;
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定外对接口为`default`。
|
||||||
|
|
||||||
有了`export default`命令,输入模块时就非常直观了,以输入 lodash 模块为例。
|
有了`export default`命令,输入模块时就非常直观了,以输入 lodash 模块为例。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@ -417,20 +433,16 @@ import _, { each } from 'lodash';
|
|||||||
export default function (obj) {
|
export default function (obj) {
|
||||||
// ···
|
// ···
|
||||||
}
|
}
|
||||||
|
|
||||||
export function each(obj, iterator, context) {
|
export function each(obj, iterator, context) {
|
||||||
// ···
|
// ···
|
||||||
}
|
}
|
||||||
|
|
||||||
export { each as forEach };
|
export { each as forEach };
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码的最后一行的意思是,暴露出`forEach`接口,默认指向`each`接口,即`forEach`和`each`指向同一个方法。
|
上面代码的最后一行的意思是,暴露出`forEach`接口,默认指向`each`接口,即`forEach`和`each`指向同一个方法。
|
||||||
|
|
||||||
如果要输出默认的值,只需将值跟在`export default`之后即可。
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
export default 42;
|
|
||||||
```
|
|
||||||
|
|
||||||
`export default`也可以用来输出类。
|
`export default`也可以用来输出类。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@ -488,14 +500,20 @@ export default es6;
|
|||||||
export { default as es6 } from './someModule';
|
export { default as es6 } from './someModule';
|
||||||
```
|
```
|
||||||
|
|
||||||
另外,ES7有一个[提案](https://github.com/leebyron/ecmascript-more-export-from),简化先输入后输出的写法,拿掉输出时的大括号。
|
下面三种`import`语句,没有对应的复合写法。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 现行的写法
|
import * as someIdentifier from "someModule";
|
||||||
export {v} from 'mod';
|
import someIdentifier from "someModule";
|
||||||
|
import someIdentifier, { namedIdentifier } from "someModule";
|
||||||
|
```
|
||||||
|
|
||||||
// 提案的写法
|
为了做到形式的对称,现在有[提案](https://github.com/leebyron/ecmascript-export-default-from),提出补上这三种复合写法。
|
||||||
export v from 'mod';
|
|
||||||
|
```javascript
|
||||||
|
export * as someIdentifier from "someModule";
|
||||||
|
export someIdentifier from "someModule";
|
||||||
|
export someIdentifier, { namedIdentifier } from "someModule";
|
||||||
```
|
```
|
||||||
|
|
||||||
## 模块的继承
|
## 模块的继承
|
||||||
@ -650,7 +668,7 @@ obj.prop = 123; // OK
|
|||||||
obj = {}; // TypeError
|
obj = {}; // TypeError
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,`main.js`从`lib.js`输入变量`obj`,可以对`obj`添加属性,但是重新赋值就会报错。因为变量`obj`指向的地址是只读的,不能重新赋值,这就好比`main.js`创造了一个名为`obj`的const变量。
|
上面代码中,`main.js`从`lib.js`输入变量`obj`,可以对`obj`添加属性,但是重新赋值就会报错。因为变量`obj`指向的地址是只读的,不能重新赋值,这就好比`main.js`创造了一个名为`obj`的`const`变量。
|
||||||
|
|
||||||
最后,`export`通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
|
最后,`export`通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
|
||||||
|
|
||||||
@ -685,7 +703,7 @@ import './x';
|
|||||||
import './y';
|
import './y';
|
||||||
```
|
```
|
||||||
|
|
||||||
现在执行`main.js`,输出的是1。
|
现在执行`main.js`,输出的是`1`。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ babel-node main.js
|
$ babel-node main.js
|
||||||
@ -712,6 +730,184 @@ $ babel-node main.js
|
|||||||
- 该脚本内部的顶层变量,都只在该脚本内部有效,外部不可见。
|
- 该脚本内部的顶层变量,都只在该脚本内部有效,外部不可见。
|
||||||
- 该脚本内部的顶层的`this`关键字,返回`undefined`,而不是指向`window`。
|
- 该脚本内部的顶层的`this`关键字,返回`undefined`,而不是指向`window`。
|
||||||
|
|
||||||
|
## Node 的加载处理
|
||||||
|
|
||||||
|
### 概述
|
||||||
|
|
||||||
|
Node 对 ES6 模块的处理比较麻烦,因为它有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。
|
||||||
|
|
||||||
|
在静态分析阶段,一个模块脚本只要有一行`import`或`export`语句,Node 就会认为该脚本为 ES6 模块,否则就为 CommonJS 模块。如果不输出任何接口,但是希望被 Node 认为是 ES6 模块,可以在脚本中加一行语句。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export {};
|
||||||
|
```
|
||||||
|
|
||||||
|
上面的命令并不是输出一个空对象,而是不输出任何接口的 ES6 标准写法。
|
||||||
|
|
||||||
|
如何不指定绝对路径,Node 加载 ES6 模块会依次寻找以下脚本,与`require()`的规则一致。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import './foo';
|
||||||
|
// 依次寻找
|
||||||
|
// ./foo.js
|
||||||
|
// ./foo/package.json
|
||||||
|
// ./foo/index.js
|
||||||
|
|
||||||
|
import 'baz';
|
||||||
|
// 依次寻找
|
||||||
|
// ./node_modules/baz.js
|
||||||
|
// ./node_modules/baz/package.json
|
||||||
|
// ./node_modules/baz/index.js
|
||||||
|
// 寻找上一级目录
|
||||||
|
// ../node_modules/baz.js
|
||||||
|
// ../node_modules/baz/package.json
|
||||||
|
// ../node_modules/baz/index.js
|
||||||
|
// 再上一级目录
|
||||||
|
```
|
||||||
|
|
||||||
|
ES6 模块之中,顶层的`this`指向`undefined`;CommonJS 模块的顶层`this`指向当前模块,这是两者的一个重大差异。
|
||||||
|
|
||||||
|
### import 命令加载 CommonJS 模块
|
||||||
|
|
||||||
|
Node 采用 CommonJS 模块格式,模块的输出都定义在`module.exports`这个属性上面。在 Node 环境中,使用`import`命令加载 CommonJS 模块,Node 会自动将`module.exports`属性,当作模块的默认输出,即等同于`export default`。
|
||||||
|
|
||||||
|
下面是一个 CommonJS 模块。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// a.js
|
||||||
|
module.exports = {
|
||||||
|
foo: 'hello',
|
||||||
|
bar: 'world'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 等同于
|
||||||
|
export default {
|
||||||
|
foo: 'hello',
|
||||||
|
bar: 'world'
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`import`命令加载上面的模块,`module.exports`会被视为默认输出。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 写法一
|
||||||
|
import baz from './a';
|
||||||
|
// baz = {foo: 'hello', bar: 'world'};
|
||||||
|
|
||||||
|
// 写法二
|
||||||
|
import {default as baz} from './a';
|
||||||
|
// baz = {foo: 'hello', bar: 'world'};
|
||||||
|
```
|
||||||
|
|
||||||
|
如果采用整体输入的写法(`import * as xxx from someModule`),`default`会取代`module.exports`,作为输入的接口。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import * as baz from './a';
|
||||||
|
// baz = {
|
||||||
|
// get default() {return module.exports;},
|
||||||
|
// get foo() {return this.default.foo}.bind(baz),
|
||||||
|
// get bar() {return this.default.bar}.bind(baz)
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,`this.default`取代了`module.exports`。需要注意的是,Node 会自动为`baz`添加`default`属性,通过`baz.default`拿到`module.exports`。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// b.js
|
||||||
|
module.exports = null;
|
||||||
|
|
||||||
|
// es.js
|
||||||
|
import foo from './b';
|
||||||
|
// foo = null;
|
||||||
|
|
||||||
|
import * as bar from './b';
|
||||||
|
// bar = {default:null};
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,`es.js`采用第二种写法时,要通过`bar.default`这样的写法,才能拿到`module.exports`。
|
||||||
|
|
||||||
|
下面是另一个例子。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// c.js
|
||||||
|
module.exports = function two() {
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// es.js
|
||||||
|
import foo from './c';
|
||||||
|
foo(); // 2
|
||||||
|
|
||||||
|
import * as bar from './c';
|
||||||
|
bar.default(); // 2
|
||||||
|
bar(); // throws, bar is not a function
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,`bar`本身是一个对象,不能当作函数调用,只能通过`bar.default`调用。
|
||||||
|
|
||||||
|
CommonJS 模块的输出缓存机制,在 ES6 加载方式下依然有效。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// foo.js
|
||||||
|
module.exports = 123;
|
||||||
|
setTimeout(_ => module.exports = null);
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,对于加载`foo.js`的脚本,`module.exports`将一直是`123`,而不会变成`null`。
|
||||||
|
|
||||||
|
由于 ES6 模块是编译时确定输出接口,CommonJS 模块是运行时确定输出接口,所以采用`import`命令加载 CommonJS 模块时,不允许采用下面的写法。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import {readfile} from 'fs';
|
||||||
|
```
|
||||||
|
|
||||||
|
上面的写法不正确,因为`fs`是 CommonJS 格式,只有在运行时才能确定`readfile`接口,而`import`命令要求编译时就确定这个接口。解决方法就是改为整体输入。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import * as express from 'express';
|
||||||
|
const app = express.default();
|
||||||
|
|
||||||
|
import express from 'express';
|
||||||
|
const app = express();
|
||||||
|
```
|
||||||
|
|
||||||
|
### require 命令加载 ES6 模块
|
||||||
|
|
||||||
|
采用`require`命令加载 ES6 模块时,ES6 模块的所有输出接口,会成为输入对象的属性。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// es.js
|
||||||
|
let foo = {bar:'my-default'};
|
||||||
|
export default foo;
|
||||||
|
foo = null;
|
||||||
|
|
||||||
|
// cjs.js
|
||||||
|
const es_namespace = require('./es');
|
||||||
|
console.log(es_namespace.default);
|
||||||
|
// {bar:'my-default'}
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,`default`接口变成了`es_namespace.default`属性。另外,由于存在缓存机制,`es.js`对`foo`的重新赋值没有在模块外部反映出来。
|
||||||
|
|
||||||
|
下面是另一个例子。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// es.js
|
||||||
|
export let foo = {bar:'my-default'};
|
||||||
|
export {foo as bar};
|
||||||
|
export function f() {};
|
||||||
|
export class c {};
|
||||||
|
|
||||||
|
// cjs.js
|
||||||
|
const es_namespace = require('./es');
|
||||||
|
// es_namespace = {
|
||||||
|
// get foo() {return foo;}
|
||||||
|
// get bar() {return foo;}
|
||||||
|
// get f() {return f;}
|
||||||
|
// get c() {return c;}
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
## 循环加载
|
## 循环加载
|
||||||
|
|
||||||
“循环加载”(circular dependency)指的是,`a`脚本的执行依赖`b`脚本,而`b`脚本的执行又依赖`a`脚本。
|
“循环加载”(circular dependency)指的是,`a`脚本的执行依赖`b`脚本,而`b`脚本的执行又依赖`a`脚本。
|
||||||
|
@ -872,7 +872,7 @@ Object.entries(obj)
|
|||||||
|
|
||||||
除了返回值不一样,该方法的行为与`Object.values`基本一致。
|
除了返回值不一样,该方法的行为与`Object.values`基本一致。
|
||||||
|
|
||||||
如果原对象的属性名是一个Symbol值,该属性会被省略。
|
如果原对象的属性名是一个 Symbol 值,该属性会被忽略。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
Object.entries({ [Symbol()]: 123, foo: 'abc' });
|
Object.entries({ [Symbol()]: 123, foo: 'abc' });
|
||||||
@ -886,13 +886,15 @@ Object.entries({ [Symbol()]: 123, foo: 'abc' });
|
|||||||
```javascript
|
```javascript
|
||||||
let obj = { one: 1, two: 2 };
|
let obj = { one: 1, two: 2 };
|
||||||
for (let [k, v] of Object.entries(obj)) {
|
for (let [k, v] of Object.entries(obj)) {
|
||||||
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
|
console.log(
|
||||||
|
`${JSON.stringify(k)}: ${JSON.stringify(v)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// "one": 1
|
// "one": 1
|
||||||
// "two": 2
|
// "two": 2
|
||||||
```
|
```
|
||||||
|
|
||||||
`Object.entries`方法的一个用处是,将对象转为真正的`Map`结构。
|
`Object.entries`方法的另一个用处是,将对象转为真正的`Map`结构。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var obj = { foo: 'bar', baz: 42 };
|
var obj = { foo: 'bar', baz: 42 };
|
||||||
|
@ -209,6 +209,7 @@
|
|||||||
- Jason Orendorff, [ES6 In Depth: Modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/): ES6模块设计思想的介绍
|
- Jason Orendorff, [ES6 In Depth: Modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/): ES6模块设计思想的介绍
|
||||||
- Ben Newman, [The Importance of import and export](http://benjamn.github.io/empirenode-2015/#/): ES6模块的设计思想
|
- Ben Newman, [The Importance of import and export](http://benjamn.github.io/empirenode-2015/#/): ES6模块的设计思想
|
||||||
- ESDiscuss, [Why is "export default var a = 1;" invalid syntax?](https://esdiscuss.org/topic/why-is-export-default-var-a-1-invalid-syntax)
|
- ESDiscuss, [Why is "export default var a = 1;" invalid syntax?](https://esdiscuss.org/topic/why-is-export-default-var-a-1-invalid-syntax)
|
||||||
|
- Bradley Meck, [ES6 Module Interoperability](https://github.com/nodejs/node-eps/blob/master/002-es6-modules.md): 介绍 Node 如何处理 ES6 语法加载 CommonJS 模块
|
||||||
|
|
||||||
## 二进制数组
|
## 二进制数组
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ v + w = 〈v1, …, vn〉+ 〈w1, …, wn〉
|
|||||||
|
|
||||||
上面代码中,`v`和`w`是两个多元矢量。它们的加运算,在 SIMD 下是一个指令、而不是 n 个指令完成的,这就大大提高了效率。这对于3D动画、图像处理、信号处理、数值处理、加密等运算是非常重要的。比如,Canvas的`getImageData()`会将图像文件读成一个二进制数组,SIMD 就很适合对于这种数组的处理。
|
上面代码中,`v`和`w`是两个多元矢量。它们的加运算,在 SIMD 下是一个指令、而不是 n 个指令完成的,这就大大提高了效率。这对于3D动画、图像处理、信号处理、数值处理、加密等运算是非常重要的。比如,Canvas的`getImageData()`会将图像文件读成一个二进制数组,SIMD 就很适合对于这种数组的处理。
|
||||||
|
|
||||||
总得来说,SIMD是数据并行处理(parallelism)的一种手段,可以加速一些运算密集型操作的速度。
|
总的来说,SIMD 是数据并行处理(parallelism)的一种手段,可以加速一些运算密集型操作的速度。将来与 WebAssembly 结合以后,可以让 JavaScript 达到二进制代码的运行速度。
|
||||||
|
|
||||||
## 数据类型
|
## 数据类型
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
1. [Set 和 Map 数据结构](#docs/set-map)
|
1. [Set 和 Map 数据结构](#docs/set-map)
|
||||||
1. [Proxy](#docs/proxy)
|
1. [Proxy](#docs/proxy)
|
||||||
1. [Reflect](#docs/reflect)
|
1. [Reflect](#docs/reflect)
|
||||||
|
1. [Promise 对象](#docs/promise)
|
||||||
1. [Iterator 和 for...of 循环](#docs/iterator)
|
1. [Iterator 和 for...of 循环](#docs/iterator)
|
||||||
1. [Generator 函数](#docs/generator)
|
1. [Generator 函数](#docs/generator)
|
||||||
1. [Promise对象](#docs/promise)
|
|
||||||
1. [异步操作和 Async 函数](#docs/async)
|
1. [异步操作和 Async 函数](#docs/async)
|
||||||
1. [Class](#docs/class)
|
1. [Class](#docs/class)
|
||||||
1. [Decorator](#docs/decorator)
|
1. [Decorator](#docs/decorator)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user