目录
1. 在ES6之前的版本中模块化代码
- 每个模块系统至少应该能够执行一下操作:
- 定义模块接口,通过接口可以调用模块的功能
- 隐藏模块的内部实现,使模块的使用者不需要关注模块内部的实现细节。同时,吟唱模块的内部实现,避免有可能产生的副作用和对bug的不必要修改
1.1 使用对象、闭包和立即执行函数实现模块
使用函数作为模块
(function countClicks() {
let numClicks = 0; // 这个变量只有通过事件处理器调用,屏蔽了外部访问
// 事件处理器创建的闭包保持局部变量numClicks活跃
document.addEventListener("click", () => {
numClicks++;
console.log(numClicks);
});
})();
// 暂时无法满足模块的第一个要求:接口
模块模式:使用函数扩展模块,使用对象实现接口
// 本例同样使用立即执行函数,
// 但通过返回对象的方式将可以暴露的内容返给了MouseCounterModule变量
// 使其成为一个接口,可以访问内部功能
// 模块内部细节可以通过接口创建的闭包保持活跃
const MouseCounterModule = function() {
let numClick = 0;
const handleClick = () => {
console.log(++numClick);
}
const print = message => console.log(message);
return {
countClicks: () => {
document.addEventListener("click", handleClick);
},
print: print
};
}(); // 立即执行函数
MouseCounterModule.countClicks();
MouseCounterModule.print("Hello!");
// Hello!
模块扩展
// 使用立即执行函数
// 将要扩展的模块作为参数传入函数
(function(module) {
let numScroll = 0;
const handleScroll = () => {
console.log(++numScroll);
}
// 将要添加的功能添加到传入模块当中
module.countScroll = () => {
document.addEventListener("wheel", handleScroll);
}
})(MouseCounterModule);
MouseCounterModule.countScroll();
通过模块扩展无法共享模块的私有变量
当开始创建模块化应用时,模块本身常常依赖其他模块的功能,而模块模式无法实现这些依赖关系。
1.2 使用AMD和CommonJS模块化JS代码
AMD
- 设计明确基于浏览器
- 自动处理依赖,无需考虑模块引入的顺序
- 异步加载模块,避免阻塞
- 在同一文件夹中可以定义多个模块
CommonJS
- 基于文件,面向通用JS环境,不显式支持浏览器环境
- 一个文件就是一个模块,文件中的代码就是模块的一部分,不需要使用立即执行函数来包装变量
- 语法简单,只需定义module.exports属性,其余与标准代码无差异,引用模块只需使用require函数
- 是Node.js默认的模块格式,所以可以使用npm无数的包
2. ES6模块
导出和导入功能
在浏览器环境中使用时,文件类型需要设置为module
<script src="./js/index.js" type="module"></script>
// info.js
const name = "Wango";
// 单独导出内容
export const age = 24;
export function say(message){
console.log(message);
}
// 或者
const name = "Wango";
const age = 24;
function say(message){
console.log(message);
}
export {age, say}; // 推荐
// index.js
// 导入的内容必须用大括号包起来
// 路径是字符串且必须以.或./或../开头
import { say } from "./info.js";
say("Hello!");
// Hello!
// 导入这个文件全部标识符,且取个别名(不用大括号)
import * as info from "./info.js";
info.say("Hello!!!");
// Hello!!!
默认导出
// Student.js
// 定义默认导出
export default class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
info() {
return `Name: ${this.name} | Age: ${this.age}`;
}
}
// 默认导出的同时也可以导出指定内容
export function say(msg) {
console.log(msg);
}
// index.js
// 导入默认导出的内容不需要大括号,也可以起别名
import Stu from "./Student.js";
const s1 = new Stu("Wango", 24);
console.log(s1.info());
// Name: Wango | Age: 24
// 同时导入默认导出和普通导出
import Stu, { say } from "./Student.js";
const s1 = new Stu("Wango", 24);
console.log(s1.info());
// Name: Wango | Age: 24
say("Hello!");
// hello
导入或导出时使用重命名
// Students.js
export default class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
info() {
return `Name: ${this.name} | Age: ${this.age}`;
}
}
function say(msg) {
console.log(msg);
}
// 用as设置别名
export {say as sayHello};
// index.js
import Stu, { say as sayHi } from "./Student.js";
const s1 = new Stu("Wango", 24);
console.log(s1.info());
// Name: Wango | Age: 24
sayHi("Hello!");
// hello
使用as设置了别名之后就只能访问别名,不能访问原始的标识符