资讯专栏INFORMATION COLUMN

给Node.js新手的7条小建议

Atom / 1115人阅读

摘要:不知不觉的已经用有将近一年了,这里我有几条出自实践的建议送给刚刚入门的的朋友。命名而不是匿名在中,我们可以创建匿名对象和匿名函数。一般来说,匿名函数可以让代码更加简短精悍。然而,对这些对象或函数进行命名,则有利于调试和优化。

不知不觉的已经用Node.js有将近一年了,这里我有几条出自实践的node.js建议送给刚刚入门的node.js的朋友。

命名而不是匿名

在JavaScript中,我们可以创建匿名对象和匿名函数。一般来说,匿名函数可以让代码更加简短精悍。

然而,对这些对象或函数进行命名,则有利于调试和优化。以下是我从Chrome DevTool的文章中借用的图片:

很明显,命名的实体更利于调试和优化。

尽早解引用

JavaScript的GC以一种类似引用计数的算法工作,一个对象当且仅当所有指向它的引用全部释放之后,它本身才会被释放掉。

然而可能你有其他的闭包或者全局对象持有它的引用,从而阻止了垃圾回收。为避免这一现象,应当尽早地解除不必要的引用:

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

如果闭包中尚有一个变量未能被释放,那么整个闭包都有可能无法被回收,从而造成其它对象也无法释放。

现在有很多工具可以辅助我们监视内存的变化情况,比如heapdump、webkit-devtools-agent等等。同时,这也更加说明了为什么要尽可能使用命名而不是匿名的对象。

别复制代码

我用Node.js开发的项目代码变化非常快,重构也很频繁,常常会到处复制代码。然而每当我这样做的时候,后来都会发现某些变量要么就是忘了改名,要么就是有些变量压根就不存在了,导致代码变得极为不稳定。

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的真正专家。不过我还是希望上面的几条能对新手有所帮助,欢迎大家指出上面的任何错漏,谢啦。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77942.html

相关文章

  • node.js中文资料导航

    摘要:中文资料导航官网七牛镜像深入浅出系列进阶必读中文文档被误解的编写实战系列热门模块排行榜,方便找出你想要的模块多线程,真正的非阻塞浅析的类利用编写异步多线程的实例中与的区别管道拒绝服务漏洞高级编程业界新闻看如何评价他们的首次尝鲜程序员如何说服 node.js中文资料导航 Node.js HomePage Node官网七牛镜像 Infoq深入浅出Node.js系列(进阶必读) Nod...

    geekidentity 评论0 收藏0
  • 猫头鹰深夜翻译:在JAVA中记录日志十个小建

    摘要:是指可能导致程序终止的非常严重的时间。具有最高的级别,旨在关闭中的日志功能。因此为每一个消息选择一个合适的日志级别是非常重要的。日志的个小建议将日志访日代码块它能显著的减少因为字符串拼接而带来的性能的影响。 前言 首先,这篇文章没有进行任何的日志功能的详细介绍,而是对日志提出了几种最佳实践。适合对日志记录有所了解的同学阅读。下面是正文: JAVA日志管理既是一门科学,又是一门艺术。科学...

    venmos 评论0 收藏0
  • PHP开发银联云闪付二维码支付

    摘要:最近刚好有在研究银联云闪付的支付模块,所以就写篇总结分享给大家。很方便的就是,银联云闪付的接入给我们准备了测试模式,就是你并不需要真的有商户号才能开发,不像微信支付那样非要有商户号才能测试开发。 你好,是我琉忆。最近刚好有在研究银联云闪付的支付模块,所以就写篇总结分享给大家。 这算是第二次接触支付的东西了,接触得最多的是接入微信支付,自己也有相关的总结文章,可以去segmentfaul...

    王笑朝 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<