模块设计
模块化
项目变大时需要把不同的业务分割成多个文件,这就是模块的思想。模块是比对象与函数更大的单元,使用模块组织程序便于维护与扩展
生产环境中一般使用打包工具如 webpack
构建,他提供更多的功能。但学习完本章节后会再学习打包工具会变得简单
- 模块就是一个独立的文件,里面是函数或者类库
- 虽然JS没有命名空间的概念,使用模块可以解决全局变量冲突
- 模块需要隐藏内部实现,只对外开发接口
- 模块可以避免滥用全局变量,造成代码不可控
- 模块可以被不同的应用使用,提高编码效率
实现原理
在过去JS不支持模块时我们使用AMD/CMD(浏览器端使用)
、CommonJS(Node.js使用)
、UMD(两者都支持)
等形式定义模块。
AMD代表性的是 require.js
,CMD 代表是淘宝的 seaJS
框架。
下面通过定义一个类似 require.js
的 AMD
模块管理引擎,来体验模块的工作原理
1 | let module = (function () { |
基础知识
标签使用
在浏览器中使用以下语法靠之脚本做为模块使用,这样就可以在里面使用模块的代码了
在html文件中导入模块,需要定义属性 type="module"
1 | <script type="module"></script> |
模块路径
在浏览器中引用模块必须添加路径如./
,但在打包工具如webpack
中则不需要,因为他们有自己的存放方式
测试的 demo.js
的模块内容如下
1 | export function two (){ |
使用需要添加上路径
1 | <script type="module"> |
严格模式
模块默认运行在严格模式,以下代码没有使用声明语句将报错
1 | <script type="module"> |
下面的 this
也会是 undefined
1 | <script> |
作用域
模块都有独立的顶级作用域,下面的模块不能互相访问,错误示范
1 | <script type="module"> |
单独文件作用域也是独立的,下面的模块 1.2.js
不能访问模块 1.1.js
中的数据
1 | <script type="module" src="1.1.js"></script> |
预解析
模块在导入时只执行一次解析,之后的导入不会再执行模块代码,而使用第一次解析结果,并共享数据。
- 可以在首次导入时完成一些初始化工作
- 如果模块内有后台请求,也只执行一次即可
引入多入hd.js
脚本时只执行一次
1 | <script type="module" src="hd.js"></script> |
在导入多次 hd.js
时只解析一次
1 | <script type="module"> |
导入导出
ES6使用基于文件的模块,即一个文件一个模块
- 使用
export
将开发的接口导出 - 使用
import
导入模块接口 - 使用
*
可以导入全部模块接口 - 导出是以引用方式导出,无论是变量还是对象,即模块内部变量发生变化将影响已经导入的变量
导出模块
下面定义模块 modules/demo.js
,使用 export
导出模块接口,没有导出的变量都是模块私有的。
下面是对定义的 two.js
模块,分别导出内容
1 | export const site = "陈若"; |
下面定义了two.js
模块,并使用指量导出
1 | const site = "陈若" |
具名导入
下面导入上面定义的 two.js
模块,分别导入模块导出的内容
1 | <script type="module"> |
批量导入
如果要导入的内容比较多,可以使用 *
来批量导入
1 | <script type="module"> |
导入建议
因为以下几点,我们更建议使用明确导入方式
- 使用
webpack
构建工具时,没有导入的功能会删除节省文件大小 - 可以更清晰知道都使用了其他模块的哪些功能
别名使用
导入别名
可以为导入的模块重新命名,下面是为了测试定义的 two.js
模块内容。
- 有些导出的模块命名过长,起别名可以理简洁
- 本模块与导入模块重名时,可以通过起别名防止错误
模块导入使用 as
对接口重命名,本模块中已经存在 func
变量,需要对导入的模块重命名防止重名错误
1 | <script type="module"> |
导出别名
模块可以对导出给外部的功能起别名,下面是two.js
模块对导出给外部的模块功能起了别名
1 | const site = "陈若" |
这时就要使用新的别名导入了
1 | <script type="module"> |
默认导出
很多时候模块只是一个类,也就是说只需要导入一个内容,这可以使用默认导入
使用default
定义默认导出的接口,导入时不需要使用 {}
- 可以为默认导出自定义别名
- 只能有一个默认导出
- 默认导出可以没有命名
单一导出
下面是two.js
模块内容,默认只导出一个类。并且没有对类命名,这是可以的
1 | export default class { |
从程序来讲如果将一个导出命名为 default
也算默认导出
1 | class User { |
导入时就不需要使用 {}
来导入了
1 | <script type="module"> |
默认导出的功能可以使用任意变量接收
1 | <script type="module"> |
混合导出
模块可以存在默认导出与命名导出。
使用export default
导出默认接口,使用 export {}
导入普通接口
1 | const site = "陈若" |
可以使用以下方式导出模块
1 | const site = "陈若" |
导入默认接口时不需要使用 {}
,普通接口还用 {}
导入
1 | <script type="module"> |
使用建议
对于默认导出和命名导出有以下建议
不建议使用默认导出,会让开发者导入时随意命名
1
2import two from "./two.js";
import xj from "./two.js";如果使用默认导入最好以模块的文件名有关联,会使用代码更易阅读
1
import two from "./two.js";
导出合并
解决问题
可以将导入的模块重新导出使用,比如项目模块比较多如下所示,这时可以将所有模块合并到一个入口文件中
这样只需要使用一个模块入口文件,而不用关注多个模块文件
1 | //two.js |
动态加载
使用 import
必须在顶层静态导入模块,而使用import()
函数可以动态导入模块,它返回一个 promise
对象
静态导入
使用 import
顶层静态导入,像下面这样在 {}
中导入是错误的,这是为了分析使用的模块方便打包,所以系统禁止这种行为
1 | if (true) { |
动态使用
测试用的 one.js
模块内容如下
1 | let site = "陈若" |
使用 import()
函数可以动态导入,实现按需加载
1 | <script> |
在点击事件发生后按需要加载模块,案例
1 | <body> |
因为是返回的对象可以使用解构语法
1 | <body> |
指令总结
表达式 | 说明 |
---|---|
export function show(){} | 导出函数 |
export const name=’xxx’ | 导出变量 |
export class User{} | 导出类 |
export default show | 默认导出 |
const name = ‘xxx’ export {name} | 导出已经存在变量 |
export {name as chen_name} | 别名导出 |
import defaultVar from ‘one.js’ | 导入默认导出 |
import {name,show} from ‘demo.j’ | 导入命名导出 |
Import {name as haName,show} from ‘two.js’ | 别名导入 |
Import * as api from ‘demo.js’ | 导入全部接口 |