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


Posted in NodeJs onJanuary 13, 2015

一,开篇分析

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

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

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

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对象中的细节内容,见下图:

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

我在下面会逐一说说挂载在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中的两种作用域

NodeJs 相关文章推荐
nodejs中使用monk访问mongodb
Jul 06 NodeJs
基于NodeJS的前后端分离的思考与实践(五)多终端适配
Sep 26 NodeJs
Nodejs学习笔记之Global Objects全局对象
Jan 13 NodeJs
Nodejs下用submit提交表单提示cannot post错误的解决方法
Nov 21 NodeJs
NodeJs模拟登陆正方教务
Apr 28 NodeJs
nodejs利用ajax实现网页无刷新上传图片实例代码
Jun 06 NodeJs
nodejs构建本地web测试服务器 如何解决访问静态资源问题
Jul 14 NodeJs
nodejs实现解析xml字符串为对象的方法示例
Mar 14 NodeJs
详解nodejs通过响应回写的方式渲染页面资源
Apr 07 NodeJs
详解redis在nodejs中的应用
May 02 NodeJs
Nodejs文件上传、监听上传进度的代码
Mar 27 NodeJs
Nodejs实现WebSocket代码实例
May 19 NodeJs
Nodejs为什么选择javascript为载体语言
Jan 13 #NodeJs
NodeJS中Buffer模块详解
Jan 07 #NodeJs
Nodejs中读取中文文件编码问题、发送邮件和定时任务实例
Jan 01 #NodeJs
Nodejs中调用系统命令、Shell脚本和Python脚本的方法和实例
Jan 01 #NodeJs
nodejs中实现路由功能
Dec 29 #NodeJs
NodeJS制作爬虫全过程(续)
Dec 22 #NodeJs
NodeJS制作爬虫全过程
Dec 22 #NodeJs
You might like
一个没有MYSQL数据库支持的简易留言本的编写
2006/10/09 PHP
php 删除记录实现代码
2009/03/12 PHP
ThinkPHP之R方法实例详解
2014/06/20 PHP
[原创]php获取数组中键值最大数组项的索引值
2015/03/17 PHP
支付宝支付开发――当面付条码支付和扫码支付实例
2016/11/04 PHP
PHP将数据导出Excel表中的实例(投机型)
2017/07/31 PHP
用js实现随机返回数组的一个元素
2007/08/13 Javascript
初识JQuery 实例一(first)
2011/03/16 Javascript
JS实现两个大数(整数)相乘
2014/04/28 Javascript
Javascript Memoizer浅析
2014/10/16 Javascript
jsonp跨域请求数据实现手机号码查询实例分析
2015/12/12 Javascript
全面详细的jQuery常见开发技巧手册
2016/02/21 Javascript
JS动态给对象添加事件的简单方法
2016/07/19 Javascript
Bootstrap Table服务器分页与在线编辑应用总结
2016/08/08 Javascript
javascript ES6中箭头函数注意细节小结
2017/02/17 Javascript
bootstrap栅格系统示例代码分享
2017/05/22 Javascript
zTree jQuery 树插件的使用(实例讲解)
2017/09/25 jQuery
WebGL three.js学习笔记之阴影与实现物体的动画效果
2019/04/25 Javascript
JS html事件冒泡和事件捕获操作示例
2019/05/01 Javascript
JavaScript静态作用域和动态作用域实例详解
2019/06/17 Javascript
详解Vue2的diff算法
2021/01/06 Vue.js
Python httplib,smtplib使用方法
2008/09/06 Python
使用Python3中的gettext模块翻译Python源码以支持多语言
2015/03/31 Python
对python 判断数字是否小于0的方法详解
2019/01/26 Python
Python基础之循环语句用法示例【for、while循环】
2019/03/23 Python
python deque模块简单使用代码实例
2020/03/12 Python
HTML5 图片预加载的示例代码
2020/03/25 HTML / CSS
自动化专业职业生涯规划书范文
2014/01/16 职场文书
我为自己代言广告词
2014/03/18 职场文书
求职信格式要求
2014/05/23 职场文书
装饰工程师岗位职责
2014/06/08 职场文书
公司外出活动方案
2014/08/14 职场文书
爱祖国爱家乡演讲稿
2014/09/02 职场文书
汽车服务工程专业自荐信
2014/09/02 职场文书
python 详解turtle画爱心代码
2022/02/15 Python
Java 定时任务技术趋势简介
2022/05/04 Java/Android