js中的模块化开发
模块设计
- 模块就是一个独立的文件,里面是函数或者类库
- 虽然 JS 没有命名空间的概念,使用模块可以解决全局变量冲突
- 模块需要隐藏内部实现,只对外开发接口
- 模块可以避免滥用全局变量,造成代码不可控
- 模块可以被不同的应用使用,提高编码效率
标签使用
在浏览器中使用以下语法靠之脚本做为模块使用,这样就可以在里面使用模块的代码了。
在 html 文件中导入模块,需要定义属性 type="module"
<script type="module"></script>
模块路径
在浏览器中引用模块必须添加路径如./
,但在打包工具如webpack
中则不需要,因为他们有自己的存放方式。
测试的 dd.js
的模块内容如下
export let dd = {
name: "dd"
};
使用需要添加上路径
<script type="module">
import { dd } from "./dd.js";
</script>
延迟解析
模块总是会在所有 html 解析后才执行,下面的模块代码可以看到后加载的 button
按钮元素。
- 建议为用户提供加载动画提示,当模块运行时再去掉动画
<body>
<script type="module">
console.log(document.querySelector("button")); //Button
</script>
<script>
console.log(document.querySelector("button")); //undefined
</script>
<button>后盾人</button>
</body>
作用域
模块都有独立的顶级作用域,下面的模块不能互相访问
<script type="module">
let hd = "houdunren.com";
</script>
<script type="module">
alert(hd); // Error
</script>
单独文件作用域也是独立的,下面的模块 1.2.js
不能访问模块 1.1.js
中的数据
<script type="module" src="1.1.js"></script>
<script type="module" src="1.2.js"></script>
文件内容如下
# 1.1.js
let hd = "houdunren";
# 1.2.js
console.log(hd)
预解析
模块在导入时只执行一次解析,之后的导入不会再执行模块代码,而使用第一次解析结果,并共享数据。
- 可以在首次导入时完成一些初始化工作
- 如果模块内有后台请求,也只执行一次即可
导入导出
ES6 使用基于文件的模块,即一个文件一个模块。
- 使用
export
将开发的接口导出 - 使用
import
导入模块接口 - 使用
*
可以导入全部模块接口 - 导出是以引用方式导出,无论是标量还是对象,即模块内部变量发生变化将影响已经导入的变量
导出模块
使用 export
导出模块接口,没有导出的变量都是模块私有的。
具名导入
import { User, site, func } from "./hd.js";
批量导入(不建议)
如果要导入的内容比较多,可以使用 *
来批量导入。
<script type="module">
import * as api from "./hd.js";
console.log(api.site);
console.log(api.User);
</script>
别名
使用 as
对接口重命名
导入别名
import { User as user, func as action, site as name } from "./hd.js";
导出别名
export { site as ss, func as ff, User };
默认导出
很多时候模块只是一个类,也就是说只需要导入一个内容,这地可以使用默认导入。
使用default
定义默认导出的接口,导入时不需要使用 {}
- 可以为默认导出自定义别名
- 只能有一个默认导出
- 默认导出可以没有命名
单一导出
下面是hd.js
模块内容,默认只导出一个类。并且没有对类命名,这是可以的
export default class {
static show() {
console.log("User.method");
}
}
从程序来讲如果将一个导出命名为 default
也算默认导出
class User {
static show() {
console.log("User.method");
}
}
export { User as default };
导入时就不需要使用 {}
来导入了
<script type="module">
import User from "./hd.js";
User.show();
</script>
默认导出的功能可以使用任意变量接收
<script type="module">
import hd from "./hd.js";
hd.show();
</script>
混合导出
模块可以存在默认导出与命名导出。
使用export default
导出默认接口,使用 export {}
导入普通接口
const site = "后盾人";
const func = function() {
console.log("is a module function");
};
export default class {
static show() {
console.log("user.show");
}
}
export { site, func };
也可以使用以下方式导出模块
const site = "后盾人";
const func = function() {
console.log("is a module function");
};
class User {
static show() {
console.log("user.show");
}
}
export { site, func, User as default };
导入默认接口时不需要使用 {}
,普通接口还用 {}
导入
<script type="module">
//可以将 hd 替换为任何变量
import hd from "./hd.js";
import { site } from "./hd.js";
console.log(site);
hd.show();
</script>
可以使用一条语句导入默认接口与常规接口
import show, { name } from "/modules/houdunren.js";
也可以使用别名导入默认导出
import { site, default as hd } from "./hd.js";
console.log(site);
hd.show();
如果是批量导入时,使用 default
获得默认导出
<script type="module">
import * as api from "./hd.js";
console.log(api.site);
api.default.show();
</script>
使用建议
对于默认导出和命名导出有以下建议
不建议使用默认导出,会让开发者导入时随意命名
import hd from "./hd.js"; import xj from "./hd.js";
如果使用默认导入最好以模块的文件名有关联,会使用代码更易阅读
import hd from "./hd.js";
导出合并
解决问题
可以将导入的模块重新导出使用,比如项目模块比较多,这时可以将所有模块合并到一个入口文件中。
这样只需要使用一个模块入口文件,而不用关注多个模块文件
//demo.js
import * as gg from './dd.js'
import * as mm from './mm.js'
export {gg, mm};
<script type="module">
import * as demo from './demo.js';
demo.gg.show();
</script>
动态加载
使用 import
必须在顶层静态导入模块,而使用import()
函数可以动态导入模块,它返回一个 promise
对象。
下面是在点击事件发生后按需要加载模块
<button>后盾人</button>
<script>
document.querySelector("button").addEventListener("click", () => {
let hd = import("./hd.js").then(({ site, func }) => {
console.log(site);
});
});
</script>