模块规范
使用分析
项目变大时需要把不同的业务分割成多个文件,这就是模块的思想。模块是比对象与函数更大的单元,使用模块组织程序便于维护与扩展。
生产环境中一般使用打包工具如 webpack
构建,他提供更多的功能。但学习完本章节后会再学习打包工具会变得简单。
- 模块就是一个独立的文件,里面是函数或者类库
- 虽然JS没有命名空间的概念,使用模块可以解决全局变量冲突
- 模块需要隐藏内部实现,只对外开发接口
- 模块可以避免滥用全局变量,造成代码不可控
- 模块可以被不同的应用使用,提高编码效率
实现原理
在过去JS不支持模块时我们使用AMD/CMD(浏览器端使用)
、CommonJS(Node.js使用)
、UMD(两者都支持)
等形式定义模块。
AMD代表性的是 require.js
,CMD 代表是淘宝的 seaJS
框架。
下面通过定义一个类似 require.js
的 AMD
模块管理引擎,来体验模块的工作原理。
仿写 AMD 规范构建
1 | let module = (function () { |
基础知识
标签使用
在浏览器中使用以下语法 靠Javascript脚本提供的模块化规范
管理各个js模块,这样就可以在js文件中使用模块代码了。
在html文件中导入模块,需要定义属性 type="module"
1 | <script type="module"></script> |
在浏览器中运行js模块化规范
管理的模块 在引入时,必须填写正确的路径 如./xxx.js
。
测试的 as.js
的模块内容如下
1 | export let as = { |
下面没有指定路径将发生错误
1 | <script type="module"> |
正确使用需要添加上路径
1 | <script type="module"> |
延迟解析
模块总是会在所有html解析后
才执行,下面的模块代码可以看到后加载的 button
按钮元素。
- 建议为用户提供加载动画提示,当模块运行时再去掉动画
1 | <body> |
默认严格
模块默认运行在严格模式,以下代码没有使用声明语句将报错
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> |
一次解析
模块在导入时只执行一次解析
,之后的导入不会再执行模块代码,而使用第一次解析结果,并共享数据。
- 可以在首次导入时完成一些初始化工作
- 如果模块内有后台请求,也只执行一次
引入多入as.js
脚本时只执行一次
1 | <script type="module" src="./as.js"></script> |
下面在导入多次 as.js
时只解析一次
1 | <script type="module"> |
导入导出
ES6使用基于文件的模块,即一个文件一个模块。
- 使用
export
将开发的接口导出 - 使用
import
导入模块接口
具名导出
下面定义模块 modules/as.js
,使用 export
具名导出模块接口,没有导出的变量都是模块私有的。
下面是对定义的 as.js
模块,分别导出内容
1 | let title="阿顺特烦恼"; |
批量导出
也可在定义被导出变量后,使用批量导出
一次性导出多个变量接口,export{…options}
1 | let title="阿顺特烦恼"; |
具名导入
具名导入
与具名导出
相对应,具名导出的模块接口,需要具名导入。
下面导入上面定义的 as.js
模块,分别导入模块导出的内容
1 | <script type="module"> |
也可只导入部分变量接口,需要哪些导入哪些,当然了 所导入的变量接口,必须被所引入的模块导出。
1 | <script type="module"> |
像下面这样在 {}
中导入是错误的,模块默认是在顶层静态导入
,这是为了分析使用的模块,方便打包
1 | if (true) { |
批量导入
也可使用批量导入语法import * as alias
,一次性将所有暴露的接口导入,并为批量导入的接口起一个别名
,之后通过别名来访问批量导出的对象。
*
整体为一个Object,为其设置别名后通过alias.propName
访问导出的具体变量
1 | <script type="module"> |
导入建议
因为以下几点,我们更建议使用明确导入方式
- 使用
webpack
构建工具时,没有导入的功能会删除节省文件大小 - 可以更清晰知道都使用了其他模块的哪些功能
别名使用
别名导入
可以为导入的模块重新命名,语法: import { oldName as newName …}
应用场景
- 有些导出的模块命名过长,起别名可以更加简洁
- 本模块与导入模块 变量重名时,可以通过起别名防止冲突
1 | let title="阿顺特烦恼"; |
模块导入使用 as
对接口重命名,本模块中已经存在 func
变量,需要对导入的模块重命名防止命名错误。
1 | <script type="module"> |
别名导出
模块可以对 暴露给外部的接口 起别名,下面是as.js
模块对暴露的接口起别名
1 | let title="阿顺特烦恼"; |
由于模块暴露时,为其接口起了别名,所以在具名导入时,应导入对应的别名。
1 | <script type="module"> |
默认导出
很多时候模块只是一个类,也就是说只需要导入一个内容,这地可以使用默认导入。
使用default
关键字 定义默认导出的接口,导入时不需要使用 {}
只能有一个默认导出
默认导出可以没有命名
在导入时可以任意命名
单一导出
下面是as.js
模块内容,默认只导出一个类。并且没有对类命名,这是可以的
1 | export default class { |
如果将一个导出的接口 分配别名为 default
也算默认导出
1 | class User { |
导入时就不需要使用 {}
来导入了
1 | <script type="module"> |
默认导出的接口 在引入时 可以随意命名
1 | <script type="module"> |
混合导出
模块可以存在默认导出与命名导出。
使用export default
导出默认接口,使用 export {}
导入普通接口
1 | let title="阿顺特烦恼"; |
也可以结合别名oldName as default
综合导出各个接口
1 | let title="阿顺特烦恼"; |
导入默认接口时不需要使用 {}
,普通接口还用 {}
导入
1 | <script type="module"> |
可以使用一条语句导入默认接口与常规接口,使用,
隔开
1 | import show, { name } from "/modules/ashun.js"; |
也可以使用别名导入 默认导出的接口
default as 自定义名称
,之后通过 自定义的变量 访问默认导出接口
1 | import { site, default as as } from "./as.js"; |
如果是批量导入时,使用 alias.default
获得默认导出
1 | <script type="module"> |
使用建议
对于默认导出和命名导出有以下建议
不建议使用默认导出,会让开发者导入时随意命名
1
2import as from "./as.js";
import shun from "./as.js";如果使用默认导入,自定义的名称最好和 模块的文件名 有关联,会使用代码更易阅读
1
import as from "./as.js";
导出合并
解决问题
可以将导入的模块重新导出使用,比如项目模块比较多如下所示,这时可以将所有模块合并到一个入口文件
中。
这样只需要使用一个模块入口文件,而不用关注多个模块文件
1 | |--test1.js |
实际使用
下面是 test1.js
模块内容
1 | const site = "阿顺特烦恼"; |
下面是 test2.js
模块内容
1 | export default class { |
下面是 test3.js
模块内容
1 | export function method() { |
下面是 index.js
模块内容,由于我们想让index.js为入口文件,所有的模块都存放其中,暴露给外部。ES模块化规范提供了对应的语法,在index.js
中导入其他模块的同时也将其导出
。
- 将
import
与export
合并使用,在导入的同时进行导出
1 | export * as as from "./test1.js"; |
使用方法如下
1 | <script type="module"> |
动态加载
使用 import
关键字 必须在顶层静态导入
模块,而使用import("path")
函数可以按需动态的导入模块,它返回一个 promise
对象。
静态导入
必须在顶层静态导入,否则会报错
1 | if (true) { |
按需动态导入
测试用的 as.js
模块内容如下
1 | const site = "阿顺特烦恼"; |
使用 import("path")
函数可以动态导入,实现按需加载
import()
返回promise
,可以使用promise方法在导入模块后进行操作import("path").then(module=>{}).catch(err=>{})
1 | <script> |
下面是在点击事件发生后按需要加载模块
1 | <button>阿顺特烦恼</button> |
因为是返回的对象可以使用解构语法
1 | <button>后盾人</button> |
指令总结
表达式 | 说明 |
---|---|
export function show(){} | 具名导出函数 |
export const name=’阿顺’ | 具名导出变量 |
export class User{} | 具名导出类 |
export { show , name , User } | 批量具名导出 |
export default show | 默认导出 |
export default show …… export{ name , User } | 混合导出 |
export { show as default , name , User } | 批量混合导出 |
export {name as shun_name} | 别名导出 |
import {name,show} from ‘as.js’ | 具名导入 |
import defaultVar from ‘ashun.js’ | 导入默认导出 |
import defaultVar,{name,show} from 'as.js' | 混合导入 |
import {name , show , default as defaultVar} from 'as.js' | 混合导入 |
Import { name as asName , show } from ‘ashun.js’ | 别名导入 |
Import * as api from ‘ashun.js’ | 导入全部接口 |
导入并导出
表达式 | 说明 |
---|---|
export { name, site } from “./as.js” | 将as.js中的name、site 具名导入并导出 |
export { name as asName , site } from “./as.js” | 将as.js中的name 别名导入导出;site 具名导入并导出 |
export { name as default } from “./as.js” | 将as.js中的name 具名导入再默认导出 |
export { default as defaultVar } from “./as.js” | 将as.js中的默认导出进行导入,再以defaultVar 具名导出 |
export * as asAPI from “./as.js”; | 将as.js中暴露的接口合并导入,再以asAPI 具名导出 |