04 【nodejs模块化规范:CommonJS】
网站越来越复杂,js代码、js文件也越来越多,会遇到一些问题 :
- 文件依赖
- 全局污染、命名冲突
程序模块化包括:
- 日期模块
- 数学计算模块
- 日志模块
- 登陆认证模块
- 报表展示模块等。
所有这些模块共同组成了程序软件系统。
一次编写,多次使用,才是提高效率的核心。
2.模块化的理解
Section titled “2.模块化的理解”2.1 什么是模块化
Section titled “2.1 什么是模块化”概念 :将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并组合在一起。
模块的内部数据、实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。
最早的时候,我们会把所有的代码都写在一个js文件里,那么,耦合性会很高(关联性强),不利于维护;而且会造成全局污染,很容易命名冲突。
2.2 模块化的好处
Section titled “2.2 模块化的好处”- 避免命名冲突,减少命名空间污染
- 降低耦合性;更好地分离、按需加载
- 高复用性 :代码方便重用,别人开发的模块直接拿过来就可以使用,不需要重复开发类似的功能。
- 高可维护性 :软件的声明周期中最长的阶段其实并不是开发阶段,而是维护阶段,需求变更比较频繁。使用模块化的开发,方式更容易维护。
- 部署方便
3.模块化规范
Section titled “3.模块化规范”3.1 模块化规范的引入
Section titled “3.1 模块化规范的引入”假设我们引入模块化,首先可能会想到的思路是:在一个文件中引入多个js文件。如下:
但是这样做会带来很多问题:
- 请求过多:引入十个js文件,就有十次http请求。
- 依赖模糊:不同的js文件可能会相互依赖,如果改其中的一个文件,另外一个文件可能会报错。
以上两点,最终导致:难以维护 。
于是,这就引入了模块化规范。
3.2 模块化的概念解读
Section titled “3.2 模块化的概念解读”模块化起源于 Node.js。Node.js 中把很多 js 打包成 package,需要的时候直接通过 require 的方式进行调用(CommonJS),这就是模块化的方式。
那如何把这种模块化思维应用到前端来呢?这就产生了两种伟大的 js:RequireJS 和 SeaJS。
3.3 模块化规范
Section titled “3.3 模块化规范”服务器端规范:
- CommonJS规范 :是 Node.js 使用的模块化规范。
CommonJS 就是一套约定标准,不是技术。用于约定我们的代码应该是怎样的一种结构。
浏览器端规范:
PS:面试时,经常会问AMD 和 CMD 的区别。
另外,还有ES6规范:import & export。
这篇文章,我们来讲一下CommonJS,它是 Node.js 使用的模块化规范。
4.CommonJS 的基本语法
Section titled “4.CommonJS 的基本语法”4.1 CommonJS 的介绍
Section titled “4.1 CommonJS 的介绍”CommonJS:是 Node.js 使用的模块化规范。也就是说,Node.js 就是基于 CommonJS 这种模块化规范来编写的。
CommonJS 规范规定:每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口对象。加载某个模块,其实是加载该模块的 module.exports 对象。
在 CommonJS 中,每个文件都可以当作一个模块:
- 在服务器端:模块的加载是运行时同步加载的。
- 在浏览器端: 模块需要提前编译打包处理。首先,既然同步的,很容易引起阻塞;其次,浏览器不认识
require语法,因此,需要提前编译打包。
4.2 模块的暴露和引入
Section titled “4.2 模块的暴露和引入”4.3 模块的暴露和引入
Section titled “4.3 模块的暴露和引入”Node.js 中只有模块级作用域,两个模块之间的变量、方法,默认是互不冲突,互不影响,这样就导致一个问题:模块 A 要怎样使用模块B中的变量&方法呢?这就需要通过 exports 关键字来实现。
Node.js中,每个模块都有一个 exports 接口对象,我们可以把公共的变量、方法挂载到这个接口对象中,其他的模块才可以使用。
接下来详细讲一讲模块的暴露、模块的引入。
4.3.1 暴露模块的方式一: exports
Section titled “4.3.1 暴露模块的方式一: exports”exports对象用来导出当前模块的公共方法或属性。别的模块通过 require 函数调用当前模块时,得到的就是当前模块的 exports 对象。
语法格式 :
这个 value 可以是任意的数据类型。
注意 :暴露的关键词是exports,不是export。其实,这里的 exports 类似于 ES6 中的 export 的用法,都是用来导出一个指定名字的对象。
代码举例 :
4.3.2 暴露模块的方式二: module.exports
Section titled “4.3.2 暴露模块的方式二: module.exports”module.exports用来导出一个默认对象,没有指定对象名。
语法格式:
这个 value 可以是任意的数据类型。
代码举例:
module.exports 还可以修改模块的原始导出对象。比如当前模块原本导出的是一个对象,我们可以通过 module.exports 修改为导出一个函数。如下:
4.4 exports 和 module.exports 的区别
Section titled “4.4 exports 和 module.exports 的区别”最重要的区别:
- 使用exports时,只能单个设置属性
exports.a = a; - 使用module.exports时,既单个设置属性
module.exports.a,也可以整个赋值module.exports = obj。
其他要点:
- Node中每个模块的最后,都会执行
return: module.exports。 - Node中每个模块都会把
module.exports指向的对象赋值给一个变量exports,也就是说exports = module.exports。 module.exports = XXX,表示当前模块导出一个单一成员,结果就是XXX。- 如果需要导出多个成员,则必须使用
exports.add = XXX; exports.foo = XXX。或者使用module.exports.add = XXX; module.export.foo = XXX。
4.5 问题: 暴露的模块到底是谁?
Section titled “4.5 问题: 暴露的模块到底是谁?”答案 :暴露的本质是exports对象。【重要】
比如,方式一的 exports.a = a 可以理解成是,给 exports 对象添加属性 。方式二的 module.exports = a可以理解成是给整个 exports 对象赋值。方式二的 module.exports.c = c可以理解成是给 exports 对象添加属性。
Node.js 中每个模块都有一个 module 对象,module 对象中的有一个 exports 属性称之为接口对象 。我们需要把模块之间公共的方法或属性挂载在这个接口对象中,方便其他的模块使用。
4.6 引入模块的方式:require
Section titled “4.6 引入模块的方式:require”require函数用来在一个模块中引入另外一个模块。传入模块名,返回模块导出对象。
语法格式 :
解释:
- 内置模块:require的是包名 。
- 下载的第三方模块:require的是包名 。
- 自定义模块:require的是文件路径 。文件路径既可以用绝对路径,也可以用相对路径。后缀名
.js可以省略。
代码举例 :
require()函数的两个作用 :
- 执行导入的模块中的代码。
- 返回导入模块中的接口对象。
4.7 主模块
Section titled “4.7 主模块”主模块是整个程序执行的入口,可以调度其他模块。
4.8 模块的初始化
Section titled “4.8 模块的初始化”一个模块中的 JS 代码仅在模块第一次被使用时 执行一次,并且在使用的过程中进行初始化,然后会被缓存起来,便于后续继续使用。
代码举例:
(1)calModule.js:
(2)main.js:(在 main.js 中引入 hello.js 模块)
在命令行执行 node main.js 运行程序,打印结果:
从打印结果中可以看出,calModule.js这个模块虽然被引用了两次,但只初始化了一次。
5.commonjs-在服务器端的实现举例
Section titled “5.commonjs-在服务器端的实现举例”(1)module1.js:
(2)module2.js:
注意,此时暴露出去的 exports 对象 等价于整个函数。
(3)module3.js:
(4)app.js:(将其他模块汇集到主模块)
这样的话,我们的代码就写完了。
我们在命令行中输入node app.js,就可以把代码跑起来了。打印结果如下:
6.commonjs-基于浏览器端的实现举例
Section titled “6.commonjs-基于浏览器端的实现举例”6.1 初始化项目
Section titled “6.1 初始化项目”在工程文件中新建如下目录和文件:
然后在根目录下新建如下命令:
然后根据提示,依次输入如下内容:
- 包名 :可以自己起包名,也可以用默认的包名。注意,包名里不能有中文,不能有大写。
- 版本 :可以用默认的版本 1.0.0,也可以自己修改包名。
其他的参数,一路回车即可。
于是,根目录下会自动生成package.json这个文件。点进去看一下:
6.2 下载第三方包:Browserify
Section titled “6.2 下载第三方包:Browserify”这里需要用到Browserify这个工具进行编译打包。Browserify 称为 CommonJS 的浏览器端的打包工具。
输入如下命令进行安装:(两个命令都要输入)
上面的代码中,-dev表示开发依赖。这里解释一下相关概念:
- 开发依赖:当前这个包,只在开发环境下使用。
- 运行依赖:当前这个包,是在生产环境下使用。
6.3 自定义模块 & 代码运行
Section titled “6.3 自定义模块 & 代码运行”(1)module1.js:
(2)module2.js:
注意,此时暴露出去的 exports 对象 等价于整个函数。
(3)module3.js:
(4)app.js:(将其他模块汇集到主模块
引入的路径解释:
./是相对路径,指的是当前路径(app.js的当前路径是src)
到此,我们的主要代码就写完了。
但是,如果我们直接在index.html中,像下面这样写,是不行的:(因为浏览器不认识 require 关键字)
为了能够让index.html引入app.js,我们需要输入如下命令:
打包处理js:
然后在index.html中引入打包后的文件: