随笔-深入理解ES6模块化(一)

========

用法注意


分别暴露 export

在阮一峰的ES6教程中关于export命令有这么一个注意点

需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

所以下面的代码报错原因都是:export后面跟的不是变量

// 报错

export 1;

// 报错

var m = 1;

export m;

有人肯定会有疑问,export 1 中 1确实不是变量,可以理解报错,但是 export m 中 m 就是变量啊,为什么还会报错?

其实这是我们一直以来口误导致的认识错误,所谓变量,其实只有在声明时的一瞬间有,声明过后,不管是单独使用,还是作为实参传递,它都是作为一个表达式,而不是变量。

我们可以通过JS代码编译成的AST抽象语法树来验证AST explorer

统一暴露 export {}

统一暴露的语法 export {} 中 {} 是一个对象吗?

var m = 1;

export {m};

如上图,很多人错误的理解{m}为是一个对象,且使用了对象的简写形式,其实不然。

export {} 是作为一个整体的语法存在,在{}中放入的是模块需要对外暴露内部变量,在形式上其实这里{}更像是一个数组,其数组元素就是模块内部变量。

而提供export {} 命令的原因,更多的是解决 export命令的不足,因为export必须要求和模块内部变量一一对应,这就需要 export 命令后面必须跟着声明式语句,而不能是表达式,限制性太高。

而 export {} 的提出,可以在表面上实现 基于模块内部变量表达式形式的导出。

默认暴露 export default

默认暴露和上面两种暴露最大的区别是,一个模块中默认暴露只能使用一次,而export 和 export {} 可以使用多次。

而默认暴露主要场景就是,针对一个模块只暴露一个接口的场景,此时我们需要对比export 和 export {} 只暴露一个接口的场景

export(导出时需要满足一一对应要求,导入时需要使用import {}命令接收 )

// b.js

export function b(){

console.log(‘b’)

}

// a.js

import {b} from ‘./b.js’

export (导出时必须要搞一个变量名给export {} 命令,否则export {} 无法获取变量,且必须使用 import {} 命令接收)

// b.js

var b = function(){

console.log(‘b’)

}

export {b}

// a.js

import {b} from ‘./b.js’

export default (由于默认暴露只能暴露一个接口,所以这个接口不需要名字就可以限定,同时模块只会暴露一个接口给import,所以imort也不需要名字限定,直接导出该接口即可,接口名可以任意定)

// b.js

export default function(){

console.log(‘b’)

}

// a.js

import haha from ‘./b.js’

实际上 export default 底层还是 定义了一个接口,该接口的名字是 default

相当于

function test() {

console.log(1)

}

export {

test as default

}

其次 分别暴露和统一暴露可以统称为具名暴露,即它们暴露的接口必须有名字,否则就会报错。而默认暴露的接口可以没有名字。

另外 具名暴露 不能暴露字面量,即没有变量名的值,因为它们需要体现和模块内部变量的一一对应关系。

而默认暴露由于只需要暴露一个接口,所以直接内置了一个默认接口default,既作为模块输出接口,又作为模块内部变量名字,所以默认暴露可以暴露字面量,底层也符合 输出接口和模块内部变量的一一对应关系。

导出的到底是啥?


export,export {}, export default 这三个导出命令,导出的东西到底是啥?

MDN的介绍

在创建JavaScript模块时,**export** 语句用于从模块中导出实时绑定的函数、对象或原始值,以便其他程序可以通过 import 语句使用它们。被导出的绑定值依然可以在本地进行修改。在使用import进行导入时,这些绑定值只能被导入模块所读取,但在export导出模块中对这些绑定值进行修改,所修改的值也会实时地更新。

The export statement is used when creating JavaScript modules to export live bindings to functions, objects, or primitive values from the module so they can be used by other programs with the import statement. The value of an imported binding is subject to change in the module that exports it. When a module updates the value of a binding that it exports, the update will be visible in its imported value.

阮一峰教程

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

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

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

MDN对于export导出的东西描述的很含糊,阮一峰大神在极力试图描述清楚export导出的东西,反复地使用了 “对外接口”,“值得引用”,“静态定义”,“动态引用”这几个词。

但是,我还是感觉阮一峰对于’模块‘和’模块的导出‘一直没说清楚,老是将两者混淆,不知道是用词不当,还是词不达意。

我觉得 无论是CommonJS还是ES6,模块都是指JS文件,每一个JS文件都是一个模块,因为每个JS文件只要被当成模块引入,就会形成一个模块作用域。即JS文件中的变量不会污染到全局。

阮一峰自己也在文章里面说了

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。

所以模块就是指JS文件(暂时不考虑其他类型文件的情况),这没啥好争议的。

当前需要搞清楚的是ES6模块的导出,即JS文件中export命令导出的是啥?

我们已经知道CommonJS模块的导出module.exports是一个对象了,阮一峰也明确说了ES6模块的导出,即JS文件中export命令输出的东西不是一个对象,而是一个对外接口,是一个值得引用,是一个静态定义,是一个动态引用。

阮大神一下子给这么多定义,其实想表达的就是一个意思:export输出的不是一个具体数据,即不是模块内部的变量的值,而是对变量的引用。

这里“引用”,困扰了我很久,因为阮大神一直在强调export命令输出的东西是在编译时确定的

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

但是,“引用” 不应该是运行时产生的东西吗?说到引用,我就想到了对象,而对象必须是在运行期间产生的,所以引用这个词有歧义。阮大神后面又给出了一个更加确切的名词:“符号连接”

JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

这个倒是有点感觉了,因为JS源码(a.mjs)

export var a = 1

function test(num){

return num++

}

const b = test(a)

console.log(b);

会被解析器解析为AST抽象语法树(d8 --print-ast a.mjs)

[generating bytecode for function: ]

— AST —

FUNC at 0

. KIND 1

. LITERAL ID 0

. SUSPEND COUNT 1

. NAME “”

. INFERRED NAME “”

. DECLS

. . VARIABLE (00000237DAE926D8) (mode = VAR, assigned = true) “a”

. . FUNCTION “test” = function test

. . VARIABLE (00000237DAE92A70) (mode = CONST, assigned = false) “b”

. EXPRESSION STATEMENT at -1

. . YIELD at 0

. . . VAR PROXY local[0] (00000237DAE92600) (mode = TEMPORARY, assigned = false) “.generator_object”

. BLOCK NOCOMPLETIONS at -1

. . EXPRESSION STATEMENT at 15

. . . INIT at 15

. . . . VAR PROXY module (00000237DAE926D8) (mode = VAR, assigned = true) “a”

. . . . LITERAL 1

. BLOCK NOCOMPLETIONS at -1

. . EXPRESSION STATEMENT at 72

. . . INIT at 72

. . . . VAR PROXY local[2] (00000237DAE92A70) (mode = CONST, assigned = false) “b”

. . . . CALL

. . . . . VAR PROXY local[1] (00000237DAE929E8) (mode = LET, assigned = true) “test”

. . . . . VAR PROXY module (00000237DAE926D8) (mode = VAR, assigned = true) “a”

. EXPRESSION STATEMENT at 83

. . ASSIGN at -1

. . . VAR PROXY local[3] (00000237DAE92CE8) (mode = TEMPORARY, assigned = true) “.result”

. . . CALL

. . . . PROPERTY at 91

. . . . . VAR PROXY unallocated (00000237DAE92DD8) (mode = DYNAMIC_GLOBAL, assigned = false) “console”

. . . . . NAME log

. . . . VAR PROXY local[2] (00000237DAE92A70) (mode = CONST, assigned = false) “b”

. RETURN at -1

. . VAR PROXY local[3] (00000237DAE92CE8) (mode = TEMPORARY, assigned = true) “.result”

[generating bytecode for function: test]

— AST —

FUNC at 33

. KIND 0

. LITERAL ID 1

. SUSPEND COUNT 0

. NAME “test”

. PARAMS

. . VAR (00000237DAE947F8) (mode = VAR, assigned = true) “num”

. DECLS

. . VARIABLE (00000237DAE947F8) (mode = VAR, assigned = true) “num”

. RETURN at 43

. . POST INC at 53

. . . VAR PROXY parameter[0] (00000237DAE947F8) (mode = VAR, assigned = true) “num”

AST会被Ignition解释器解释为字节码(d8 --print-bytecode a.mjs)

[generated bytecode for function: (0x036e0824fa5d )]

Parameter count 1

Register count 6

Frame size 48

0000036E0824FB06 @ 0 : ae fb 00 01 SwitchOnGeneratorState r0, [0], [1] { 0: @29 }

0000036E0824FB0A @ 4 : 27 fe f7 Mov , r4

0000036E0824FB0D @ 7 : 27 02 f6 Mov , r5

0000036E0824FB10 @ 10 : 64 0a f7 02 InvokeIntrinsic [_CreateJSGeneratorObject], r4-r5

0000036E0824FB14 @ 14 : 26 fb Star r0

0000036E0824FB16 @ 16 : 81 01 00 00 CreateClosure [1], [0], #0

0000036E0824FB1A @ 20 : 26 fa Star r1

0000036E0824FB1C @ 22 : 25 fb Ldar r0

0000036E0824FB1E @ 24 : af fb fb 04 00 SuspendGenerator r0, r0-r3, [0]

0000036E0824FB23 @ 29 : b0 fb fb 04 ResumeGenerator r0, r0-r3

0000036E0824FB27 @ 33 : 26 f7 Star r4

0000036E0824FB29 @ 35 : 64 0b fb 01 InvokeIntrinsic [_GeneratorGetResumeMode], r0-r0

0000036E0824FB2D @ 39 : a1 02 02 00 SwitchOnSmiNoFeedback [2], [2], [0] { 0: @49, 1: @46 }

0000036E0824FB31 @ 43 : 25 f7 Ldar r4

0000036E0824FB33 @ 45 : a8 Throw

0000036E0824FB34 @ 46 : 25 f7 Ldar r4

0000036E0824FB36 @ 48 : aa Return

0000036E0824FB37 @ 49 : 0c 01 LdaSmi [1]

0000036E0824FB39 @ 51 : 2c 01 00 StaModuleVariable [1], [0]

0000036E0824FB3C @ 54 : 2b 01 00 LdaModuleVariable [1], [0]

0000036E0824FB3F @ 57 : 26 f6 Star r5

0000036E0824FB41 @ 59 : 5d fa f6 00 CallUndefinedReceiver1 r1, r5, [0]

0000036E0824FB45 @ 63 : 26 f9 Star r2

0000036E0824FB47 @ 65 : 13 04 02 LdaGlobal [4], [2]

0000036E0824FB4A @ 68 : 26 f6 Star r5

0000036E0824FB4C @ 70 : 28 f6 05 04 LdaNamedProperty r5, [5], [4]

0000036E0824FB50 @ 74 : 26 f7 Star r4

0000036E0824FB52 @ 76 : 59 f7 f6 f9 06 CallProperty1 r4, r5, r2, [6]

0000036E0824FB57 @ 81 : 26 f8 Star r3

0000036E0824FB59 @ 83 : aa Return

Constant pool (size = 6)

0000036E0824FAC5: [FixedArray] in OldSpace

  • map: 0x036e080404b1

  • length: 6

0: 29

1: 0x036e0824fa85

2: 10

3: 7

4: 0x036e081c6971 <String[#7]: console>

5: 0x036e081c69e5 <String[#3]: log>

Handler Table (size = 0)

Source Position Table (size = 0)

[generated bytecode for function: test (0x036e0824fa85 )]

Parameter count 2

Register count 1

Frame size 8

0000036E0824FC6E @ 0 : 25 02 Ldar a0

0000036E0824FC70 @ 2 : 76 00 ToNumeric [0]

0000036E0824FC72 @ 4 : 26 fb Star r0

0000036E0824FC74 @ 6 : 4c 00 Inc [0]

0000036E0824FC76 @ 8 : 26 02 Star a0

0000036E0824FC78 @ 10 : 25 fb Ldar r0

0000036E0824FC7A @ 12 : aa Return

Constant pool (size = 0)

Handler Table (size = 0)

Source Position Table (size = 0)

以上过程就可以其实就是JS源码的预编译阶段工作(当然预编译不止这些工作,还有其他工作),和Java有点类型,将源码变为为字节码。

我们不关注其中具体流程,只是去理解,阮一峰教程所说的 ES6模块export输出的是一种静态定义的概念。

阮一峰教程中,一直在强调import,export在编译期间就完成了,即在运行之前就完成了,那么export导出的东西肯定不是运行时产生的对象,所以只能是AST树或者字节码中东西,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值