js模板化
课程目标
- js模块历史
- 不同模块化原理和实现
- 相关面试题
知识点
- 无模块化
- IIFE
- CommonJs
- AMD & CMD & UMD
- ES6
- 现代型模块化?
- 模块化跟工程化?
- 模块化跟组件化?
- 模块化与设计模式?
无模块化
JS诞生之初面向简单页面开发,没有模块的概念。
开始需要在页面中增加一些不同的js:动画、表格、格式化。
js文件的顺序不能乱放,被依赖的文件放在前面,比如jquery.js要放在最前面。各个js文件的依赖关系不明显。
<script src='jquery.js'></script>
<script src='main.js'></script>
- 问题出现:
污染全局作用域 => 不利于大型项目的开发以及多个团队的共建,有可能出现变量及方法重命名覆盖的问题
。接着往下看。
IIFE
(Imdiately Invoked Function Expression),立即执行的函数表达式
作用域的把控。举个栗子:
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
increase();
reset();
两个方法调用同一个count属性,reset()会把increase()相加的值清空,可以利用函数块极作用域来保证调用increase()方法时,count=1,不会被其他方法覆盖。
(() =>{
let count =0;
...
})();
下面来初步实现一个模块
const iifeModule=(() => {
let count = 0;
return {
inrease: () => ++ count,
reset: () => {
count = 0;
}
}
})();
// 上面只是一个函数表达式 调用inrease和reset方法
iifeModule.inrease();
iifeModule.reset();
面试题一
- 如果依赖其他模块如何优化IIFE代码? 优化:依赖其他模块的IIFE,比如dependencyModule1,dependencyModule2就是其他模块
const iifeModule=((dependencyModule1,dependencyModule2) => {
let count = 0;
return {
inrease: () => ++ count,
reset: () => {
count = 0;
}
}
})(dependencyModule1,dependencyModule2);
iifeModule.inrease();
iifeModule.reset();
面试题二
- 了解早期jquery的依赖处理以及模块加载方案吗? | 了解传统IIFE如何解决多方依赖的问题?
答:IIFE加传参调配。就是上面代码所示。实际上jquery等框架其实应用了
revealing(揭示模式)
的写法。
CommonJS
- CommonJS标志了JavaScript模块化编程正式诞生,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限。CommonJS属于服务端模块。node.js的模块系统,是参照CommonJS规范实现的。 特征:
- 通过
module + exports
去对外暴露接口 - 通过
require
来调用其他模块 模块组织方式
// main.js文件
// 引入部分
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 处理部分
let count =0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
// 暴露接口部分
exports.increase = increase;
exports.reset = reset;
// moudule.exports = {
// increase,reset
// }
// main.js模块文件使用
const {increase,reset} = require('./main.js');
increase();
reset();
面试题
- 用立即执行函数实现上面代码,thisValue代表当前值
(function(thisValue,exports,require,module){
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 逻辑代码...
}.call(thisValue,exports,require,module));
CommonJS优点:
率先在服务端实现了,从框架层面解决依赖、全局变量污染的问题。
CommonJS缺点:主要针对了服务端的解决方案。服务端的文件都是保存在本地硬盘或云盘上,可以同步加载完成,等待时间就是硬盘的读取时间,但对于浏览器来说,模块放在服务器端,等待时间取决于网速的快慢。对于异步拉取依赖的处理整合不是那么友好
。
AMD
- AMD(Asynchronous Module Definition)异步模块定义
- 经典实现框架是:require.js
- 通过
异步加载 + 允许制定回调函数
- 通过
define
来定义一个模块,然后require
, - 语法:
define(id?,dependencies?, factory)
require([module],callback)
id
:模块名,是个字符串,指的是定义中模块中的名字,参数是可选的,如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字dependencies
:定义中模块所依赖模块的数组,参数是可选的,定义了三种特殊的依赖关键字,如果“require”、“exports”、“module”出现在依赖列表中,参数应该按照CommonJS模块规范自由变量去解析factory
:为模块初始化要执行的函数或对象,如果为函数,应该只被执行一次,如果是对象,此对象应该为模块的输出值。 栗子:
// 模块定义方式
define('amdModule',['dependencyModule1','dependencyModule2'],(dependencyModule1,dependencyModule2) => {
// 逻辑代码
const increate = () => console.log
return {
increate
}
})
// 引入模块
require(['amdModule'],amdMoudle => {
amdMoudle.increase();
})
- AMD优点:
适合在浏览器中加载异步模块,可以并行加载多个模块
- AMD缺点:
会有引入成本,不能按需加载
面试题一
- 如果在AMDmodule中想兼容已有代码,怎么办? define的回调函数已经提供require去兼容已有代码
define('amdModule', [], require => {
// 处理部分
let count =0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
return {
increase,
reset
}
})
面试题二
- AMD中使用revealing
define('amdModule', [], (require, exports, module) => {
// 处理部分
let count =0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
exports.increase = increase();
exports.reset = reset();
})
define('module1', [], require => {
// 使用方法
const ontherModule = require('amdModule');
ontherModule.increase();
ontherModule.reset();
})
面试题三
- 兼容AMD&CommonJS(
UMD
) | 如何判断是 CommonJS?
(define('amdModule', [], (require, exports, module) => {
// 处理部分
let count =0;
const increase = () => ++count;
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, exprots, moudle)
:
// 是AMD
define('moduleName',[],require => {})
)
CMD
- 按需加载,
- 主要应用的框架:sea.js
- CMD优点:按需加载,依赖就近
- CMD缺点:依赖于打包,加载逻辑存在于每个模块中,扩大模块体积 栗子:
define('admModule', (require, exports, module) => {
// 引入部分
const dependencyModule1 = require('./dependencyModule1');
//dependencyModule1相关逻辑...
const dependencyModule2 = require('./dependencyModule2');
//dependencyModule2相关逻辑...
})
面试题
- AMD&CMD区别?
答:
依赖就近,按需加载
,AMD里的require分全局和局部,CMD里没有全局的require,对于依赖的模块AMD是提前执行,CMD是延迟执行,,不过RequireJS从2.0开始,也改成可以延迟执行。CMD推崇依赖就近,AMD推崇依赖前置。
UMD(Universal Module Definition)
- 通用的模块化系统,兼容AMD和CommonJS,保证模块可以被AMD和CommonJS调用。
ES6
- 引入关键字:
import
- 卖出关键字:
export
//esModule.js
// 处理部分
let count =0;
export const increase = () => ++count;
export const reset = () => {
count = 0;
}
export default {
increase,
reset
}
// html中js引入方式
<script type="moudle" src="esModule.js"/>
// node中使用 mjs是node里定义模块的文件后缀
import { increase, reset} from './esModule.mjs'
increate();
reset();
// 两个方式都可以
import esModule from './esModule.mjs'
esModule.increate();
esModule.reset();
- ES6优点:
通过一种最统一的形态整合了js的模块化
- ES6缺点:
局限性,本质上还是运行时的依赖分析
面试题
- import解决动态模块
// ES11原生解决方案
import('./esModule.js').then(dynamicEsModule => {
dynamicEsModule.increate();
})
- 解决模块化的新思路--前端工程化
- 前端的模块化处理方案依赖于运行时分析
- 解决方案:线下执行:
grunt
gulp
webpack