本文从以工夫为轴从以下几个方面进行总结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加载:(立刻执行)解析到标签开始异步加载,下载实现后开始执行并阻塞渲染,执行实现后持续渲染
- 兼容性:> IE9
- 问题可能被疏导到 => 1. 浏览器的渲染原理 2.同步异步原理 3.模块化加载原理
- 呈现的问题
- 净化全局作用域
成长期(模块化前夜) - 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)
阻断思路
一. window
- 全局作用域转换为部分作用域,window是全局作用域,如果不转成部分作用域会有一个向上查找晓得全局的过程,晋升执行效率
- 编译时优化:编译后会变成(优化压缩老本,销毁)
(function(c){})(window) // window会被优化成c//window在外面所有别的执行所有的变动都会随着执行结束都会跟着c一起被销毁
二. jquery
- 独立定制复写和挂载
- 避免全局串扰
- 三. undefined
避免改写:在执行外部这段代码的时候保障undefined是正确的,不会被改写,如在内部定义一个undefined =1
undefined对jquery自身是一个很重要的一个存在
优缺点
- 长处:CommonJS率先在服务端实现了,从框架层面解决了依赖,全局变量未然的问题
- 毛病: 针对服务端的解决方案,异步拉取,依赖解决不是很敌对
=> 异步依赖的解决
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模块化
新增定义:
- 引入:import
- 引出:export
面试:
- 性能 - 按需加载
// 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框架的组件化 + 设计模式