1.javaScript是单线程,所以是同步。Node通过事件循环(event loop)模拟异步环境。
2.Node环境和浏览器环境的本质区别是二进制数据的缓存。
- 虽然Node现在可以操作ES6的ArrayBuffer和类型化数组。但是大部分很二进制有关的还是用Buffer类实现。
- buffer是Node的一个全局对象,另一个全局对象是global本身。Node还能访问另一个全局变量process——Node应用和其环境的桥梁。
1.global和process对象
1.与浏览器中的不同,Node中的全局变量只能在定义它的模块中使用。
2.process提供了Node应用和当前运行环境的信息。通过process可以操作标准I/O。
3.标准流是用于应用和环境之间沟通的通道。
4.标准流由标准输入(stdin)、标准输出(stdout)、标准错误(stderr)组成。
- 这些通道可以帮助Node应用和控制台之间通信。
- process.stdin:stdin的可读流。
- process.stdout:stdout的可写流。
- process.stderr:stderr的可写流。
5.process的I/O函数继承自EventEmitter。它可以触发事件,捕获事件和处理数据。
process.stdin.setEncoding(utf8);
//为了从process.stdin中读取数据,要设置流的编码,否则读的是二进制的buffer而不是字符串。
process.stdin.on('readable',()=>{
//监听readable事件,当有很多数据可以读取时,该事件会通知我们。
var input=process.stdin.read();//读取数据
if(input!==null){
process.stdout.write(input);//数据打印到process.stdout
var command=input.trim();
if(command=='exit'){//监听到退出字符串时
process.exit(0);//结束这个程序
}
}
})
运行这个应用,所有敲出来的内容都会被立刻输出。如果输入exit程序就会立即结束。
2.缓冲器(buffer)、类型化数组、字符串
1.当将buffer转换成类型化数组时:
- 缓冲器的内存会被复制一份,不是共享内存。
- buffer内存被解释成数组,而不是字节数组。(new Buffer([1,2,3,4])会被转换成有4个元素的类型化数组[1,2,3,4],而不是一个元素的[0x1020304])
2.Node的buffer是存储于V8堆之外的原始二进制数据。
- 一旦分配了存储空间就不能再修改空间大小。
- buffer是读写文件的默认数据类型。
3.创建新的Node buffer不会初始化其中的内容。而类型化数组会。
4.可以在填充buffer的内容时指定编码。
5.如果在创建buffer时直接向函数传入一个字节数组或另一个缓冲器或一个字符串,那么Node会复 制参数内容用来创建新的buffer。
6.buffer可以转换成JSON或字符串。
let buf=new Buffer.from('This is my pretty example');
//<Buffer 54 68 69 73 20 69 73 20 6d 79 20 70 72 65 74 74 79 20 65 78 61 6d 70 6c 65>
let json=JSON.stringify(buf);
//{"type":"Buffer","data":[84,104,105,115,32,105,115,32,109,121,32,112,114,101,116,116,121,32,101,120,97,109,112,108,101]}
let buf2=new Buffer.from(JSON.parse(json).data);
console.log(buf2.toString());//This is my pretty example
7.StringDecoder也可以将buffer中的数据转化为字符串,而且结果可逆。
- 如果遇到一个不完整的字符序列,StringDecoder会将它存到缓冲器中,直到序列变得完整再输出,而buffer.toString()则会直接输出导致乱码。
8.以指定的偏移量(offset)来对buffer读写。
let buf=Buffer.alloc(4);
buf.writeUint8(0x63,0 );
buf.writeUint8(0x61,1);
buf.writeUint8(0x74,2);
buf.writeUint8(0x73,3);
console.log(buf);//<Buffer 63 61 74 73>
console.log(buf.toString());//cats
9.字节序是指数据被存储的格式。
- 最高位被存储在最低的内存地址上,为大端格式。
- 最低位被存储在最低的内存地址上,为小端格式。
3.Node回调函数,异步事件处理
1.事件驱动架构:
- 进程被调用。应用不会等待其完成。
- 进程通过触发一个事件来表示其结束。
- 该事件被添加到队列或事件循环中。
- 所有依赖于该事件的函数都会针对它注册一个监听函数。
- 当事件被取出和处理时,依赖该事件的函数会执行,同时系统会将事件相关数据发送给这些函数。
2.异步回调函数:
- 最后一个参数是一个回调函数。
- 错误发生时,创建一个Error对象并将其作为回调函数的第一个参数。
- 如果没有错误,就调用回调函数callback(null,data)。
- 回调函数必须在process.nextTick()中调用。从而确保进程不被阻塞。
3.为了确保回调是异步的,需要在process.nextTick()函数中调用它。
- process.nextTick()可以确保在调用函数之前清除事件循环。
- 这意味着在调用阻塞功能之前,所有的同步功能都已经被处理。
4.大多数情况下,Node是单线程。
- Node的事件循环是单线程。
- 但这不意味着后台没有多个线程在工作。(C++和libuv库)
3.1 EventEmitter
1.只要看见一个对象触发事件,或者一个事件被on函数捕获,那就是EventEmitter的功能。
2.EventEmitter激活了Node中的异步事件处理。
const eventEmitter=require('events').EventEmitter
let counter=0
const em=new eventEmitter()
setInterval(()=>{
em.emit('timed',counter++)
},3000)
em.on('timed',(data)=>{
console.log('timed'+data)
})
/*每3秒输出一个timed
timed0
timed1
timed2
......
*/
3.EventEmitter的功能是继承来的,所以要用Util对象来启用继承功能。
- util.inherits(obj,EventEmitter)使一个构造函数继承另一个构造函数的原型方法。
- util.inherits()还可以直接在构造函数中访问父构造函数。
- util.inherits()还可以让让我们在任何类中继承Node的事件队列功能。
4.on是addListener的缩写。