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(三)--- Node.js模块
May 25 NodeJs
nodejs中实现阻塞实例
Mar 24 NodeJs
浅析Nodejs npm常用命令
Jun 14 NodeJs
NodeJS使用formidable实现文件上传
Oct 27 NodeJs
详解nodejs操作mongodb数据库封装DB类
Apr 10 NodeJs
深入理解Nodejs Global 模块
Jun 03 NodeJs
NodeJS使用七牛云存储上传文件的方法
Jul 24 NodeJs
nodejs调取微信收货地址的方法
Dec 20 NodeJs
nodejs结合Socket.IO实现的即时通讯功能详解
Jan 12 NodeJs
nodejs提示:cross-device link not permitted, rename错误的解决方法
Jun 10 NodeJs
Nodejs 识别图片类型的方法
Aug 15 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/11/16 PHP
推荐一篇入门级的Class文章
2007/03/19 PHP
ThinkPHP权限认证Auth实例详解
2014/07/22 PHP
PHP中配置IIS7实现基本身份验证的方法
2015/09/24 PHP
浅谈PHP中new self()和new static()的区别
2017/08/11 PHP
jQuery 页面 Mask实现代码
2010/01/09 Javascript
js两行代码按指定格式输出日期时间
2011/10/21 Javascript
利用Javascript判断操作系统的类型实现不同操作系统下的兼容性
2013/01/29 Javascript
JavaScript 语言基础知识点总结(思维导图)
2013/11/10 Javascript
javascript中apply、call和bind的使用区别
2016/04/05 Javascript
深入剖析JavaScript面向对象编程
2016/07/12 Javascript
浅谈Cookie的生命周期问题
2016/08/02 Javascript
Angularjs实现带查找筛选功能的select下拉框示例代码
2016/10/04 Javascript
canvas实现图片根据滑块放大缩小效果
2017/02/24 Javascript
JavaScript中引用vs复制示例详析
2018/12/06 Javascript
Node.js事件的正确使用方法
2019/04/05 Javascript
ReactRouter的实现方法
2021/01/25 Javascript
python dataframe astype 字段类型转换方法
2018/04/11 Python
Python使用Pickle库实现读写序列操作示例
2018/06/15 Python
python 使用cx-freeze打包程序的实现
2020/03/14 Python
简述python&pytorch 随机种子的实现
2020/10/07 Python
python基于selenium爬取斗鱼弹幕
2021/02/20 Python
.NET现在共支持多少种语言
2014/02/26 面试题
高中生学习生活的自我评价
2013/11/27 职场文书
个人简历自荐信
2013/12/05 职场文书
学生干部培训方案
2014/06/12 职场文书
基层党员学习党的群众路线教育实践活动心得体会
2014/11/04 职场文书
2014年保育员工作总结
2014/12/02 职场文书
财务总监岗位职责
2015/02/03 职场文书
银行实习推荐信
2015/03/27 职场文书
2015年大学社团工作总结
2015/04/09 职场文书
排球赛新闻稿
2015/07/17 职场文书
党员反腐倡廉学习心得体会
2015/08/15 职场文书
年终工作总结范文
2019/06/20 职场文书
导游词之广西漓江
2019/11/02 职场文书
Python语言规范之Pylint的详细用法
2021/06/24 Python