JS本身是为了简单页面设计的补充,页面动画,表单提交等,没有模块化概念
初始:无模块化
-
页面增加不同类型的js文件,validate.js, moment.js...
-
多种js为了维护和可读性,被分在不同的js文件中
-
不同的js在同一个文件里被引用
<script src='./jquery.js'> <script src='./main.js'>
优势:多个js文件是简单的模块化思想
问题:只是做了代码的拆分而已;
- 会污染全局作用域;没有独立作用域上下文变量名/函数名重合
雏形:立即执行函数 (语法侧)
对作用域的把控
let count = 0
const increase = () => ++count;
const reset = () => {
count = 0
console.log("count is reset")
}
increase()
reset()
这里count是全局变量,可以被任意引用和修改,想要只能被increase(), reset()修改,利用函数作用域
const iifemodule = (()=>{
let count = 0;
return {
increase: () => ++count,
reset: ()=> count = 0
}
})()
iifemodule.increase();
完成模块的封装,实现对外暴露功能,变量只在内部
IIFE优化,如果依赖其他模块 --> 传参传进来就好
const iifemodule = ((module1, module2)=>{
// 使用module1,module2
let count = 0;
return {
increase: () => ++count,
reset: ()=> count = 0
}
})(module1, module2)
一个模块的封装:输入(传参) + 内部逻辑 + 对外暴露接口(功能)
面试题:早期jQuery依赖处理,模块加载方案
IIFE + 传参调配(就是外部的传参以及结果的传出 ), revealing写法
const revealingCounterModule = (() => {
let count = 0
// dependencyModule1, dependencyModule2
const increase = () => ++count
const reset = () => {
count = 0
console.log("count is reset")
}
API也以局部变量定义在作用域中
return {
increase,
reset
}
})()
revealingCounterModule.increase()
只暴露接口而没有业务逻辑,外部拿到的是组成好的,直接调用就可以,也看不到内部如何实现
本质的实现和方案上并无不同,只是在写法思想上,更强调局部API做原理逻辑,暴露出去的是可调用的面向使用方的接口抽象
成熟:CommonJS(规范化体系)
nodejs制定
-
一个文件一个模块
-
每个模块单独作用域
-
module + exports导出成员,require调用其他模块 具体实现
const module1 = require('./module1') const module1 = require('./module2') let count = 0 // 使用module1,module2 const increase = () => ++count const reset = () => { count = 0 console.log("count is reset") } exports.increase = increase exports.reset = reset module.exports = { increase, reset } // 使用 const { increase, reset } = require('./commonJSCounterModule.js') increase() reset() -
优点:CommonJS规范在服务端率先完成了JS的模块化,解决了依赖、全局变量污染的问题
-
缺点:CommonJs主要诞生针对的环境是服务端,对于同步加载没问题。 => 浏览器端的异步问题
成熟:AMD规范 -- 非同步模块的加载
require.js
定义方式 - 回调
// 1. 通过define来定义一个模块
define(id, [depends], callback) // id当前定义的模块, depends需要哪些外部的依赖,callback
// 2. 外部加载
require([module], callback)
上面的例子
define(amdCounterModule,[module1,modle2],(module1,module2)={
let count = 0
// dependencyModule1, dependencyModule2
const increase = () => ++count
const reset = () => {
count = 0
console.log("count is reset")
}
return {
increase,
reset
}
})
引入模块
require(['amdCounterModule'], amdCounterModule => {
amdCounterModule.increase()
amdCounterModule.reset()
})
(像是前两者的结合)
问AMD使用require加载同步模块可以吗
可以的,AMD支持向前兼容,可以用require加载同步模块
define(require => { //传入require 也可以使用exports,module
const dependencyModule1 = require('./dependencyModule1')
const dependencyModule2 = require('./dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
}
return {
increase,
reset
}
})
- 优点:适合在浏览器环境中异步加载模块,可以并行加载多个模块
- 缺点:提高了开发成本,不能按需加载,而是必须提前加载所有依赖
成熟:CMD规范 按需加载 -- 编译型
sea.js
define(function(require, exports, module){ // 同样向前兼容
let $ = require('jquery');
// 使用jquery
let module1 = require('./module1');
// ...使用
})
- 优点:就近加载
- 缺点:加载逻辑存在于每个模块中
完全体 ESModule
=> 底层语言的统一 ES 新增定义方式:引入 + 导出 => import + export (ES新语法)兼容前面所有
import module1 from './module1'
import module2 from './moduel2'
let count = 0
export const increase = () => ++count
export const reset = () => {
count = 0
}
export default {
increase,
reset
}
==> 服务器端 CommonJS + 浏览器端 ESModule
UMD 兼容 AMD CJS
AMD通过define定义,retur暴露 CJS require引入,module.export暴露
(define => define((require, exports, module) => {
const dependencyModule1 = require('dependencyModule1')
const dependencyModule2 = require('dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
}
module.exports = {
increase,
reset
}
}))(
typeof module === 'object'
&& module.exports
&& typeof define !== 'function'
? // 当前为node / commonJS场景
factory => module.exports = factory(require, exports, module)
: // 当前amd
define
)