手记

Nodejs提炼与升华(四) Nodejs核心模块 PART2

4.2 常用工具util

util是Nodejs核心模块,提供常用函数的集合,用于弥补JS的功能过于精简的不足。

使用util模块首先需要导入它

var util = require('util');

4.2.1 util.inherits(constructor,superConstructor)

util.inherits(constructor,superConstructor)是一个实现对象间原形继承的函数。用法如下:

var util = require('util');

function Animal() {
    this.nickName = "ANIMAL";
   this.sayHello = function () {
    console.log("hello, We are " + this.nickName);
}
}

Animal.prototype.sayName = function () {
    console.log("We are " + this.nickName);
};

function Dog () {
    this.nickName = "DOG"
}

var animal = new Animal();
animal.sayName();
animal.sayHello();
console.log(animal);

util.inherits(Dog,Animal);

var dog = new Dog();
dog.sayName();
console.log(dog);
// dog.sayHello();

在Animal类的构造函数中,有一个叫做nickName的属性和一个叫做sayHello的方法。同时,我们使用prototype为Animal类定义了一个叫做sayName的方法。运行程序,输出:

We are ANIMAL
hello, We are ANIMAL
Animal { nickName: 'ANIMAL', sayHello: [Function] }
We are DOG
Dog { nickName: 'DOG' }

通过打印Dog的构造器可见,使用inherits方法可使子类继承父类原型中的方法,即sayName方法,但不可继承在父类构造器中定义的东西,即nickName和sayHello。最后一行注释掉的sayHello如果打开,程序会报错。注意,第一个参数是子类,第二个参数是超类(即父类),不要写反了。

4.2.2 util.inspect(obj)

util.inpect(obj,[showHidden], [depth], [color])可以将任意对象转化成字符串(这种方式并非简单的直接把对象转化成字串,所以不会理会里面的toString方法)。

  1. obj即你要转化成字符串的对象
  2. showHidden是一个可选参数,默认是fasle,如果值为true,将会输出更多隐藏信息。(主要是输出对象成员方法的详细信息,比如参数信息等等,但对对象的属性作用不大)
  3. depth是一个可选参数,表示遍历的层数,默认2层(也就是能够详细的遍历打印出成员属性的属性的属性的值),如果设置为null则将不限递归层数完整遍历对象。
  4. color是一个可选参数,如果设置为true,终端则会使用ANSI颜色编码使效果更佳。

下面的代码:

var util = require('util');

var Tom = {
    name: "Tom",
    sayName:function () {
    console.log(this.name);
    },
    age:15,
    family: {
        parent: {father: "Old Tom", mother: "MS.Tom"},
    pet: {
        dog: {"husky": "dingDing"}
    }
    }
};

console.log(util.inspect(Tom));
console.log(util.inspect(Tom,true));
console.log(util.inspect(Tom,false,null));
console.log(util.inspect(Tom,false,null,true));

运行结果为:

{ name: 'Tom',
  sayName: [Function],
  age: 15,
  family: 
   { parent: { father: 'Old Tom', mother: 'MS.Tom' },
 pet: { dog: [Object] } } }
{ name: 'Tom',
  sayName: 
   { [Function]
 [length]: 0,
 [name]: '',
 [arguments]: null,
 [caller]: null,
 [prototype]: { [constructor]: [Circular] } },
  age: 15,
  family: 
   { parent: { father: 'Old Tom', mother: 'MS.Tom' },
 pet: { dog: [Object] } } }
{ name: 'Tom',
  sayName: [Function],
  age: 15,
  family: 
   { parent: { father: 'Old Tom', mother: 'MS.Tom' },
 pet: { dog: { husky: 'dingDing' } } } }
{ name: 'Tom',
  sayName: [Function],
  age: 15,
  family: 
   { parent: { father: 'Old Tom', mother: 'MS.Tom' },
 pet: { dog: { husky: 'dingDing' } } } }

注:最后的代码函数、属性的值等是彩色的,在md里显示不出来。

util.inspect与JSON.stingify的区别在于inspect显得更加细致一些,并且会有格式缩进。后者直接循环遍历所有层,没有多余的设置,没有缩进,整个字串都出现在一行

还有一点需要说明,因为这个函数参数比较多,如果你想设置后面的参数,前面的参数必须要设置,可以设置成和默认值相同。

4.2.3 util.isXXX()

util对象还定义了很多判断数据类型的函数,都是是某一类型放回true,否则返回false,这里就不做示范了。

但要注意,在使用util.isDate()函数时,如果这样

util.isDate(Date());

会返回false,因为Date()产生的被认为是一个字串。需要在前面加上new,才被认为是一个日期对象。

4.3 事件驱动

events是nodejs最重要的模块,没有之一。原因是Nodejs本身的架构是事件式的,而它提供了唯一的接口,所以堪称Nodejs事件编程的基础。events模块不仅用于代码与Nodejs下层事件循环的交互,还几乎被所有模块依赖

在使用events模块时,首先需要导入它,实际上,这个模块暴露的是EventEmitter构造函数(是使用第一种模块导出模型暴露的,也就是exports.XXX = XXX)。绑定事件和发射事件都是在这个构造函数的实例化对象上进行的。

var events = require('events');
var EventEmitter = events.EventEmitter;
var event = new EventEmitter();

4.3.1 事件发射器

4.3.1.1 事件发射了!!

events模块只暴露了一个构造函数:events.EventEmitter(),它的核心就是事件发射(触发)和事件监听功能的封装。

下面的代码将会帮助你理解events模块最简单的用法——注册事件和发射事件:

var events = require('events')
var EventEmitter =events.EventEmitter;
var event = new EventEmitter();

// 注册一个最简单的事件监听器
event.on("sayHello",function (arg){
    console.log("hello " + arg);
});

event.emit("sayHello", "world");

运行上面的代码,输出hello world。

原理是在event注册了一个“sayHello”事件的监听器,然后我们通过emit发射sayHello事件,触发该监听器,执行里面设置的回调函数。下面我们将会介绍EventEmitter常用的API(下面代码中的event是EventEmitter的实例化对象)

4.3.1.2 注册事件

(1)最简单的注册事件的方式如下:

event.on(事件名称字符串,函数)  或  event.addListener(事件名称字符串,函数)

(2)你还可以once方法设置单次监听,这种监听器最多只会触发一次,触发后该监听器会立即失效。

event.once(事件名称字符串,函数)
4.3.1.3 发射事件:
event.emit(事件名称字符串,[监听器回调函数参数1], [监听器回调函数参数2] ……)

值得注意的是,EventEmitter支持为每一个事件(名字相同,我们就认为是一个事件)注册若干个事件监听器。在默认状态下,每个事件最多支持10个事件监听器来“监视”它,超过仍可执行,但会抛出警告。可以通过event.setMaxListeners(n)可以设置最多绑定事件监听器的上限,但建议不要超过十个,否则有可能会导致内存泄露。当事件发射时,注册到这个事件的监听器依次被调用。

另外说一句,如果你熟悉了在浏览器上点击某个按钮触发某个事件的机制,那么你可能不得不花一些时间去适应node的这种用代码发射事件的机制了。

4.3.1.4 解除事件绑定

当解除事件绑定时,我们面对有两种情况:一种是解除某一个事件的某一个监听器;第二种是完全删除某一个事件的所有监听器。

(1)我们可以先从简单的完全删除某一个事件的所有监听器入手:

event.removeAllListener("事件名")

(2)如果你想要删除某一个事件的某一个监听器,按照正常的逻辑,需要两个参数:一个是事件的名称,第二个是监听器名称。第一个参数好说,但有没有搞错,我们根本不知道监听器叫什么好不好。第二个参数应该选择什么呢?Nodejs是这样设计的:第二个参数填监听器回调函数的名称。所以,如果你考虑到以后有可能存在删除某一事件的某一监听器的时候,在注册监听器的时候,回调函数就不要使用匿名函数,而采取命名函数的方式,下面的代码是一个简单的例子:

var EventEmitter = require("events").EventEmitter;

var event = new EventEmitter();

event.on("sayHello", sayHello);

function sayHello() {
    console.log("hello");
}

console.log(EventEmitter.listenerCount(event, "sayHello")); // =>1

event.removeListener("sayHello", sayHello);

console.log(EventEmitter.listenerCount(event, "sayHello")); // =>0

这里我们“顺便”隆重介绍一个EventEmitter的类方法:

EventEmitter.listenerCount(event, "事件名")

这个方法返回你在某一个EventEmitter实例上注册的某一个事件的监听器数量。它接收两个参数,第一个是EventEmitter的实例化对象,也就是你注册事件监听器的对象。第二个参数是事件名。

4.3.2 error事件

EventEmitter 定义了一个特殊的事件 error,它包含了错误的语义,我们在遇到异常的时候通常会触发 error 事件(是我们手动通过代码去触发,而不是程序遇到异常就触发error事件!)。当 error 被触发时,EventEmitter规定如果没有响应的监听器,Node.js 会把它当作异常,退出程序并输出错误信息。我们一般要为会触发 error 事件的对象设置监听器,避免遇到错误后整个程序崩溃。例如:

var events = require('events'); 
var emitter = new events.EventEmitter(); 
emitter.emit('error'); 

会提出程序,并打印调用栈。

但注意上面加粗的文字,下面的代码照样会报错,起不到我们想像的作用:

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

event.on("sayHello", function () {
    d===d // 错误的代码
});

event.on("error", function () {
    console.log("错误已处理");
});

event.emit("sayHello");

4.3.3 继承EventEmitter

大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter的子类。

为什么要这样做呢?原因有两点:
首先,具有某个实体功能的对象实现事件符合语义, 事件的监听和发射应该是一个对象的方法。

其次 JavaScript 的对象机制是基于原型的,支持部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。

3人推荐
随时随地看视频
慕课网APP