本文从以时间为轴从以下几个方面进行总结 JS 模块化。从无模块化 => IIFE => CJS => AMD => CMD => ES6 => webpack 这几个阶段进行分析。
历史
幼年期:无模块化
方式
需要在页面中加载不同的 js,用于动画,组件,格式化
多种 js 文件被分在了不同的文件中
不同的文件被同一个模板所引用
<script src="jquery.js"></script><script src="main.js"></script><script src="dep1.js"></script>
复制代码
此处写法文件拆分是最基础的模块化(第一步)
* 面试中的追问
script 标签的参数:async & defer
<script src="jquery.js" async></script>
复制代码
总结
普通加载:解析到立即阻塞,立刻下载执行当前 script
defer 加载:解析到标签开始异步加载,在后台下载加载 js,解析完成之后才会去加载执行 js 中的内容,不阻塞渲染
async 加载:(立即执行)解析到标签开始异步加载,下载完成后开始执行并阻塞渲染,执行完成后继续渲染
污染全局作用域
成长期(模块化前夜) - IIFE(语法测的优化)
作用域的把控
let count = 0;const increase = () => ++count;const reset = () => { count = 0;}
复制代码
利用函数的块级作用域
(() => { let count = 0; ...})//最基础的部分
复制代码
实现一个最简单的模块
const iifeModule = (() => { let count = 0; const increase = () => ++count; const reset = () => { count = 0; } console.log(count); increase();})();
复制代码
优化 1:依赖其他模块的传参型
const iifeModule = ((dependencyModule1,dependencyModule2) => { let count = 0; const increase = () => ++count; const reset = () => { count = 0; } console.log(count); increase(); ...//可以处理依赖中的方法})(dependencyModule1,dependencyModule2)
复制代码
面试 1:了解 jquery 或者其他很多开源框架的模块加载方案
将本身的方法暴露出去
const iifeModule = ((dependencyModule1,dependencyModule2) => { let count = 0; const increase = () => ++count; const reset = () => { count = 0; } console.log(count); increase(); ...//可以处理依赖中的方法 return increase,reset }})(dependencyModule1,dependencyModule2)iifeModule.increase()
复制代码
=> 揭示模式 revealing => 上层无需了解底层实现,仅关注抽象 => 框架
继续模块化横向展开
转向框架:jquery|vue|react 模块细节
转向设计模式
成熟期
CJS (Commonjs)
node.js 指定
特征:
通过 module + exports 对外暴露接口
通过 require 去引入外部模块,
参考 前端进阶面试题详细解答
main.js
const dependencyModule1 = require('./dependencyModule1')const dependencyModule2 = require('./dependencyModule2')
let count = 0;const increase = () => ++count;const reset = () => { count = 0;}console.log(count);increase();
exports.increase = increase;exports.reset = reset;
module.exports = { increase, reset}
复制代码
exe
const {increase, reset} = require(./main.js)
复制代码
复合使用
(function(this.value,exports,require,module){ const dependencyModule1 = require('./dependencyModule1') const dependencyModule2 = require('./dependencyModule2')}).call(this.value,exports,require,module)
复制代码
追问:一些开源项目为何要把全局、指针以及框架本身作为参数
(function(window,$,undefined){ const _show = function(){ $("#app").val("hi zhuawa") } window.webShow = _show;})(window,jQuery)
复制代码
阻断思路
优缺点
=> 异步依赖的处理
AMD
通过异步执行 + 允许指定回调函数经典实现框架:require.js
新增定义方式:
//define来定义模块define(id, [depends], callback)//require来进行加载reuqire([module],callback)
复制代码
模块定义的地方
define('amdModule',[dependencyModule1,dependencyModule2],(dependencyModule1,dependencyModule2) => { //业务逻辑 let count = 0; const increase = () => ++count; module.exports = { increase }})
复制代码
引入的地方
require(['amdModule'],amdModule => { amdModule.increase()})
复制代码
面试题:如果在 AMDModule 中想兼容已有代码,怎么办?
define('amdModule',[],require => { const dependencyModule1 = require('./dependencyModule1') const dependencyModule2 = require('./dependencyModule2') //业务逻辑 let count = 0; const increase = () => ++count; module.exports = { increase }})
复制代码
面试题:手写兼容 CJS&AMD
//判断的关键: 1. object还是function 2. exports ? 3. define
(define('AMDModule'),[],(require,export,module) => { const dependencyModule1 = require('./dependencyModule1') const dependencyModule2 = require('./dependencyModule2')
let count = 0; const increase = () => ++count; const reset = () => { count = 0; } console.log(count); export.increase = increase();})( //目标:一次性区分CJS还是AMD 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')})
复制代码
优缺点
ES6 模块化
新增定义:
面试:
性能 - 按需加载
// ES11原生解决方案import('./esMModule.js').then(dynamicModule => { dynamicModule.increase();})
复制代码
优点:通过一种统一各端的形态,整合了 js 模块化的方案缺点:本质上还是运行时分析
解决模块化新思路 - 前端工程化
遗留
根本问题:运行时进行依赖分析解决方案:线下执行
编译时依赖处理思路
<script src="main.js"></script><script> // 给构建工具一个标识位 require.config(__FRAME_CONFIG__);</script><script> require(['a', 'e'], () => { // 业务逻辑 })</script>
define('a', () => { let b = require('b') let c = require('c')})
复制代码
完全体:webpack 为核心的前端工程化 + mvvm 框架的组件化 + 设计模式
评论