首頁 > web前端 > js教程 > 主體

Nodejs學習筆記之Global Objects全域物件_node.js

WBOY
發布: 2016-05-16 16:20:44
原創
1031 人瀏覽過

一,開頭分析

在上個章節中我們學習了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 eventany other / 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 = 函數(物件){
  var util = require('util');
  process.stdout.write(util.inspect(object) 'n');
}
var time = 函數(標籤){
  times[label] = Date.now();
}
var timeEnd = 函數(標籤){
  var 持續時間 = Date.now() - times[label];
  Exports.log('未定義: NaNms', 標籤, 持續時間);
}
var trace = 函數(標籤){
  // TODO 可能可以使用 V8 的偵錯物件做得更好
  // 暴露。
  var err = 新錯誤;
  err.name = 'Trace';
  err.message = 標籤 || '';
  Error.captureStackTrace(err,arguments.callee);
  console.error(err.stack);
}
var 斷言 = 函數(表達式){
  if (!表達式) {
    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';
名稱 = '名稱';
global.name='全域名稱';
this.name = '模組名稱';
console.log(全域.名稱);
console.log(this.name);
console.log(名稱);

我們看到var name = 'var-name';name = 'name'; 是定義的局部變數;

而global.name='global-name';是為全域物件定義name屬性,

那麼我們來驗證一下,將下面保存成test2.js,運行

複製程式碼程式碼如下:

var t1 = require('./test1'); 
console.log(t1.name); 
console.log(全域名稱);

從結果可以看出,我們成功導入了test1模組,並且運行了test1的程式碼,因為在test2中輸出了global.name,

和 t1.name test1 模組中透過this.name定義的,說明this指向的是模組作用域物件。

導出與module.exports的一點差異

    Module.exports才是真正的接口,導出只是它的一個輔助工具。最終回傳給呼叫的是Module.exports而不是匯出。

所有的exports收集到的屬性和方法,都賦予了Module.exports。當然,這是有前提的,就是Module.exports本身不具备任何属性和方法

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

舉個栗子:

新建一個檔案 bb.js

複製程式碼程式碼如下:

Exports.name = function() {
    console.log('我的名字是大熊!') ;
} ;

建立一個測試檔 test.js

複製程式碼程式碼如下:

var bb= require('./bb.js');
bb.name(); // '我的名字是大熊! '

修改bb.js如下:

複製程式碼程式碼如下:

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

  再次引用執行bb.js

複製程式碼 程式碼如下:

var bb= require('./bb.js');
bb.name(); // 有 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中的兩種作用域


相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!