1. 什么是模块化开发
1.1 什么是模块化开发
- 事实上模块化开发最终的目的是将程序划分成一个个小的结构
- 这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构
- 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用
- 也可以通过某种方式,导入另外结构中的变量、函数、对象等
- 上面说提到的结构,就是模块,按照这种结构划分开发程序的过程,就是模块化开发的过程
1.2 JavaScript
设计缺陷
- 比如
var
定义的变量作用域问题 - 比如
JavaScript
的面向对象并不能像常规面向对象语言一样使用class
- 比如
JavaScript
没有模块化的问题 Brendan Eich
本人也多次承认过JavaScript
设计之初的缺陷,但是随着JavaScript
的发展以及标准化,存在的缺陷问题基本都得到了完善- ES6(2015)推出了自己的模块化方案
- 在此之前,为了让
JavaScript
支持模块化,涌现出了很多不同的模块化规范:AMD
、CMD
、CommonJS
等
1.3 没有模块化的问题
- 我们假设有两个人:小明和小丽同时在开发一个项目,并且会将自己的
JavaScript
代码放在一个单独的js
文件中 - 小明开发了
aaa.js
文件,代码如下:var flag = true if (flag) { console.log("aaa的flag为true") }
- 小丽开发了
bbb.js
文件,代码如下:var flag = false if (!flag) { console.log("bbb使用了flag为false") }
- 很明显出现了一个问题:大家都喜欢使用
flag
来存储一个boolean
类型的值,但是一个人赋值了true
,一个人赋值了false
,如果之后都不再使用,那么也没有关系,但是,小明又开发了ccc.js
文件if (flag) { console.log("使用了aaa的flag") } // 小明发现ccc中的flag值不对,因为是小丽将flag赋值为了false,
- 没有模块化对于一个大型项目来说是灾难性的
2. CommonJS
规范
2.1 CommonJS
和Node
- 我们需要知道
CommonJS
是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS
,后来为了体现它的广泛性,修改为CommonJS
,平时我们也会简称为CJS
a.Node
是CommonJS
在服务器端一个具有代表性的实现
b.Browserify
是CommonJS
在浏览器中的一种实现
c.webpack
打包工具具备对CommonJS
的支持和转换 - 所以,
Node
中对CommonJS
进行了支持和实现,让我们在开发node
的过程中可以方便的进行模块化开发
a. 在Node
中每一个js
文件都是一个单独的模块
b. 这个模块中包括CommonJS
规范的核心变量:exports
、module.exports
、require
c. 我们可以使用这些变量来方便的进行模块化开发 - 前面我们提到过模块化的核心是导出和导入,
Node
中对其进行了实现:
a.exports
和module.exports
可以负责对模块中的内容进行导出
b.require
函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容
2.2 Node
模块化开发
- 在
node
中每一个文件都是一个独立的模块,有自己的作用域 - 那么,就意味着别的模块中不能随便访问另外一个模块中的内容
- 模块需要导出自己想要暴露的变量、函数、对象等等
- 另一个模块就可以导入自己想要使用的变量、函数、对象等等
2.3 exports
导出
exports
是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出// a.js 模块导出 exports.name = name exports.age = age exports.sayHello = sayHello // syaHolle是函数
// b.js 导入 const bar = require('./bar') console.log(bar.name) console.log(bar.age) console.log(bar.sayHolle())
2.4 module.exports
导出
- 在
Node
中我们经常导出东西的时候,是通过module.exports
导出的module.exports = 'hello' // 或者 module.exports = function () {}
- 一个模块中只能有一个
module.exports
2.5 module.exports
导出
- 模块成员导出的另一种方式:module.exports
a.module.exports
导出和exports
完全一样,它们之间只是名称不一样,
b.exports
是module.exports
的别名(地址引用关系,指向同一内存地址)
c. 当它们指向不同地址的时候,导出对象最终以module.exports
为准
d. 比如一个模块要导出的是某个成员,而非对象,这时候只能改变module.exports
的指向才有效
2.6 module.exports
和exports
的关系
-
在 Node 中,每个模块内部都有一个自己的
module
对象 -
该
module
对象中,有一个成员叫exports
也是一个对象 -
因为每次导出成员需要
module.exports.xxx = xxx
比较麻烦 -
所以为了简化,它的内部,做了这样的操作
exports = module.exports
-
exports
等价于module.exports
它们指向了同一个内存地址 -
由于
require
方法中返回的是module.exports
对象,所以当返回导出单个成员的时候:
require = 'hah'
它改变了指向,跟module.exports
没有关系
module.exports = 'hah'
而它改变指向,导出的就是改变指向后的它
2.7 require
导入
-
require 有两个作用:
执行加载模块中的代码
和得到被加载模块中的exports导出接口对象
。// 例如:b.js 文件导出的对象,导入到 a.js 文件中 let a = require('./b.js') // require 加载并执行了 b.js 文件模块,返回被加载模块中 exports 导出的接口对象 a.num a.sayHi // 然后利用 a 对象就可以访问, b.js 模块中导出对象的属性了 // 注:导入模块,在当前文件夹下路径必须以./开头,后缀可以省略
-
优先从缓存加载:
require
加载页面的时候,会把加载的页面先缓存起来
,后面再有require 加载同一个页面
会先去缓存中查找,如果有就从缓存拿,从缓存拿就意味着不会从新加载页面,页面也就不会重复执行,而是直接获取到加载的对象,这样做的目的是避免重复加载,提高模块加载的效率 -
require(参数) 加载的参数分为三种:
模块标识
、路径形式的模块
、包
a. 模块标识加载的为:核心模块
b. 路径形式模块加载的为:自己写的模块
,自己写的模块./
或者../
不能省略,后缀
可以省略,/
(根目录)
c. 包名加载的是:第三方模块