JS模块化
1.历史
JS本身就是为了满足简单的页面设计:页面动画 + 表单提交并无模块化或者命名空间概念
JS的模块化需求日益增长
幼年期:无模块化
- 开始需要在页面中加载不同JS:动画、组建、格式化
- 多种js文件会被分在不同的文件中
- 不同的文件又被同一个模板所引用
<script src='main.js'></script>
<script src='demo.js'></script>
文件分离拆分是最基础的模块化(第一步)
- 拓展
- script标签的参数 —— async & defer
<script src='main.js' async></script>
无参数 —— 解析到立即阻塞,立刻下载执行当前script async —— 解析到标签开始异步下载,解析完成后开始执行 defer —— 解析到标签开始异步下载,下载完成后开始执行并且阻塞渲染,执行完成后继续渲染
兼容性 >IE9
存在问题:
- 污染全局作用域,不利于大型项目的开发以及多人团队的共建
成长期: 模块化雏形 —— IIFE(语法侧的优化)
作用域的把控
let count=0;
const increase = ()=> ++count;
const reset = ()=>{
count=0;
}
利用函数的块级作用域 —— 隔离区
(()=>{
let count=0;
const increase = ()=> ++count;
const reset = ()=>{
count=0;
}
})();
这样就初步实现一个最最最简单的模块
- 扩展
- 独立模块本身的依赖优化
方法1: 依赖其他模块传参,把需要加载的模块通过参数传进去
const iifeModule = ((dependencyModule1,dependencyModule2)=>{
let count=0;
const increase = ()=> ++count;
const reset = ()=>{
count=0;
}
……
return{
increase,reset
}
})(dependencyModule1,dependencyModule2);
成熟期:
CJS — Commonjs
node.js 指定
特征:
- 通过 module + exports 去对外暴露接口
- 通过require 去引入外部模块
const dependencyModule1=require('./dependencyModule1');
const dependencyModule2=require('./dependencyModule2');
let count=0;
const increase = ()=> ++count;
const reset = ()=>{
count=0;
}
increase();
exports.increase=increase;
exports.reset=reset;
module.exports={
increase,reset
}
//使用
const { increase,reset } =require('./main.js')
//复合使用
(function(){
const dependencyModule1=require('./dependencyModule1');
const dependencyModule2=require('./dependencyModule2');
……
}).call(thisValue,exports,require,module);
//一些开源项目为啥要把全局、指针以及框架本身引用作为参数传给本身
(function(window,$,undefined){
const _show = function(){
$('#app').val("hi. ")
}
})(window,jQuery)
window
1. 全局作用域转化成局部作用域,提升执行效率
2. 编译时优化
(function(c){})(window) //window会被优化成c,执行结束之后c会被销毁
jquery
1. 独立定制复写和挂载
2.防止全局串扰
undefined
防止重写
- 优点: CommonJs率先在服务端实现了从框架层面解决了依赖,全局变量污染的问题
- 缺点: 针对了服务端的解决方案,异步来取依赖处理不是很完美
新的问题 —— 异步依赖
AMD规范
通过异步加载 + 允许制定回调函数
经典实现框架:require.js
新增定义方式:
//define 来定义模块
define(id,[depends],callback);
//require 进行加载
require([modules],callback);
模块定义地方
define('amdModule',['dependencyModule1','dependencyModule2'],(dependencyModule1,dependencyModule2)=>{
……
})
引入的地方
require(['amdModule'],amdModule=>{
amdModule.xxx();
})
- 扩展:在AMDModule中兼容已有代码
define('amdModule',[],require=>{
const dependencyModule1=require('./dependencyModule1');
const dependencyModule2=require('./dependencyModule2');
……
})
- 手写兼容CJS & AMD
//判断关键1. object 还是 function 2. 有没有export 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;
}
export .increase = increase();
})(
//一次性区分CJS和AMD
typeof module === 'object' && module.exports && typeof define !== 'function'? //CJS
factory => module.exports = factory(require,exports,module)
: //AMD
define
)
- 优点: 适合在浏览器中加载异步模版的方案
- 缺点: 引入成本,define形式很难做到绝对的按需加载
CMD
按需加载 主要应用框架: sea.js
define('module' , (require,exports , module)=>{
let $=require('jquery');
//jquery相关
……
let dependencyModule1=require('./ dependencyModule1');
//dependencyModule1 相关逻辑
……
})
- 优点: 按需加载,依赖就近
- 缺点: 依赖打包,加载逻辑存在于每个模块中,扩大了模块体积,同时功能上依赖编译
ES6模块化
走向新时代
新增定义
引入:import
导出:export
模块在引入、导出和定义的地方:
//引入
import dependencyModule1 from './dependencyModule1';
import dependencyModule2 from './dependencyModule2';
……
//导出
export const increase=()=> ++count;
export const reset = ()=>{
count=0;
}
export default{
increase,reset
}
ES动态模块原生解决方案
import('./esModule.js').then(dynamicModule=>{
dynamicModule.increase();
})
- 优点(重要性): 通过一种最终统一各端的形态,整合了js模块化的通用方案
- 局限性:本质上还是运行时的依赖分析
解决模块化的新思路 —— 前端工程化
遗留问题
根本问题 - 运行时进行依赖分析
解决方案 - 线下执行
简述实现一个编译依赖的思路
<! 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')
})
工程化扫描
- 扫描依赖关系表
{
a:['b','c'],
b:['d'],
e:[]
}
- 根据依赖关系表重制模版
<! doctype html>
<script src='main.js'></script>
<script>
//给构建工具生成数据
require.config({
"deps":{
a:['b','c'],
b:['d'],
e:[]
}
});
</script>
<script>
require(['a','e'],()=>{
……
})
</script>
</html>
define('a',()=>{
let b = require('b')
let c = require('c')
})
- 执行工具,采用模块化解决方案处理
define('a',['b','c'],()=>{
export.run = ()=>{
……
}
})
优点:
- 构建时生成配置,进行时去运行
- 最终转化为可执行的依赖处理
- 可以拓展