Appearance
前言
随着项目越来越大,不可避免的要进行模块化,模块化的目的是使得将功能分割到各个模块,需要用到什么功能的时候,就引入对应的模块。
模块化最基本的功能就是:
- 模块导入导出
- 避免命名冲突
避免命名冲突这个是模块自带的属性,了解即可,在模块内部和模块外部的变量使用系统的名字,也不会冲突。
最关键的模块的导入和导出。由于JS语言实现模块化的方案有多种,每一种模块化方案是不同的规范标准,因此其导入导出规则也不相同,我们需要掌握的就是常用模块化规范的导入导出规则。
常见模块化方案
ES6
ES6从js语法层面解决了模块化的问题。
ES6模块化可以运行在浏览器和服务端。
ES6模块化详细参考:Module 的语法
导出
需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
js
// 报错
export 1;
// 报错
var m = 1;
export m;// 报错
export 1;
// 报错
var m = 1;
export m;正确的写法是下面这样。
js
// 写法一
export var m = 1;
// 写法二
var m = 1;
export { m };
// 写法三
var n = 1;
export { n as m };// 写法一
export var m = 1;
// 写法二
var m = 1;
export { m };
// 写法三
var n = 1;
export { n as m };同样的,function和class的输出,也必须遵守这样的写法。
js
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};导出别名
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
js
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};导入别名
如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
js
import { lastName as surname } from "./profile.js";import { lastName as surname } from "./profile.js";模块整体加载
使用 * 代替所有导出:
js
import * as circle from "./circle";
console.log("圆面积:" + circle.area(4));
console.log("圆周长:" + circle.circumference(14));import * as circle from "./circle";
console.log("圆面积:" + circle.area(4));
console.log("圆周长:" + circle.circumference(14));export 与 import 的复合写法
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
js
export { foo, bar } from "my_module";
// 可以简单理解为
import { foo, bar } from "my_module";
export { foo, bar };export { foo, bar } from "my_module";
// 可以简单理解为
import { foo, bar } from "my_module";
export { foo, bar };需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。
模块的接口改名和整体输出,也可以采用这种写法。
js
// 接口改名
export { foo as myFoo } from "my_module";
// 整体输出
export * from "my_module";// 接口改名
export { foo as myFoo } from "my_module";
// 整体输出
export * from "my_module";默认接口的写法如下。
js
export { default } from "foo";export { default } from "foo";具名接口改为默认接口的写法如下。
js
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;同样地,默认接口也可以改名为具名接口。
js
export { default as es6 } from "./someModule";export { default as es6 } from "./someModule";ES2020新增了import写法
js
export * as ns from "mod";
// 等同于
import * as ns from "mod";
export { ns };export * as ns from "mod";
// 等同于
import * as ns from "mod";
export { ns };import.meta
i
在node环境中使用ES模块化的时候,如果需要获取__dirname,可以像下面这样获取:
js
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));IIFE 匿名函数自调用
主要是用在浏览器端,利用闭包的特性来保存私有变量,达到模块化的效果
js
(function (args) {
// do something...
})(args);(function (args) {
// do something...
})(args);CommonJS
参考文章:CommonJS规范
CommonJS规范主要应用于nodejs中,浏览器运行的话,需要借助其他工具(Browserify)
AMD
CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。
AMD规范使用define方法定义模块,使用require方法导入模块,在浏览器中导入AMD模块,需要先引入require方法
html
<!-- index.html -->
<body>
<!-- 引入require.js并指定js主文件的入口 -->
<script
data-main="main"
src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js"
></script>
</body><!-- index.html -->
<body>
<!-- 引入require.js并指定js主文件的入口 -->
<script
data-main="main"
src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js"
></script>
</body>对于AMD模块具体导出和导入的细节就不深究了,大部分情况下用不到。
CMD
CMD是由SeaJS定义的模块化方案,SeaJS是淘宝团队推出的JS框架。这是一个同步模块化规范。
与AMD比较,CMD也是运行在浏览器环境,不过模块导入的时机不同,CMD是在使用的地方用require导入,而AMD是在一开始就将要导入的模块写到require的第一个参数里面,即前置依赖。
UMD
特点是兼容AMD和CommonJS,而且兼容全局引入。
UMD原理:
- 先判断是否支持 AMD(define 是否存在),存在则使用 AMD 方式加载模块;
- 再判断是否支持 Node.js 模块格式(exports 是否存在),存在则使用 Node.js 模块格式;
- 前两个都不存在,则将模块公开到全局(window 或 global)