1
0
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:
ruanyf 2017-02-02 08:54:35 +08:00
parent 4d599e8785
commit 4bf719ef8f
5 changed files with 241 additions and 42 deletions

View File

@ -12,7 +12,9 @@ let { stat, exists, readFile } = 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个方法。这种加载称为“运行时加载”因为只有运行时才能得到这个对象导致完全没办法在编译时做“静态优化”。
@ -58,6 +60,8 @@ ES6 的模块自动采用严格模式,不管你有没有在模块头部加上`
上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6所以请参阅相关 ES5 书籍,本书不再详细介绍了。
其中,尤其需要注意`this`的限制。ES6 模块之中,顶层的`this`指向`undefined`,即不应该在顶层代码使用`this`
## export 命令
模块功能主要由两个命令构成:`export``import``export`命令用于规定模块的对外接口,`import`命令用于输入其他模块提供的功能。
@ -71,7 +75,7 @@ export var lastName = 'Jackson';
export var year = 1958;
```
上面代码是`profile.js`文件保存了用户信息。ES6将其视为一个模块里面用`export`命令对外部输出了三个变量。
上面代码是`profile.js`文件保存了用户信息。ES6 将其视为一个模块,里面用`export`命令对外部输出了三个变量。
`export`的写法,除了像上面这样,还有另外一种。
@ -86,7 +90,7 @@ export {firstName, lastName, year};
上面代码在`export`命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在`var`语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。
export命令除了输出变量还可以输出函数或类class
`export`命令除了输出变量还可以输出函数或类class
```javascript
export function multiply(x, y) {
@ -163,7 +167,7 @@ setTimeout(() => foo = 'baz', 500);
上面代码输出变量`foo`,值为`bar`500毫秒之后变成`baz`
这一点与CommonJS规范完全不同。CommonJS模块输出的是值的缓存不存在动态更新详见下文《ES6模块加载的实质》一节。
这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存不存在动态更新详见下文《ES6模块加载的实质》一节。
最后,`export`命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的`import`命令也是如此。这是因为处于条件代码块之中就没法做静态优化了违背了ES6模块的设计初衷。
@ -399,6 +403,18 @@ export default var a = 1;
上面代码中,`export default a`的含义是将变量`a`的值赋给变量`default`。所以,最后一种写法会报错。
同样地,因为`export default`本质是将该命令后面的值,赋给`default`变量以后再默认,所以直接将一个值写在`export default`之后。
```javascript
// 正确
export default 42;
// 报错
export 42;
```
上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定外对接口为`default`
有了`export default`命令,输入模块时就非常直观了,以输入 lodash 模块为例。
```javascript
@ -417,20 +433,16 @@ import _, { each } from 'lodash';
export default function (obj) {
// ···
}
export function each(obj, iterator, context) {
// ···
}
export { each as forEach };
```
上面代码的最后一行的意思是,暴露出`forEach`接口,默认指向`each`接口,即`forEach``each`指向同一个方法。
如果要输出默认的值,只需将值跟在`export default`之后即可。
```javascript
export default 42;
```
`export default`也可以用来输出类。
```javascript
@ -488,14 +500,20 @@ export default es6;
export { default as es6 } from './someModule';
```
另外ES7有一个[提案](https://github.com/leebyron/ecmascript-more-export-from),简化先输入后输出的写法,拿掉输出时的大括号
下面三种`import`语句,没有对应的复合写法
```javascript
// 现行的写法
export {v} from 'mod';
import * as someIdentifier from "someModule";
import someIdentifier from "someModule";
import someIdentifier, { namedIdentifier } from "someModule";
```
// 提案的写法
export v from 'mod';
为了做到形式的对称,现在有[提案](https://github.com/leebyron/ecmascript-export-default-from),提出补上这三种复合写法。
```javascript
export * as someIdentifier from "someModule";
export someIdentifier from "someModule";
export someIdentifier, { namedIdentifier } from "someModule";
```
## 模块的继承
@ -540,9 +558,9 @@ console.log(exp(math.e));
## ES6模块加载的实质
ES6模块加载的机制与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝而ES6模块输出的是值的引用。
ES6 模块加载的机制,与 CommonJS 模块完全不同。CommonJS模块输出的是一个值的拷贝 ES6 模块输出的是值的引用。
CommonJS模块输出的是被输出值的拷贝也就是说一旦输出一个值模块内部的变化就影响不到这个值。请看下面这个模块文件`lib.js`的例子。
CommonJS 模块输出的是被输出值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件`lib.js`的例子。
```javascript
// lib.js
@ -591,7 +609,7 @@ $ node main.js
4
```
ES6 模块的运行机制与 CommonJS 不一样。JS引擎对脚本静态分析的时候遇到模块加载命令`import`就会生成一个只读引用。等到脚本真正执行时再根据这个只读引用到被加载的那个模块里面去取值。换句话说ES6 的`import`有点像 Unix 系统的“符号连接”,原始值变了,`import`加载的值也会跟着变。因此ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令`import`就会生成一个只读引用。等到脚本真正执行时再根据这个只读引用到被加载的那个模块里面去取值。换句话说ES6 的`import`有点像 Unix 系统的“符号连接”,原始值变了,`import`加载的值也会跟着变。因此ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
还是举上面的例子。
@ -635,9 +653,9 @@ bar
baz
```
上面代码表明ES6模块不会缓存运行结果而是动态地去被加载的模块取值并且变量总是绑定其所在的模块。
上面代码表明ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
由于ES6输入的模块变量只是一个“符号连接”所以这个变量是只读的对它进行重新赋值会报错。
由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。
```javascript
// lib.js
@ -650,7 +668,7 @@ obj.prop = 123; // OK
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`通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
@ -685,7 +703,7 @@ import './x';
import './y';
```
现在执行`main.js`输出的是1。
现在执行`main.js`,输出的是`1`
```bash
$ babel-node main.js
@ -712,6 +730,184 @@ $ babel-node main.js
- 该脚本内部的顶层变量,都只在该脚本内部有效,外部不可见。
- 该脚本内部的顶层的`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`脚本。

View File

@ -872,27 +872,29 @@ Object.entries(obj)
除了返回值不一样,该方法的行为与`Object.values`基本一致。
如果原对象的属性名是一个Symbol值该属性会被省略。
如果原对象的属性名是一个 Symbol 值,该属性会被忽略。
```javascript
Object.entries({ [Symbol()]: 123, foo: 'abc' });
// [ [ 'foo', 'abc' ] ]
```
上面代码中,原对象有两个属性,`Object.entries`只输出属性名非Symbol值的属性。将来可能会有`Reflect.ownEntries()`方法,返回对象自身的所有属性。
上面代码中,原对象有两个属性,`Object.entries`只输出属性名非 Symbol 值的属性。将来可能会有`Reflect.ownEntries()`方法,返回对象自身的所有属性。
`Object.entries`的基本用途是遍历对象的属性。
```javascript
let obj = { one: 1, two: 2 };
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
// "two": 2
```
`Object.entries`方法的一个用处是,将对象转为真正的`Map`结构。
`Object.entries`方法的一个用处是,将对象转为真正的`Map`结构。
```javascript
var obj = { foo: 'bar', baz: 42 };

View File

@ -209,6 +209,7 @@
- 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模块的设计思想
- 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 模块
## 二进制数组

View File

@ -2,9 +2,9 @@
## 概述
SIMD发音`/sim-dee/`是“Single Instruction/Multiple Data”的缩写意为“单指令多数据”。它是JavaScript操作CPU对应指令的接口你可以看做这是一种不同的运算执行模式。与它相对的是SISD“Single Instruction/Single Data”即“单指令单数据”。
SIMD发音`/sim-dee/`是“Single Instruction/Multiple Data”的缩写意为“单指令多数据”。它是 JavaScript 操作 CPU 对应指令的接口,你可以看做这是一种不同的运算执行模式。与它相对的是 SISD“Single Instruction/Single Data”即“单指令单数据”。
SIMD的含义是使用一个指令完成多个数据的运算SISD的含义是使用一个指令完成单个数据的运算这是JavaScript的默认运算模式。显而易见SIMD的执行效率要高于SISD所以被广泛用于3D图形运算、物理模拟等运算量超大的项目之中。
SIMD 的含义是使用一个指令完成多个数据的运算SISD 的含义是使用一个指令,完成单个数据的运算,这是 JavaScript 的默认运算模式。显而易见SIMD 的执行效率要高于 SISD所以被广泛用于3D图形运算、物理模拟等运算量超大的项目之中。
为了理解SIMD请看下面的例子。
@ -22,7 +22,7 @@ c // Array[6, 8, 10, 12]
上面代码中,数组`a``b`的对应成员相加,结果放入数组`c`。它的运算模式是依次处理每个数组成员一共有四个数组成员所以需要运算4次。
如果采用SIMD模式只要运算一次就够了。
如果采用 SIMD 模式,只要运算一次就够了。
```javascript
var a = SIMD.Float32x4(1, 2, 3, 4);
@ -32,22 +32,22 @@ var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12]
上面代码之中,数组`a``b`的四个成员的各自相加只用一条指令就完成了。因此速度比上一种写法提高了4倍。
一次SIMD运算可以处理多个数据这些数据被称为“通道”lane。上面代码中一次运算了四个数据因此就是四个通道。
一次 SIMD 运算可以处理多个数据这些数据被称为“通道”lane。上面代码中一次运算了四个数据因此就是四个通道。
SIMD通常用于矢量运算。
SIMD 通常用于矢量运算。
```javascript
v + w = 〈v1, …, vn〉+ 〈w1, …, wn〉
= 〈v1+w1, …, vn+wn〉
```
上面代码中,`v``w`是两个多元矢量。它们的加运算在SIMD下是一个指令、而不是n个指令完成的这就大大提高了效率。这对于3D动画、图像处理、信号处理、数值处理、加密等运算是非常重要的。比如Canvas的`getImageData()`会将图像文件读成一个二进制数组SIMD就很适合对于这种数组的处理。
上面代码中,`v``w`是两个多元矢量。它们的加运算,在 SIMD 下是一个指令、而不是 n 个指令完成的这就大大提高了效率。这对于3D动画、图像处理、信号处理、数值处理、加密等运算是非常重要的。比如Canvas的`getImageData()`会将图像文件读成一个二进制数组SIMD 就很适合对于这种数组的处理。
得来说SIMD是数据并行处理parallelism的一种手段可以加速一些运算密集型操作的速度。
的来说SIMD 是数据并行处理parallelism的一种手段可以加速一些运算密集型操作的速度。将来与 WebAssembly 结合以后,可以让 JavaScript 达到二进制代码的运行速度。
## 数据类型
SIMD提供12种数据类型总长度都是128个二进制位。
SIMD 提供12种数据类型总长度都是128个二进制位。
- Float32x4四个32位浮点数
- Float64x2两个64位浮点数
@ -71,7 +71,7 @@ SIMD提供12种数据类型总长度都是128个二进制位。
- 无符号的整数Uint比如1
- 布尔值Bool包含`true``false`两种值)
每种SIMD的数据类型都是一个函数方法可以传入参数生成对应的值。
每种 SIMD 的数据类型都是一个函数方法,可以传入参数,生成对应的值。
```javascript
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
@ -712,5 +712,5 @@ function average(list) {
}
```
上面代码先是每隔四位将所有的值读入一个SIMD然后立刻累加。然后得到累加值四个通道的总和再除以`n`就可以了。
上面代码先是每隔四位,将所有的值读入一个 SIMD然后立刻累加。然后得到累加值四个通道的总和再除以`n`就可以了。

View File

@ -7,7 +7,7 @@
## 目录
1. [前言](#README)
1. [ECMAScript 6简介](#docs/intro)
1. [let和const命令](#docs/let)
1. [let const 命令](#docs/let)
1. [变量的解构赋值](#docs/destructuring)
1. [字符串的扩展](#docs/string)
1. [正则的扩展](#docs/regex)
@ -16,13 +16,13 @@
1. [函数的扩展](#docs/function)
1. [对象的扩展](#docs/object)
1. [Symbol](#docs/symbol)
1. [Set和Map数据结构](#docs/set-map)
1. [Set Map 数据结构](#docs/set-map)
1. [Proxy](#docs/proxy)
1. [Reflect](#docs/reflect)
1. [Iterator和for...of循环](#docs/iterator)
1. [Generator函数](#docs/generator)
1. [Promise对象](#docs/promise)
1. [异步操作和Async函数](#docs/async)
1. [Promise 对象](#docs/promise)
1. [Iterator 和 for...of 循环](#docs/iterator)
1. [Generator 函数](#docs/generator)
1. [异步操作和 Async 函数](#docs/async)
1. [Class](#docs/class)
1. [Decorator](#docs/decorator)
1. [Module](#docs/module)