前景
- 未出现模块化之前,通过将方法函数和变量封装到命名空间对象上来实现模块化
// home.js
let homeUtil = {
msg: 'home.js',
foo: function() {},
bar: function() {}
}
在html中使用
<html>
<body>
<script src="./home.js"></script>
<script>
console.log(homeUtil.msg)
homeUtil.foo()
homeUtil.bar()
</script>
</body>
</html>
注意 此种方式虽然将方法和变量封装到一个对象中,减少了全局变量和命名冲突的可能,但还是出现命名冲突覆盖的可能,并且此种方式会暴露出所有的模块成员,内部状态会被外部修改
- 通过立即执行函数IIFE
// home.js
(function(global, $) {
let msg = 'home'
function foo() {
console.log('home foo() ')
}
function setBg() {
$('body').css('background', 'blue')
}
window.homeUtil = {
foo,
setBg
}
})(window, jQuery)
- 原始的模块依赖关系,通过引入script的先后顺序控制依赖
// index.js
// home中引用jquery中方法,如果顺序出错会导致报错
<script src="./jquery.min.js"></script>
<script src="./home"></script>
<script>
homeUtil.setBg()
</script>
暴露的问题
- 会出现全局污染
- 需要手动控制依赖关系,增加维护成本
- 拆分多个文件之后,增加http请求数
- 依赖关系不明确
模块化方案
CommonJS
定义
CommonJS是一种定义了一种模块规范,每个文件都是一个模块。
在服务端使用: 模块的加载是运行时同步加载的,
在浏览器端使用,需要提前使用Browserify进行编译
Browserify使用
- 全局安装browserify
npm install -g browserify
- 业务代码main.js
var unique = require('uniq');
var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];
console.log(unique(data));
- 转换代码(浏览器端不认识require)
// main.js 源文件
// -o 代表output输出文件
// bundle.js 目标文件
browserify main.js -o bundle.js
基本使用
- 暴露模块
function foo() {}
function bar() {}
module.expors = {
foo,
bar
}
// module.exports 不能重复定义, 后定义的会覆盖前面的,module.exports实际上是一个对象
// 或者
exports.xxx = yyy
exports.foo = function() {}
exports.bar = function() {}
- 引入模块
//第三方模块
const fs = require('fs')
//自定义模块
const home = require('./home')
home.foo()
home.bar()
module.exports 和 exports
node中默认提供一个exports对象,这个exports对象引用指向module.exports, 相当于exports = module.exports;
- module.exports不能重复定义,如果重复定义后面的会覆盖前面的
- module.exports和exports.xxx = yyy 不能公用,如果同时存在,只有module.exports生效
- 不能直接使用exports = xxx这样会破坏exports的指向,导致module.exports找不到指定的导出模块
AMD (Asynchronous module definition)
定义
异步模块化定义,专门用于浏览器端, 需要配合requirejs
基本使用
- 定义模块
// 没有依赖模块
define(function() {
return 模块内容
})
// 有依赖模块
define(['module1','module2'], function(m1, m2){
return 模块内容
})
- 引入模块
require(['module1', 'module2'], function(m1, m2){
// ....
})
html中使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- data-main指定入口文件, src引入requirejs依赖 -->
<script data-main="main" src="lib/require.js"></script>
<!-- 通过引入requirejs, 使用模块通过require(['module1', 'module2'], callback) -->
</body>
</html>
CMD (Common Module Definition)
定义
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。而且CMD是综合了CJS和AMD的特点,可以同步也可以异步,代表Sea.js
基本使用
// module1.js
// 定义没有依赖的模块
define(function(require, exports, module){
// 通过module或者exports导出模块内容 跟cjs类似
module.exports = {
num: 1,
add: function() {
this.num++
}
}
})
// 定义有依赖的模块
define(function(require, exports, module){
// 同步加载依赖
let m2 = require('./module2.js')
// 异步加载依赖
require.async('./module2.js', function(m2) {
// ....
})
// 暴露模块内容
exports.xxx = {}
})
在html中使用
<script src="./lib/sea.js"></script>
<script>
// seajs 的简单配置
seajs.config({
base: "./lib", // 依赖模块目录
alias: {
"module2": "module2.js"
}
})
// 加载入口模块
seajs.use("./main.js")
</script>
ESM (ES6 Modules or JavaScript Modules)
定义
打包 JavaScript 代码以供重用的官方标准格式。 模块是使用各种导入和导出语句定义的
基本使用
- 导出模块 export
// module1.js
// 默认导出 export default xxxx
export default {
msg: 'xxx',
showMsg: function() {
console.log('showMsg...')
}
}
// 单个导出 export xxx
// 导出变量
export let arr = [1,2,3,4]
// 导出方法
export function add() {console.log('add')}
- 导入模块 import
// main.js 引入module1.js
// module1 代表默认导出部分
// 单个导出在{}中引入
import module1, {arr, add} from './module.js'
console.log(module1.msg)
module1.showMsg()
console.log(arr)
add()
- hmtl文件中使用
// 目前Chrome浏览器已经支持es6语法,需要在script标签设置type为module即可
// 不支持es6的浏览器 需要使用babel进行编译之后使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./main.js" type="module"></script>
</body>
</html>
ES6模块和CommonJS 模块的差异
- CommonJS模块输出的是一个
值的拷贝, ES6模块输出的是值的引用 - CommonJS导入值可以修改,ES6引入的值都是
只读的,不能重新赋值 - CommonJS模块是运行时加载, ES6模块是编译时输出接口
- CommonJS模块的require()是同步加载模块, ES6模块的import命令是异步加载,有一个独立的模块依赖的解析阶段
- CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段才会生成
差异 -- 值拷贝和引用
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值(仅针对基本类型,引用类型会影响),模块内部的变化就影响不到这个值
// module.js
let num = 1
let info = {
count: 1
}
function add() {
num++
info.count++
}
module.exports = {
info,
num,
add
}
// main.js
const m1 = require('./module/module')
console.log(m1.num) // 1
console.log(m1.info.count) // 1
m1.add()
console.log(m1.num) // 1
console.log(m1.info.count) // 2
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块
// module.js
export let count = 1
export function add() {
count++
}
// main.js
import {count, add } from './modules/module.js'
console.log(count) // 1
add()
console.log(count) // 2
count = 4 // TypeError