nodejs(第四篇):NodeJS的模块机制补充、node中引入模块的机制、包和 NPM

 

一、NodeJS的模块机制补充

 

exports 与 module.exports

为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

var exports = module.exports;

  

于是我们可以直接在 exports 对象上添加方法,表示对外输出的接口

 

根据使用方法来说:

通常exports方式使用方法是:

exports.[function name] = [function name]

moudle.exports方式使用方法是:

moudle.exports= [function name]

 

这样使用两者根本区别是

exports 返回的是模块函数

module.exports 返回的是模块对象本身,返回的是一个类

使用上的区别是 exports的方法可以直接调用 module.exports需要new对象之后才可以调用

 

导出多个成员(必须在对象中):

  • 方法一(使用 exports):

        exports.a = 123;
        exports.b = 'hello';
        exports.c = () => {
            console.log('ccc');
        };
        exports.d = {
            foo: 'bar'
        };
    

      

     

  • 方法二(使用module.exports):

        module.exports = {
            add: (x, y) => {
                return x + y;
            },
            str: 'hello'
        };
    

      

     

导出单个成员(拿到的直接就是 字符串, 数字 等):

但是的话,因为只能导出单个成员,所以会出现覆盖情况,如下所示:

    module.exports = 'hello';
​
    // 以这个为准,后者会覆盖前者
    module.exports = (x, y) => {
        return x + y;
    };

  

exports是module.exports的一个引用,exports指向的是module.exports

 

也就是说exports的方法module.exports也是一定能完成的

exports.[function name] = [function name]
moudle.exports= [function name]

  

所以,在使用上

** moudle.exports.[function name]   = [function name] **
**  是完全和 **
** exports.[function name] = [function name]  **
**  相等的   **

  

但是我们通常还是推荐使用exports.[function name],各司其职,代码逻辑清晰

 

 

 

总结:

  1. 每一个模块中都有一个 module 对象, module 对象中有一个 exports 对象

  2. 我们可以把需要导出的成员都放到 module.exports 这个接口对象中,也就是 module.exports.xxx = xxx 的方式

  3. 但是,这样显得特别麻烦,为了方便操作,在每一个模块中又提供了一个叫 exports 的成员

  4. 所以,有了这样的等式: module.exports === exports

  5. 所以,对于:module.exports.xxx = xxx 的方式等价于 exports.xxx = xxx

  6. 当一个模块需要导出单个成员的时候,必须要使用 module.exports = xxx

  7. 因为每个模块最终向外 return 的是 module.exports,而 exports 只是 module.exports 的一个引用,所以即便你为 exports = xxx 重新赋值,也不会影响 module.exports

  8. exports 和module.exports很少混用,一般只用一个

 

 

二、node中引入模块的机制

在Node中引入模块,需要经历3个步骤

(1)路径分析

(2)文件定位

(3)编译执行

 

在Node中,模块一般分为两种

(1)Node提供的模块,例如http、fs等,称为核心模块。核心模块在node源代码编译的过程中就编译进了二进制执行文件,在Node进程启动的时候,部分核心模块就直接加载进内存中了,因此这部分模块是不用经历上述的(2)(3)两个步骤的,而且在路径分析中是优先判断的,因此加载速度最快。

 

(2)用户自己编写的模块,称为文件模块。文件模块是按需加载的,需要经历上述的三个步骤,速度较慢。

 

核心模块在Node.js源代码编译的时候编译进了二进制执行文件,在nodejs启动过程中,部分核心模块直接加载进了内存中,所以这部分模块引入时可以省略文件定位和编译执行两个步骤,所以加载的速度最快。另一类文件模块是动态加载的,加载速度比核心模块慢。但是Node.js对核心模块和文件模块都进行了缓存,于是在第二次require时,是不会有重复开销的。其中原生模块都被定义在lib这个目录下面,文件模块则不定性。

 

ps:核心模块又分为两部分,C/C++编写的Javascript编写的,前者在源码的src目录下,后者则在lib目录下。(lib/*.js)(其中lib/internal部分不提供给文件模块)

require 方法的加载规则**

  1. 优先从缓存中加载

  2. 核心模块

  3. 路径形式的模块

  4. 第三方模块

 

与浏览器会缓存静态脚本文件以提高页面性能一样,Node对引入过的模块也会进行缓存。不同的地方是,node缓存的是编译执行之后的对象而不是静态文件。require()方法在对同一模块的二次加载一律采用缓存优先的方式。但是对核心模块的缓存检查优先于对文件模块的缓存检查。

 

优先从缓存中加载测试

main.js:执行加载a.js模块

require('./a')

  

a.js:执行加载b.js模块,并输出a被加载了

require('./b')
console.log('a.js 被加载了')

  

b.js:输出b被加载了

console.log('b.js 被加载了')

  

结果:

 

分析:

main去加载a.js,然后a在去加载b.js过程中,并没有打印两次 a.js被加载,Node会直接从require.cache中根据传入的id,取出该对象的exports值,不会再次执行该模块代码。

测试2

 

核心模块的加载

 

核心模块的本质也是文件,核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了。如:

  • require(‘fs’)

  • require(‘http’)

 

路径形式的模块加载(自己写的模块加载)

我们说的路径形式的模块,其实就是加载自己写的JS文件,有四种方式可以加载

var fooExports = require('./index') //相对路径,常用
var fooExports = require('../index')    //相对路径,常用
var fooExports = require('/index')  //根目录,不常用
var fooExports = require('D:/demo/index')   //根目录,不常用

  

第三方模块

  1. 凡是用到第三方模块,都必须通过 npm 来下载

  2. 使用的时候就可以通过 require('包名') 的方式来进行加载才可以使用

  3. 不可能有任何一个第三方包和核心模块的名字是一样的

以 art-template为例分析require的加载过程

(0)测试准备,创建项目文件夹project,进入这个目录的命令提示行

通过npm安装 art-template模块

 

安装成功后,项目文件夹内会出现一个package-lock.json(包描述文件)和node_modules

 

 

创建main.js写入内容

require('art-template')
console.log('执行完成')

  

 

 

(1)执行main.js,分析require加载art-template模块的过程

 

 

分析执行过程如下:

//    先找到当前文件所处目录中的 node_modules 目录
//   node_modules/art-template
//   node_modules/art-template/package.json 文件
//   node_modules/art-template/package.json 文件中的 main 属性
//   main 属性中就记录了 art-template 的入口模块index.js
//   然后加载使用这个第三方包
//   实际上最终加载的还是文件

 

(3) 如果 package.json 文件不存在或者 main 指定的入口模块是也没有 则 node 会自动找该目录下的 index.js, 也就是说 index.js 会作为一个默认备选项

测试:在node_modules文件夹中创建a文件夹,package.json文件(包描述文件)、index.js文件

在main.js中require('a')执行后

 

 

 

(4) 如果以上所有任何一个条件都不成立,则会进入上一级目录中的 node_modules 目录查找 如果上一级还没有,则继续往上上一级查找, 如果直到当前磁盘根目录还找不到,最后报错: can not find module xxx var template = require('a')

测试效果

 

 

 

总结:

注意:我们一个项目有且只有一个 node_modules,放在项目根目录中,这样的话项目中所有的子目录中的代码都可以加载到第三方包,不会出现有多个 node_modules

模块查找机制

优先从缓存加载 核心模块 路径形式的文件模块 第三方模块 找到node_modules/art-template/ 找到 node_modules/art-template/package.json 找到node_modules/art-template/package.json main 找到 index.js 备选项 进入上一级目录找 node_modules

按照这个规则依次往上找,直到磁盘根目录还找不到,最后报错:Can not find moudle xxx 一个项目有且仅有一个 node_modules 而且是存放到项目的根目录

 

 

三、包和 NPM

 

什么是包

由于 Node 是一套轻内核的平台,虽然提供了一系列的内置模块,但是不足以满足开发者的需求,于是乎出现了包(package)的概念: 与核心模块类似,就是将一些预先设计好的功能或者说 API 封装到一个文件夹,提供给开发者使用。

Node 本身并没有太多的功能性 API,所以市面上涌现出大量的第三方人员开发出来的 Package。

 

NPM:Node Package Manager。官方链接: https://www.npmjs.com/

随着时间的发展,NPM 出现了两层概念:

  • 一层含义是 Node 的开放式模块登记和管理系统,亦可以说是一个生态圈,一个社区。

  • 另一层含义是 Node 默认的模块管理器,是一个命令行下工具,用来安装和管理 Node 模块。

    这里学习使用的是npm命令工具。

 

nvm与npm区别

nvm就是nodejs version manage 叫做nodejs 版本管理,而nodejs有很多版本,场景如下:

   1、而你手上开发的有多个项目又分别是不同的nodejs版本,咱们就可以用nvm轻松切换!

   2、假设你正在开发的项目开始使用的nodejs版本是8.0,而现在因为某些原因,你需要升级 或者 降级 nodejs 版本,也可以使用 nvm 轻松切换npm全称为Node Package Manager,是一个基于Node.js的包管理器,也是整个Node.js社区最流行、支持的第三方模块最多的包管理器。

 

npm的初衷:JavaScript开发人员更容易分享和重用代码。

npm的使用场景:

  • 允许用户获取第三方包并使用。

  • 允许用户将自己编写的包或命令行程序进行发布分享。

 

NPM 的安装(不需要单独安装)

NPM 不需要单独安装。默认在安装 Node 的时候,会连带一起安装 NPM:

 

nvm安装node成功,npm失败问题

通过nvm install 版本号 安装你要的nodejs版本,必须是npm和nodejs都成功,因为有时候会npm或者nodejs不会下载成功,不成功的原因很多。

打开nvm安装的文件夹,进入具体版本文件夹,有npm文件的才算安装成功

 

安装失败情况,下图是不行的,但是 你可以自己去下载一个nodejs版本,但后解压后放复制到nvm目录,注意命名,如:v11.11.0,这样就不需要使用 nvm install 命令,

 

 

问题解决

其实安装和使用的过程并不难,但是在安装过程中,新一些的版本总是安装npm不成功,导致我一度以为是自己的安装出现问题或者环境变量和全局变量设置有问题,各种重装和设置,后来发现有人说 目前发现 8.11以上版本的node版本对应的npm都没法自动安装,需要自己到npm官网( https://npm.taobao.org/mirrors/npm/)下载手动安装对应的npm版本。 node和npm版对应关系可以在使用nvm install 版本号安装过程中看到

下载完成,将解压后的文件复制到,对应版本 ode_modules目录下,并重命名为npm(注意必须重命名为npm)

 

还需要将npm解压后的bin文件夹下的四个文件复制到对应版本目录下:

 

复制文件完成,测试npm

 

 

 

 

 

 

 

参考资料

[1]https://blog.csdn.net/fenfeidexiatian/article/details/96993384

[2]http://www.imooc.com/article/34483

[3]https://www.cnblogs.com/EricZLin/p/9289278.html

[4]https://www.infoq.cn/article/nodejs-module-mechanism/

原文地址:https://www.cnblogs.com/Nicholas0707/p/12577953.html