《深入浅出NodeJS》 读书学习总结

《深入浅出NodeJS》 读书学习总结

成果

读书笔记

  • NodeJS 5个特点:异步I/O,事件驱动与回调,单线程事件轮询,跨平台。
  • NodeJS 5个大坑:异常处理,嵌套太深,没有Sleep,多线程编程,异步转同步。
  • NodeJS 4种提升性能的方法:动静分离,缓存(Redis),多进程,数据库读写分离。

NodeJS简介

  • 高性能,符合时间驱动,没有历史包袱这三个主要原因,JavaScript成为了Node的实现语言。
  • NodeJS基于Google V8引擎。Node优秀的运算能力主要来自V8的深度优化。
  • NodeJS特点:异步I/O,事件驱动与回调函数,单线程事件轮询,跨平台
  • NodeJS单线程的缺点:
    • 无法利用多核CPU.
    • 错误会引起整个应用进程退出。
    • 大量计算占用CPU导致无法继续调用异步I/O.
    • 解决单线程缺点的方法是引入子进程方法(Cluster,见后边)和C/C++模块扩展(利用它们的多线程机制)。

模块机制

  • Node出现之前,服务器端的JS基本没有市场的。
  • CommonJS主要是为了弥补当前JS没有标准的缺陷,希望JS能够在任何地方运行。
模块引用

var xxx = require(‘模块标识’);

例如:var math = require(‘math’);

模块定义

模块中module对象代表模块自身,exports对象是module的属性,用于导出当前模块的方法或者变量,它是唯一导出的出口。module和exports是node在编译过程中给加上去的。

NodeJS中,一个文件就是一个模块,将方法或者变量挂在在exports对象上作为属性即可定义导出的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
exports.xxx = ……

例如: exports.add = function(){

var sum = 0;

var i = 0;

….

return sum;

}

(exports和module.exports区别见后)

模块标识

就是传给require()的参数。它必须符合小驼峰命名的字符串,或者以。,。。开头的相对路径,或者绝对路径。可以有或者没有文件后缀名(最好有后缀,p/17).

模块标识分类:

  • 核心模块,如http, fs, path等。
  • .或者..开始的相对路径文件模块。
  • 以/开始的绝对路径文件模块。
  • 非路径形式的文件模块,如自定义的connect模块。
Node中引入模块步骤

(找文件->找文件扩展名->编译执行)

  1. 路径分析:定位文件位置,标识符中有路径,甚至没有。
  2. 文件定位:标识符中没有文件扩展名,所以需要确定类型。
  3. 编译执行:不同文件类型,载入方式不一样。
Node中模块分类
  1. 核心模块(Node提供):核心模块在Node源代码的编译过程中,编译进了二进制文件,在Node进程启动时,部分核心模块就被加载进内存中,所以这部分核心模块引入时,文件定位和编译执行两步省略掉,且在路径分析时优先判断,所以核心模块加载速度是最快的。
  2. 文件模块(用户编写):在运行时动态加载,三步骤都需要。
    Node对引用过的模块都会进行缓存,以减少二次引入时的开销。
模块路径分析策略

即node在定位文件模块的具体文件时指定的查找策略。其策略是从当前node-modules开始一级一级向根的node-modules查找,核心模块最快,相对、绝对路径模块次之,自定义文件模块最慢。
所以引用模块时,最好加上路径以及扩展名!

文件定位策略
  • 因为模块标识符中可以不包含文件扩展名,在这种情况下,Node会按照.js, .json, .node的次序补足扩展名,依次尝试。在尝试的过程中,需要调fs模块的同步阻塞式的判断文件是否存在,所以最好传入文件扩展名。
  • require()通过分析文件扩展之后,可能没找到对应的文件,但却得到了一个目录,此时node会将目录当做一个包来出来(node对包处理会遵守CommonJS包规范)。
  • Node中,每个文件模块都是一个对象,定位到具体的文件后,Node会新建一个模块对象,然后根据路径载入并编译。不同的文件扩展名,其载入和编译的方法也不一样:
  • .js文件:通过fs模块同步读取文件后编译执行。
  • .node文件:这是C/C++编写的扩展文件,通过process.dlopen()方法加载和执行。它不需要编译,因为它是编写C/C++模块之后编译生成的. C/C++带来的优势主要是执行效率方面的。
  • .json文件:通过fs模块同步读取文件后,用JSON.parse()解析返回结果。
  • .其余扩展名文件:当做.js文件处理。
  • JS模块中require, exports, module这三个变量在模块中并没有定义,是node在编译过程中给加上的,这样每个模块中这三个变量的作用域是隔离的。
  • exports和module.exports区别:
  • exports指向module.exports的引用, require()返回的是module.exports而不是exports.如果module.exports指向了一个新对象则exports则断开了对module.exports的引用。刚开始module.exports为空对象,所以exports收集的属性和方法都赋给module.exports,而一旦module.exports有了属性,方法,则exports收集的信息将被忽略,所以在不调用module.exports时,采用exports.
  • Node的核心模块在编译成可执行文件的过程中被编译进了二进制文件。核心模块分为C/C++编写的和JS编写的两部分。而在编译所有的C/C++文件之前,编译程序需要将所有的JS模块文件编译为C/C++代码。

包和NPM是将模块联系起来的一种机制

包组织模块示意图:

包实际上是一个存档文件,即一个目录直接打包为.zip,安装解压还原为目录。

包结构:
  • package.json:包描述文件。(详细说明p/35)
  • bin:用于存放可执行二进制文件的目录。
  • Lib:用于存放js代码的目录。
  • Doc:存放文档的目录。
  • Test:存放单元测试用例的代码。

产品化

构建工具

构建工具完成的功能主要是合并静态文件,压缩文件大小,打包应用,编译模块等。Grunt是跨平台的构建工具,它通过Node写成,借助Node的跨平台能力,实现了很好的平台兼容性。
代码检测工具JSLint
可以通过gitlab等开源工具搭建了内部的代码托管平台。

Node提升性能的4个方法:
  1. 动静分离:node处理静态文件的能力不算突出。将图片,脚本,样式表和多媒体等静态文件都导入到专业的静态文件服务器(比如Nginx)上,让node只处理动态请求即可。
  2. 启用缓存Redis或Memcached.
  3. 多进程架构– cluster模块。
  4. 数据库读写分离:就任意数据库而言,读取的速度远远高于写入的速度。而某些数据库在写入时为了保证数据一致性,会进行锁表操作,这同时会影响到读取的速度。为了提升性能,通常会进行数据库的读写分离,将数据库进行主从设计。这样读数据操作不再受写入的影响了。
日志

在node中可用connect提供的日志中间件来记录访问日志,当然我们可以在Nginx反向代理里记录访问日志。
异常日志的实现:p/296-298
Node通过nodemailer模块实现邮件报警。

调试NodeJS

Node调试可用三种方法:Debugger, console.log()和Node-Inspector
Debugger:需要通过debugger;语句在代码中设置断点。使用方法p/310 - 311
Node-Insepector:可在浏览器中进行调试 或者公共idea 工具调试。

编程规范

  • 缩进:采用两个空格而不是Tab
  • 变量声明:永远用var声明变量
  • 空格:操作符前后需要加空格
  • 单双引号的使用:尽量使用单引号,这样无需转译
  • 大括号的位置:大括号{无需另起一行
  • 逗号:如果逗号不在行结尾,后面需要一个空格
  • 分号:给表达式结尾添加分号
  • 变量命名:小驼峰式
  • 方法命名:小驼峰式
  • 类命名:大驼峰式
  • 常量命名:所有字母大写,以下划线分割
  • 文件命名:采用下划线分割单词