給Node.js新手的7條小建議

不知不覺的已經用Node.js有將近一年了,這裏我有幾條出自實踐的node.js建議送給剛剛入門的node.js的朋友。javascript

命名而不是匿名

在JavaScript中,咱們能夠建立匿名對象和匿名函數。通常來講,匿名函數可讓代碼更加簡短精悍。html

然而,對這些對象或函數進行命名,則有利於調試和優化。如下是我從Chrome DevTool的文章中借用的圖片:java

Picture

很明顯,命名的實體更利於調試和優化。node

儘早解引用

JavaScript的GC以一種相似引用計數的算法工做,一個對象當且僅當全部指向它的引用所有釋放以後,它自己纔會被釋放掉。git

然而可能你有其餘的閉包或者全局對象持有它的引用,從而阻止了垃圾回收。爲避免這一現象,應當儘早地解除沒必要要的引用:github

var some_var = new net.Server();
// other code...
var i_want_it_temperoray = some_var;
some_operation(i_want_it_temperoray);
i_want_it_temperoray = null; // derefernce
// other code...

若是閉包中尚有一個變量未能被釋放,那麼整個閉包都有可能沒法被回收,從而形成其它對象也沒法釋放。 web

如今有不少工具能夠輔助咱們監視內存的變化狀況,好比heapdump、webkit-devtools-agent等等。同時,這也更加說明了爲何要儘量使用命名而不是匿名的對象。算法

別複製代碼

我用Node.js開發的項目代碼變化很是快,重構也很頻繁,經常會處處複製代碼。然而每當我這樣作的時候,後來都會發現某些變量要麼就是忘了更名,要麼就是有些變量壓根就不存在了,致使代碼變得極爲不穩定。chrome

JS是一門高度動態的語言,它不像C++這樣能夠利用編譯器作靜態檢查,所以你幾乎沒有機會依靠工具檢查出這些問題。因此個人建議是,儘量地本身再輸入一遍代碼,這樣IDE或者編輯器有機會利用代碼自動補全來爲你檢查。若是你的IDE還沒這功能,那你真得考慮換掉它了。數據庫

慎重引入新模塊

Node.js社區很是活躍,有成千上萬的現成模塊能夠取用,然而其中有些其實已經沒人管了。Node.js的API也經常發生變化,適配node v0.8.x的模塊,有可能不支持v0.10.x。

所以當你考慮引入新的模塊的時候,務必先去它的pull request列表或者issue列表看看,確認一下這個模塊是否是已經被拋棄,或者存在重大的隱患。

用async.js或者promise

Node.js基於回調。由於回調的本質,咱們很容易寫出嵌套多層的回調函數。回調對於異步來講是好事,但對於代碼維護來講倒是壞事。

若是你發現代碼須要3層以上的回調函數嵌套,那你應該考慮一下,要不要使用async.js或者基於promise概念的模塊。

如下是一個用async.js寫出來的一系列異步操做:

async.auto([
  'init_logger': function(done){
    set_handlers_to_logger(done);
  },
  'load_config': ['init_logger', function(done){
    load_my_config(done);
  }],
  'init_database': ['load_config', function(done){
    connect_to_db_here(done);
  }],
  // 假定open_cache與數據庫無關
  'open_cache': ['load_config', function(done){
    open_cache_here(done);
  }],
  // warm_up一般用於從數據庫預先抓取一些數據
  // 注意,warm_up只會在'init_database'以及'open_cache'結束後執行
  'warm_up': ['init_database', 'open_cache', function(done){
    fetch_some_data(done);
  }],
  'init_routers': ['load_config', function(done){
    install_routers(done);
  }],
  'emit_out': ['warm_up', 'init_routers', function(done){
    notify_others(done);
  }]
], function(err) {
  if(err){
    // 在這處理異常
  }
});

我對promise相關的模塊不是很熟,但使用Q,應該能夠寫出這樣的代碼,,一樣易於閱讀:

Q.nfcall(function init_logger(){
  set_handlers_to_logger();
})
.then(function load_config(){
  load_my_config();
})
.then(function init_database(){
  connect_to_db_here();
})
.then(function open_cache(){
  open_cache_here();
})
.then(function warm_up(){
  fetch_some_data();
})
.then(function init_routers(){
  install_routers();
})
.then(function emit_out(){
  notify_others();
})
.catch(function (error) {
    // 在這處理異常
})
.done();

選取什麼樣的庫來簡化邏輯通常都是隨便你。一般來講,async.js很是簡單,而Q則更加靈活強大。

好比說async.js中各個函數獨立而不嵌套,所以若是你想經過捕獲某個函數中的變量就顯得有些困難,而在Q中就可使用嵌套的then語句。

原本我還想寫一寫不使用任何輔助模塊的上述代碼,但其實我都很懷疑本身能不能寫對。

另外,你是CoffeeScript的擁泵麼?若是是,那你還能夠嘗試一下IcedCoffeeScript。IcedCoffeeScript在語言層面上提供了兩個很是有用的關鍵字: awaitdefer。基本上全部的流程控制均可以經過這兩個關鍵字進行改造。不過我本人對IcedCoffeeScript並不熟悉,因此就不在這獻醜了。這裏感謝brettof86向我介紹了這一利器,不過我還須要花些時間來熟悉它。

客戶端也許特別慢

用Node.js的時候咱們可能會變得有點過於理想化了,所以很容易寫出下面這樣的聊天室代碼:

var net = require('net');
var clientList = [];
var server = net.createServer(function(c) { //'connection' listener
  console.log('server connected');
  clientList.push(c);
  c.on('end', function() {
    console.log('server disconnected');
    unpipe_all(c, clientList);
    remove_from(c, clientList);
  });
  clientList.forEach(function(item){
    item.pipe(c); // 注意這
    c.pipe(item); // 還有這
  });
});
server.listen(8124, function() { //'listening' listener
  console.log('server bound');
});

我以爲整個結構沒什麼大問題,但當咱們趕上網絡情況很差的客戶端時,狀況就不大好了。這裏的兩個pipe會把數據緩存在內存中,所以當客戶端不能及時接收數據時,這些數據就會大量滯留在內存當中。咱們每每假設客戶端的速度還不錯,但其實那都只是假設!

我想到的一個方法是,用一箇中間件來作緩存,當數據太多時使用文件緩衝,而數據很少則用內存,以下:

Delegate delegate; 
clientList.forEach(function(item){
  delegate.append(item);
  // delegate內部會有一個與文件關聯的緩存
    // 若是數據太大則把數據存入文件
});

若是你有其它的方案來處理這種狀況,不妨也分享一下:)

用事件通知完成,並且不要太早

大多數小對象都是同步構造的,但對於某些封裝了複雜操做的對象來講,初始化都有多是異步的。

若是一個對象須要異步構造,那麼最好使用事件通知完成。這時你要留意官方文檔 中的一小段話:

This is important in developing APIs where you want to give the user the chance to assign event handlers after an object has been constructed, but before any I/O has occurred.

function MyThing(options) {
    this.setupOptions(options);
  
    process.nextTick(function() {
       this.startDoingStuff();
    }.bind(this));
  }
  
  var thing = new MyThing();
  thing.getReadyForStuff();
  
  // thing.startDoingStuff() gets called now, not before.

典型的解決方案是,在構造結束時用process.nextTick來發消息:

function SomeTCPServer(options) {
  var self = this;
  // 其餘可能異步的初始化工做
  process.nextTick(function(){
    self.emit('ready');
  });
}

// 其餘代碼
var server = new SomeTCPServer(ops);
server.on('ready', function when_ready(){
  // 其它事情
});

由於用的是process.nextTickwhen_ready不會錯過ready事件。

其它建議?

我暫時就想起來這麼多,由於我本身也不是Node.js的真正專家。不過我仍是但願上面的幾條能對新手有所幫助,歡迎你們指出上面的任何錯漏,謝啦。

相關文章
相關標籤/搜索