js模块化发展脉络
无模块化→初具雏形→成熟→新时代
幼年期:无模块化
文件分离是最基础的模块化
-
小问:
script
标签的两个参数- 默认:解析到标签,立即pending,下载并执行
- defer:解析到标签,开始异步下载,继续解析完成后,再开始执行
- async:解析到标签,开始异步下载,下载完成后立即执行,并阻塞解析;执行完成后,再继续解析
-
面试抛问题方向:
- 浏览器渲染原理
- 同步异步原理
- 模块化加载原理
问题:
- 污染全局作用域→不利于大型项目的开发及多人团队共建
成长期:模块化雏形—IIFE(立即调用函数表达式)
🌰:最简单的模块— 模块 + 外部
// iifemodule
const module = (() => {
let count = 0;
return {
increase: () => ++count;
reset: () => {
count = 0;
}
}
})();
module.increase();
module.reset();
-
面试追问1:有额外依赖时,如何优化处理IIFE
答:依赖其他的IIFE
const iifeModule = ((dependecyModule1, dependecyModule2) => { let count = 0; // dependecyModule…… return { increase: () => ++count; reset: () => { count = 0; } } })(dependecyModule1, dependecyModule2);// 调用时,将依赖作为参数传入
-
面试追问2:了解早期jQuery依赖处理/模块加载的方案么? / 了解传统IIFE是如何解决多方依赖的问题么?
答:IIFE + 传参调配
const iifeModule = ((dependencyModule1, dependencyModule2) => { let count = 0; const increase = () => ++count; const reset = val => { count = 0; // dependencyModule } return { increase, reset } })(dependencyModule1, dependencyModule2); iifeModule.increse(1); iifeModule.reset(); // 返回的是能力 = 使用方传参 + 本身逻辑能力 + 依赖能里 // $('').attr('title', 'new');
-
面试抛问题方向:
- 深入模块化的实现
- 转向框架(jquery、vue、react)模块化实现的细节,框架的特征原理
- 设计模式—模块化的设计模式
成熟期
CJS—CommonJS
-
node.js 指定的标准
-
特征:
- 通过
module
+export
对外暴露接口 - 通过
require
调用其他模块
- 通过
// 引入部分
const dependecyModule1 = require(./dependecyModule1);
const dependecyModule2 = require(./dependecyModule2);
// 核心逻辑
let count = 0;
const increase = () => ++count;
const reset = val => {
// ……
}
// 暴露接口部分
export.increase = increase;
export.reset = reset;
module.exports = {
increase,
reset
}
const { increase, reset } = require('dep.js');
increase();
reset();
- 优点:CJS从服务侧解决依赖全局污染的问题
- 缺憾:只针对服务端
- 新的问题:异步依赖
AMD
- 通过异步加载 + 允许定制回调函数
- 经典的实现框架:require.js
新增定义方式:
// 通过define来定义一个模块,然后用require去加载
define(id, [depends], callback);
require([module], callback);
define('amdModule', ['dependecyModule1', 'dependecyModule2'], (dependecyModule1,
dependecyModule2) => {
// 业务逻辑
let count = 0;
const increase = () => ++count;
const reset = val => {
count = 0;
// dependecyModule1……
}
})
require(['amdModule'], amdModule => {
amdModule.increase();
})
-
面试追问1:如何对已有代码做兼容
- 增加定义阶段
- 返回作为回调内部的return
define('amdModule', [], require => { // 引入部分 const dependecyModule1 = require(./dependecyModule1); const dependecyModule2 = require(./dependecyModule2); // 核心逻辑 let count = 0; const increase = () => ++count; const reset = val => { // …… } // 暴露接口部分 export.increase = increase; export.reset = reset; return { increase, reset } })
-
面试追问2:兼容判断
AMD
和CJS
UMD
出现
(define('amdModule', ['dependencyModule1'], require => {
// 核心逻辑
let count = 0;
const increase = () => ++count;
const reset = val => {
// ……
}
// 暴露接口部分
export.increase = increase;
export.reset = reset;
return {
increase,
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 depends1 = require('./dependencyModule1');
})
-
优点:按需加载,依赖就近
-
缺点:
- 依赖于打包
- 扩大了模块内的体积
-
区别:按需加载,依赖就近
踏入新时代
ESM
-
新增定义:
- 引入关键字:import
- 导出关键字:export
// 引入区域
import dependencyModule1 from './dependencyModule1.js';
// 逻辑处理区域
// ……
export default {
increase, reset
}
---------------------------
export {increase, reset};
import {increase, reset};
- 面试追问1:ESM动态模块
考察: export promise
ES11 原生解决方案
import('./esModule.js').then(dynamicEsModule => {
dynamicEsModule.increase();
})
- 优点:通过一种最统一的形态整合了所有js的模块化