这里有新鲜出炉的 Node.JS 入门教程,程序狗速度看过来!
Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台, 用来方便地搭建快速的 易于扩展的网络应用 · Node.js 借助事件驱动, 非阻塞 I/O 模型变得轻量和高效, 非常适合 运行在分布式设备 的 数据密集型 的实时应用
本文是 NodeJS 学习笔记系列文章的第二篇,从这篇开始我们就根据官方文档来逐个学习下 NodeJS 的各个模块,首先我们来学习下 Global
一,开篇分析
在上个章节中我们学习了 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 scope
will define a global variable.
- var something
In Node this is different. The top-level scope is not the global scope;
inside a Node module will be local to that module.
- var something
全局对象这个概念我想大家应该不会感到陌生,在浏览器中,最高级别的作用域是 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 的一点区别
才是真正的接口,exports 只不过是它的一个辅助工具。最终返回给调用的是
- Module.exports
而不是 exports。
- Module.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 中的两种作用域
来源: http://www.phperz.com/article/17/0426/273367.html