模块化
模块(Module):一块具有独立功能的代码,可以是一个函数、一个对象、甚至一个字符串或数字,通常存储为一个单独的 JS 文件。
概述
过去,JS 很难编写大型应用,因为有以下两个问题:
- 全局变量污染
- 难以管理的依赖关系
这些问题,都导致了 JS 无法进行精细的模块划分,因为精细的模块划分会导致更多的全局污染以及更加复杂的依赖关系
于是,先后出现了两大模块化标准,用于解决以上两个问题:
- CommonJS
- ES6 Module
注意:上面提到的两个均是模块化标准,具体的实现需要依托于 JS 的执行环境
CommonJS
node
目前,只有 node 环境才支持 CommonJS 模块化标准,所以,要使用 CommonJS,必须要先安装 node
官网地址:https://nodejs.org/zh-cn/
浏览器运行的是 html 页面,并加载页面中通过 script 元素引入的 js
nodejs 直接运行某个 js 文件,该文件被称之为入口文件
nodejs 遵循 EcmaScript 标准,但由于脱离了浏览器环境,因此:
- 你可以在 nodejs 中使用 EcmaScript 标准的任何语法或 api,例如:循环、判断、数组、对象等
- 你不能在 nodejs 中使用浏览器的 web api,例如:dom 对象、window 对象、document 对象等
CommonJS 标准和使用
node 中的所有代码均在 CommonJS 规范下运行
具体规范如下:
- 一个 JS 文件即为一个模块
- 如果一个模块需要暴露一些数据或功能供其他模块使用,需要使用代码
module.exports = xxx
,该过程称之为模块的导出 - 如果一个模块需要使用另一个模块导出的内容,需要使用代码
require("模块路径")
- 路径必须以./或../开头
- 如果模块文件后缀名为.js,可以省略后缀名
- require 函数返回的是模块导出的内容
- 模块中的所有全局代码产生的变量、函数,均不会对全局造成任何污染,仅在模块内使用
- 模块具有缓存,第一次导入模块时会缓存模块的导出,之后再导入同一个模块,直接使用之前缓存的结果。
有了 CommonJS 模块化,代码就会形成下面的结构:
同时也解决了 JS 的两个问题
原理
node 实际上是将模块文件中的代码放置到一个函数环境中执行
当调用require
函数时,它会按照以下的步骤处理:
// require函数的原理
function require(modulePath) {
//1. 获取模块的绝对路径
var moduleId = abs(modulePath)
//2. 查看模块是否缓存过
if (cache[moduleId]) {
return cache[moduleId]
}
//3. 执行模块,得到执行结果
var module = {
exports: {}
}
function _moduleExcutor(module, exports) {
// 这里是你模块中的代码
}
_moduleExcutor.call(module.exports, module, module.exports)
//4. 缓存模块
cache[moduleId] = module.exports
//5. 返回结果
return module.exports
}
ES6 module
由于种种原因,CommonJS 标准难以在浏览器中实现,因此一直在浏览器端一直没有合适的模块化标准,直到 ES6 标准出现
ES6 规范了浏览器的模块化标准,一经发布,各大浏览器厂商纷纷在自己的浏览器中实现了该规范
模块的引入
浏览器使用以下方式引入一个 ES6 模块文件
<script src="入口文件" type="module">
标准和使用
- 模块的导出分为两种,基本导出和默认导出
可以将整个模块的导出想象成一个对象,基本导出导出的是该对象的某个属性,默认导出导出的是该对象的特殊属性default
//导出结果:想象成一个对象
{
a: xxx, //基本导出
b: xxx, //基本导出
default: xxx, //默认导出
c: xxx //基本导出
}
可以看出:
- 基本导出可以有多个,默认导出只能有一个
- 基本导出必须要有名字,默认导出由于有特殊名字,所以可以不用写名字
导出方式:
export var a = 1 //基本导出 a = 1
export var b = function(){} //基本导出 b = function(){}
export function method(){} //基本导出 method = function(){}
var c = 3;
export {c} //基本导出 c = 3
export { c as temp } //基本导出 temp = 3
export default 3 //默认导出 default = 3
export default function(){} //默认导出 default = function(){}
export { c as default } //默认导出 default = 3
export {a, b, c as default} //基本导出 a=1, b=function(){}, 默认导出 default = 3
- 模块的导入
使用以下的代码导入模块
import { a, b } from '模块路径' //导入属性 a、b,放到变量a、b中
import { a as temp1, b as temp2 } from '模块路径' //导入属性a、b,放到变量temp1、temp2 中
import { default as a } from '模块路径' //导入属性default,放入变量a中,default是关键字,不能作为变量名,必须定义别名
import { default as a, b } from '模块路径' //导入属性default、b,放入变量a、b中
import c from '模块路径' //相当于 import {default as c} from "模块路径"
import c, { a, b } from '模块路径' //相当于 import {default as c, a, b} from "模块路径"
import * as obj from '模块路径' //将模块对象放入到变量obj中
import '模块路径' //不导入任何内容,仅执行一次模块
导入模块时,注意以下细节
1). ES6 module 采用依赖预加载模式,所有模块导入代码均会提升到代码顶部
2). 不能将导入代码放置到判断、循环中
3). 导入的内容放置到常量中,不可更改
4). ES6 module 使用了缓存,保证每个模块仅加载一次