什么是模块化?模块化是指将一个复杂的程序进行分解,划分为若干个独立且可复用的模块,每个模块有特定的功能,然后通过一定的规则组合在一起,构建出完整的应用程序。模块化有利于代码的可读性、可维护性、复用性,并降低项目的复杂度。
模块化解决了哪些问题?
- 全局污染,命名冲突等
- 依赖混乱,一时半会看不出来各个方法之间的依赖关系
前端目前常见的模块化方案
目前前端主要有 CommonJS、AMD、CMD、UMD、ESModule,他们分别有对应的用途和写法。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
CommonJS
CommonJS
是 Node.js
的默认模块化规范,简称为 CJS
,用于服务端,CommonJS
的加载是同步的,只有加载完成才能执行后面的操作。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
特点:每一个文件有自己的作用域,内部定义的变量、函数都是私有的,对其它文件都是封闭的。模块可以多次加载,但是只会在第一次加载的时候运行一次,然后将结果缓存,后面的加载直接读取缓存的结果。模块加载的顺序按照代码中出现的顺序加载。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
为实现模块的导入和导出,使用 Module
类,每一个模块都是 Module
的一个实例就是 module
。每个模块文件上存在三个变量 module
、exports
、require
,其中 exports
是 module.exports
的引用。以下是 CommonJS
的导入导出用法:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
a.js:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
js
const name = "zhangsan";
const age = 25;
const say = () => {
console.log("Hello Word");
};
module.exports = {
name,
age,
say,
};
// 导出也可以这样写
exports.name = name;
exports.age = age;
exports.say = say;
js
const person = require("./a.js");
console.log("name:", person.name);
console.log("age:", person.age);
person.say();
// 也可以直接解构
const { name, age, say } = require("./a.js");
console.log("name:", name);
console.log("age:", age);
say();
由于浏览器没有 module
、exports
、require
、global
这四个变量,所以没办法直接在浏览器中使用 CommonJS
,但是可以使用 browserify
CommonJS 转换工具让你在浏览器中使用。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
AMD
AMD
是一个可以在浏览器端使用的模块发开发规范,也叫异步模块加载方案,他需要使用 RequireJS
的支持,由于它支持异步,所以能更好的在浏览器端使用。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
AMD
与 CommonJS
的主要区别就是异步模块加载,即使 require
的模块还没有获取到,也不会影响后面代码的执行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
RequireJS 定义了一个全局函数 define
,用于定义模块:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
js
define(id?, dependencies?, factory);
- id: 模块标识,如果没有指定该参数,则使用文件名(去掉文件后缀)
- dependencies: 定义当前模块的依赖数组
- factory: 模块初始化需要执行的函数或者对象,如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值
下面是 define
的例子:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15916.html
person.js:
js
// 不依赖其它模块
define("person", [], function () {
const name = "zhangsan";
const age = 25;
const say = function () {
console.log("Hello Word");
};
return {
name,
age,
say,
};
});
js
// 依赖person模块
define(["person"], function (person) {
console.log("name:", person.name);
console.log("age:", person.age);
person.say();
});
js
// 依赖多个模块的时候函数的入参是按依赖模块的顺序来的
define(["person", "packages", "article"], function (
person,
packages,
article
) {});
RequireJS 定义了一个全局函数 require
,用于引用模块:
js
require([module], callback);
- module: 需要加载的模块
- callback: 模块加载完成之后执行的回调函数
js
// 依赖多个模块的时候回调函数的入参是按依赖模块的顺序来的
require(["person", "packages", "article"], function (
person,
packages,
article
) {});
CMD
CMD
与 AMD
类似,需要 SeaJS
的支持,用法也与 RequireJS
一致:
js
define(id?, dependencies?, factory);
那他们两有什么区别呢?
主要是在模块定义时对依赖的处理不同:
- AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块
- CMD 推崇就近依赖,只有在用到某个模块的时候再去 require
UMD
UMD
为通用模块定义,从名字可以看出它既可以在服务端使用,又可以在客户端使用,结合了 AMD 和 CommonJS,写起来也比较麻烦,不过多介绍,下面是使用例子:
js
(function (root, factory) {
if (typeof module === "object" && typeof module.exports === "object") {
// CommonJS
module.exports = factory();
} else if (typeof define === "function") {
// AMD
define(factory);
} else {
// 没有模块环境,直接挂载在全局对象上
root.umdModule = factory();
}
})(this, function () {
// do ....
const name = "zhangsan";
return {
name,
};
});
ESModule
ESModule
在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS
和 AMD
规范,成为浏览器和服务器通用的模块解决方案。
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
严格模式主要有以下限制。
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用 with 语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量 delete prop,会报错,只能删除属性 delete global[prop]
- eval 不会在它的外层作用域引入变量
- eval 和 arguments 不能被重新赋值
- arguments 不会自动反映函数参数的变化
- 不能使用 arguments.callee
- 不能使用 arguments.caller
- 禁止 this 指向全局对象
- 不能使用 fn.caller 和 fn.arguments 获取函数调用的堆栈
- 增加了保留字(比如 protected、static 和 interface)
以上内容摘录自阮一峰-ECMAScript6 入门
ESModule
主要由 export
、import
两个命令组成,以下是使用的方式:
person.js
js
export const name = "zhangsan";
export const age = 25;
export const say = function () {
console.log("Hello Word");
};
js
import { name, age, say } from "./person.js";
console.log("name: ", name);
console.log("age: ", age);
say();
也可以使用 export default
为模块指定默认输出,import()
返回的是一个 promise
,所以你可以使用 .then
去接收它
person.js
js
const name = "zhangsan";
const age = 25;
const say = function () {
console.log("Hello Word");
};
export default {
name,
age,
say,
};
js
import("./person.js").then((module) => {
const { name, age, say } = module.default;
console.log("name: ", name);
console.log("age: ", age);
say();
});
那怎么直接在 html 中直接使用ESModule
呢?我们可以设置 script
标签上 type
属性为 module
即可:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ES Module Example</title>
</head>
<body>
<script type="module">
import { name, age, say } from "./person.js";
console.log("name: ", name);
console.log("age: ", age);
say();
</script>
</body>
</html>
ESModule
是我们目前用的最多的模块化方案了,其他功能就不再赘述,如有不懂,请查看阮一峰大佬的ECMAScript6 入门
ESModule
的优点:
- 异步加载:
ESModule
支持动态导入,可以在运行的时候根据需要异步加载模块。这对于按需加载和延迟加载模块非常有用,可以提高应用程序的性能和加载速度。 - 静态分析:
ESModule
的导入导出是静态的,意味着在代码运行之前就可以解析和确定,这使得工具和编译器可以在构建的过程中进行静态分析和优化,比如代码压缩、树摇优化(tree shaking)和按需加载。 - 基于 cors 的请求:当
ESModule
从外部加载 js 模块的时候,他使用 CORS(跨资源共享)协议进行请求。 - 细粒度控制:
ESModule
的导入和导出是精确的,可以按需导入和导出具体的函数、类或变量,而不需要一次性导入整个模块。这样可以减少不必要的代码依赖,提高应用程序的性能。 - 跨平台兼容:
ESModule
是 JavaScript 的官方模块标准,已经被广泛采用并得到了现代浏览器和 Node.js 环境的支持。它提供了一种通用的模块化方案,使得编写可在不同环境中运行的代码变得更加容易。
评论