最开始的模块化
目录如下:
// => A.js
let name = "HH";
const sum = (...params) => {
let len = params.length;
if (len === 0) return 0;
if(len === 1) return params[0];
params.reduce((x, item) => (+x) + (+item));
}
// => B.js
// B需要依赖A中的sum方法
let name = "RR";
// 求平均数
const average = (...params) => {
let total = sum(...params);
let len = params.length;
if ( len === 0) return 0;
return (total/len).toFixed(2);
}
// => main.js入口模块
// 调用AB模块的中的方法
console.log(sum(10,20,30));
console.log(average(10,20,30));
//=> index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--
最开始的模块化开发:
- 把每个模块代码写在不同的文件中,最后在页面导入
- 缺点:
+ 需要分析依赖导入的先后顺序(麻烦)
+ 如果不基于闭包私有化处理,相同的变量容易引发"全局变量污染"
-->
<script src="./A.js"></script>
<!-- B依赖A所以后导入 -->
<script src="./B.js"></script>
<!-- main.js依赖A和B -->
<script src="./main.js"></script>
</body>
</html>
单例设计模式早期处理"全局变量污染"方式,模块之间相互访问
- 解决私有化:自执行函数执行产生闭包即可
- 解决模块之间的相互访问, 把需要访问的内容暴露到全局上, window.xxx = xxx
- window.xxx === xxx也不是很好的方式,如果方法多了就很麻烦,模块的名称一样也可能导致
全局变量污染 - 把模块中暴露的东西放在对象管理,最后基于模块名存储这个对象即可(模块名必须唯一)
- window.xxx === xxx也不是很好的方式,如果方法多了就很麻烦,模块的名称一样也可能导致
- 但是还是不能解决顺序问题
// => A.js
const AModule = (function () {
let name = "HH";
const sum = (...params) => {
let len = params.length;
if (len === 0) return 0;
if (len === 1) return params[0];
params.reduce((x, item) => (+x) + (+item));
}
return {
sum
}
})();
// => B.js
// B需要依赖A中的sum方法
const BModule = (function () {
let name = "RR";
// 求平均数
const average = (...params) => {
let total = AModule.sum(...params);//调用AModule下的sum方法
let len = params.length;
if (len === 0) return 0;
return (total / len).toFixed(2);
}
return {
average
}
})();
//=> main.js
console.log(AModule.sum(10,20,30));
console.log(BModule.average(10,20,30));
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./A.js"></script>
<!-- B依赖A所以后导入 -->
<script src="./B.js"></script>
<!-- main.js依赖A和B -->
<script src="./main.js"></script>
</body>
</html>
- 结果如下:
- 总结:
- 这种处理方式即保证代码之间的私有化,也支持模块之间的相互访问,避免的全局变量污染
- 我们把这种代码设计方案称之为单例设计模式
- 所有的设计模式都是一种思想,这种思想解决的某一类问题
- 还是需要手动分析各个模块之间的依赖关系,还有模块的命名空间的问题
AMD模块化思想按需引入思想
- 金典requre.js
- 解决的顺序所有问题,不过写法太麻烦了哈哈
- 定义模块用defined
- 定义模块的时候可以把依赖的模块"前置导入"
// => A.js
defined(function() {
'use strict';
let name = "HH";
const sum = (...params) => {
let len = params.length;
if (len === 0) return 0;
if(len === 1) return params[0];
params.reduce((x, item) => (+x) + (+item));
}
return {
sum
}
})
// => B.js
// B需要依赖A中的sum方法
// => 定义模块的时候可以把依赖的模块"前置导入"
defined(['A'],function(AModule) {
let name = "RR";
// 求平均数
const average = (...params) => {
let total = AModule.sum(...params);//调用A模块的sum
let len = params.length;
if ( len === 0) return 0;
return (total/len).toFixed(2);
}
return {
average
}
})
// => main.js
require.config({
baseUrl: './lib',// => 配置找模块的文件夹路径
})
// => 导入相关模块,处理内容
require(['A', 'B'], function(A, B) {
// 调用AB模块的中的方法
console.log(A.sum(10,20,30));
console.log(B.average(10,20,30));
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 导入requre.js下载好的包 -->
<script src="require.min.js"></script>
<script src="./main.js"></script>
</body>
</html>
- 总结
- 在单例设计模式的基础上有效的解决了模块之间的依赖问题,告别导入顺序问题
- 可以结合gulp/grount等,最后把各个模块代码合并压缩打包到一起
- 依赖的模块需要前置导入,不过写法太麻烦了(爷懒)哈哈
CommonJS规范
- 随着nodeJS的发展,node里面有这commonJS思想,随导随用
- CommonJS模块规范[模块的导入和导出]:Node自带的模块化规范(浏览器端不支持)
- 比AMD用起来简单,从导入机制等原理也比AMD性能高
// => A.js
let name = "HH";
const sum = (...params) => {
let len = params.length;
if (len === 0) return 0;
if (len === 1) return params[0];
return params.reduce((x, item) => (+x) + (+item));
}
module.exports = {
sum
}
// => B.js
const { sum } = require('./A.js');
let name = "RR";
// 求平均数
const average = (...params) => {
let total = sum(...params);
let len = params.length;
if (len === 0) return 0;
return (total / len).toFixed(2);
}
module.exports = {
average
}
//=> main.js
/*
CommonJS模块规范[模块的导入和导出]:Node自带的模块化规范(浏览器端不支持)
CommonJS模块的导入是按需的
定义模块: 创建一个JS文件,就是定义一个模块;每个模块之间的代码本身就是私有的了
导出模块中的方法:
module.exports = {
sum//包含外界需要的属性和方法
}
导入指定模块
const XModule = require('./A.js');
使用 XModule.sum()
or
const { sum } = require('./A.js');
sum();
*/
const { sum } = require('./A.js');
const { average } = require('./B.js');
console.log(sum(10,20,30));
console.log(average(10,20,30));
- 再node的环境中执行 node .\CommonJS\main.js,结果如下:
- 补充
- CommonJS不支持浏览器端,所以淘宝玉伯 写了一个插件 sea.js[把其定义为CMD模块规范]
- CMD模块规范,本质把CommonJS规范搬到浏览器端运行(不在维护已过时)
ES6Module
- 后来的ES6本身提供了更好的模块化规范,sea.js代表的CMD规范被pass掉了
- vscode 记得安装live server插件
- type = "module"
- 基于标准的http/https协议的web服务预览页面
- 创建一个js就是一个模块
// => A.js
let name = "HH";
export const sum = (...params) => {
let len = params.length;
if (len === 0) return 0;
if (len === 1) return params[0];
return params.reduce((x, item) => (+x) + (+item));
}
//=> B.js
import { sum } from './A.js';
let name = "RR";
// 求平均数
export const average = (...params) => {
let total = sum(...params);
let len = params.length;
if (len === 0) return 0;
return (total / len).toFixed(2);
}
// => main.js
import { sum } from './A.js';
import { average } from './B.js';
console.log(sum(10,20,30));
console.log(average(10,20,30));
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="main.js" type="module"></script>
</body>
</html>
- 运行结果如下:
ES6Module总结:
- 问价你的后缀名不能省略.js
- export default 一个模块中只能用一次
- export default sum -> import sum from './文件路径' -> sum()
- export default { sum } -> import A from './文件路径' -> A.sum()
- export,可以使用多次,声明 + 赋值的形式
- export const sum = () =>{...} -> import { sum } from './文件路径' -> sum()
- export const sum = () =>{...} -> import A from './文件路径' -> A.sum()
- 如果2个都有如下的常见情况
- import A from './文件路径' (
只有它的导出生效) - 可以 import * as A from './文件路径',就可以拿到2者导出的了
- 把当前模块导出的所有内容获取到,后期基于A.xxx访问即可
- import A from './文件路径' (
- import要放在文件的最上面导入需要模块的内容
当代前端开发都是基于模块化进行的,而模块化方案已CommonJS 和 ESModule为主
我们编写的JS代码,可以运行的环境
- 浏览器 <script src="...">,类似还有 webview环境
- 1.1 直接支持ES6Module,不支持CommonJS
- 1.2 全局对象 window
- Node
- 2.1 支持CommonJS,但不支持ES6Module
- 2.2 全局对象 global
- webpack [基于node实现代码代码的合并压缩打包;最后把打包的结果导入浏览器运行]
- 3.1 直接支持ES6Module,支持CommonJS,而且相互之间的"混用"
- 3.2 原理: webpack把2中模块化规范都实现了一遍
- 3.3 支持window&global
- vite [新的打包工具]
- 4.1 不像webpack的打包模式,它的本质[基于ES6Module规范,实现模块之间的相互引用]