js红宝石 第二十六章-模块

26.1 理解模块模式

把代码拆分成独立的块,在连接起来,可以通过模块模式实现

把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么

26.1.1 模块标识符

模块标识符是所有模块系统通用的概念

将模块标识符解析为实际模块的过程要根据模块系统对标识符的实现

26.1.2 模块依赖

模块系统的核心是管理依赖

每个模块都会和唯一的标识符关联,该标识符可以用来检索模块

这个标识符通常是JS的文件路径

26.1.3 模块加载

加载模块的概念派生自依赖契约

当一个外部模块被指定为依赖时,本地模块期望在执行它时,依赖已经准备好并已经初始化

26.1.4 入口

相互依赖的模块必须指定一个模块作为入口(entry point)

因为JS的代码是顺序执行的,而且是单线程的,所以代码必须有执行的起点

可以通过依赖图来表示

模块加载是阻塞的,前置的操作必须完成才能执行后续操作

26.1.5 异步依赖

可以让JS在必要的时候通知加载新模块,并且在加载完成后执行回调

可以通过下面的伪代码的实现:

// 在模块A中
load("moduleB").then(function(moduleB){
    moduleB.doStuff()
})

26.1.6 动态依赖

有些模块系统音系开发者在程序结构中动态添加依赖

动态依赖支持更复杂的依赖关系,但是增加了对模块静态分析的难度

26.1.7 静态分析

模块中的代码会被JS静态分析,分析工具会检查代码结构,而不是在实际运行代码的情况下推断其行为

对静态分析友好的模块系统可以让模块打包系统更容易把代码处理为较少文件

26.1.7 循环依赖

几乎不可能构造一个没有循环依赖的JS应用程序

即使依赖图中存在循环依赖,任何模块都可以作为入口模块

修改主模块中用到的模块会改变依赖加载顺序

26.2 凑合的模块系统

为按照模块模式提供必要的封装,ES6之前的模块有时候会使用函数作用域和立即调用函数表达式将模块定义在封装的匿名闭包中

还有一种模式叫泄露模块模式,只返回一个对象,是私有数据和成员的引用

不建议在实际开发中自己写模块

26.3 使用ES6之前的模块加载器

26.3.1 CommonJS

CommonJS规范概述了同步声明依赖的模块定义

主要用于在服务器实现模块化代码组织

CommonJS模块语法不能在浏览器中直接运行

CommonJS需要使用require()指定依赖,exports定义自己的公共API

    var moduleB = require(URL)

    module.exports = {
      stuff: moduleB.stuff()
    }

CommonJS依赖require和module.exports属性,要在其非原生的模板语法之间构建桥梁

常见的解决方法是提前把模块文件打包好,最终只提供一个文件

26.3.2 异步模块定义

CommonJS是以服务器端为目标环境,可以一次性把所有模块加载到内存

而异步模块定义AMD则以浏览器为执行环境,要考虑网络延迟

AMD模块的实现核心是用函数包装模块定义.这样可以防止声明全局变量,并允许加载器库控制何时加载模块

AMD模块可以使用字符串标识指定自己的依赖,AMD会在所有依赖模块加载完毕后调用模块工厂函数

AMD也支持require和module.exports的操作,可以在AMD模块的内部定义CommonJS风格的模块

26.3.3 通用模块定义

为了统一CommonJS和AMD生态系统.通用模块定义UMD应运而生

UMD定义的模式会在启动时检测要使用哪一个模块系统,然后进行适当配置

此模式有支持严格CommonJS和浏览器全局的上下文变体,所以不建议手写包装函数

模块加载器会被(已经)被ES6取代

26.4 使用ES6模块

ES6模块是AMD和CommonJS的集大成者

26.4.1 模块标签及定义

ES6模块是作为一整块JS代码而引进的

带有type="module"属性的<script>标签会告诉浏览器响应代码是模块

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

当脚本执行到 module时,会立即下载模块文件,但是执行会推迟到文档解析完成

也可以给模块增加async属性,这样的影响:模块执行顺序不再以<script>标签的位置绑定,而且模块也不会等待文档才完成解析才执行

module属性的标签被认为是模块图中的入口模块

同一个模块,无论重复多少次,也无论是怎样加载的,实际上就只会加载一次

嵌入的模块定义代码不能使用import加载到其他模块

26.4.2 模块加载

ES6的模块,既可以通过原生浏览器加载,也可以通过第三方加载工具和构建工具一起加载

完全支持ES6模块的浏览器可以从顶级模块加载整个依赖图,而且是异步完成的

26.4.3 模块行为

ES6借用了CommonJS和AMD的很多优秀特性

p784

26.4.4 模块导出

ES6的模块到处使用export关键字

export关键字必须在模块顶级,不能再某个嵌套中

命名导出就好像模块是被导出值的容器

    const foo = 'foo'
    export { foo }

也可以使用別名

    const foo = 'foo'
    export { foo as MyFoo }

 默认导出就好像模块和被导出值是一回事

使用default将一个值声明为默认导出

    const foo = 'foo'
    export { foo as default }
    // 等价于
    export default foo

26.4.5 模块导入 

模块可以通过使用import关键字使用其他模块导出的值

import必须在模块的顶级

import语句被提升到模块的顶部,但是建议书写时就放在顶部

模块标识符可以是绝对路径或者相对路径

导入对于模块而言是只读的,相当于const声明的变量

命名导出可以使用*批量获取并赋值给导出集合的别名

    const foo = 'foo',bar = 'bar',baz = 'baz'
    export {foo, bar, baz}
    import * as Foo from './foo.js'

    Foo.foo
    Foo.bar
    Foo.baz

要指明导入,需要把标识符放在import中

    const foo = 'foo',bar = 'bar',baz = 'baz'
    export {foo, bar, baz}
    import { foo,bar,baz as myBaz} as Foo from './foo.js'

    foo
    bar
    myBaz

26.4.6 模块转移导出

模块导入的值可以直接通过管道转移到导出

要想把一个模块中所有命名导出集中在一块,可以使用*导出

    // 在bar.js中
    export * from './foo.js'

这样子foo.js中所有的命名导出都会出现在导入bar.js的模块中

26.4.7 工作者模块

ES6模块与Worker实例完全兼容

在实例化时可以给Worker构造函数传入一个指向模块文件的路径

Worker构造函数也接受第二个参数,用来说明传入的是模块文件

26.4.8 向后兼容

不兼容的浏览器会忽略 <script type='module'>下面的代码,而执行<script nomodule src="script.js">中的代码

兼容的浏览器则不会执行第二段,防止调用两次

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值