组内Node.js分享

- 技术

    昨天得知今天要进行小组分享,但是我内心并不慌张。关于今天的分享“当我在学习Node时,我在学什么”,是自己一路学习的心得体会,即便临场讲述也能言之有物。

聊什么

    其实在两周前,我报名分享时,我打算自下而上来分享Node,从底层架构到上层应用,但后来我可以很明显的预知到这样分享的效果会不好。所以我改变了分享的内容,同时由于时间原因,这次分享侧重点并不是在讲述Node是什么,以及Node Api相关的东西,我也相信以大家的自学能力,入门并不是问题。我今天分享的是个人在初期学习Node的过程中的经历、感受。

说说历史

    我喜欢听故事也喜欢讲故事,所以我还是想从最开始说起。那是2009年,Ryan Dahl想写一个基于事件驱动和非阻塞IO的高性能服务器,他尝试了许多语言来构建,比如C、Lua、Haskell等,但由于自己hold不住或语言本身已存在这些东西,他最后瞄向了没有历史包袱的Javascript,从而创建了Node。但要注意的是,Node并不是一门语言,只是一个Javascript的运行时,可以理解为PythonVM之于Python,JVM之于Java。

特点

    异步IO和基于事件和回调函数。比如在Node中使用文件模块读取文件:

fs.readFile('./1.txt', (err, data) => {
    console.log(data);
});

fs.readFile('./2.txt', (err, data) => {
    console.log(data);
});

对于这种风格显然我们不陌生,比如:

oneDom.addEventListener('click', function() {
    // do something
});

$.get('/url', function(data) => {
    // do something
})

这种异步IO,带来的好处就是性能更快,它的耗时取决于最慢的操作,但是也带来了成本,一是异步风格带来的理解成本,因为编码顺序和执行顺序并不一致;二是错误的处理。

另外,我们可以看到代码风格是基于事件和回调函数风格,再举一个例子,使用原生http模块来接收post数据时:

let str = '';

req.on('data', (trunk) => {
    str += trunk;
});  

req.on('end', () => {
    res.end(str);
});

这种基于事件的回调风格可以有一些优点,也是我个人最喜欢Node的地方,Don't Call Me, I Wll Call You。这种风格可以带来轻解耦,我们只需要关注事务即可。但是也有一些不足,比如每个事件事务相对独立,如果事件之间要彼此协作就不太方便。

    单线程。Node上层是Javascript,所以它是单线程的,单线程好处就是不用考虑锁、线程之间通信的问题。但是也有问题,比如:错误引起整个程序退出、无法利用多核CPU、当有大量计算占据CPU时,异步IO回调无法执行。不过这些问题都有解决方案。不知道大家在此处是否会有疑问,既然Node是单线程的,那它是如何实现异步IO的呢?这个问题在后面会说到。

我们可以用Node做什么

如何学习

    终于到了主题了。当我们学习Node时,很容易陷入不知道该学什么的圈子,因为仿佛大家都是在用npm包。

    原生模块。我们刚入门时,应当从原生模块看起,原生模块量比较多,并不需要全看,只需要从自己需求最常用的看起。比如fs、http、url等。我个人在学习Node的初期,能写一些东西,但是总感觉有层膜隔着,很不自在,后来的转变是理解异步和熟悉基于事件的异步回调风格,而通过原生模块的练习,刚好可以达到这个目的,同时我们在这个阶段可能理解异步处理方案的一步步改变的原因,也能理解为什么早期包括现在还在被人黑的callback hell。比如做一些类似于这个例子的事情

    之后就可以使用Node来做一些事情了,比如写一个静态服务器,通过这个例子,可以了解使用原生模块写一个简单地静态服务器,以及自己从零撸起的繁杂,所以我们要使用框架。

框架

    Node的web框架太多了,我个人接触的第一个框架是Koa2。

    首先说说Koa2。Koa2很简单,它只是封装了http层,以及提供了一个中间件模型。但Koa提供的基础功能还是太简单了,要完成一个web应用的话,自己要做的事情还有很多,所以我们需要引入中间件来快速开发。关于Koa入门的代码有很多,随着框架的使用,会发现写法很自由,没有架构可言的的设计是不适合做大一点项目的,代码如面条一般,所以我们要进行抽象分层,分层的方式就太多了,每个人都有自己的方式,这里有个例子提供了一种方式。这样分层后,显然可维护性提高了,项目规模可以大一点了,我们可以使用Koa去构建自己的web应用了,比如部门的门户后端就是使用Koa构建。但是写多了,就会发现不是很舒服,我个人的感受是,个人开发时会感觉每次写应用就需要自己引入中间件进行初始化、分层等;比如团队协作的话,风格和目录结构的约定五花八门。

    于是我接触了Nest.js。不知道大家有没有听过Java的spring的大名,Nest.js和spring比较像,我跟我写Java的朋友看Nest的代码时,他表示很亲切。多提一句,当你学会Nest.js后,Angular你也入门了。这是我学习时候的一个Nest.js的例子。可以看到,Nest.js默认使用typescript编码,大量的装饰器使用,相比于Koa也多了很多概念,比如提供者、守卫、拦截器等。通过这个例子可以看出来,Nest.js构建的应用,代码清晰,职责分明,适合团队和大型项目,但是上手成本比较高。

    当然还有很多框架,比如阿里的egg,midway,沃尔玛的hapi等,感兴趣可以去看看他们的思想和实现。

底层

    学Node很容易陷入Node庞大的的生态之中,仅是使用其作为工具完成开发,自然是没有问题的,但是从技术学习层面论,我认为你学会使用了一个npm包,你学会了一个新的web框架,并不代表你进步了。于是我觉得我的眼光还是要放在Node本身上,现在也在这方面学习沉淀。要深入学习Node,首先应该了解Node的架构,才知道学什么。 Node架构

所以可以去了解:

  1. libuv。通过学习libuv,可以对异步的实现更加了然;也可以回答出来为什么Node单线程可以实现非阻塞IO等。

  2. Js和C++交互。Js是不能进行IO操作的,Node可以使用Js进行IO操作,本质上是通过C++扩展实现的。

  3. V8引擎。这个不用多说。

  4. 模块化实现 V8引擎

  5. 。。。。

其他

    不得不说,表象之学是很简单的,也是最容易过时的。也不得不说一句扎心的话,大多前端用的Node和后端用的Node不是同一个东西。要构建稳定的应用,还有很多问题需要学习和解决,数据库、缓存、安全、网络编程、服务化、设计模式等。我喜欢Node,一是可以使用熟悉的Javascript做更多的事,二是以Node为入口为工具去学习其他更多的东西。