模块化
1 概述
1.1 模块化介绍
Node 应用由模块(每一个JS
即是一个模块)组成,采用CommonJS
模块规范(提供了模块引入导出的规则)。每个文件就是一个模块,有自己的作用。在一个文件里面定义的变量、函数、类(class),都是私有的,对其他文件不可见(模块作用域)。在服务器端,模块的加载是运行时同步加载的;
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程,对于整个系统来说,模块是可组合,分解和更换的单元。
1.2 模块化的好处
- 提高代码的复用性
- 提高代码的可维护性
- 可以实现按需加载
- 防止命名冲突
1.3 模块化规范
① CommonJS 规范
CommonJS 是一种模块化规范,最初提出来是在浏览器以外的地方使用,并且当时命名为 ServerJS,后来为了体现它的广泛性,更名为 CommonJS,也可以简称为 CJS。 Node 是 CommonJS 在服务端一个具有代表性的实现,Browserify 是 CommonJS 在浏览器端的一种实现,webpack 具备对 CommonJS 的支持与转换。
② AMD 规范
AMD 主要是应用于浏览器端的一种模块化规范,AMD 是 Asynchronous Module Definition(异步模块定义)的缩写,它采用的是异步加载模块,事实上 AMD 的规范早于 CommonJS,但是现在 CommonJS 仍被使用,但 AMD 已经很少用了。 实现 AMD 规范的库主要是 require.js 和 curl.js。
③ CMD 规范
CMD 也是应用于浏览器端的一种模块化规范,CMD 是 Common Module Definition(通用模块定义)的缩写,他也是采用了异步加载模块,但是它将 CommonJS 的优点吸收了过来,这个目前也很少使用了。
实现 CMD 规范的库主要是 sea.js。
④ ES Module 规范
ES Module 规范是 ES 提出的,是官方的模块化规范。
1.4 Node 中 模块的分类
Node.js中根据模块来源的不同,将模块分为了3大类,分别是:
- 内置模块(由Node.js官方提供,例如:fs,path,http)
- 自定义模块:用户创建的每个JS文件,都是自定义模块。,
- 第三方模块:由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载。
Node 支持 CommonJS 和 ES6 两种模块化规范。
2 CommonJS 模块规范
2.1 在模块中暴露数据
1)模块内如果没有暴露数据,引人模块的时候会得到一个空对象。
2)通过为 module.exports
赋值,实现暴露数据。module.exports
的值就是要暴露的数据。
module.exports = true;
module.exports = 5211314;
const data = [10,20,30,40,50,60];
module.exports = data;
module.exports = () => {
console.log(123456);
}
module.exports = {
school: "克莱登大学",
name: "铭哥",
age: 100
}
3)通过为 module.exports
设置属性,module.exports
的默认值是个空对象,可以为空对象添加属性
module.exports.isNB = true;
module.exports.msg = 'hahaha';
module.exports.say = ()=>{};
4)通过为 exports
设置属性,暴露数据。 exports
与 module.exports
指向同一个对象,为 exports
设置属性就是为 moudule.exports
设置属性;但不能给 exports
赋值,那样会改变其引用地址,exports
与 module.exports
就不再是一个对象了。
const userName = "mingge";
const age = 12;
// 以下方式可以暴露数据
exports.userName = userName; // 等价于 module.exports.userName = userName;
exports.age = age; // 等价于 module.exports.userName = userName;
// 以下写法无法暴露数据,因为修改了 exports 的引用地址
exports = {username, age};
2.2 导入(引入)模块
const 变量名 = require('自定义模块地址');
const {变量1,变量2} = require('自定义模块地址'); // 如果模块暴露的数据是对象,可以使用结构赋值获取其中的属性方法
const mod = require('./mode'); // 等同于 require('./mod.js')
1)通过 require()
方法可以引入模块,该方法的返回值就是模块中暴露的数据。
2)自定义模块的地址需要以 ./
、 ../
开头,这是模块文件的相对路径,相对于当前的执行的 JS 脚本的位置,并非命令行打开的目录,如果模块文件的地址没有以 ./
、 ../
开头,会被认为是内置模块或第三方模块的模块名。
3)自定义模块的地址可以省略扩展名,如果模块路径没有扩展名,会依次查找 .js
文件、.json
文件、目录。
4)对于不同扩展名的模块文件,Node.js 有不能的处理方式:
- 扩展名是
.js
的模块文件: 读取文件内容并编译执行并获取模块中暴露的数据。 - 扩展名是
.json
的模块文件: 读取文件,用JSON.parse()
解析返回结果作为获取的数据。 - 扩展名是
.node
的模块文件: 这是 c/c++ 编写的扩展文件,通过dlopen()
方法编译。 - 其他扩展名,文件内容会被当做 JavaScript 代码去解析。
5)如果将一个目录作为一个模块导入,需要遵循以下规则:
- 会默认加载该目录下
package.json
文件中main
属性定义的入口文件。 - 如果没有package.json, 或者
main
属性对应的文件不存在,则自动找index.js
、index.json
作为入口文件。
2.3 模块导入的加载流程
require
导入自定义模块会按照以下流程加载:
- 将相对路径转为绝对路径,定位目标文件
- 缓存检测
- 读取目标文件代码
- 包裹为一个函数并执行(自执行函数)。通过
arguments.callee.toString()
查看自执行函数 - 缓存模块的值
- 返回
module.exports
的值
3 ES6 模块规范
3.1 Node 中使用 ES 模块规范
Node.js 要求 ES6 模块采用.mjs
后缀文件名,也就是说,只要脚本文件里面使用import
或者export
命令,那么就必须采用.mjs
后缀名。
如果不希望将后缀名改成.mjs
,可以在项目的package.json
文件中,指定type
字段为module
。
3.2 在模块中暴露数据
① 暴露单个数据
使用 export default
可以在模块中暴露单个数据,注意每个脚本文件中 export default
语句只能出现一次,出现多个export default
语句会报错!
export default 100;
const data = [10,20,30,40,50];
export default data;
function say() {}
function eat() {}
export default {
say,
eat
}
② 暴露多个数据
使用 export
可以暴露多个数据,有两种写法:
// 第一种写法 在声明变量的同时暴露
export const firstName = 'Lee';
export const lastName = 'KeQiang';
export const year = 1918;
export function fn() {};
export const obj = {name:'mingge',age:100}
// 第二种写法 在文件底部统一暴露(推荐)
const firstName = 'Lee';
const lastName = 'KeQiang';
const year = 1918;
function fn() {};
const obj = {name:'mingge',age:100}
// 注意 export 右边的是一种语法结构,并不是 {} 表示的对象
export {firstName, lastName, year, fn, obj}
3.3 引入模块并使用模块中暴露的数据
① 模块使用 export default
暴露单个数据
import 变量名 from '模块地址';
② 模块使用 export
暴露多个数据
// 获取的变量名必须与模块暴露的变量名一致,可以多次分别获取,可以取别名
import {name, year as y} from '模块地址';
import {fn} from '模块地址';
// 可以将模块中的数据整体加载
import * as 别名 from '模块地址';