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方法)。
- obj即你要转化成字符串的对象
- showHidden是一个可选参数,默认是fasle,如果值为true,将会输出更多隐藏信息。(主要是输出对象成员方法的详细信息,比如参数信息等等,但对对象的属性作用不大)
- depth是一个可选参数,表示遍历的层数,默认2层(也就是能够详细的遍历打印出成员属性的属性的属性的值),如果设置为null则将不限递归层数完整遍历对象。
- 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 不会打乱对象原有的继承关系。