模块化的理解

pic.1708326768564

什么是模块化

模块化,就是把一个大的问题,化解为小的问题在这种思维下提炼出来的工程化解决方案。

核心思想

核心思想就是隔离。要有自己的内部属性,内部方法,以及自己来决定哪些属性与方法能够被其他模块访问。

模块的设计目的是什么?

有了模块,我们可以更方便的使用别人的代码,想要什么功能,就加载什么模块。

什么是 js 模块?

在 es6 之前,JavaScript 没有官方规范,主要采用 CommonJS 和 AMD 这两种规范。

无模块化

函数 -> 命名空间 -> 立即执行函数

CommonJS 规范

nodeJS 采用的就是 CommonJS 规范,运行在 nodeJS 中的规范。

CommonJS 的加载方式称为运行时加载,以下代码的实质是加载整个fs对象,再从fs对象上读取这三个方法。

1
2
3
4
5
6
7
8
// CommonJS模块
let { stat, exists, readfile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

require()

在 node 源码中有一个 module.js 文件,这个文件实现了 node 的整个模块加载系统。

1
2
3
4
5
6
7
// module.js

function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
//...

我们在 nodeJS 中使用的 require()方法调用的就是 module.require 方法。具体可参考 Node.js 模块加载机制 Require()

注意:node 中使用的**_require()_**和 RequireJS 是两个东西。

使用

主要有 module、exports、require。

导出:module

module.exports 由 Module 对象创建。如果我们想导出一个对象

1
2
3
4
5
// a-common.js(导出)
module.exports = { foo: 'bar' };

或者;
module.exports.foo = 'bar';

exports 是 module.exports 的一个引用,所以我们可以使用

1
2
// ✅
exports.foo = 'bar';

错误使用,这相当于重新定义了 exports

1
2
// ❎
exports = {};

导入:require

1
2
3
4
var o = require('./a-common.js');

使用;
console.log(o.foo);

优缺点

优点:
解决了依赖、全局变量污染的问题

缺点:
因为 nodeJS 是运行在服务端,服务端所有的模块是放在本地的,模块的加载速度就是硬盘的读取速度,是同步的。但是浏览器的模块是放在服务器端,如果一个模块过大,浏览器会处于一种假死状态。

因此,浏览器端的模块不能采用”同步加载”,只能采用”异步加载”。所以 AMD 规范诞生了。

AMD 规范

AMD 是”Asynchronous Module Definition”的缩写,意思是”异步模块定义“。模块的加载不影响后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等模块加载完毕,回调函数才会执行。
define 和 require 就是 require.js 在全局注入的函数。

AMD 规范主要由 require.jscurl.js 这两个 JavaScript 库实现。
在网页中嵌入 require.js,就可以进行模块化编程了。

1
<script data-main="scripts/main" src="scripts/require.js"></script>

定义模块

使用 define 方法

1
2
3
4
5
define('A', ['require', 'exports'], function (require, exports) {
exports.fun = function () {
return require('B').fun;
};
});

导入模块

AMD 也是采用 require()语句加载模块,require([module], callback)

1
2
3
require(['math'], function (math) {
math.add(2, 3);
});

注意,不要与 NodeJS 中的 require()混淆。CommonJS 中的 require 是 NodeJS 源代码 module.js 文件中定义的方法,AMD 中的 require()是由 RequireJs 实现的。

CMD

CMD,全称 Common Module Definition,它整合了 CommonJS 和 AMD 规范的特点。
主要区别为一下两点:

  1. CMD 可以同步加载模块,也可以异步加载,而 AMD 需要异步加载模块
  2. CMD 遵循依赖就近原则,而 AMD 遵循依赖前置原则。也就是说 CMD 在使用模块前,引入模块即可,而 AMD 需要把所有的依赖提前放在依赖数组中。

UMD

umd,全称 Universal Module Definition,它允许在环境中同时使用 AMD 规范和 CommonJS 规范。

ES Module

module 可完全取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的解决方案。
ES Module 的设计思想是静态化,也就是说在编译时就确定了模块的依赖关系,而 CommonJS 和 AMD 是在运行时确定,这是 EM 模块和其他模块最显著的差别;第二个差别是 ES6 模块输出的是值的引用,而 CommonJS 输出的是值的拷贝。

ES6 模块的加载方式称为“编译时加载”,在编译时就完成了加载,效率要比 CommonJS 和 AMD 的“运行时加载”效率高。

1
2
// ES6模块
import { stat, exists, readFile } from 'fs';

以上代码只会从fs中加载这 3 个方法,其他方法不会加载。

引入模块

1
2
3
// test.js

console.log('my name is test.js');
1
2
3
4
// index.js
import test from './test';

console.log('test: ', test);
  • import 表示引入一个模块
  • test 可理解为要引入模块的名字
  • from 表示从哪里引入
  • ‘./test’ 为 ‘./test.js’ 的简写,表示将要引入的模块的路径

引入这个模块的同时,'./test'里的代码也执行了。因为 test.js 没有对外暴露接口,所以 test 为空对象。
pic.1708326785878

对外提供接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log('my name is test.js');

const name = 1;
const obj = { a: 1 };
const arr = [1, 2, 3];
const fn = () => {
console.log('fn');
};

export default {
name,
obj,
arr,
fn,
};

使用 export default 对外暴露一个对象。当使用import test from './test'时,test 就是这个暴露的对象
pic.1708326805238

我们还可以仅通过 export 暴露几个方法和属性,在 test 中添加以下代码,看看 index.js 中的 test 会发生什么变化

1
2
3
4
5
6
// test.js

export const age = 20;
export const bar = () => {
console.log('bar');
};

结果发现 test 并没有什么变化,因为它仅仅等于export defaule所暴露的对象

如果想要获取 test.js 对外暴露的所有接口,可通过如下模式获取

1
import * as test from './test';

*号表示所有,是比较常用的通配符,as 表示别名。* as test 表示将 test.js 对外暴露的所有接口组成的对象,命名为 test。
pic.1708326814698

1
2
3
4
5
6
7
import test, { age, bar } from './test';

// 等于以下写法

import test from './test';
import * as allTest from './test';
const { age, bar } = allTest;

这种写法非常常见,test 仍表示为export default 所暴露的对象,{ age, bar } 表示用解构的语法从返回的整个对象中取对应的接口。结果显而易见
pic.1708326822951

在 react,这种使用方法很常见,我们能根据导入语法,就知道 react 内部是如何封装接口的

1
2
3
4
5
6
import { lazy } from 'react'

// 对应的导出
expoet default {
lazy: () => {}
}

还有另一种写法:

1
2
3
4
5
6
export { default as RightMain } from './right-main';

// import RightMain from './right-main';
// export {
// RightMain
// }

参考:
https://developer.aliyun.com/article/113478

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×