JS模块化
背景
js自身简略的页面设计:页面动画 + 表单提交
并无模块化 or 命名空间的概念
JS模块化需要日益增长
幼年期:无模块化
- 开始须要在页面中减少一些不同的js:动画、表单、格式化
- 多种js文件被分在不同的文件中
- 不同的文件又被同一个模板饮用
// 过后是被认可的:文件拆散是最根底的<script src="jquery.js"> </script><script src="main.js"> </script><script src="dep1.js"> </script><script src="dep2.js"> </script>
问题:
- 净化全局作用域 => 不利于大型项目的开发以及多人团队的共建
成长期:模块化的雏形 - IIFE(立刻函数、语法侧的优化)
作用域的把控
例子:
// 定义一个全局变量let count = 0// 代码块1const increase = () => ++count// 代码块2const reset = () => { count = 0}increase()reset()
利用函数的块级作用域
(() => { let conut = 0 // ...})
仅定义了一个函数,并没有执行,如果立刻执行
(() => { let conut = 0 // ...})()
初步实现了一个最简略的模块(通过立刻执行函数)
尝试去定一个最简略的模块
const iifeModule = (() => { let count = 0 // 代码块1 const increase = () => ++count// 代码块2 const reset = () => { count = 0 } return { // 代码块1 increase: () => ++count, // 代码块2 reset: () => { count = 0 } }})()iifeModule.increase()iifeModule.reset()
诘问:如果有额定依赖,如何优化iife? - 参数的调用
优化1:依赖其余模块的IIFE
const iifeModule = ((dependencyModule1, dependencyModule2) => { let count = 0 return { // 代码块1 increase: () => ++count, // 代码块2 reset: () => { count = 0 } }})(dependencyModule1, dependencyModule2)iifeModule.increase()iifeModule.reset()
问:理解jquery的依赖解决以及模块加载计划吗?/ 理解传统IIFE是如何解决多方依赖的问题吗?
答:IIFE + 传参调配
实际上,jquery 等框架其实利用了 revealing module(揭示模式) 的写法
const iifeModule = ((dependencyModule1, dependencyModule2) => { let count = 0 // 代码块1 const increase = () => ++count // 代码块2 const reset = () => { count = 0 } // 仅裸露接口,不裸露办法 return { increase, reset } })(dependencyModule1, dependencyModule2)iifeModule.increase()iifeModule.reset()
成熟期
CJS - CommonJS
由node制订的一套计划
特色:
- 通过 module + exports 去对外裸露借口
- 通过 require 来调用其余模块
模块组织形式
main.js 文件中
// 援用const dependencyModule1 = require(dependencyModule1)const dependencyModule2 = require(dependencyModule2)// 解决let count = 0// 代码块1const increase = () => ++count// 代码块2const reset = () => { count = 0}// 做一些跟引入依赖相干事宜……// 裸露接口exports.increase = increaseexports.reset = resetmodule.exports = { increase, reset}
模块应用形式
const { increase, reset } = require(main.js)increase()reset()
可能被问到的货色
理论执行解决
转化成立即执行函数
CJS 和 IIFE 的联合
// 须要穿参(function () { const dependencyModule1 = require(dependencyModule1) const dependencyModule2 = require(dependencyModule2) // 业务逻辑……}).call(thisValue, exports, require, module)
CJS 长处:
CommonJS 率先在服务端实现了,从框架层面解决依赖、全局变量净化的问题CJS 毛病:
次要针对了服务端的解决方案,都是同步引入。对于异步拉取依赖整合没有那么敌对。
新的问题 —— 异步依赖
AMD标准
通过异步加载 + 容许定制回调函数
经典实现框架:require.js
新增定义形式:
// 通过deifine来定义一个模块,而后require进行加载/** * id: 模块名 * [depends]:依赖模块 * callback:工厂办法 */define(id, [depends], callback)require([module], callback)
模块定义形式:
define('amdModule', ['dependencyModule1', 'dependencyModule2'], (dependencyModule1,dependencyModule1) => { // 解决 let count = 0 // 代码块1 const increase = () => ++count // 代码块2 const reset = () => { count = 0 } return { increase, reset }})
引入模块:
require(['amdModule'], amdModule => { amdModule.increase()})
问:如果在amdModule中想兼容已有代码,怎么办?
define('amdModule', [], require => { // 援用 const dependencyModule1 = require(dependencyModule1) const dependencyModule2 = require(dependencyModule2)// 解决 let count = 0// 代码块1 const increase = () => ++count// 代码块2 const reset = () => { count = 0 }// 做一些跟引入依赖相干事宜……// 裸露接口 exports.increase = increase exports.reset = reset module.exports = { increase, reset }})
问:AMD中应用revealing
define('amdModule', [], (require, exports, module) => { // 援用 const dependencyModule1 = require(dependencyModule1) const dependencyModule2 = require(dependencyModule2) // 解决 let count = 0 // 代码块1 const increase = () => ++count // 代码块2 const reset = () => { count = 0 } // 做一些跟引入依赖相干事宜…… // 裸露接口 exports.increase = increase exports.reset = reset})define(require => { const otherModule = require('amdModule') otherModule.increase() otherModule.reset()})
问:兼容 AMD & CommonJS?答:UMD
UMD的呈现
(define('amdModule', [], (require, exports, module) => { // 援用 const dependencyModule1 = require(dependencyModule1) const dependencyModule2 = require(dependencyModule2) // 解决 let count = 0 // 代码块1 const increase = () => ++count // 代码块2 const reset = () => { count = 0 } // 做一些跟引入依赖相干事宜…… // 裸露接口 exports.increase = increase exports.reset = reset}))( // 指标是一次性辨别 CommonJS or AMD typeof module === "object" && module.exports && typeof define !== "function" ? // 是 CommonJS factory => module.exports = factory(require, exports, module) : // 是AMD define)
- 长处:适宜在浏览器中加载异步模块,能够并行加载多个模块
- 毛病:会有引入老本,不能按需加载
CMD标准
按需加载
次要利用的框架 sea.js
define('module', [], (require, exports, module) => { let $ = require('jquery'); // jquery 相干的代码 let dependencyModule1 = require('dependencyModule1') // dependencyModule1 相干逻辑})
- 长处:按需加载,依赖就近
- 毛病:依赖打包,加载逻辑存在每个模块中,扩充模块体积
ES6模块化
走向新时代
新增定义:
引入关键字 —— import
导出关键字 —— export
模块引入、导出和定义
esModule.js
// 引入import dependencyModule1 from 'dependencyModule1'import dependencyModule2 from 'dependencyModule2'// 实现let count = 0export const increase = () => ++countconst reset = () => { count = 0}// 导出export default { reset}
模板引入的中央:
<script type="module" src="esModule.js"></script>
node中:
import { reset } from 'esModule.js'reset()import esModule from 'esModule.js'esModule.reset()
- 长处(重要性):通过一种最对立的状态整合js模块化
- 毛病(局限性):实质上还是运行了依赖剖析
解决模块化的新思路 —— 前端工程化
背景
基本问题 — 运行时进行依赖剖析
前端的模块化解决计划依赖于运行时剖析
解决方案:线下执行
grunt、gulp、webpack
<!doctype html> <script src="main.js"></script> <script> // 给构建工具一个标示位 require.config(__FRAME_CONFIG__) </script> <script> require(['a', 'e'], () => { // 业务逻辑 }) </script></html>
define('a', () => { let b = require('b'); let c = require('c'); exports.run = () => { // run }})
工程化实现
step1: 扫描依赖关系表:
{ a: ['b', 'c'] b: ['d']}
step2: 从新生成依赖数据模板
<!doctype html> <script src="main.js"></script> <script> // 构建工具生成数据 require.config({ "deps": { a: ['b', 'c'], b: ['d'], e: [] } }) </script> <script> require(['a', 'e'], () => { // 业务逻辑 }) </script></html>
step3: 执行工具,采纳模块化计划解决模块化解决依赖
define('a', ['b', 'c'], () => { // 执行代码 exports.run = () => { // run }})
长处:
- 构建时生成配置,运行时执行
- 最终转化执行解决依赖
- 能够扩大
齐全体:webpack为外围的工程化 + MVVM框架组件化 + 设计模式
总结:
- 无模块化 -> IIFE => 从无到有的过程
- IIFE -> CommonJS => 从代码层到框架层
- CommonJS -> AMD => 解决了异步问题
- AMD -> CMD => 解决了按需加载的问题