Node.js小白开路(一)-- 全局变量篇

       全局内容是有点类似于我们在浏览器编程的时候的window对象的,当时在node之中虽然我们编写的变量会自动的给出上下文环境(模块),但是全局变量的存在还是大大的帮助了我们编程时候的便捷性。我们可以在任意的模块之中使用全局环境下面定义的内容。

  global其实就相当于在web编写线面的window内容。是为nodeJS中的全局变量内容。其中存储的内容包括,console的全局实例,process信息内容,定时函数内容,Buffer对象内容等,当然还有一些特殊信息的变量。下面我们一一来说明。


  ## 首先我们来看一看其中连个特殊的全局变量,__dirname和__filename。我们先来上一段代码吧。

-----------------mod.js-----------------
console.log('mod.js的filename:'+ __filename);
console.log('mod.js的dirname:'+ __dirname);

-----------------main.js-----------------
require('./mod.js');

  当我们运行main.js的时候nodeJS会自动的先将mod.js的内容运行一遍,然后我们就可以在控制台看到打印出来的信息了。

  当然两者之间还是有些差别的,__dirname 的值其实相当于path.dirname(__filename),__filename展示的是文件的局对路径包括文件名称。而__dirname展示的是文件所在的文件夹的路径信息。

 


  ## 接下来我们聊一聊require,module,exports的部分。

  相信只要稍写过nodeJS的人都知道这3个次。require方法来请求模块,module表示当前文件模块本身,exports内容表示的是模块对外接口部分。

  require内容用于我们请求相关模块内容,当我们传入了文件的相对路径给require方法之后,我们将得到一个引入模块的对象。但是我们常常使用npm来下载项目支持的模块文件,这是我们常常只需要输入模块名称之后reqiure会自动的引入相关的内容。这是因为在工程文件之下,在node_modules文件夹之中,我们用模块名称命名的文件夹,require会一句模块名称到相应的文件夹之中找到默认的index.js文件来进行引入。当然也是可以修改名称的,只是这时我们需要运用到package.json文件来描述这一模块内容的信息,这样我们可以指定进入模块的文件。当然require在引入了某一个文件以此之后再次引入的时候其实是无需再次读取文件的,因为require之中有缓存机制内容,之后再次请求的时候其实是直接从对应的内存之中获取。在0.3的版本之后require之中添加了require.cache对象以键值对的形式存储,当然我们能通过键值key的部分来进行缓存内容的删除,那么下一次再加载的时候则模块会重新加载。我们;爱看一段代码吧:

var ser = require('./src/h_server.js');
var buf = require('./src/h_buffer.js');
console.log(require.cache); //引入当前的两个外界模块,打印相关的require.cache内容

结果如下:

  这里我截取的是其中一段内容,一个对象属性,可以看到,齐以文件路径内容为变量名称,以module内容座位属性来进行存储,所以我们要删除档当前对象中的内容的情况只需要通过文件路径查找到module对象并删除就好。

  P.S.:包管理器内容(nodeJS文档中与module模块文档下的附加内容)

  这里详细说一下包引入与管理,当我们在某一文档下创建自己的node工程的时候,我们常常需要外部引入许多的模块内容,这是我们通过NPM来下载模块的时候回发现工程目录之下会多出一个文件夹名称为node_module,并为每一个下载的外链模块有一个单独的文件夹内容。这时候我们可以通过模块名称来直接引用相关的模块内容,这是因为包管理器,当我们内容引入的时候会遵从一定的规则内容,这里我引用一张官网的总结内容。

 

从 Y 路径的模块 require(X)
1. 如果 X 是一个核心模块,
   a. 返回核心模块
   b. 结束
2. 如果 X 是以 '/' 开头
   a. 设 Y 为文件系统根目录
3. 如果 X 是以 './' 或 '/' 或 '../' 开头
   a. 加载文件(Y + X)
   b. 加载目录(Y + X)
4. 加载Node模块(X, dirname(Y))
5. 抛出 "未找到"

加载文件(X)
1. 如果 X 是一个文件,加载 X 作为 JavaScript 文本。结束
2. 如果 X.js 是一个文件,加载 X.js 作为 JavaScript 文本。结束
3. 如果 X.json 是一个文件,解析 X.json 成一个 JavaScript 对象。结束
4. 如果 X.node 是一个文件,加载 X.node 作为二进制插件。结束

加载索引(X)
1. 如果 X/index.js 是一个文件,加载 X/index.js 作为 JavaScript 文本。结束
3. 如果 X/index.json  是一个文件,解析 X/index.json 成一个 JavaScript 对象。结束
4. 如果 X/index.node 是一个文件,加载 X/index.node 作为二进制插件。结束

加载目录(X)
1. 如果 X/package.json 是一个文件,
   a. 解析 X/package.json,查找 "main" 字段
   b. let M = X + (json main 字段)
   c. 加载文件(M)
   d. 加载索引(M)
2. 加载索引(X)

加载Node模块(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
   a. 加载文件(DIR/X)
   b. 加载目录(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
   a. if PARTS[I] = "node_modules" CONTINUE
   b. DIR = path join(PARTS[0 .. I] + "node_modules")
   c. DIRS = DIRS + DIR
   d. let I = I - 1
5. return DIRS

  当然,在我们加载模块的时候,当两个相同的模块放在不一样的位置的时候加载的时候可能会重复加载并存储两个分加载之后的模块对象,因为齐路径的不相同,所以可能nodeJS无法进行比对。

  当让在我们引入的模块的时候有的时候会遇到循环引入的情况。这时node很可能会返回一个没有执行完成的模块对象内容来。

 

  ## module内容为模块,在我们编写每一个JS文件的时候,node都认为他是一个模块,而module对象是代表当前编写的这一模块的信息。

  module模块是nodeJS的重要的组成部分,之前戳到nodeJS是以COMMONJS的规则来进行编写的,而module着可以说是组成这一规则的核心内容了。

  由于NODEJS会将每一个文件看成一个模块,所以通过node来进入的模块实际上是我们的主体逻辑模块内容,所以我们又是需要判别当前运行模块是否为主模块,这时我们可以通过require.main属性来确认。module模块之中会存储文件入口路径,我们可以通过module中的filename属性来获取,其值与在模块之中调用__filename获取的值多数情况之下是相同的。当然我们如果想要获取住模块的入口路径我们也可以通过,require.main.filename来获取,由此可见require中的main属性存储的实际上是组模块对象。

  下面说一下模块的包管理器,其原理实际上是将模块放入到一个自执行函数之中,这样的好处显而易见了,首先是可以隔绝当前的模块与全局环境,以免全局环境被污染,同时这样为模块确定独立的上下文使得每一个模块之间的上下文不相互干扰,当某一模块之中与另一模块之中存在相同名称的命名的时候不会出现冲突的情况,nodeJS的实现方式如下:

(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});

  接下来我们来看一看module对象之中的某些属性内容:

  -- children:这个属性展示的是一个列表内容,其中存储的是当前模块引用的模块对象。

  -- parent:这个属性则存储的是引用当前模块对象的模块对象。

  -- loaded:这个属性表明的是当前的模块是否已经加载完成了。

  -- path:这个属性存储的是模块的搜索路径。

  -- filename:这个属性内容存储的是模块的完全解析后的文件名。

  -- id: 模块的标识符。 通常是完全解析后的文件名。

  -- exports:这个属性是重中之重,属性中存储的是当前模块的公用方法内容,当使用require来引入模块的时候,实际上就是通过这属性来确立的,exports可以理解为模块暴露在大众视线下面的接口。当然我们也能说得更为的概念化一点,官方文档是如是说的—— exports变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋予 module.exports 的值。

 

  ## exports属性是一个文件级别作用域有效的属性,之前在module之中有提到这一属性,但是module.exports这一属性和单独的exports还是有些不相同的所以才会将这一属性单独拿出来说事。exports属性只要是编写的朋友都知道,通过它其实也是可以对module.exports这一属性进行相关的赋值的。例如,当我们对某一文件下面使用exports对象属性赋予a属性,并对a赋值为‘1’,如展示

exports.a = '1';

这时候nodeJS会自动的将其内容复制给module.exports属性之中。当module.exports指向的对象没有变而只是变化对象内容的时候,则exports指向的对象其实也是会变的。代码如下:

console.log(module.exports); //展示最初的module.exports属性中的内容
console.log('_______________________________'); 

//改变exports的值,看module.exports内容会如何变化。
exports.main = 'main.js';
console.log('exports:');
console.dir(exports);
console.log('module.exports:');
console.dir(module.exports);
console.log('_______________________________');

//改变module.exports的值看exports会如何变化
module.exports.checkData = 'checkData';
console.log('exports:');
console.dir(exports);
console.log('module.exports:');
console.dir(module.exports);
console.log('_______________________________');

//改变module.exports指向的对象,看exports的变化。
module.exports = {
    main:'afterChange'   
}
console.log('exports:');
console.dir(exports);
console.log('module.exports:');
console.dir(module.exports);
console.log('_______________________________');

//再次改变exports的值,看module.exports怎样变化。
exports.checkAgain = 'checkAgain';
console.log('exports:');
console.dir(exports);
console.log('module.exports:');
console.dir(module.exports);
console.log('_______________________________');

相应结果内容:

由上面可以知道,在我们编写模块的时候初始情况下exports和module.exports是指向同一个对象的,所以exports在改变的时候module.exports也会跟着改变,而当module.exports改变其指向的obj之后我们可以发现其之间的变化不相关了。所以我们其实可以这样理解,当初始化的是时候的全局的exports属性和module.exports是指向同一个空的obj的。而当改变了其中一个属性的指向的时候则两个变量将不会再有任何关系。


  ##定时函数内容的学习。

  我们在编写JS的时候经常会使用到定时函数等内容,来延时执行或者是循环执行某一段逻辑内容当然nodeJS之中也是有相对应的函数的,其中主要的有3个形式的函数内容。分别是setInterval,setTimeout,setImmediate。前两个函数内容就不用多说了,写过JS的都知道,第三个函数的作用是当前事件循环结束的时候立马执行(事件循环之后会有说)。

  下面我们来了解一下timeout类,这是nodeJS中的一个定时器对象,setTimeout和setInterval对象执行的时候其返回的内容在nodeJS中是一个定时器对象。当定时器对象是活动的状态的时候,则事件循环就会将其添加到的档次循环待执行列表之中。当然nodeJS为我们添加了两个设置timeout对象活动状态的函数,ref()使得定时器对象处于活动状态,unref()使得当前定时器函数处于非活动状态。

原文地址:https://www.cnblogs.com/pingchuanxin/p/8085906.html