如何正确学习Node

0 :Node.js简介

现在,越来越多的科技公司和开发者开始使用 Node.js 开发各种应用。Node.js除了能够辅助大前端开发外,还可以编写Web应用,封装Api,组装RPC服务等,甚至是开发VSCode编辑器一样的PC客户端。和其它技术相比, Node.js 简单易学,性能好、部署容易,能够轻松处理高并发场景下的大量服务器请求。Node.js 周边的生态也非常强大,NPM(Node包管理)上有超过60万个模块,日下超过载量3亿次。但编写 Node.js 代码对新人和其它语言背景的开发者来说,不是一件容易的事,在入门之前需要弄懂不少复杂的概念。

a)Node.js简介

  • 优点: Node.js 不是一门语言也不是框架,它只是基于 Google V8 引擎的 JavaScript 运行时环境,同时结合 Libuv 扩展了 JavaScript 功能,使之支持 io、fs 等只有语言才有的特性,使得 JavaScript 能够同时具有 DOM 操作(浏览器)和 I/O、文件读写、操作数据库(服务器端)等能力,是目前最简单的全栈式语言。

早在2007年,Jeff Atwood 就提出了著名的 Atwood定律

任何能够用 JavaScript 实现的应用系统,最终都必将用 JavaScript 实现

  • 缺点:

当然了,Node.js 也有一些缺点。Node.js 经常被人们吐槽的一点就是:回调太多难于控制(俗称回调地狱)和 CPU 密集任务处理的不是很好。但是,目前异步流程技术已经取得了非常不错的进步,从Callback、Promise 到 Async函数,可以轻松的满足所有开发需求。至于 CPU 密集任务处理并非不可解,方案有很多,比如通过系统底层语言 Rust 来扩展 Node.js,但这样会比较麻烦。笔者坚信在合适的场景使用合适的东西,尤其是在微服务架构下,一切都是服务,可以做到语言无关。如果大家想使 JavaScript 做 CPU 密集任务,推荐 Node.js 的兄弟项目 fibjs,基于纤程(fiber,可以简单理解为更轻量级的线程),效率非常高,兼容npm,同时没有异步回调烦恼。 b)什么是Node.js?

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.

  • Node.js 不是 JavaScript 应用,不是语言(JavaScript 是语言),不是像 Rails(Ruby)、 Laravel(PHP) 或 Django(Python) 一样的框架,也不是像 Nginx 一样的 Web 服务器。Node.js 是 JavaScript 运行时环境
  • 构建在 Chrome's V8 这个著名的 JavaScript 引擎之上,Chrome V8 引擎以 C/C++ 为主,相当于使用JavaScript 写法,转成 C/C++ 调用,大大的降低了学习成本
  • 事件驱动(event-driven),非阻塞 I/O 模型(non-blocking I/O model),简单点讲就是每个函数都是异步的,最后由 Libuv 这个 C/C++ 编写的事件循环处理库来处理这些 I/O 操作,隐藏了非阻塞 I/O 的具体细节,简化并发编程模型,让你可以轻松的编写高性能的Web应用,所以它是轻量(lightweight)且高效(efficient)的
  • 使用 npm 作为包管理器,目前 npm 是开源库里包管理最大的生态,功能强大,截止到2017年12月,模块数量超过 60 万+
  • 应用: 大多数人都认为 Node.js 只能写网站后台或者前端工具,这其实是不全面的,Node.js的目标是让并发编程更简单,主要应用在以网络编程为主的 I/O 密集型应用。它是开源的,跨平台,并且高效(尤其是I/O处理),包括IBM、Microsoft、Yahoo、SAP、PayPal、沃尔玛及GoDaddy都是 Node.js 的用户。

c)基本原理

下面是一张 Node.js 早期的架构图,来自 Node.js 之父 Ryan Dahl 的演讲稿,在今天依然不过时,它简要的介绍了 Node.js 是基于 Chrome V8引擎构建的,由事件循环(Event Loop)分发 I/O 任务,最终工作线程(Work Thread)将任务丢到线程池(Thread Pool)里去执行,而事件循环只要等待执行结果就可以了。

核心概念

  • Chrome V8 是 Google 发布的开源 JavaScript 引擎,采用 C/C++ 编写,在 Google 的 Chrome 浏览器中被使用。Chrome V8 引擎可以独立运行,也可以用来嵌入到 C/C++ 应用程序中执行。
  • Event Loop 事件循环(由 libuv 提供)
  • Thread Pool 线程池(由 libuv 提供)

梳理一下

  • Chrome V8 是 JavaScript 引擎
  • Node.js 内置 Chrome V8 引擎,所以它使用的 JavaScript 语法
  • JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事
  • 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
  • 如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为 I/O 很慢,不得不等着结果出来,再往下执行
  • CPU 完全可以不管 I/O 设备,挂起处于等待中的任务,先运行排在后面的任务
  • 将等待中的 I/O 任务放到 Event Loop 里
  • 由 Event Loop 将 I/O 任务放到线程池里
  • 只要有资源,就尽力执行
  • Chrome V8 解释并执行 JavaScript 代码(这就是为什么浏览器能执行 JavaScript 原因)
  • libuv 由事件循环和线程池组成,负责所有 I/O 任务的分发与执行

1. 前言:学习 Node.js 的三个境界

  • 打日志:console.log
  • 断点调试:断点调试:node debugger 或node inspector 或vscode
  • 测试驱动开发(tdd | bdd)

2. 准备与学习:

基础学习

1)js语法必须会

  1. js基本语法,都是c语系的,有其他语言背景学习起来相对更简单
  2. 常见用法,比如正则,比如数据结构,尤其是数组的几种用法。比如bind/call/apply等等
  3. 面向对象写法。js是基于对象的,所以它的oo写起来非常诡异。参见红皮书JavaScript高级编程,很多框架都是自己实现oo基础框架,比如ext-core等。

2)个人学习和技术选型都要循序渐进

  1. 先能写,采用面向过程写法,简单理解就是定义一堆function,然后调用,非常简单
  2. 然后再追求更好的写法,可以面向对象。对于规模化的编程来说,oo是有它的优势的,一般java、c#,ruby这些语言里都有面向对象,所以后端更习惯,但对于语言经验不那么强的前端来说算高级技巧。
  3. 等oo玩腻了,可以有更好的追求:函数式编程,无论编程思维,还是用法上都对已有的编程思维是个挑战。我很喜欢函数式,但不太会在团队里使用,毕竟oo阶段还没完全掌握,风险会比较大。但如果团队水平都非常高了,团队稳定是可以用的。

可以看出我的思路,先能写,然后再追求更好的写法,比如面向对象。等团队水平到一定程度了,并且稳定的时候,可以考虑更加极致的函数式写法。

3)各种高级的JavaScript友好语言

JavaScript友好语言指的是能够使用其他语法实现,但最终编译成js的语言。自从Node.js出现后,这种黑科技层出不穷。比如比较有名的coffee、typescript、babel(es)等。

CoffeeScript虽然也是JavaScript友好语言,但其语法借鉴ruby,崇尚极简,对于类型和OO机制上还是偏弱,而且这么多年也没发展起来,仍然是比较小众的活着。未来比例会越来越少的。

显然TypeScript会越来越好,TypeScript 的强大之处是要用过才知道的。

  • 1)规模化编程,像Java那种,静态类型,面向对象,前端只有TypeScript能做到
  • 2)亲爹是微软安德斯·海尔斯伯格,不知道此人的请看borland传奇去
  • 3)开源,未来很好
  • 4)组合拳:TypeScript + VSCode = 神器

当下前端发展速度极快,以指数级的曲线增长。以前可能1年都不一定有一项新技术,现在可能每个月都有。大前端,Node全栈,架构演进等等都在快速变化。可以说,前端越复杂,有越多的不确定性,TypeScript的机会就越大。 4)再论面向对象

面向对象想用好也不容易的,而且js里有各种实现,真是让人眼花缭乱。

  • 基于原型的写法,纵观JavaScript高级编程,就是翻来覆去的讲这个,这个很基础,但不好是很好用。可以不用,但不可以不会。
  • 自己写面向对象机制是最好的,但不是每个人都有这个能力的。好在es6规范出了更好一点的面向对象,通过class、extends、super关键字来定义类,已经明显好很多了,虽然还很弱,但起码勉强能用起来了。从面向过程走过来的同学,推荐这种写法,简单易用。但要注意面向对象要有面向对象的写法,要理解抽象,继承,封装,多态4个基本特征。如果想用好,你甚至还需要看一些设计模式相关的书。好在有《JavaScript设计模式》一书。Koa2里已经在用这种写法了。
  • js是脚本语言,解释即可执行。所以它的最大缺点是没有类型系统,这在规模化编程里是非常危险的,一个函数,传参就能玩死人。于是现在流行使用flow和typescript来做类型校验。flow只是工具,比较轻量级。而typescript是es6超级,给es6补充了类型系统和更完善的面向对象机制,所以大部分人都会对ts有好感,很有可能是未来的趋势。

Node.js应用场景

《Node.js in action》一书里说,Node.js 所针对的应用程序有一个专门的简称:DIRT。它表示数据密集型实时(data-intensive real-time)程序。因为 Node.js 自身在 I/O 上非常轻量,它善于将数据从一个管道混排或代理到另一个管道上,这能在处理大量请求时持有很多开放的连接,并且只占用一小部分内存。它的设计目标是保证响应能力,跟浏览器一样。

这话不假,但在今天来看,DIRT 还是范围小了。其实 DIRT 本质上说的 I/O 处理的都算,但随着大前端的发展,Node.js 已经不再只是 I/O 处理相关,而是更加的“Node”!

  • 1)跨平台:覆盖你能想到的面向用户的所有平台,传统的PC Web端,以及PC客户端 nw.js/electron 、移动端 cordova、HTML5、react-nativeweex,硬件 ruff.io
  • 2)Web应用开发:网站、Api、RPC服务等
  • 3)前端:三大框架 React \ Vue \ Angular 辅助开发,以及工程化演进过程(使用Gulp /Webpack 构建 Web 开发工具)
  • 4)工具:npm上各种工具模块,包括各种前端预编译、构建工具 Grunt / Gulp、脚手架,命令行工具,各种奇技淫巧等

Node.js 应用场景非常丰富,比如 Node.js 可以开发操作系统,但一般我都不讲的,就算说了也没多大意义,难道大家真的会用吗?一般,我习惯将 Node.js 应用场景氛围7个部分。

1)初衷,server端,不想成了前端开发的基础设施 2)命令行辅助工具,甚至可以是运维 3)移动端:cordova,pc端:nw.js和electron 4)组件化,构建,代理 5)架构,前后端分离、api proxy 6)性能优化、反爬虫与爬虫 7) 全栈最便捷之路

Node核心:异步流程控制

Node.js是为异步而生的,它自己把复杂的事儿做了(高并发,低延时),交给用户的只是有点难用的Callback写法。也正是坦诚的将异步回调暴露出来,才有更好的流程控制方面的演进。也正是这些演进,让Node.js从DIRT(数据敏感实时应用)扩展到更多的应用场景,今天的Node.js已经不只是能写后端的JavaScript,已经涵盖了所有涉及到开发的各个方面,而Node全栈更是热门种的热门。

直面问题才能有更好的解决方式,Node.js的异步是整个学习Node.js过程中重中之重。

    1. 异步流程控制学习重点
  • 2)Api写法:Error-first Callback 和 EventEmitter
  • 3)中流砥柱:Promise
  • 4)终极解决方案:Async/Await
  1. 异步流程控制学习重点

我整理了一张图,更直观一些。从09年到现在,8年多的时间里,整个Node.js社区做了大量尝试,其中曲折足足够写一本书的了。大家先简单了解一下。

  • 红色代表Promise,是使用最多的,无论async还是generator都可用
  • 蓝色是Generator,过度货
  • 绿色是Async函数,趋势

结论:Promise是必须会的,那你为什么不顺势而为呢?

推荐:使用Async函数 + Promise组合,如下图所示。

结论

  1. Node.js SDK里callback写法必须会的。
  2. Node.js学习重点: Async函数与Promise
    1. 中流砥柱:Promise
    2. 终极解决方案:Async/Await

2)Api写法:Error-first Callback 和 EventEmitter a)Error-first Callback 定义错误优先的回调写法只需要注意2条规则即可:

  • 回调函数的第一个参数返回的error对象,如果error发生了,它会作为第一个err参数返回,如果没有,一般做法是返回null。
  • 回调函数的第二个参数返回的是任何成功响应的结果数据。如果结果正常,没有error发生,err会被设置为null,并在第二个参数就出返回成功结果数据。

下面让我们看一下调用函数示例,Node.js 文档里最常采用下面这样的回调方式:

function(err, res) {
  // process the error and result
}
复制代码

这里的 callback 指的是带有2个参数的函数:"err"和 "res"。语义上讲,非空的“err”相当于程序异常;而空的“err”相当于可以正常返回结果“res”,无任何异常。 b)EventEmitter

事件模块是 Node.js 内置的对观察者模式“发布/订阅”(publish/subscribe)的实现,通过EventEmitter属性,提供了一个构造函数。该构造函数的实例具有 on 方法,可以用来监听指定事件,并触发回调函数。任意对象都可以发布指定事件,被 EventEmitter 实例的 on 方法监听到。

在node 6之后,可以直接使用require('events')

var EventEmitter = require('events')
var util = require('util')

var MyEmitter = function () {
 
}
util.inherits(MyEmitter, EventEmitter)

const myEmitter = new MyEmitter();

myEmitter.on('event', (a, b) => {
  console.log(a, b, this);
    // Prints: a b {}
});

myEmitter.emit('event', 'a', 'b');
复制代码

和jquery、vue里的Event是非常类似的。而且前端自己也有EventEmitter。 c)如何更好的查Node.js文档

API是应用程序接口Application Programming Interface的简称。从Node.js异步原理,我们可以知道,核心在于 Node.js SDK 中API调用,然后交由EventLoop(Libuv)去执行,所以我们一定要熟悉Node.js的API操作。

Node.js的API都是异步的,同步的函数是奢求,要查API文档,在高并发场景下慎用。

笔者推荐使用 Dash 或 Zeal 查看离线文档,经常查看离线文档,对Api理解会深入很多,比IDE辅助要好,可以有效避免离开IDE就不会写代码的窘境。 3)中流砥柱:Promise 回调地狱

Node.js 因为采用了错误优先的回调风格写法,导致sdk里导出都是回调函数。如果组合调用的话,就会特别痛苦,经常会出现回调里嵌套回调的问题,大家都非常厌烦这种写法,称之为Callback Hell,即回调地狱。一个经典的例子来自著名的Promise模块q文档里。

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});
复制代码

这里只是做4步,嵌套了4层回调,如果更多步骤呢?很多新手浅尝辄止,到这儿就望而却步,粉转黑。这明显不够成熟,最起码你要看看它的应对解决方案吧! Node.js 约定所有Api都采用错误优先的回调方式,这部分场景都是大家直接调用接口,无太多变化。而Promise是对回调地狱的思考,或者说是改良方案。目前使用非常普遍,可以说是在async函数普及之前唯一一个通用性规范,甚至 Node.js 社区都在考虑 Promise 化,可见其影响之大。

Promise最早也是在commonjs社区提出来的,当时提出了很多规范。比较接受的是promise/A规范。后来人们在这个基础上,提出了promise/A+规范,也就是实际上现在的业内推行的规范。ES6 也是采用的这种规范。 Promise意味着[许愿|承诺]一个还没有完成的操作,但在未来会完成的。与Promise最主要的交互方法是通过将函数传入它的then方法从而获取得Promise最终的值或Promise最终最拒绝(reject)的原因。要点有三个:

  • 递归,每个异步操作返回的都是promise对象

  • 状态机:三种状态转换,只在promise对象内部可以控制,外部不能改变状态

  • 全局异常处理 Async函数要点如下:

  • Async函数语义上非常好

  • Async不需要执行器,它本身具备执行能力,不像Generator需要co模块

  • Async函数的异常处理采用try/catch和Promise的错误处理,非常强大

  • Await接Promise,Promise自身就足够应对所有流程了,包括async函数没有纯并行处理机制,也可以采用Promise里的all和race来补齐

  • Await释放Promise的组合能力,外加co和Promise的then,几乎没有不支持的场景

综上所述

  • Async函数是趋势,如果Chrome 52. v8 5.1已经支持Async函数(https://github.com/nodejs/CTC/issues/7)了,Node.js支持还会远么?
  • Async和Generator函数里都支持promise,所以promise是必须会的。
  • Generator和yield异常强大,不过不会成为主流,所以学会基本用法和promise就好了,没必要所有的都必须会。
  • co作为Generator执行器是不错的,它更好的是当做Promise 包装器,通过Generator支持yieldable,最后返回Promise,是不是有点无耻?
    1. 异步流程控制学习重点
  • 2)Api写法:Error-first Callback 和 EventEmitter
  • 3)中流砥柱:Promise
  • 4)终极解决方案:Async/Await

Web编程要点

一般,后端开发指的是 Web 应用开发中和视图渲染无关的部分,主要是和数据库交互为主的重业务型逻辑处理。但现在架构升级后,Node.js 承担了前后端分离重任之后,有了更多玩法。从带视图的传统Web应用面向Api接口应用,到通过 RPC 调用封装对数据库的操作,到提供前端 Api 代理和网关,服务组装等,统称为后端开发,不再是以往只有和数据库打交道的部分才算后端。这样,就可以让前端工程师对开发过程可控,更好的进行调优和性能优化。 对 Node.js 来说,一直没有在后端取得其合理的占有率,原因是多方面的,暂列几条。

  • 1)利益分配,已有实现大多是Java或者其他语言,基本是没法撼动的,重写的成本是巨大的,另外,如果用Node写了,那么那些写Java的人怎么办?抢人饭碗,这是要拼命的。
  • 2)Node相对年轻,大家对Node的理解不够,回调和异步流程控制略麻烦,很多架构师都不愿意花时间去学习。尽管在Web应用部分处理起来非常简单高效,但在遇到问题时并不容易排查定位,对开发者水平要求略高。
  • 3)开发者技能单一,很多是从前端转过来的,对数据库,架构方面知识欠缺,对系统设计也知之不多,这是很危险的,有种麻杆打狼两头害怕的感觉。
  • 4)Node在科普、培训、布道等方面做的并不好,国外使用的非常多,国内却很少人知道,不如某些语言做得好。

尽管如此,Node.js 还是尽人皆知,卷入各种是非风口,也算是在大前端浪潮中大红大紫。原因它的定位非常明确,补足以 JavaScript 为核心的全栈体系中服务器部分。开发也是人,能够同时掌握并精通多门语言的人毕竟不多,而且程序员的美德是“懒”,能使用 JavaScript 一门语言完成所有事儿,为什么要学更多呢? 我们可以根据框架的特性进行分类

框架名称 特性 点评
Express 简单、实用,路由中间件等五脏俱全 最著名的Web框架
Derby.js && Meteor 同构 前后端都放到一起,模糊了开发便捷,看上去更简单,实际上上对开发来说要求更高
Sails、Total 面向其他语言,Ruby、PHP等 借鉴业界优秀实现,也是 Node.js 成熟的一个标志
MEAN.js 面向架构 类似于脚手架,又期望同构,结果只是蹭了热点
Hapi和Restfy 面向Api && 微服务 移动互联网时代Api的作用被放大,故而独立分类。尤其是对于微服务开发更是利器
ThinkJS 面向新特性 借鉴ThinkPHP,并慢慢走出自己的一条路,对于Async函数等新特性支持,无出其右,新版v3.0是基于Koa v2.0的作为内核的
Koa 专注于异步流程改进 下一代Web框架
Egg 基于Koa,在开发上有极大便利 企业级Web开发框架

对于框架选型

  • 业务场景、特点,不必为了什么而什么,避免本末倒置
  • 自身团队能力、喜好,有时候技术选型决定团队氛围的,需要平衡激进与稳定
  • 出现问题的时候,有人能够做到源码级定制。Node.js 已经有8年历史,但模块完善程度良莠不齐,如果不慎踩到一个坑里,需要团队在无外力的情况能够搞定,否则会影响进度

Tips:个人学习求新,企业架构求稳,无非喜好与场景而已 Web编程核心

  • 异步流程控制(前面讲过了)

  • 基本框架 Koa或Express,新手推荐Express,毕竟资料多,上手更容易。如果有一定经验,推荐Koa,其实这些都是为了了解Web编程原理,尤其是中间件机制理解。

  • 数据库 mongodb或mysql都行,mongoose和Sequelize、bookshelf,TypeOrm等都非常不错。对于事物,不是Node.js的锅,是你选的数据库的问题。另外一些偏门,想node连sqlserver等估计还不成熟,我是不会这样用的。

  • 模板引擎, ejs,jade,nunjucks。理解原理最好。尤其是extend,include等高级用法,理解布局,复用的好处。其实前后端思路都是一样的。

  • Node 的书几乎都过时了,我该买哪本?

  • Node 用途那么多,我该从哪里学起?

  • Node Web 框架那么多,我该怎么选?

你可能感兴趣的:(如何正确学习Node)