天天看點

大熊君大話NodeJS之------Global Objects全局對象

一,開篇分析

在上個章節中我們學習了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 scope 

var something

 will define a global variable.

In Node this is different. The top-level scope is not the global scope; 

var something

 inside 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. 

  控制台 {對象} 用于列印到标準輸出和錯誤輸出。看如下測試:

  

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

  會得到以下輸出結果:

1 var log = function () {
 2   process.stdout.write(format.apply(this, arguments) + '\n');
 3 }
 4 var info = function () {
 5   process.stdout.write(format.apply(this, arguments) + '\n');
 6 }
 7 var warn = function () {
 8   writeError(format.apply(this, arguments) + '\n');
 9 }
10 var error = function () {
11   writeError(format.apply(this, arguments) + '\n');
12 }
13 var dir = function (object) {
14   var util = require('util');
15   process.stdout.write(util.inspect(object) + '\n');
16 }
17 var time = function (label) {
18   times[label] = Date.now();
19 }
20 var timeEnd = function (label) {
21   var duration = Date.now() - times[label];
22   exports.log('undefined: NaNms', label, duration);
23 }
24 var trace = function (label) {
25   // TODO probably can to do this better with V8's debug object once that is
26   // exposed.
27   var err = new Error;
28   err.name = 'Trace';
29   err.message = label || '';
30   Error.captureStackTrace(err, arguments.callee);
31   console.error(err.stack);
32 }
33 var assert = function (expression) {
34   if (!expression) {
35     var arr = Array.prototype.slice.call(arguments, 1);
36     require('assert').ok(false, format.apply(this, arr));
37   }
38 }      

  通過這些函數,我們基本上知道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中的兩種作用域

             哈哈哈,本篇結束,未完待續,希望和大家多多交流夠溝通,共同進步。。。。。。呼呼呼……(*^__^*)            

繼續閱讀