1 什么是模块化,模块化解决什么问题?
1.1 什么情况下提出的
前端业务需求越来越复杂,导致代码量越来越大,维护管理越来越困难。这就需要一种更好的模式,去更好的进行代码的管理、组织、通信。
1.2 什么是模块化
本质上模块就是一种提供对外通信接口,进行代码切分/组合的管理方式。其呈现的方式因不同的模块化方案而不同,基本是以文件粒度区分。目标是管理变量。
1.3 为什么要用模块化
-
将复杂的问题分为多个子问题
关注代码分离,单一职责
-
大型软件开发的技术基础
- 更优雅的代码管理
- 易于替换、复用、拓展
- 🔥 内聚(变量,行为内聚在模块内,对外暴露接口进行通信)
-
方便多人协同
2 模块化的发展
2.1 函数时代
模块化混沌时期,组织代码就是靠「经验」,基本就是全局函数各种调用
问题:
- 命名冲突
2.2 命名空间
模块化思想雏形初现,通过简单的命名空间进行「块儿」的切分,更分离,和内聚弥补了全局函数的一些缺点
问题
- 可以被别人修改
2.3 闭包
模块化的解决方案再次提升,利用闭包使得污染的问题得到解决,更加纯粹的内聚。
3 AMD CMD UMD
3.1 AMD
AMD 代表库是 require.js
James Burke 提出,解决 cmj 不能在浏览器使用的问题。提出了 AMD 规范,使用异步处理模块依赖
3.1.1 使用
define(id?, depencies?, factory);
define('foo', ['utils'], function(utils){
})
3.1.2 行为分析
- 分析
define('a', function(){
console.log('a load');
return {
run: function() { console.log('a run'); }
}
})
define('b', function(){
console.log('b load');
return {
run: function() { console.log('b run'); }
}
})
require(['a', 'b'], function(a, b) {
console.log('main run');
a.run()
b.run()
})
// a load
// b load
// main run
// a run
// b run
- 结论:
require 的时候加载依赖模块
3.1.3 主要方法
- 可以配置依赖路径
rj.config({path: {
'jqurey': 'xxxx'
}})
- 定义模块
define('moduleA', ['jquery'], function(jquery){ })
- 加载模块
rj(['moduleA'], function(moduleA){ })
- 简单模拟实现
// 创建依赖的缓存
const def = new Map()
const defaultOptions = {
paths: ''
}
// From CDN
const __import = url => {
return new Promise((resove, reject) => {
System.import(url).then(resove, reject)
})
}
// normal script
const __load = url => {
return new Promise((resolve, reject) => {
const head = document.getElementsByTagName('head')[0]
const node = document.createElement('script')
node.type = 'text/javascript'
node.src = url
node.async = true
node.onload = resolve
node.onerror = reject
head.appendChild(node)
})
}
rj = {}
// 实现 config 方法:一般用来收集外界引入的 模块
rj.config = options => Object.assign(defaultOptions, options)
// 定义模块,触发的时机其实是在 require 的时候,所以 -> 收集
define = (name, deps, factory) => {
// todo 参数的判断,互换
def.set(name, { name, deps, factory })
}
// dep -> a -> a.js -> 'http:xxxx/xx/xx/a.js';
// 这里默认 文件名称 和 定义时的 name 一致
const __getUrl = dep => {
const p = location.pathname
return p.slice(0, p.lastIndexOf('/')) + '/' + dep + '.js'
}
// 其实才是触发加载依赖的地方
require = (deps, factory) => {
return new Promise((resolve, reject) => {
Promise.all(
deps.map(dep => {
// 走 CDN
if (defaultOptions.paths[dep])
return __import(defaultOptions.paths[dep])
// 走 script 标签下载
return __load(__getUrl(dep)).then(() => {
const { deps, factory } = def.get(dep)
if (deps.length === 0) return factory(null)
return require(deps, factory)
})
})
).then(resolve, reject)
}).then(instances => factory(...instances))
}
3.2 CMD
代表人物 玉伯, 代表库 sea.js
3.2.1 用法及行为
// sea.js
define('a', function (require, exports, module) {
console.log('a load')
exports.run = function () { console.log('a run') }
})
define('b', function (require, exports, module) {
console.log('b load')
exports.run = function () { console.log('b run') }
})
define('main', function (require, exports, module) {
console.log('main run')
var a = require('a')
a.run()
var b = require('b')
b.run()
})
seajs.use('main')
// main run
// a load
// a run
// b load
// b run
3.3 AMD UMD的对比
-
依赖前置,依赖后置
✏ AMD 是在 require 的时候加载依赖的全部模块,然后再运行主方法。即所谓的依赖前置
✏ CMD 是在 使用模块的时候 才触发加载。即所谓的依赖后置
🔥 实际上本质是是否需要按需加载
4 ES6 import
JS 模块化的发展方向,语法层面的模块化规范
- 用法
// ./a.js
export const a = 123
// ./b.js
import { a } from './a.js'
// ./c.js
export const b = 456
export default function fn() {}
// ./d.js
import fn from './c.js'
// import './c.js'
// ./e.js
import fn, { b } from './c.js'
-
注意点
- export 和 export default 可同时使用。互不影响
- 导出时 只能声明时导出和以对象的形式导出
- 引入时不能将大括号里的内容简单的想成对象,不可使用其赋默认值。可以使用 as 改名称
5 node commonJS
nodeJs 目前使用的模块化规范
- 用法
// ./a.js
exports.a = 123
// ./b.js
const { a } = require('./a.js')
// ./c.js
module.exports = function () {}
// ./d.js
const fn = require('./c.js')
-
注意点
- exports其实是指向module.exports的引用;
- require() 返回的是 module.exports 而不是 exports;
- module.exports 的初始值为一个空对象{},所以 exports也为空对象{};
- module.exports对象不为空的时候exports对象就自动忽略;所以同时使用只有 module.exports 的内容会生效
6 知识点 --》待完善
-
按需加载的本质是使用 promise
-
amd cmd 依赖前置,依赖后置 --> 按需加载
-
iife ❓ 立即调用函数表达式
-
cmj esm 值拷贝,引用拷贝
-
cmj 同步,esm 异步
-
cmj esm 缓存
-
cmj 运行时加载,esm 编译时加载
-
cmj esm 怎么解决循环依赖
-
webpack 的模块化机制