blog icon indicating copy to clipboard operation
blog copied to clipboard

前端模块化 - ES6 Module

Open HXWfromDJTU opened this issue 4 years ago • 0 comments

溯源

  • 与前辈CommonJSAMD 不同, 而ES6 尝试在语言层面上实现了模块化的功能
  • CommonJSAMD 都需要程序运行时才能够知道模块之间的依赖关系。而ES 6 Module要做到的是尽量的静态化,在编译时就确定模块的依赖关系,以及输出和输出的内容。

export

建立对应关系

export 命令规定的是对外的接口,起的作用必须是与模块内部建立起一以对应的关系。

动态绑定

export语句输出的接口,与其对应的是动态绑定的关系,模块内部值的变化,会引起外部引用的变化。

例子一:

// modulesA.js
export const number = 3
export function add () {
  number ++
}

// app.js
import { number, add } from 'moduleA'
console.log(number) // 3
add()
console.log(number) // 4

例子二:

// moduleA.js
class ModuleA {
  this.name = 'moduleA'
  this.getName = function () {
    return this.name
  }
}

export const objA = new ModuleA()

// a.js
import { objA } from 'moduleA'
objA.name = 'name has been changed'

// b.js
import { objA } from 'moduleA'
setTimeout(() => {
  console.log(objA.name) // 'name has been changed'
})

以上两个例子都可以说明,模块内部,引用方拿到的模块引用,都是指向同一个内存空间,任意一方改变了内存空间存储的值,那么任意一方取到的都是最新的值。

必须处于顶级模块

ES6 Module 的初衷是实现静态分析模块化,所以export必须处于模块顶层中,不能处于块级作用域内。

import

变量提升

import会在静态解析时,会因为变量提升解拆分成声明与赋值两个部分。

console.log(abc)
import { abc } from 'module.js' // 并不会报错
静态模块

export相类似,同样遵循ES 6静态模块分析的设计初衷,import 语句同样不能处于块级作用域中,导出的值也不能够是需要运算后得出的结果

引用不可修改

import命令输入的变量都是只读的,因为本质上来说,引入的是输入接口。改变引用本身,则会破坏模块之间的引用关系。

原则上不允许修改接口,但可以修改接口里面的值,其他的模块也能够拿到修改后的值,但是我们不推荐这样做,因为这样的修改是不可以追踪的。

如下例子:

// common.js
export const config = {
  port: '8899',
  host: '123.77.33.56'
}

// a.js
import { config } from 'common.js'
config.port = '1234' // 不会报错

//b.js
import { config } from 'common.js'
console.log(config.port) // 一脸懵逼地拿到了 1234这个值,b的开发者回去看 common.js 又发现不了问题
仅执行但不引入

一下的语句,仅仅会执行my.module模块的内容,但并不会引入任何代码。

import 'my-module'
整体引入
import * as myModule from 'my-module'
路径解析

优先解析./../等相对路径,直接写模块名称则需要根据配置文件的规则来约定。

$ node --experimental-modules a.mjs  # 这里用node来运行一下

比如出现以上循环引用的情况下,a.js为入口moduleB被首先加载

import()

补充了运行时解析
  • ES 6 Module的最终目的是要取代CommonJSAMD,一统天下。那么在运行时的模块化中,使用import()函数来替代require()方法。
  • import()import 语句不一样,前者产生的是运行时执行的异步加载,后者产生的是静态的连接关系
异步加载

import() 进行异步加载,方法返回的是一个Promise对象,那么显而易见的require()则是一个运行时同步加载的过程。

// 异步加载
import list from './list';

// 同步加载
const list = require('./list');
使用场景

因为import()是运行时执行,显而易见的我们可以用于做code spliting

const router = new VueRouter({
  routes: [
    { 
      path: '/foo', component: import('./Foo.vue'),
      path: '/bar', component: import('./Bar.vue'),
    }
  ]
})

详细请参考👉这里

export default

默认名称为 default

使用 export default相当于对外暴露了一个名为 default的变量,变量的值直接跟在export default后面。

export from

模块转发

使用export from可以实现模块转发,(表面上相当于是import 与 export的结合),但要实际进行静态解析的时候,当前模块是没有引入目标对象的。

// moduleA
export { foo, bar } from 'moduleB'

console.log(foo) // error
转发接口改名

中间模块转发接口的时候,可以对接口进行改名

export { default as myModule } from 'that-module'

export * as myModule from 'that-module'

标签引入

type="module"
<script type="module" src="./foo.js" type="module"></script>

<!-- 效果类似于 -->
<script src="./foo.js" defer></script>

defer关键字表示脚本的执行延迟到文档加载完成之后。

module关键字也能够使得JS在DOM加载完后才执行,兼容性不如前者,IE封杀,edge 16+,FF60+,Chrome 61+

当二者同时出现的时候,执行顺序是一般脚本 > module脚本 > defer脚本 > DOMContentLoaded事件

参考资料

ES 6 入门 阮一峰- Module 的语法

HXWfromDJTU avatar May 17 '20 09:05 HXWfromDJTU