Nodejs学习笔记之Global Objects全局对象

一,开篇分析

在上个章节中我们学习了NodeJS的基础理论知识,对于这些理论知识来说理解是至关重要的,在后续的章节中,我们会对照着官方文档逐步学习里面的各部分模块,好了该是本文主角登台亮相的时候了,Global

让我们来看一下官方的定义:

Global Objects全局对象These objects are available in all modules. Some of these objects aren't actually in the global scope but in the module scope - this will be noted.

这些对象在所有的模块中都可用。实际上有些对象并不在全局作用域范围中,但是在它的模块作用域中------这些会标识出来的。

In browsers, the top-level scope is the global scope. That means that in browsers if you're in the global scopevar somethingwill define a global variable.

In Node this is different. The top-level scope is not the global scope;var somethinginside a Node module will be local to that module.

全局对象这个概念我想大家应该不会感到陌生,在浏览器中,最高级别的作用域是Global Scope ,这意味着如果你在Global Scope中使用 "var" 定义一个变量,这个变量将会被定义成Global Scope。

但是在NodeJS里是不一样的,最高级别的Scope不是Global Scope,在一个Module里用 "var" 定义个变量,这个变量只是在这个Module的Scope里。

在NodeJS中,在一个模块中定义的变量,函数或方法只在该模块中可用,但可以通过exports对象的使用将其传递到模块外部。

但是,在Node.js中,仍然存在一个全局作用域,即可以定义一些不需要通过任何模块的加载即可使用的变量、函数或类。

同时,也预先定义了一些全局方法及全局类Global对象就是NodeJS中的全局命名空间,任何全局变量,函数或对象都是该对象的一个属性值。

在REPL运行环境中,你可以通过如下语句来观察Global对象中的细节内容,见下图:

我在下面会逐一说说挂载在Global对象上的相关属性值对象。

(1),Process

  process {Object} The process object.See the process object section.

  process {对象} 这是一个进程对象。 在后续的章节中我会细说,但在这里我要先拿出一个api来说一下。

  process.nextTick(callback)

  On the next loop around the event loop call this callback. This is not a simple alias to setTimeout(fn, 0), it's much more efficient. It typically runs before any other I/O events fire, but there are some      exceptions. See process.maxTickDepth below.

  在事件循环的下一次循环中调用 callback 回调函数。这不是 setTimeout(fn, 0) 函数的一个简单别名,因为它的效率高多了。

  该函数能在任何 I/O 事前之前调用我们的回调函数。如果你想要在对象创建之后而I/O 操作发生之前执行某些操作,那么这个函数对你而言就十分重要了。

  有很多人对Node.js里process.nextTick()的用法感到不理解,下面我们就来看一下process.nextTick()到底是什么,该如何使用。

   Node.js是单线程的,除了系统IO之外,在它的事件轮询过程中,同一时间只会处理一个事件。你可以把事件轮询想象成一个大的队列,在每个时间点上,系统只会处理一个事件。

   即使你的电脑有多个CPU核心,你也无法同时并行的处理多个事件。但也就是这种特性使得node.js适合处理I/O型的应用,不适合那种CPU运算型的应用。

   在每个I/O型的应用中,你只需要给每一个输入输出定义一个回调函数即可,他们会自动加入到事件轮询的处理队列里。

  当I/O操作完成后,这个回调函数会被触发。然后系统会继续处理其他的请求。

  

  在这种处理模式下,process.nextTick()的意思就是定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行。我们来看一个例子。例子中有一个foo(),你想在下一个时间点上调用他,可以这么做:

代码如下:

function foo() {
    console.error('foo');
}
 
process.nextTick(foo);
console.error('bar');

运行上面的代码,你从下面终端打印的信息会看到,"bar"的输出在“foo”的前面。这就验证了上面的说法,foo()是在下一个时间点运行的。

代码如下:

bar
foo

  你也可以使用setTimeout()函数来达到貌似同样的执行效果:

代码如下:

setTimeout(foo, 0);
console.log('bar');

  但在内部的处理机制上,process.nextTick()和setTimeout(fn, 0)是不同的,process.nextTick()不是一个单纯的延时,他有更多的特性

  更精确的说,process.nextTick()定义的调用会创建一个新的子堆栈。在当前的栈里,你可以执行任意多的操作。但一旦调用netxTick,函数就必须返回到父堆栈。然后事件轮询机制又重新等待处理新的事件,如果发现nextTick的调用,就会创建一个新的栈。

  下面我们来看看,什么情况下使用process.nextTick():

  在多个事件里交叉执行CPU运算密集型的任务:

  在下面的例子里有一个compute(),我们希望这个函数尽可能持续的执行,来进行一些运算密集的任务。

  但与此同时,我们还希望系统不要被这个函数堵塞住,还需要能响应处理别的事件。这个应用模式就像一个单线程的web服务server。在这里我们就可以使用process.nextTick()来交叉执行compute()和正常的事件响应。

代码如下:

var http = require('http');
function compute() {
    // performs complicated calculations continuously
    // ...
    process.nextTick(compute);
}
http.createServer(function(req, res) {
     res.writeHead(200, {'Content-Type': 'text/plain'});
     res.end('Hello World');
}).listen(5000, '127.0.0.1');
compute();

  在这种模式下,我们不需要递归的调用compute(),我们只需要在事件循环中使用process.nextTick()定义compute()在下一个时间点执行即可。

  在这个过程中,如果有新的http请求进来,事件循环机制会先处理新的请求,然后再调用compute()。

  反之,如果你把compute()放在一个递归调用里,那系统就会一直阻塞在compute()里,无法处理新的http请求了。你可以自己试试。

  当然,我们无法通过process.nextTick()来获得多CPU下并行执行的真正好处,这只是模拟同一个应用在CPU上分段执行而已。

  (2),Console

  console {Object} Used to print to stdout and stderr.See the stdio section.

  控制台 {对象} 用于打印到标准输出和错误输出。看如下测试:

  

代码如下:

console.log("Hello Bigbear !") ;
for(var i in console){
    console.log(i+"  "+console[i]) ;
}

  会得到以下输出结果: 

代码如下:

var log = function () {
  process.stdout.write(format.apply(this, arguments) + '\n');
}
var info = function () {
  process.stdout.write(format.apply(this, arguments) + '\n');
}
var warn = function () {
  writeError(format.apply(this, arguments) + '\n');
}
var error = function () {
  writeError(format.apply(this, arguments) + '\n');
}
var dir = function (object) {
  var util = require('util');
  process.stdout.write(util.inspect(object) + '\n');
}
var time = function (label) {
  times[label] = Date.now();
}
var timeEnd = function (label) {
  var duration = Date.now() - times[label];
  exports.log('undefined: NaNms', label, duration);
}
var trace = function (label) {
  // TODO probably can to do this better with V8's debug object once that is
  // exposed.
  var err = new Error;
  err.name = 'Trace';
  err.message = label || '';
  Error.captureStackTrace(err, arguments.callee);
  console.error(err.stack);
}
var assert = function (expression) {
  if (!expression) {
    var arr = Array.prototype.slice.call(arguments, 1);
    require('assert').ok(false, format.apply(this, arr));
  }
}

  通过这些函数,我们基本上知道NodeJS在全局作用域添加了些什么内容,其实Console对象上的相关api只是对Process对象上的"stdout.write“进行了更高级的封装挂在到了全局对象上。

 (3),exports与module.exports

   在NodeJS中,有两种作用域,分为全局作用域和模块作用域  

代码如下:

var name = 'var-name';
name = 'name';
global.name='global-name';
this.name = 'module-name';
console.log(global.name);
console.log(this.name);
console.log(name);

  我们看到var name = 'var-name';name = 'name'; 是定义的局部变量;

  而global.name='global-name';是为 全局对象定义一个name 属性,

  而 this.name = 'module-name';是为模块对象定义了一个name 属性

  那么我们来验证一下,将下面保存成test2.js,运行

代码如下:

var t1 = require('./test1'); 
console.log(t1.name); 
console.log(global.name);

  从结果可以看出,我们成功导入 了test1 模块,并运行了 test1的代码,因为在test2 中 输出 了global.name,

  而 t1.name 则是 test1 模块中通过this.name 定义的,说明this 指向 的是 模块作用域对象。

  exports与module.exports的一点区别

    Module.exports才是真正的接口,exports只不过是它的一个辅助工具。最终返回给调用的是Module.exports而不是exports。

    所有的exports收集到的属性和方法,都赋值给了Module.exports。当然,这有个前提,就是Module.exports本身不具备任何属性和方法

    如果,Module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略。

  举个栗子:

    新建一个文件 bb.js

代码如下:

exports.name = function() {
    console.log('My name is 大熊 !') ;
} ;

    创建一个测试文件 test.js

  

代码如下:

var bb= require('./bb.js');
bb.name(); // 'My name is 大熊 !'

    修改bb.js如下:

代码如下:

module.exports = 'BigBear!' ;
exports.name = function() {
    console.log('My name is 大熊 !') ;
} ;

  再次引用执行bb.js

代码如下:

var bb= require('./bb.js');
bb.name(); // has no method 'name'

  由此可知,你的模块并不一定非得返回“实例化对象”。你的模块可以是任何合法的javascript对象--boolean, number, date, JSON, string, function, array等等。

 (4),setTimeout,setInterval,process.nextTick,setImmediate

  以下以总结的形式出现

    Nodejs的特点是事件驱动,异步I/O产生的高并发,产生此特点的引擎是事件循环,事件被分门别类地归到对应的事件观察者上,比如idle观察者,定时器观察者,I/O观察者等等,事件循环每次循环称为Tick,每次Tick按照先后顺序从事件观察者中取出事件进行处理。

   调用setTimeout()或setInterval()时创建的计时器会被放入定时器观察者内部的红黑树中,每次Tick时,会从该红黑树中检查定时器是否超过定时时间,超过的话,就立即执行对应的回调函数。setTimeout()和setInterval()都是当定时器使用,他们的区别在于后者是重复触发,而且由于时间设的过短会造成前一次触发后的处理刚完成后一次就紧接着触发。

   由于定时器是超时触发,这会导致触发精确度降低,比如用setTimeout设定的超时时间是5秒,当事件循环在第4秒循到了一个任务,它的执行时间3秒的话,那么setTimeout的回调函数就会过期2秒执行,这就是造成精度降低的原因。并且由于采用红黑树和迭代的方式保存定时器和判断触发,较为浪费性能。

   使用process.nextTick()所设置的所有回调函数都会放置在数组中,会在下一次Tick时所有的都立即被执行,该操作较为轻量,时间精度高。

   setImmediate()设置的回调函数也是在下一次Tick时被调用,其和process.nextTick()的区别在于两点:

    1,他们所属的观察者被执行的优先级不一样,process.nextTick()属于idle观察者,setImmediate()属于check观察者,idle的优先级>check。

   2,setImmediate()设置的回调函数是放置在一个链表中,每次Tick只执行链表中的一个回调。这是为了保证每次Tick都能快速地被执行。

二,总结一下

  1,理解Global对象存在的意义

  2,exports与module.exports的一点区别

  3,Console的底层是什么构建的(Process对象的高层封装)

  4,setTimeout,setInterval,process.nextTick,setImmediate的区别

  5,NodeJS中的两种作用域

(0)

相关推荐

  • 详解Node全局变量global模块

    环境:Node v8.2.1; Npm v5.3.0; OS Windows10 在浏览器中我们有window对象用于挂载全局的变量,在Node中我们有global对象可以进行挂载,很多共用的属性就可以挂载到global对象上了,本身它自己也拥有很多的属性. 1.API结构图 2.一些例子 下面是部分API的例子,对部分API的一个简单代码实现 2.1 global 全局命名空间,通过 global 定义的变量,在任何地方都可以使用,类似于浏览器端定义在全局范围中的变量. // foo.js g

  • 深入理解Nodejs Global 模块

    浏览器上有自己的全局对象 window,同理, nodejs 下也有自己的全局对象 global,并且在各个模块下 都可以直接访问 global 对象. 在 nodejs 中,除了可以直接使用 V8 JavaScript 引擎中所支持的原生 JavaScript 的函数和对象外,它还追加了一些其他的函数和对象(比如:Buffer 对象.require 函数等). Buffer 对象: 用于处理二进制数据 module 对象: 用于访问当前模块的信息 process 对象: 用于访问进程信息 co

  • Nodejs学习笔记之Global Objects全局对象

    一,开篇分析 在上个章节中我们学习了NodeJS的基础理论知识,对于这些理论知识来说理解是至关重要的,在后续的章节中,我们会对照着官方文档逐步学习里面的各部分模块,好了该是本文主角登台亮相的时候了,Global 让我们来看一下官方的定义: Global Objects全局对象These objects are available in all modules. Some of these objects aren't actually in the global scope but in the

  • JavaScript高级程序设计(第3版)学习笔记11 内建js对象

    内建对象就好比是JDK中的类库,开发者可以直接拿来使用,这极大的方便了常见的编程任务.这篇文章就来浏览一下主要的内建对象,当然,我们并不是第一次接触内建对象,前面已经接触到的就有Object.Function.Boolean.Number.String,对于已经介绍过的,这里再总结复习一下,没有介绍过的,根据相关性来对比的总结,RegExp对象及正则表达式在下一篇中再单独介绍. 1.内建全局单例对象 (1)内建全局单例对象:在整个执行环境中只有一个对象实例,这些对象没有内部属性[[Constru

  • NodeJS学习笔记之Http模块

    一,开篇分析 首先"Http"这个概念大家应该比较熟悉了,它不是基于特定语言的,是一个通用的应用层协议,不同语言有不同的实现细节,但是万变不离其宗,思想是相同的, NodeJS作为一个宿主运行环境,以JavaScript为宿主语言,它也有自己实现的一套标准,这篇文章我们就一起来学习一下 "Http模块" .但是作为前提来说, 希望大家可以先阅读一下官网提供的api,有一个前置了解,这样就方便多了,以下是Http部分的api概览: 复制代码 代码如下: HTTP   

  • Nodejs学习笔记之NET模块

    一,开篇分析 从今天开始,我们来深入具体的模块学习,这篇文章是这个系列文章的第三篇,前两篇主要是以理论为主,相信大家在前两篇的学习中, 对NodeJS也有一个基本的认识,没事!!!趁热打铁,让我们继续将NodeJS进行到底,好了废话不多说,直接进入今天的主题 "Net模块" ,那么"Net"应该如何理解那? 它是做什么用的那?(Net模块可用于创建Socket服务器或Socket客户端.NodeJS 的数据通信,最基础的两个模块是 Net 和 Http,前者是基于

  • Nodejs学习笔记之入门篇

    分享第一篇,关于 NodeJS -- Javascript 的常用知识以及如何从 Javascript 开发者过渡到 NodeJS 开发者(不会介绍具体的框架).在读本文前,希望你对 javascript 有一些初步的认识. Javascript 是一门原型模型的解释型语言.解释型将在后面的 NodeJS 里面讨论,原型链是 ES6 之前的 Javascript 的面向对象的实现方式之一,在 ES6 中支持的 class 增加了一种新的实现方式.在 Javascript 里面所有东西都是对象,包

  • NodeJS学习笔记之Connect中间件应用实例

    一,开篇分析 大家好哦,大熊君又来了,昨天因为有点个人的事没有写博客,今天又出来了一篇,这篇主要是写一个记事本的小应用,前面的文章, 我也介绍过"Connect"中间件的使用以及"Mongodb"的用法,今天就结合这两个中间件,写个实际的例子,不断完善和重构,已达到 充分学习的目的.好了,废话不说了,直接进入主题. 二,需求分析 (1),用户注册,登录功能(没有涉及很复杂的交互场景,注册时会有用户判断是否已存在). (2),用户登录成功,进入笔记管理系统的后台(笔记

  • NodeJS学习笔记之FS文件模块

    一,开篇分析 文件系统模块是一个简单包装的标准 POSIX 文件 I/O 操作方法集.可以通过调用 require("fs") 来获取该模块.文件系统模块中的所有方法均有异步和同步版本. (1),文件系统模块中的异步方法需要一个完成时的回调函数作为最后一个传入形参. (2),回调函数的构成由调用的异步方法所决定,通常情况下回调函数的第一个形参为返回的错误信息. (3),如果异步操作执行正确并返回,该错误形参则为null或者undefined.如果使用的是同步版本的操作方法,一旦出现错误

  • NodeJS学习笔记之MongoDB模块

    一,开篇分析 这篇属于扩展知识篇,因为在下面的文章中会用到数据库操作,所以今天就来说说它(Mongodb模块). (1),简介 MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. MongoDB是一个高性能,开源,无模式的文档型数据库,是当前NoSql数据库中比较热门的一种. MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的.他支持的数据结构非常松散,是类似json的bj

  • NodeJS学习笔记之Module的简介

    Node.js模块系统 Node.js有一个简单的模块加载系统. 在Node.js中,文件和模块是一一对应的(每个文件被视为单独的模块). 例如,考虑下面这个名为 foo.js 的文件: const circle = require('./circle.js'); console.log(`The area of a circle of radius 4 is ${circle.area(4)}`); 在第一行, foo.js 加载与 foo.js 同一目录的模块 circle.js . cir

  • Nodejs学习笔记之Stream模块

    一,开篇分析 流是一个抽象接口,被 Node 中的很多对象所实现.比如对一个 HTTP 服务器的请求是一个流,stdout 也是一个流.流是可读,可写或兼具两者的. 最早接触Stream是从早期的unix开始的, 数十年的实践证明Stream 思想可以很简单的开发出一些庞大的系统. 在unix里,Stream是通过 "|" 实现的.在node中,作为内置的stream模块,很多核心模块和三方模块都使用到. 和unix一样,node stream主要的操作也是.pipe(),使用者可以使

随机推荐