JS 模块化详解
Last Edited Time
Feb 9, 2022 07:33 AM
date
Feb 11, 2021
slug
js-module
status
Published
tags
NodeJS
JS Module
Blog
前端工程
NPM
必读系列
summary
模块化总结笔记
type
Post
模块化详解
大致对比:
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行
- CMD 推崇依赖就近,AMD 推崇依赖前置
总结:
- CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
- AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
- CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
- ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
全局函数模式
将不同的功能封装成不同的全局函数
Namespace 模式
封装成简单的对象
let myModule = {
data: 'www.baidu.com',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo() // foo() other data
IIFE 模式
匿名函数自执行闭包, 早期
JS 插件
, JQuery
, Lowdash
, 以及现在的 webpack
都在用// module.js文件
(function(window, $) {
let data = 'www.baidu.com'
//操作数据的函数
function foo() {
//用于暴露有函数
console.log(`foo() ${data}`)
$('body').css('background', 'red')
}
function bar() {
//用于暴露有函数
console.log(`bar() ${data}`)
otherFun() //内部调用
}
function otherFun() {
//内部私有的函数
console.log('otherFun()')
}
//暴露行为
window.myModule = { foo, bar }
// IIFE模式增强 : 引入依赖, 这就是现代模块实现的基石
})(window, jQuery)
CommonJS
在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。
特点
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
- 输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
- CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性
代码
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter); // 3
incCounter();
console.log(counter); // 3
// 这是因为counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
AMD
AMD规范则是非同步加载模块,允许指定回调函数, 一般使用 RequireJS
特点
- 每个资源只会请求一次
- 引入的js文件顺序固定, 能够清楚地显示依赖关系
- 依赖到的资源会在第一时间加载
代码
AMD 定义:
//定义没有依赖的模块
define(function(){
return 模块
})
//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
return 模块
})
require(['module1', 'module2'], function(m1, m2){
使用m1/m2
})
RequireJS 库:
// dataService.js文件
// 定义没有依赖的模块
define(function() {
let msg = 'www.baidu.com'
function getMsg() {
return msg.toUpperCase()
}
return { getMsg } // 暴露模块
})
//alerter.js文件
// 定义有依赖的模块
define(['dataService'], function(dataService) {
let name = 'Tom'
function showMsg() {
alert(dataService.getMsg() + ', ' + name)
}
// 暴露模块
return { showMsg }
})
// main.js文件
(function() {
require.config({
baseUrl: 'js/', //基本路径 出发点在根目录下
paths: {
//映射: 模块标识名: 路径
alerter: './modules/alerter', //此处不能写成alerter.js,会报错
dataService: './modules/dataService'
}
})
require(['alerter'], function(alerter) {
alerter.showMsg()
})
})()
// <!-- 引入require.js并指定js主文件的入口 -->
// <script data-main="js/main" src="js/libs/require.js"></script>
CMD
特点
- CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行
- CMD规范整合了CommonJS和AMD规范的特点
代码
CMD 定义:
//定义没有依赖的模块
define(function(require, exports, module){
exports.xxx = value
module.exports = value
})
//定义有依赖的模块
define(function(require, exports, module){
//引入依赖模块(同步)
var module2 = require('./module2')
//引入依赖模块(异步)
require.async('./module3', function (m3) {
})
//暴露模块
exports.xxx = value
})
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
SeaJS 使用:
// module1.js文件
define(function (require, exports, module) {
//内部变量数据
var data = 'atguigu.com'
//内部函数
function show() {
console.log('module1 show() ' + data)
}
//向外暴露
exports.show = show
})
// module2.js文件
define(function (require, exports, module) {
module.exports = {
msg: 'I Will Back'
}
})
// module3.js文件
define(function(require, exports, module) {
const API_KEY = 'abc123'
exports.API_KEY = API_KEY
})
// module4.js文件
define(function (require, exports, module) {
//引入依赖模块(同步)
var module2 = require('./module2')
function show() {
console.log('module4 show() ' + module2.msg)
}
exports.show = show
//引入依赖模块(异步)
require.async('./module3', function (m3) {
console.log('异步引入依赖模块3 ' + m3.API_KEY)
})
})
// main.js文件
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
// 在index.html中引入
// <script type="text/javascript">
// seajs.use('./js/modules/main')
// </script>
ES6 Module
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
特点
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
- 因为静态化特性, 方便实现
Tree Shaking
, 代码提示等功能
代码
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
ele.textContent = add(99 + basicNum);
}