| 年份 | 标准规范 | 构建相关 | 视图相关 |
|---|---|---|---|
| 1997 | HTTP/1.1 | ||
| 2002 | AJAX | ||
| 2006 | jQuery | ||
| 2009 | CJS | Node | |
| 2010 | AMD | npm | Angular、backbone |
| 2011 | UMD | Browserify 、RequireJS 、SeaJS+spm | |
| 2012 | Gulp | ||
| 2013 | SystemJS | react | |
| 2014 | Wbpack、Babel | Vue | |
| 2015 | HTTP/2.0 、 ES Module | Rollup | |
| 2016 | Yarn、Grunt | Svelte | |
| 2018 | Chrome/Safari/Firefox Support ESM | ||
| 2019 | Snowpack | ||
| 2020 | vite 、esbuild |
无模块化标准阶段前端的开发手段
1、文件划分
将应用的状态和逻辑分散到不同文件中、然后通过HTML中的 script来一一引入
弊端
1、模块变量相当于在全局声明和定义 ,会有变量名冲突的问题 2、由于变量都定义在全局,很难知道某个变量数据哪些模块,调试不便 3、无法清晰管理模块的依赖关系和加载顺序,
module-a依赖module-b,那么上述 HTML 的 script 执行顺序需要手动调整,不然可能会产生运行时错误
2、命名空间
命名空间可以解决文件划分中全局变量定义带来的 一系列问题
// module-a.js
window.moduleA = {
data: "moduleA",
method: function () {
console.log("execute A's method");
},
};
//module-b.js
window.moduleB = {
data: "moduleB",
method: function () {
console.log("execute B's method");
},
}
<!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="./module-a.js"></script>
<script src="./module-b.js"></script> <script>
// 此时 window 上已经绑定了 moduleA 和 moduleB console.log(moduleA.data); moduleB.method(); </script>
</body>
</html>
3、 IIFE(立即执行函数)
//module-a.js
(function(){
let data = "moduleA";
function method(){
console.log(data+"execute")
window.moduleA={
method:method,
}
})()
//module-b.js
(function(){
let data = "moduleB";
function method(){
console.log(data+"execute")
window.moduleB={
method:method,
}
})()
<!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>
// 此时 window 上已经绑定了 moduleA 和 moduleB
console.log(moduleA.data);
moduleB.method();
</script>
</body>
</html>
三大主流模块化规范 CommonJS、AMD、ES Module
1、 CommonJS规范
// module-a.js
var data = "hello world";
function getData(){
return data
}
module.exports ={
getData
}
// index.js
const {getData} = require('./module-a.js');
console.log(getData())
代码中使用 require 来导入一个模块,用module.exports来导出一个模块。实际上 Node.js 内部会有相应的 loader 转译模块代码,最后模块代码会被处理成下面这样:
(function (exports, require, module, __filename, __dirname) {
// 执行模块代码
// 返回 exports 对象
});
1、模块加载器由node.js提供,依赖了node.js本身的功能实现,比如文件系统,如果commonjs模块直接放在浏览器中是无法执行的,。当然业界产生了 browerify这种打包工具来支持打包CommonJS模块,相当于社区实现了一个第三方loader
2、CommonJs 本身约定以同步的方式进行模块加载,这种加载机制放在服务端是没问题的, 一来模块都在本地,不需要网络IO,二来只有服务启动时才会加载模块,而服务通常启动后会一直运行,所以服务的性能并没有太大的影响,但是这种机制放在浏览器端,会带来明显性能问题。它会产生大量同步的模块请求,浏览器要等待响应返回后才能继续解析模块。也就是说,模块请求会造成浏览器JS解析过程的阻塞,导致页面速度缓慢。
2、AMD规范
AMD 异步模块定义规范,模块根据这个规范,在浏览器环境中被异步加载。
//main.js
define(["./print"],function(printModule){
printModule.print("main")
})
//print.js
define(function(){
return {
print:function(msg){
console.log("print"+msg)
}
}
})
在AMD规范当中,我们可以通过define 去定义 或者加载一个模块,比如上面的main.js模块和print模块,如果模块需要导出一些成员需要通过在定义模块的函数中return出去,如果当前模块依赖了一些其他的模块则可以通过define的第一个参数来生命依赖(参考main),这样模块的代码执行之前浏览器会先加载依赖模块。
当然,你也可以使用require关键词来加载一个模块
// module-a.js
require(["./print.js"],function(printModule){
printModule.print("module-a");
})
require 与 define 的区别在于前者只能加载模块,而
不能定义一个模块
由于没有得到浏览器的原生支持,AMD规范需要由第三方的loader来实现,最经典的就是 requireJS库,他完整实现了AMD规范,。
CMD规范由淘宝SeaJS实现,解决的问题和AMD一样,随着社区发展,SeaJS已经被requireJS 兼容了
UMD规范 是兼容AMD和CommonJS的一个模块化方案,可以同时运行在浏览器和Node.js环境,ES Module也具备这种跨平台的能力
3、ES6 Module
ES6 Module也被称为ES Module,是由ECMAScript官方提出的模块化规范, 已经得到了现代浏览器的内置支持。 在现代浏览其中, HTML中加入含有type= “module” 属性的script标签,那么浏览器会按照ES Module规范来进行依赖加载和模块解析。
一直以CommonJS作为模块化标准的NodeJS,从12.2版本开始支持原生ES Module
//main.js
import {methodA} from “./module-a.js”
methodA()
//module-a.js
const methodA = () => { console.log("a"); };
export {methodA}
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title> </head>
<body>
<div id="root"></div>
<script type="module" src="./main.js"></script>
</body>
</html>
如果在 node.js环境中,你可以在 package.json中声明, type:"module"属性
{
"type": "module"
}
Node.js 便会默认以 ES Module 规范去解析模块
node main.js
// 结果 为 a
在node.js中,即使在commonJS模块里面,也可以通过import方法顺利加载ES模块
async function func() {
// 加载一个 ES 模块
// 文件名后缀需要是 mjs
const { a } = await import("./module-a.mjs"); console.log(a);
}
func();
module.exports={
func
}