模块化

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 设置属性,暴露数据。 exportsmodule.exports 指向同一个对象,为 exports 设置属性就是为 moudule.exports 设置属性;但不能给 exports 赋值,那样会改变其引用地址,exportsmodule.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.jsindex.json 作为入口文件。

2.3 模块导入的加载流程

require 导入自定义模块会按照以下流程加载:

  1. 将相对路径转为绝对路径,定位目标文件
  2. 缓存检测
  3. 读取目标文件代码
  4. 包裹为一个函数并执行(自执行函数)。通过 arguments.callee.toString() 查看自执行函数
  5. 缓存模块的值
  6. 返回 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 '模块地址';

results matching ""

    No results matching ""