《了不起的Node.js》读书笔记

Node.js快速高效的优点得益于:事件轮询(event loop)技术;以及其构建与V8之上(V8是Google为Chrome Web浏览器设计的JavaScript解释器和虚拟机,它运行JS非常快)。

Part 1 基础

JavaScript概览

这章总结了可能会用到的js基础知识,还蛮精要的。

  1. 基本类型:访问值,number, boolean, string, null, undefined
    复杂类型:访问引用,array, function, object
  2. 基本类型变量可以通过直接赋值创建或调用构造器创建,通过typeofinstanceof判断基本类型时,会有很大差异,所以最好始终通过直观方式定义,避免使用new构造器。
  3. 注意:在条件表达式中,这些特定值会被判定为falsenull, undefined, ' ', 0
  4. 注意:typeof不会把null识别类型为null,也不会把数组识别类型为数组,即使是通过直观方式定义的:
    1
    2
    3
    4
    typeof null == 'object'; //true
    typeof [] == 'object'; //true
    // 判断数组的hack方式 ==>
    Object.prototype.toString.call([]) == '[object Array]';

V8提供了判定是否为数组类型的方式,让我们免于使用hack(to be continued)

  1. 函数的length属性:函数声明时可接收的参数数量
  2. 用函数定义类,定义在类的prototype上的函数中的this,并不像普通函数那样指向global对象而是指向通过该类创建的实例对象。
  3. 一种比较好的继承实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //父类 Animal
    function Animal() {}
    Animal.prototype.eat = function(food) {}
    //子类 Ferret
    function Ferret() {}
    //实现继承
    Ferret.prototype = new Animal(); //这里后面有改进
    //为所有子类实例定义属性或方法
    Ferret.prototype.type = 'domestic';
    //通过prototype重写和调用父类函数
    Ferret.prototype.eat = function (food) {
    Animal.prototype.eat.call(this, food);
    //ferret特有的逻辑写在这里
    }

最大的不足:声明继承时总要初始化一个对象。两种改进方案:
(1)构造器中加判断条件

1
2
3
4
function Animal (a) {
if(false !=== a) return;
//initiate
}

(2)定义一个新的空构造器,重写其原型

1
2
3
4
5
6
function Animal () {
//constructor stuff
}
function f() {};
f.prototype = Animal.prototype;
Ferret.prototype = new f;

(3)V8提供了更简洁的方案(__proto__

  1. 如果不使用try{} catch()捕获异常,则抛出异常时代码会停止执行

V8中的JavaScript

  1. 迭代对象的键
    for...in会将通过prototype扩展的属性也迭代到
    for...in中加判断:hasOwnProperty,可避免迭代prototype扩展的属性,只获取对象的所有自有键
    Object.keys():V8用法,只获取对象的所有自有键
  2. 判断是否为数组:Array.isArray(arr);
  3. 常用数组方法:forEach, filter, map
  4. 字符串trim(),JSONJSON.stringify, JSON.parse
  5. 对函数使用.bind()改变this的指向
  6. 函数声明时最好写明函数名,有利于抛出错误时V8内部的堆栈追踪:

    1
    var a = function woot(){} //函数名为woot
  7. __proto__继承,免去中间构造器

    1
    2
    3
    function Animal () {}
    function Ferret () {}
    Ferret.prototype.__proto__ = Animal.prototype;
  8. 属性存取器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //自定义新属性
    Date.prototype.__defineGetter__('ago', function(){
    var diff = ((new Date()).getTime() - this.getTime()) / 1000, //seconds
    day_diff = Math.floor(diff / 86400); //days
    return day_diff == 0 && (
    diff < 60 && "just now" ||
    diff < 120 && "1 minute ago" ||
    diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
    diff < 7200 && "1 hour ago" ||
    diff < 86400 && Math.floor( diff / 3600 ) + " hours ago" ) ||
    day_diff == 1 && "Yesterday" ||
    day_diff < 7 && day_diff + " days ago" ||
    Math.ceil( day_diff / 7 ) + " weeks ago"
    });
    //新属性的使用
    var a = new Date('12/12/1990');
    console.log(a.ago); //1379 weeks ago

阻塞与非阻塞IO

非阻塞,事件轮询

  1. Node与PHP基础架构上的区别:Node采用一个长期运行的进程,Apache会产出多个PHP线程(每个请求一个线程),每个PHP请求线程的解释器都会运行,刷新状态。 –> 返回请求的函数中。修改状态,则PHP和Node分别运行的效果不同。
  2. 阻塞与非阻塞:PHP的sleep()是同步的,会阻塞线程的执行;Node.js使用事件轮询机制,setTimeout是异步、非阻塞的。Node仅仅是注册一个回调函数,随后不停地询问内核这些事件是否已经分发。当事件分发时,对应的回调函数被触发,如果没有事件触发,则继续执行其他代码,直到有新事件时再去执行相应回调函数。
  3. 除了setTimeouthttp, net中的IO部分也采用了事件轮询,触发文件描述符相关的消息通知。
  4. JS中的timeout并不能严格遵守时钟设置,因为回调函数可能需要执行很长时间,阻塞了事件轮询。

    单线程 却 高并发

  • 关于单线程:当V8首次调用一个函数时,创建一个调用堆栈。由于Node运行于单线程环境中,当调用堆栈展开时,Node无法处理其他客户端或HTTP请求。
  • 关于V8 + 非阻塞IO的搭配: V8执行JS非常快,同一时刻不需要处理多个请求;非阻塞IO确保了单线程执行时不会有数据库访问或者硬盘访问等操作而导致被挂起。运用非阻塞IO的例子:亚马逊云(AWS)的硬件租用,搜索文件非阻塞;从数据库中读取数据非阻塞-读取数据并不阻塞Node处理其他事情,当读取数据库完毕后会有消息通知。

    错误处理

    Node应用依托在一个拥有大量共享状态的进程中,如果某个回调函数发生错误未被捕获,整个进程都会遭殃。需要进行错误处理:
  • 可添加uncaughtException处理器

    1
    2
    3
    4
    process.on('uncaughtException', function(err){
    console.log(err);
    process.exit(1); //手动退出
    });
  • 许多像http, net这样的原生模块都会分发error事件,如果未处理,就会抛出未捕获的异常。

    1
    2
    3
    4
    5
    6
    7
    var net = require('net');
    net.createServer(function(connection){
    connection.on('error', function(err){
    //对错误对象err进行处理
    });
    }).listen(400);
  • 大部分Node异步API接收的回调函数,第一个参数都是错误对象或null

    1
    2
    3
    4
    5
    6
    var fs = require('fs');
    fs.readFile('etc/passwd', functon(err, data){
    if(err) return console.error(err); //处理错误对象
    console.log(data);
    });

堆栈追踪

  • 当错误发生时,在错误信息中可以看到一系列的函数调用。
  • 如果引入事件轮询,在setTimout函数中抛出错误,则堆栈信息会丢失很多有意义的调用信息,而是从事件轮询开始显示。
  • 无法捕获未来才会执行到的函数所抛出的错误,这会直接抛出未捕获的异常,且catch代码块永远不会执行:
    1
    2
    3
    4
    5
    try {
    setTimeout(function(){
    throw new Error('here');
    }, 10);
    } catch (e) {}

所以Node.js中每步都要正确进行错误处理。

Node中的JavaScript

1 global对象

  • global对象:全局对象,其属性可以全局访问到
  • process对象:其中包含所有全局执行上下文中的内容。window.name对应浏览器中窗口的名字,process.name对应Node中进程的名字。
  • process.nextTick的理解:通过异步的方法在最近的将来调用该函数,或视作setTimeout(fn, 1)
    1
    2
    3
    4
    5
    console.log(1);
    process.nextTick(function(){
    console.log(3);
    });
    console.log(2);

上述执行结果为1,2,3

2 模块系统

  • JS原生引入第三方模块时,第三方模块暴露一个或多个全局变量,然后引入JS全局空间。- - - Node模块系统有三个核心的全局对象:require,module, exports.
  • 绝对模块:通过npm安装的,可以在其内部node_modules目录中查找到的模块或Node内置的模块。绝对模块可以直接通过模块名字来require
  • 相对模块:定义在相对工作目录下的JS文件,需要通过相对路径require
  • 暴露API:如果模块暴露了API,则可以通过调用require将其赋值给一个变量。
    两种方式:直接将要暴露的变量和方法写作exports对象的属性和方法;直接重写module.exports对象

3 事件

  • EventEmitterAPI:提供了on, emit, removeListener方法
    可以将这个API添加到自己的类中:MyClass.prototype.__proto__ = EventEmitter.prototype;
  • 事件是Node非阻塞设计的重要体现。Node通常不会直接返回数据,否则可能在等待某个资源时发生线程阻塞,而是采用分发事件来传递数据。例如,HTTP服务器,当请求到达时,数据可能没有全部到达,Node调用回调函数,监听dataend事件。
  • a.once():某个事件可能触发多次而回调函数只执行一次

4 buffer

buffer是一个表示固定内存分配的全局对象。放到buffer中的字节数需提前确定。
处理二进制数据。eg. 可以进行数据编码转换:将base64编码表示的图片(仅用ASCII字符书写二进制数据)转为二进制PNG图片存起来:

1
2
3
var mybuffer = new Buffer('==iij2i3h1i23h', 'base64');
console.log(mybuffer);
require('fs').writeFile('logo.png', mybuffer);

Part 2 Node重要的API

用fs模块实现Node应用:file-explorer

fs模块是唯一一个同时提供同步和异步API的模块

stream

  • process全局对象中包含了三个流对象,分别对应三个UNIX标准流:
  • stdin:可读流,程序从键盘读入输入
  • stdout:可写流,程序输出数据来display
  • stderr:可写流
    流:持续不断地对数据进行读写。
    stream对象继承自EventEmitter对象。还有其他类型流:TCP套接字,HTTP请求等。

argv

process.argv:Node程序运行时的参数数组,第一个元素是node路径,第二个元素是执行的文件路径,紧接着才是命令行后紧跟的参数:
argv

工作目录

分享
0%