模块机制
在学习模块机制的时候,可以结合es6中的模块化来理解。
1.什么是模块
(1).模块是啥?
在js中的模块化开发中,模块其实就是文件。
模块和文件是一一对应的。
一个模块就是一个文件。反之,一个文件对应于一个模块。
模块是Node.js应用程序的基本组成部分。
(2).为什么需要模块
以前,我们的js都是在浏览器端来运行的。
我们的代码,通常会写成这个样子的:
但是,现在不是在浏览中来运行js,而是在node环境中来运行js。
我们的代码都是js代码,根本就没有html。
如果编写如下代码,只会出错:
实际上,以前,在浏览器端,js文件也不能相互引用。之所以不影响最终的开发,只是因为它借助了html文件来引入。
在服务端情况就完全不一样。如果我们不能在js文件中引入js文件,开发将无法开展。
为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。
(3).js模块化发展简史
在node.js中,一般都使用commonjs的模块化
在浏览器端,一般都使用es6的模块化方案
基于此,我们必须要掌握两种模块化方案:
- CommonJS
- Es6的模块化
模块化在服务端是必须的,这是在js文件中引入js文件的唯一方式。
在浏览器端,模块化主要解决如下两大问题:
- 避免全局变量污染
- 更好的实现文件依赖
(4).具体表现为哪些文件?
在node中,如下三种文件都可以作为模块来引入
js文件,以.js作为后缀的
Json文件,以.json为后缀的
基于C/C++的json扩展文件,以.node为后缀的。
回顾一下,请问,在浏览器端,哪些文件可以作为模块引入?
js文件
css文件
图片文件
vue文件
…
2.Node.js常见模块
在node中,模块有不同的表现形式,其使用方式也有差异。
基本上,我们可以将node.js中的模块分为两种:
核心模块(系统模块)
用户模块
(1).核心模块
核心模块,也叫系统模块,是node.js自带,只要安装了node.js,它就已经具备了这些模块。
是可以直接引入并使用的。
系统模块在源代码中已经编译了,以二进制文件存在,模块引入时可以直接加到内存。
常见的核心模块有http、fs、path、url。
比如:
提示:如果你安装了webstorm,并使用它来编辑代码,运行代码。就有这么一个文件夹。
核心模块是我们学习的重点。
(2).用户模块
所有非核心模块,都属于用户模块。
实际上,用户模块,有可以分为两种:
- 第三方模块
- 自定义模块
通常来说,第三方模块也是自定义模块,只不过这个模块更为通用,强大,然后将其开源了,发布到npm社区,所有的开发人员都可以下载使用的。
3.载入模块
Node.js默认是使用CommonJS规范,就包含两个东西:
- require方法,用于载入模块
- module.exports对象,用于导出模块
在node.js中,要载入模块,必须要使用requrie方法。
格式:const 模块 = require(模块名);
在使用require的时候,需要分成如下三种情况来讨论:
核心模块
第三方模块
自定义模块
(1).核心模块
这是最简单的,因为核心模块是node自带。只需要传入模块的名称即可。
如:
注意,常量的名称可以是随意的,但是为了语义,尽量和模块名称保持一致。
不能再加任何和路径相关的写法,否则就出错了:
(2).第三方模块
属于用户模块,是非核心。
所以,必须要分两步完成:
- 先下载
- 然后使用require引入
比如,有一个非常知名的第三点工具库–
第一步,使用npm 下载之,如下:
第二步,引入使用,如下:
lodash官网:https://lodash.com/
除了,需要安装之外,引入的方式是一致的,只需要写上模块名即可。千万不要画蛇添足,加任何路径的写法。
注意:第三方模块和核心模块的引入写法是一模一样的,但是其原理是有区别的。
核心模块是已经被编译好了,本身就存在于内存中。
第三方模块,其实是放在当前的node_modules文件夹中的。
关于node_modules文件夹的查找机制:
首先,会在当前目录查找node_modules,如果有,就用这个来查找对应的模块,如果没有node_modules这个目录,继续向上一级目录,重复这个过程,直到根目录(windows下就是磁盘目录),如果仍然没有找到,就报错。
在使用npm命令安装的时候,也会有这种机制。在使用npm 命令时候,没有携带–save或者–save-dev选项的时候,就会使用这种查找机制。
(3).自定义模块
需要分成两步:
第一步,需要定义一个模块
第二步,引入自定义模块
第一步,定义一个模块
此处,使用一个空文件来作为一个模块。如下:
第二步,使用require引入,如下:
注意:
对于自定义模块,必须要使用相对路径来引入,必须是使用./或者…/开头。否则报错:
文件的后缀名是可以省略的,包括js后缀和json后缀。如下:
一旦省略了后缀,引入机制有些变化,依照 目录 ->.js-> .json-> .node 的顺序进行查找,如果所有模块路径都找不到该文件,则抛出异常。
如果require的是文件夹名称,默认会引入该文件夹下的index.js文件,如下:
还需要注意:针对核心模块和第三方模块,千万不要画蛇添足加上.js后缀。
基于Commonjs的require引入,这些所有的原理和细节,es6中的import同样遵循。
4.自定义模块实现
(1).使用module.exports导出模块
在开发中,会经常需要使用自定义模块。
它的整体的过程如下:
重点是创建模块,和导出模块。
可以和es6中的export进行对比。
在CommonJS规范中,导出模块使用的module.exports对象。
换言之,module.exports 是一个全局变量。可以直接在js文件中对其赋值。
在使用require方法引入一个模块的时候,返回的就是module.exports。
module.exports默认是空对象。
所以,针对一个空js文件,引入之后的结果是{}。
所以,在导出模块的时候,就可以直接给module.exports赋值即可。如下:
再比如:
(2).exports对象
在CommonJS中,导出模块是通过module.exports来实现的。
在CommonJS中,使用requrie方法,得到的结果就是module.exports。
在CommonJS中,给module.exports起了一个别名—exports。
所以,在有些地方,它是使用exports来导出的。如下:
如果这么写:
这种写法是错误的。
所谓的起别名,就是 相当于,定义了exports对象,然后将module.exports的值赋给它。
好比是执行了如下代码:
exports = module.exports
如果,你做了如下操作:
exports = {
add,
sub,
}
这就意味着,exports指向了新的对象,不再指向module.exports,二者毫无关系。
而require方法真正返回的module.exports。
这么写为什么可以?
因为,此时只是给exports对象添加了新的属性,和module.exports还是指向同一个对象,相当于给module.exports添加了属性。
注意:
自己在导出模块的时候,尽量使用module.exports
如果看到别人的代码,要理解exports的作用及用法
强调一下:
CommonJS主要是node平台上使用。
Es6的模块化主要是浏览器端开发来使用。
但是,
- 在浏览器端,其实也是可以使用CommonJS规范的,尤其是基于webpack的开发。
- 在不久的将来,在node中,也是可以直接使用es6的import和export。