组内Node.js分享
2019-08-22
昨天得知今天要进行小组分享,但是我内心并不慌张。关于今天的分享“当我在学习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做什么
- 工具
- Web应用,尤其是实时应用
- 并行IO,抛弃同步等待式请求加速数据获取,加快渲染
- 游戏开发(因为对实时和并发要求高)
- 客户端开发
- 但
不太适合
CPU密集型任务
如何学习
终于到了主题了。当我们学习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的架构,才知道学什么。
所以可以去了解:
libuv。通过学习libuv,可以对异步的实现更加了然;也可以回答出来为什么Node单线程可以实现非阻塞IO等。
Js和C++交互。Js是不能进行IO操作的,Node可以使用Js进行IO操作,本质上是通过C++扩展实现的。
V8引擎。这个不用多说。
模块化实现
。。。。
其他
不得不说,表象之学是很简单的,也是最容易过时的。也不得不说一句扎心的话,大多前端用的Node和后端用的Node不是同一个东西。要构建稳定的应用,还有很多问题需要学习和解决,数据库、缓存、安全、网络编程、服务化、设计模式等。我喜欢Node,一是可以使用熟悉的Javascript做更多的事,二是以Node为入口为工具去学习其他更多的东西。