什么是模块化?

  • 模块化就是把单独的一个功能封装到一个模块(文件)中,模块之间相互隔离,但是可以通过特定的接口公开内部成员,也可以依赖别的模块;
  • 将一个项目拆分为若干块,每块之间以一定的形式进行通信,而每块内部的内容都是独立的;

模块化的好处?

  • 抽离公共的代码,提高了代码的维护性和复用性;
  • 隔离作用域,避免变量的冲突;
  • 将一个复杂的系统分解为多个子模块,便于开发和维护;

常用的模块化规范?

CommonJs

nodejs 使用的 commonJs 规范;在 nodejs 中,有且仅有一个入口文件 (启动文件),而开发一个应用肯定会涉及多个文件配合,因此,nodejs 对模块化的需求比浏览器端要大得多;

特点

  • CommonJs 最大的特点就是同步加载;(缺点)
  • 每一个文件都是一个 Module 对象,通过关键字 module.exports 或者 exports 来暴露内容,并可以通过 require 来引入指定模块;
  • 模块一旦加载一次之后就会被缓存;

工作环境

服务端;两个原因使 CommonJs 无法在浏览器端使用:

  • 根本原因:CommonJs 中使用了 Node 的 api,无法在浏览器中运行。
  • 直接原因:作为一门同步加载的模块规范必须得在 require 完全加载好之后才能执行下一步,所以会有阻塞的情况,在网络状态比较差的情况下,就会导致浏览器的假死现象。(对于服务端,require 的等待时间是硬盘读取时间,而对于浏览器端来说,这取决于网速的快慢。)

使用

  • 使用 exports.x 或者 module.exports 暴露模块;exports 是一个空对象 ==>exports={}
  • 通过 require 来完成对模块的引入;
  • 如果一个 JS 文件存在 exportsrequire,该 JS 文件就是一个模块,拥有独立的作用域;

使用 exports.x 暴露;

使用 exports.x 暴露;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 新建m1.js文件
let num = 10;

function sum(a,b){
return a+b
}

class Animal{
constructor(){
this.age=10;
}
}
// exports是个对象;给exports对象上面添加属性进行导出;
// exports.x
exports.num1=num; //num1是随便取的;尽量和变量名保持一致
exports.sum=sum;
exports.Animal=Animal;

使用 require 引入 m1.js

js
1
2
3
4
5
6
7
8
9
10
// 新建index.js文件用来引入m1.js
// require函数的返回值是个对象,是exports对象;
const M1 = require("./m1.js"); //这行代码的本质是 M1=exports;(对象赋值,其实是地址)
console.log(M1); //{ num1: 10, sum: [Function: sum], Animal: [class Animal] }

console.log(M1.num1); //10
console.log(M1.sum(2,3)); // 5

const DOG = new M1.Animal(); //const声明的常量 建议大写;
console.log(DOG.age) //10

使用 module.exports 暴露多个;

使用 module.exports 暴露;在一个自定义模块中,默认情况下,module.exports={};

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 新建m2.js文件
let num = 10;

function sum(a,b){
return a+b
}

class Animal{
constructor(){
this.age=10;
}
}
// methods 1

// module.exports是个对象;给module.exports对象上面添加属性进行导出;
// module.exports.num=num;
// module.exports.sum=sum;
// module.exports.Animal=Animal;

//methods 2

// module.exports导出方式 es6对象简写形式(上面声明了变量,下面json的键值对名一样,可以简写;(值就是上面的变量))
module.exports={
num,
sum,
Animal,
}

使用 require 引入 m2.js

js
1
2
3
4
5
6
7
8
9
// 两种方式的引入方式都是一样的,都是require("相对路径");
const M2 = require("./m2.js"); //require函数的返回值是个对象,是module.exports对象;
console.log(M2); //{ num: 10, sum: [Function: sum], Animal: [class Animal] }

console.log(M2.num); //10
console.log(M2.sum(5,3)); //8

const CAT = new M2.Animal(); //const声明的常量 建议大写;
console.log(CAT.age) //10

使用 module.exports 暴露单个;

使用 module.exports 暴露;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 新建m2.js文件
let num = 10;

function sum(a,b){
return a+b
}

class Animal{
constructor(){
this.age=10;
}
}

// module.exports是个对象,但是不加属性,直接导出;
module.exports=sum;

使用 require 引入 m2.js

js
1
2
3
4
5
const M2 = require("./m2.js"); 
console.log(M2); //[Function: sum] 这里的M2就是m2.js里导出的东西,就不再是对象了;

// console.log(M2.sum(5,3)); // TypeError: M2.sum is not a function
console.log(M2(5,3)); // 8

module.exports 与 exports 有什么关系或者区别呢?

  • 我们追根溯源,通过维基百科中对 CommonJS 规范的解析:
    • CommonJS 中是没有 module.exports 的概念的;
    • 但是为了实现模块的导出,Node 中使用的是 Module 的类,每一个模块都是 Module 的一个实例,也就是
      module;
    • 所以在 Node 中真正用于导出的其实根本不是 exports,而是 module.exports;
    • 因为 module 才是导出的真正实现者;
  • 但是,为什么 exports 也可以导出呢?
    • 这是因为 module 对象的 exports 属性是 exports 对象的一个引用;
    • 也就是说 module.exports = exports = index.js 中的 M2;
module 对象

Node 中使用的是 Module 的类,每一个模块都是 Module 的一个实例,也就是 module;

module

module.exports 对象

module.exports=xxx 的过程其实是引用赋值 (浅拷贝) 的过程;exports (也是一个对象) 之所以可以导出,就是把 exports 对象的地址赋值给了 module.exports,借助 module.exports 导出;

  • 使用 require () 方法导入模块时,导入的结果,永远以 最下面的 module.exports 指向的对象为准;
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 新建m2.js文件
let num = 10;

function sum(a,b){
return a+b
}

// module.exports是个对象;给module.exports对象上面添加属性进行导出;
module.exports.num=num;
module.exports.sum=sum;

// module.exports开辟了新的引用类型地址;
module.exports={
name:"开辟新的引用类型地址"
}
js
1
2
const M2 = require("./m2.js"); //require函数的返回值是个对象,是module.exports对象;
console.log(M2); //{ name: '开辟新的引用类型地址' }
  • 上面的 module.exports 属性要是在下面结果就不一样了;相当于在原来的对象上添加了属性;
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 新建m2.js文件
let num = 10;

function sum(a,b){
return a+b
}

// module.exports开辟了新的引用类型地址;
module.exports={
name:"开辟新的引用类型地址"
}

// module.exports是个对象;给上面的那个module.exports对象上面添加属性进行导出;
module.exports.num=num;
module.exports.sum=sum;
js
1
2
const M2 = require("./m2.js"); //require函数的返回值是个对象,是module.exports对象;
console.log(M2); //{ name: '开辟新的引用类型地址', num: 10, sum: [Function: sum] }
  • exports 与 module.exports 同时导出

由于真正用于导出的其实根本不是 exports,而是 module.exports;所以同时导出的时候 module.exports 开辟了新的内存地址,exports 就相当于被架空了;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
// 新建m2.js文件
let num = 10;

function sum(a,b){
return a+b
}

module.exports.num=num;
module.exports.sum=sum;

exports={
name:"开辟新的引用类型地址"
}
js
1
2
const M2 = require("./m2.js"); //require函数的返回值是个对象,是module.exports对象;
console.log(M2); //{ num: 10, sum: [Function: sum] }

ES6

特点

  • ES6 最大的特点是按需加载;

工作环境

  • 是浏览器端与服务器端通用的模块化开发规范;

使用

  • 目前浏览器使用如下方式引入一个 ES6 模块文件: <script src="入口文件" type="module"></script>
  • 使用 export 或者 export default 暴露模块;
  • 通过 import 来完成对模块的引入;
  • export defalut 每个 js 模块里面只能有一个;export 可以有多个;

使用 export 直接导出

使用 export 暴露;

js
1
2
3
// 新建m1.js文件
export let name = "张三";
export let age = 12;

使用 import {xx,xx} from "xxx" 引入;

js
1
2
3
4
// 新建index.js文件用来引入m1.js
import {name,age} from "./m1.js";
console.log(name);//张三
console.log(age);//12

使用 export 一起导出

使用 export{} 暴露;

js
1
2
3
4
5
6
7
// 新建m1.js文件
let name = "张三";
let age = 13;
export {
name,
age
}

使用 import {xx,xx} from "xxx" 引入;

js
1
2
3
4
// 新建index.js文件用来引入m1.js
import {name,age} from "./m1.js";
console.log(name);//张三
console.log(age);//13

使用 export default 导出

使用 export default 暴露;

js
1
2
3
4
5
6
7
// 新建m1.js文件
let name = "张三";
let age = 14;
export default{ //默认导出:一个js文件中只能写一次
name,
age
}

使用 import xxx from "xxx" 引入;

js
1
2
3
4
5
6
// 新建index.js文件用来引入m1.js
import m1 from "./m1.js"; //import 后面的 m1 是随便起的;
console.log(m1); //{ name: '张三', age: 14 }

console.log(m1.name);//张三
console.log(m1.age);//14

混用

使用 exportexport default 暴露;

js
1
2
3
4
5
6
7
8
// 新建m1.js文件
export let email = "nodeJs@163.com";
let name = "张三";
let age = 18;
export default{ //默认导出一个js文件中只能写一次
name,
age
}

使用 import xxx,{xx,xx} from "xxx" 引入;

js
1
2
3
4
5
6
7
// 新建index.js文件用来引入m1.js
import m1,{email} from "./m1.js";
console.log(m1); //{ name: '张三', age: 18 }

console.log(m1.name);//张三
console.log(m1.age);//18
console.log(email);//nodeJs@163.com

参考文献