1、node.js创建第一个应用
node.js是由以下三个部分组成:
- 引入require模块:我们可以使用require指令来载入Node.js模块;
- 创建服务器:服务器可以监听客户端的请求,类似于Apach、Nginx等http服务器;
- 接受请求和相应请求:服务器很容易创建,客户端可以使用浏览器火终端发送http请求,服务器接受请求后返回相应数据。
// 使用require来载入http模块
let http = require('http');
http.createServer((req,res) => {
// 发送HTTP头部
// HTTP状态值:200 => OK
// 内容类型:text/plain
res.writeHead(200,{'Content-type': 'text/plain'})
// 发送响应数据“Hello World”
res.end('Hellow World\n');
}).listen(3000);
// 终端打印如下信息
console.log('server running on http://localhost:3000/');
2、Node.js REPL(交互式解释器)
Node.js REPL(Read Eval Pring Loop:交互式解释器)表示一个电脑的环境,类似于Windows 系统的终端或 Unix/Linux shell,我们可以再终端中输入命令,
并接收系统的相应。
Node自带了交互式解释器,并且可以执行以下任务:
- 读取 —— 读取用户输入,解析输入的javascript数据结构并存储在内存中;
- 执行 —— 执行输入的数据结构;
- 打印 —— 输出结果;
- 循环 —— 循环操作以上步骤,知道用户两次按下ctrl-c按键结束。
3、Node.js回调函数
Node.js的异步编程直接体现就是回调。
一步编程依托于回调来实现,但不能说是使用了回调程序就异步化了。
回调函数一般作为函数的最后一个参数出现:
function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }
3.1、阻塞代码实例
创建一个input.txt,内容如下
我永远喜欢阿狸
创建main.js,代码如下:
let fs = require('fs');
let data = fs.readFileSync('../input.txt');
console.log(data.toString());
console.log('程序执行结束');
程序执行结果:
> node main.js
我永远喜欢阿狸
程序执行结束
3.2、非阻塞代码实例
创建一个input.txt,内容如下:
我永远喜欢希斯特利亚
再创建main.js,内容如下:
let fs = require('fs');
fs.readFile('../input.txt',(err,data) => {
if(err) console.log(err)
else {
console.log(data.toString());
}
})
console.log('程序结束');
结果如下:
> node main.js
程序结束
我永远喜欢希斯特利亚
因此,阻塞是按顺序执行的,非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,就需要写在回调函数里。
4、Node.js 事件循环
Node.js是单进程单线程应用程序,但是因为V8引擎提供的异步执行回调接口,通过这些接口可以处理大量的并发,所以性能非常高。
Node.js几乎每一个API都是支持回调函数的。
Node.js中几乎所有的事件机制都是使用设计模式中的观察者模式实现。
Node.js单线程类似进入一个while(true) 事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数。
4.1、事件驱动程序
Node.js使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。
当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
这个模型非常高效,可扩展性非常强,因为webserver一直接收请求而不进行任何读写操作。(这也被称作非阻塞式IO或事件驱动IO)
在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。
[外链图片转存失败(img-ue436fFh-1562161654041)(readme_files/1.jpg)]
Node.js有多个内置的事件,我们可以通过引入events模块,并通过实例化EventEmitter类来绑定和监听事件,如下实例:
// 引入events模块
let events = require('events');
// 创建eventEmitter对象
let eventEmitter = new events.EventEmitter();
// 创建事件处理器
let eventHandler = function() {
console.log('连接成功');
eventEmitter.emit('data_handler');
}
// 绑定事件及事件的处理程序
eventEmitter.on('connection',eventHandler);
eventEmitter.on('data_handler',function() {
console.log('处理数据')
})
// 触发事件
eventEmitter.emit('connection');
console.log('程序执行完毕');
4.2、Node.js应用程序是如何工作的?
在Node应用程序中,执行异步操作的函数将回调函数作为最后一个参数,回调函数接受错误对象作为第一个参数。
接下来重新看看前面的实例,创建一个input.text,内容如下:
我永远喜欢笠笠笠
然后创建main.js,内容如下:
let fs = require('fs');
fs.readFile('../input1.txt',(err,data) => {
if(err) console.log(err)
else {
console.log(data.toString());
}
})
console.log('程序结束')
文件名不对,读取中发生错误,错误err对象就会输出错误信息。
程序结束
{ [Error: ENOENT: no such file or directory, open 'C:\Users\Documents\HBuilderProjects\my_node\input1.txt']
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path:
'C:\\Users\\Documents\\HBuilderProjects\\my_node\\input1.txt' }
Node.js EventEmitter
Node.js所有的异步I/O操作在完成时都会发送一个事件到事件队列。
Node.js里的许多对象都会分发事件,:一个net.server对象在有新连接是会触发一个事件,一个fs.readStream对象在文件被打开的时候触发一个事件。所有这些产生事件的对象都是events.EventEmitter的实例。
EventEmitter类
events模块只提供了EventEmitter一个对象,EventEmitter对象的核心就是事件触发与事件监听功能的封装。
// 引入events模块
const events = require('events');
// 创建eventEmitter对象
// eventEmitter对象在实例化时发生错误时会触发error事件
// 当添加新的监听器时,newListener事件会触发
// 当监听器被移除时,removerListener事件会触发
let eventEmitter = new events.EventEmitter();
eventEmitter.on('some_event',function() {
console.log('我永远喜欢阿狸');
})
setTimeout(() => {
eventEmitter.emit('some_event');
},1000)
EventEmitter 的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持若干个事件监听器。
当事件触发时,注册到这个事件上的事件监听器被依次调用,事件参数作为回调参数传递。
// 引入EventEmitter对象
const EventEmitter = require('events').EventEmitter;
// 创建EventEmitter对象实例
let emitter = new EventEmitter();
emitter.on('some_event',function(arg1,arg2) {
console.log('some_event1',arg1,arg2);
})
emitter.on('some_event',function(arg1,arg2) {
console.log('some_event2',arg1,arg2);
})
emitter.emit('some_event','我永远喜欢','阿狸');
结果:
> node eventEmitter.js
some_event1 我永远喜欢 阿狸
some_event2 我永远喜欢 阿狸
error事件
EventEmitter 定义了一个特殊的事件error,它包含了错误的语义,我们在遇到异常的时候通常会触发error事件。
当error触发时,如果EventEmitter 没有响应的监听器,Node.js会把它当做异常,退出程序并输出错误信息。
我们一般要为触发error事件的对象绑定监听器,避免遇到错误后整个程序崩溃。
const events = require('events');
let eventEmitter = new events.EventEmitter();
eventEmitter.emit('error');
输出错误信息为:
> node eventEmitter.js
events.js:180
throw err; // Unhandled 'error' event 没有响应error事件的监听器
^
Error [ERR_UNHANDLED_ERROR]: Unhandled error.
at EventEmitter.emit (events.js:178:17)
at Object.<anonymous> (C:\Users\Documents\HBuilderProjects\my_node\js\eventEmitter.js:33:14)
at Module._compile (internal/modules/cjs/loader.js:701:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
继承EventEmitter
大多数时候我们不会直接使用EventEmitter,而是在对象中继承它。包括fs、net、http在内的,只要是支持事件响应的核心模块,都是EventEmitter的子类。
这么做的原因有两点:
- 首先,具有某个实体功能的对象实现事件符合语义,事件的监听和发生应该是一个对象的方法。
- 其次,javascript的对象机制是基于原型的,支持部分多重继承,继承EventEmitter对象不会打乱原有的继承关系。