JS模块化
1.不得不说的历史
背景
JS本身定位: 简单的页面你设计-页面动画 + 基本的表单提交 并无模块化 or 命名空间的概念
js的模块化的需求日益增长
2. 传统:无模块化 => CJS => AMD => CMD => ESM
幼年期:无模块化
1.开始需要在页面中增加一些不同的js:动画、表单、格式化工具 2.多种js文件被分在不同的文件中 3.不同的文件又被同一个模板所引用
//test.html
<script src = "jqery.js"></script>
<script src = "mian.js"></script>
<script src = "tools.js"></script>
认可: 文件分离是最基础的模块化,第一步
- 追问: script标签两个参数 - async & defer
<script src="jquery.js" type="text/jacascript" async> </script>
总结: 普通- 解析到标签,立刻pending,并且下载执行 defer - 解析到标签开始异步下载,解析完成后开始执行 async - 解析到标签开始异步下载,下载完成后立刻执行并阻塞渲染,执行完成后,继续渲染 1.兼容性 > IE9 => 其他兼容性问题 2.问题方向 => 浏览器渲染原理、同步异步原理、模块化加载原理
问题出现:
- 污染全局作用域=>不利于大型项目的开发以及多人团队共建
成长期:模块化的雏形 - IIFE(语法侧的优化)
作用域的把控
例子:
//定义一个全局变量
let count = 0;
//代码块1
const increase = {} => ++count;
//代码块2
const reset = () =>{
count = 0;
}
increase();
console.log(count);
reset();
console.log(count);
利用函数块级作用域
(()=>{
let count = 0;
// ...
})()
定义函数+立即执行 => 独立的空间 初步实现了一个最最最简单的模块
尝试去定义一个最简单的模块 - 模块 and 外部
IIFE模块(立即执行函数模块)
//iifeModule
const module = (() =>{
let count = 0;
return {
increase:() => ++count;
reset:() =>{
count = 0;
}
}
})();
module.increase();
module.reset();
** 追问:有额外依赖的时候,如何优化IIFE的相关代码 **
优化1:依赖其他模块的IIFE
const iifeModule = (()=>{
let count = 0;
const obj = {
increase:()=> ++count;
reset:()=>{
count = 0;
}
}
})()
** 追问:有额外依赖的时候,如何优化IIFE的相关代码**
优化1:依赖其他模块的IIFE
const iifeModule = (()=>{
let count = 0;
const obj = {
increase:()=>++count;
reset:()=>{
count = 0;
}
}
//__dependencyModule
})(dependencyModule,dependencyModule2);
** 面试1:了解早期jQuery依赖处理还有模块加载方案? / 了解传统IIFE是如何解决多方依赖的问题 答:IIFE + 传参调配 **
实际上,传统框架应用了一种revealing的写法 揭示模式
const iifeModule = (()=>{
let count = 0;
const increase = () => ++count;
const reset = () =>{
count = 0;
//dependencyModule1.xxx
//...
}
return {
increase,
reset
}
})(dependencyModule,dependencyModule2)
iifeModule.increase(1);
iifeModule.reset();
//返回的是能力= 使用方传参(x) +本身逻辑能力 + 依赖的能力
//$('').attr('title','new');
- 追问: 面试后续引导方向: 1.深入模块化实现 2.转向框架:jquery、vue、react的模块化细节,以及框架特征原理 3.转向设计模式 - 注重模块化的设计模式
成熟期
CJS - Common.js
node.js制定 特征:
- 通过module + export 去对外暴露接口
- 通过require来调用其他模块
模块组织方式 main.js
//引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
//逻辑部分
let count = 0;
const increase = () => ++ count;
const reset = val =>{
count = 0;
//dependencyModule.xxx
//...
}
//暴露接口部分
export.increase = increase;
export.reset = reset;
module.exports = {
increase,reset
}
const {increase,reset } = require('main.js');
increase();
reset();
** 可能被问到的问题 ** 实际执行处理
(function(thisValue,export,require,module){
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
}).call(thisValue,exports,require,module);
//部分开源项目分别插入全局,指证,框架作为参数
(function(window,$,undefined){
const _show = function(){
$("#app".val("hi zhaowa");
}
window.webShow = _show;
})(window,jQuery);
//window - 1.全局作用域转成局部作用域,执行不用全局调用,提升效率2.编译时候,优化压缩(function(c){}(window)
//jQuery - 1.独立定制复写,保障稳定 2。防止全局污染
//undefined - 防止重写
- 优点 CJS率先在服务端实现了从框架层面解决依赖,全局变量污染的问题
- 缺点: 针对的是服务端,对于异步以来都没有很友好的处理和考虑
新的问题 -- 异步依赖
AMD规范
通过异步加载 + 允许定制回调函数 经典的实现框架:require.js
新增定义方式:
//通过define来定义一个模块,然后用require去加载
define(id,[depend],callback);
require([module],callback);
define('amoModule',['dependencyModule1','dependencyModule2'],(dependencyModule1,dependencyModule2)
=>{
//业务逻辑
let count = 0;
const increase = () => ++ count;
const reset = val =>{
count = 0;
//dependencyModule.xxx
//...
}
})
require(['amdModule'].amdModule => {
amdModule.increase();
);
** 面试题2,如果在AMD中想兼容已有的代码 ** 1.增加定义阶段 2.返回做回调内return
define('amdModule',[],require =>{
//引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
//逻辑部分
let count = 0;
const increase = () => ++ count;
const reset = val =>{
count = 0;
//dependencyModule.xxx
//...
}
return {
increase,reset
}
})
** 面试题3:兼容判断AMD&CJS UMD的出现
(define('amdModule',[],(require,export,module) =>{
//核心逻辑
let count = 0;
const increase = () => ++ count;
const reset = val =>{
count = 0;
//dependencyModule.xxx
//...
}
export.increase = increase;
export.reset = reset;
}))(
//目标:一次性去区分CJS和AMD
//1.CJS factory
//2.module module.exports
//3.define
typeOf module === "Object"
&& module.exports
&& typeOf define !== "function"
? //是CJS
factory => module.exports = factory(require,exports,module);
: //是AMD
define
)
- 优点:解决了浏览器中异步加载模块,可以并行加载多个模块
- 缺点:会有引入成功,没有考虑按需加载
CMD规范
按需加载 足以应用框架 sea.js
//依赖就近
define('module',(require,exports,module)=>{
let $ = require('jquery');
let dependencyModule1 = require('./dependencyModule1');
})
- 优点:按需加载,依赖就近
- 缺陷:依赖于打包,加载逻辑存在与每个模块中,扩大了模块的体积
追问: AMD和CMD => 依赖就近,按需加载
ESM
走向新时代
新增定义:引入关键字 -- import 导出关键字 -- export
模块引入和导出的地方
//引入区域
import dependencyModule1 from `./dependencyModule1.js`;
import dependencyModule2 from `./dependencyModule2.js`;
//核心代码区域
//核心逻辑
let count = 0;
const increase = () =>++count;
const reset = val =>{
count = 0;
//dependencyModule1.xxx
//
}
export default{
increase,reset
}
** 追问 :ESM动态模块 ** 考察 export promise
** 懒加载 ** ESM11 原生解决方案
import('./esModule.js').then(dynamicEsModule => {
dynamicEsdule.increase();
{)
//emd
- 优点:通过一种最统一的形态整合了Js的模块化
- 缺憾:本质上是运行时做的依赖分析