JS 历史
背景
JS本身简单的页面设计:页面动画 + 页面提交,
并没有模块化 or 命名空间的概念
幼年期
1.开始需要在页面添加一些不同的JS : 动画,表单,格式化
2.多种JS文件被分在不同的文件中
3.不同的文件又被同一个模板引用
```js
<script scr='jquery.js'></script>
<script scr='main.js'></script>
<script scr='dep1.js'></script>
```
认可: 文件分离是最基础的模块化第一步
问题污染: 污染全局作用域 => 不利于大型项目的开发以及多人团队的共建
成长期
模块化的雏形 -- IIFE(语法侧的优化)
作用域的把控
let count = 0
// 例子一
const increase = () => ++count
// 例子二
const reset = () =>{ count = 0 }
increase()
reset()
利用函数块级作用域
```js
(()=>{
let count = 0
// ...
}();
```
初步实现了一个最最最简单的一个模块
尝试去定义一个最简单的模块
const ifModule = (()=>{
let count = 0
return {
const increase = () => ++count
// 例子二
const reset = () =>{ count = 0 }
}
})
ifModule.increaas();
ifModule.reset();
追问:有额外依赖时,如何优化IIFE相关代码 >优化1:依赖其他模块的IIFE
const ifModule = ((value1,value2)=>{
let count = 0
return {
const increase = () => ++count
// 例子二
const reset = () =>{ count = 0 }
}
})(value1,value2)
ifModule.increaas();
ifModule.reset();
面试1:早期juquery的依赖处理以及模块加载方式吗
了解传统IIFE是如何解决多方依赖的问题
答:IIFE加参条调配
实际上,jquery,等框架其实应用了revealing(揭示模式) 写法
const ifModule = ((value1,value2)=>{
let count = 0
const increase = () => ++count
const reset = () =>{ count = 0 }
return {
increase,
reset
}
})(value1,value2)
ifModule.increaas();
ifModule.reset();
成熟期
CJS - commonjs
node.js 指定
特性
通过 module + export 去对外暴露接口
通过require 来调用其他模块
main.js 部分
// 引入部分
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 处理部分
let count = 0
const increase = () => ++count
const reset = () =>{ count = 0 }
// 做一些跟引入依赖相关事宜...
// 暴露接口部分
export.increase = increase;
export.reset = reset;
module.expors ={
increase,
reset
}
模块使用方式
const { increase,reset } = require('./main.js')
increase();
reset();
可能被问到的问题 实际执行处理
(function() {
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 做一些业务逻辑
}).call(thisValue,exports, require,module);
commjs 优点
commjs 率先在服务端实现了,从框架层面解决依赖、全局变量污染问题
commjs 缺点
主要针对于服务端的解决方案。对于异步拉去依赖的处理整合不是那么友好
新的问题 —— 异步依赖
AMD 规范
通过异步加载 + 允许指定回调函数 经典时间线框架是 require.js
新增定义方式:
// 通过define 来定义一个模块 热庵后require 进行加载
/**
* define
* params: 模块名,依赖模块,工厂方法
**/
define(id , [depends], callback)
require([module],callback)
模块定义方式
define('amdModule',['dependencyModule1','dependencyModule2'],
(dependencyModule1,dependencyModule2)=>{
let count = 0
const increase = () => ++count
const reset = () =>{ count = 0 }
return { increase, reset }
})
引入模块:
require(['amdModule'],amdModule=>{
amdModule.increase();
})
面试题2: 如果在AMDmodule 中想兼容已有代码,怎么办
define(amdModule=>{
// 引入部分
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 处理部分
let count = 0
const increase = () => ++count
const reset = () =>{ count = 0 }
// 做一些跟引入依赖相关事宜...
// 暴露接口部分
export.increase = increase;
export.reset = reset;
return {
increase,
reset
}
})
**面试题3: AMDmodule 中使用 revealing **
define('amdModule',[],(require,export, module)=> {
amdModule=>{
// 引入部分
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 处理部分
let count = 0
const increase = () => ++count
const reset = () =>{ count = 0 }
// 做一些跟引入依赖相关事宜...
// 暴露接口部分
export.increase = increase;
export.reset = reset;
export.increase = increase();
export.reset = reset();
}
})
define('amdModule',[],requirre=>{
const otherModule = require('amdModule')
otherModule.increase();
otherModule.reset();
})
面试题3: 兼容 AMD & commonjs / 如何判断 CJS
UMD 出现
( define('amdModule',[],(require,export, module)=> {
amdModule=>{
// 引入部分
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 处理部分
let count = 0
const increase = () => ++count
const reset = () =>{ count = 0 }
// 做一些跟引入依赖相关事宜...
// 暴露接口部分
export.increase = increase;
export.reset = reset;
export.increase = increase();
export.reset = reset();
}
}))(
// 目标是一次性区分commonjs or AMD
typeof module === 'object'
&& module.exports
&& typeof define !== 'function'
?// 是 CJS
// factory 执行的是一个回调
factory => module.exports = factory(require,exports,module)
: // 是AMD
define
)
优点: 适合在浏览器中加载异步模块,可以并行加载多个模块 缺点: 会有引入成本,不能按需加载
CMD 规范
按需加载
主要用的框架 sea.js
define('module',[],(require,exports,module)=>{
let $ = require('jquery')
// jquery 相关逻辑
let dependencyModule = require('./dependencyModule')
// dependencyModule1 相关逻辑
})
优点: 按需加载,依赖就近
缺点:依赖打包,加载逻辑存在于每个模块中,扩大模块体积
面试题5: AMD & CMD 区别
答: 依赖就近,按需加载
ES6 模块化
走进新时代
新增定义:
引入关键字 —— import
导出关键字 —— export
// 引入区域
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2')
// 实现代码逻辑
let count = 0
const increase = () => ++count
const reset = () =>{ count = 0 }
// 导出区域
export default {
increase,reset
}
模板引入的地方
<script type='module' scr="esModule.js"></script>
node引入的地方
import { increase , reset } = './esMoule.js'
increase();
resset();
import esModule from './esModue.mjs'
esModule.increase();
esModule.resset();
面试题6: 动态模块
答: export import
ES11原生解决方案:
import('./exModule.js').then(dynamicEsModule => {
dynamicEsModule.increase();
})
优点(重要点): 通过一种最统一的形态政和路js的模块化
缺点(局限性):本质上还是运行时的依赖分析
解决模块化的新思路 —— 前端工程化
背景
根本问题 - 运行时 进行依赖分析
前端的模块化处理方案依赖于运行时分析
解决方案: 线下执行 grunt gulp webpack
工程化的实现
<!DOCTYPE html>
<html>
<script scr="main.js"></script>
<script>
// 给构建工具一个标识位
webpack.config(_FRAME_CONFIG_);
</script>
<script>
require(['a'],()=>{
// 业务处理
})
</script>
</html>
define('a',()=>{
let b = require('b')
export.run = () {
// run
}
})
工程化实现
step1: 扫描依赖关系表:
{
a:['b','c'],
b:['d'],
e:[]
}
step2: 重新申城依赖数据模板:
<!DOCTYPE html>
<html>
<script scr="main.js"></script>
<script>
// 给构建工具一个标识位
webpack.config({
'deps': {
a:['b','c'],
b:['d'],
e:[]
}
});
</script>
<script>
require(['a','e'],()=>{
// 业务处理
})
</script>
</html>
step3: 执行工具,采用模块化方案解决模块化处理依赖
define('a',['b','c'],()=>{
export.run = () => {}
})
优点: 1.构建时生产配置,运行时运行 2.最终转化成执行处理依赖 3.可以扩展