文章目录
- 1. 模块化概述
- 2. 模块化演变[^2]
- 2.1.文件划分模式(`了解`)
- 2.2.命名空间模式(`了解`)
- 2.3.IIFE(立即执行函数表达式)和参数依赖声明(`了解`)
- 3.模块化规范
- 3.1 CommonJS
- `Require的基本实现逻辑(重点看)`
- 3.2 AMD
- `AMD的基本实现逻辑(重点看)`
- 3.3 CMD
- 4.模块化标准规范
1. 模块化概述
-
提高代码的复用性
:模块化可以将代码划分成可重用的部分,降低代码的冗余和重复,提高代码的复用性。 -
简化代码的维护和调试
:当一个软件系统变得越来越复杂时,进行模块化开发可以使得每个模块都相对独立,这样就可以方便地维护和调试每个模块,而不必考虑整个系统的复杂性。 -
提高代码的可读性
:模块化可以使得代码更加结构化,清晰明了,从而提高代码的可读性和可维护性。 -
提高开发效率
:模块化开发可以使得团队成员在不同模块上并行开发,从而提高开发效率。 -
降低项目的风险
:模块化开发可以使得开发人员更加关注模块之间的接口和依赖关系,从而降低项目的风险。
总之,模块化开发是一种有效的软件开发模式,可以提高软件开发的质量、效率和可维护性,特别是在大型软件系统的开发中,模块化更是必不可少的
。1
2. 模块化演变2
2.1.文件划分模式(了解
)
将每个功能以及它相关的一些状态数据,单独存放到不同的文件当中,我们去约定每一个文件就是一个独立的模块。
我们去使用这个模块,就是将这个模块引入到页面当中,然后直接调用模块中的成员(变量 / 函数)。
一个script标签就对应一个模块
,所有模块都在全局范围内工作缺点:
污染全局作用域
命名冲突问题
无法管理模块依赖关系
示例代码
:
// Student.js
var maths = 80;
var chinese = 90;
function score() {return maths + chinese;
}
// Cook.js
var name = "煮饭";
function make_food() {return new Promise(function (resolve, reject) {setTimeout(() => {resolve("煮好饭了")}, 1000);})
}
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件划分模式</title>
</head>
<body><script src="Cook.js"></script><script src="Student.js"></script><script>var studentScore = score();console.log(studentScore );make_food().then((message) => console.log(message));</script></body>
</html>
2.2.命名空间模式(了解
)
我们约定每个模块只暴露一个全局的对象,我们所有的模块成员都挂载到这个全局对象下面。
在第一阶段的基础上,通过将每个模块「包裹」为一个全局对象的形式实现,
有点类似于为模块内的成员添加了「命名空间」的感觉。
没有私有空间
模块成员仍然可以在外部被访问/修改
无法管理模块依赖关系
迪米特法则
:又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。英文简写为: LOD
。
// Student.js
var Student = {maths: 80,chinese: 90,score: function () {return this.maths + this.chinese;},skills: [Cook]
}
// Cook.js
var Cook = {name: "煮饭",make_food: function () {return new Promise(function (resolve, reject) {setTimeout(() => {resolve("煮好饭了")}, 1000);})}
}
<!-- index.html -->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>命名空间划分模式</title>
</head><body><!-- 在这里因为有引用,顺序不能乱 --><script src="Cook.js"></script><script src="Student.js"></script><script>// 我们可以用命名空间来访问属性,成员var studentScore = Student.score();console.log(studentScore);Student.skills[0].make_food().then((message) => console.log(message));</script></body></html>
2.3.IIFE(立即执行函数表达式)和参数依赖声明(了解
)
使用
立即执行函数
的方式,去为我们的模块提供私有空间。将模块中每个成员都放在一个函数提供的私有作用域当中,
对于需要暴露给外部的成员,我们可以通过return
的方式实现。确保了私有成员的安全。
有了私有成员的概念,私有成员只能在模块成员内通过闭包的形式访问。
利用立即执行函数
的参数传递模块依赖项
立即执行函数
: (function() {})()
// Student.js
; let Student = (function (modules) {var maths = 80;var chinese = 90;var skills = [modules['cook']];score = function () {return maths + chinese;}return {score,skills}
})({ cook: Cook }); // 模块的传入
// Cook.js
// 为什么加 ;
// 怕别的导入的库后面没有加 ";"
; let Cook = (function () {var name = "煮饭";make_food = function () {return new Promise(function (resolve, reject) {setTimeout(() => {resolve("煮好饭了")}, 1000);})}return {make_food: make_food}
})();
<!-- index.html -->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>立即执行函数</title>
</head><body><script src="Cook.js"></script><script src="Student.js"></script><script>// 我们可以用命名空间来访问属性,成员var studentScore = Student.score();console.log(studentScore);Student.skills[0].make_food().then((message) => console.log(message));</script></body></html>
3.模块化规范
3.1 CommonJS
一个文件就是一个模块
每个模块都有单独的作用域
通过module.exports
导出成员
通过require
函数载入模块缺点:
- CommonJS的出现本来是为了解决Node.js问题,约定的是以
同步模式加载模块
,在浏览器端
使用会导致效率低下
// Student.js
let cook = require("./Cook")var maths = 80;
var chinese = 90;
var skills = [cook];
function score() {return maths + chinese;
}module.exports = {score,skills
}
var name = "煮饭";
function make_food() {return new Promise(function (resolve, reject) {setTimeout(() => {resolve("煮好饭了")}, 1000);})
}
module.exports = { make_food }
/*** 一个文件就是一个模块* 每个模块都有单独的作用域* 通过module.exports导出成员* 通过require函数载入模块* CommonJS约定的是以同步模式加载模块,在浏览器端使用会导致效率低下。*/
let student = require('./Student')
var studentScore = student.score();
student.skills[0].make_food().then((message) => console.log(message));
console.log(studentScore)
Require的基本实现逻辑(重点看)
/*** require的基本实现原理* webpack打包原理*/(function () {var modules = {"./Cook.js": function (module, require) {var name = "煮饭";function make_food() {return new Promise(function (resolve, reject) {setTimeout(() => {resolve("煮好饭了")}, 1000);})}module.exports = { make_food }},"./Student.js": function (module, require) {let cook = require("./Cook.js")var maths = 80;var chinese = 90;var skills = [cook];function score() {return maths + chinese;}module.exports = {score,skills}},"./main.js": function (module, require) {let student = require('./Student.js')var studentScore = student.score();student.skills[0].make_food().then((message) => console.log(message));console.log(studentScore)}}// 为什么多次require只执行一次 因为有缓存// 为什么多次require只执行一次 因为有缓存var caches = {};// 基本实现逻辑// 基本实现逻辑function require(moduleId) {if (caches[moduleId]) {return caches[moduleId];}var module = { exports: {} }//需要把模块运行一下modules[moduleId](module, require);caches[moduleId] = module.exports;return module.exports;}require("./main.js")
})();
3.2 AMD
这个规范的出现主要是为了解决在浏览器里面,因为网络慢,加载JS卡顿的问题
作为一个规范,只需定义其语法API,而不关心其实现。AMD规范简单到只有一个API,即define函数:
define([module-name?], [array-of-dependencies?], [module-factory-or-object]);
module-name
: 模块标识,可以省略。array-of-dependencies
: 所依赖的模块,可以省略。module-factory-or-object
: 模块的实现,或者一个JavaScript对象。
AMD的基本实现逻辑(重点看)
// 缓存 为什么多个require只会执行一次
var modules = {};// 代码实现页面可能有需要依赖的其他js
// 所以里面也用到了loadScript
function define(name, deps, factory) {var pending = deps.length;// 创建一个数组 来看看依赖是否全部加载完成var resolvedDeps = new Array(pending);deps.forEach(function (dep, index) {if (modules[dep]) {resolvedDeps[index] = modules[dep];pending--;} else {loadScript(dep + ".js", function () {resolvedDeps[index] = modules[dep];pending--;// 这里面是异步执行了的 所以在这里// 在if外面都要做一个加载完成的判断if (pending === 0) {modules[name] = factory.apply(null, resolvedDeps);}})}if (pending === 0) {modules[name] = factory.apply(null, resolvedDeps);}});
}// 引用的时候也有可能需要依赖其他的js
// 所以在这个里面也用到了 loadScript
function require(deps, callback) {var pending = deps.length;var resolvedDeps = new Array(pending);deps.forEach(function (dep, index) {if (modules[dep]) {resolvedDeps[index] = modules[dep];pending--;} else {loadScript(dep + ".js", function () {resolvedDeps[index] = modules[dep];pending--;// 这里面是异步执行了的 所以在这里// 在if外面都要做一个加载完成的判断if (pending === 0) {callback.apply(null, resolvedDeps);}})}if (pending === 0) {callback.apply(null, resolvedDeps);}})
}// 动态加载script的话,并不会让document的渲染进行卡顿
function loadScript(url, callback) {var script = document.createElement("script");script.src = url;// 加载的过程实际就是 执行 define 函数的过程script.onload = callback || function () { };document.head.appendChild(script);
}require(["student"], function (student) {var studentScore = student.score();console.log(studentScore)
})
//student.js
// AMD的实现
define("student", [], function () {var maths = 80;var chinese = 90;function score() {return maths + chinese;}return { score }
});
3.3 CMD
实现逻辑与AMD基本类似,就不写了
4.模块化标准规范
ToDO
什么是模块化?为什么要进行模块化开发? ↩︎
细说前端模块化开发 ↩︎