Buffer和浏览器的事件循环

Buffer和浏览器的事件循环

Buffer

buffer与数据的二进制

计算机中所有内容,文字音频视频都是二进制来表示的

js很难表示二进制,可以使用node中的buffer,或者库Sharp,对buffer进行处理

buffer相当于一个存储了二进制的数组,数组中的每一项都可以保存八位二进制:00000000

八位二进制数字合在一起称作单元,称为一个字节

1byte = 8bit ,1kb = 1024byte,1m = 1024kb

buffer和字符串

一般情况下中文字符对应三个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建方式1:已过期,不推荐
const buffer = new Buffer('cabbage')
console.log(buffer) //

//创建方式2
const buffer = Buffer.from('cabbage','utf-8') //默认utf-8
console.log(buffer)

//解码
consoe.log(buffer.toString('utf8'))//解码时需要传递与编码相同的类型

const buffer = Buffer.alloc(8) // 创建了一个八位
buffer[0] = 88

buffer和文件操作

1
2
3
4
5
6
fs.readFile('./asset/xx.png',(err,data)=>{
// data是buffer格式
fs.writeFile('./asset/yyy.png',data,(err,data)=>{

})
})

sharp

可以在node中使用图片裁剪的插件

1
2
3
4
5
6
7
8
9
const fs = require('fs');
const sharp = require('sharp');

sharp('./foo.png')
.resize(300, 300)
.toBuffer()
.then(data => {
fs.writeFile('./bax.png', data, err => console.log(err));
})

buffer的创建过程

创建buffer时,不会频繁申请内存空间,默认先申请一个 8 * 1024个字节大小的空间,也就是8kb

事件循环和异步IO

什么是事件循环?

浏览器事件循环

1:JavaScript是一种单线程的编程语言,同一时间只能做一件事,所有任务都需要排队依次完成,事件循环分为两种,分别是浏览器事件循环和node.js事件循环,JavaScript是一门单线程语言,指主线程只有一个。Event Loop事件循环,其实就是JS引擎管理事件执行的一个流程,具体由运行环境确定。目前JS的主要运行环境有两个,浏览器和Node.js。事件循环机制告诉了我们JS代码的执行顺序,是指浏览器或Node的一种解决JS单线程运行时不会阻塞的一种机制。

2、执行过程
所有同步任务都在主线程上执行,形成一个执行栈(调用栈);
主线程之外,还存在一个‘任务队列’(task queue),浏览器中的各种 Web API 为异步的代码提供了一个单独的运行空间,当异步的代码运行完毕以后,会将代码中的回调送入到 任务队列中(队列遵循先进先出得原则)
一旦主线程的栈中的所有同步任务执行完毕后,调用栈为空时系统就会将队列中的回调函数依次压入调用栈中执行,当调用栈为空时,仍然会不断循环检测任务队列中是否有代码需要执行;
3、执行顺序
先执行同步代码,
遇到异步宏任务则将异步宏任务放入宏任务队列中,
遇到异步微任务则将异步微任务放入微任务队列中,
当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,
微任务执行完毕后再将异步宏任务从队列中调入主线程执行,
一直循环直至所有任务执行完毕。
注意:当宏任务和微任务都处于 任务队列(Task Queue) 中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务;

进程

process计算机已经运行的程序

启动一个应用程序,就会默认启动一个进程,也可能是多个

线程

thread操作系统能够运行运算调度的最小单位

每一个进程都会启动一个线程来执行程序的代码,这个线程被称为主线程,进程是线程的容器

操作系统是工厂,车间是进程,车间里的工人是线程

多进程多线程开发

现代操作系统可以做到多进程多线程,得益于CPU运算速度的提升,快速切换多个进程

js的线程容器是浏览器和node

js是单线程的,意味着同一时间只能做一件事,因此后续的线程会被前面的阻塞

宏任务和微任务

宏任务队列:ajax,定时器,DOM监听,UI Rendering

微任务队列:Promise.then,Mutation Observer API,queueMicrotask(()=>{}) //自定义微任务

优先级:

执行栈代码,同步任务优先执行,在执行任何一个宏任务之前,都会查看是否有微任务,执行完微任务,保证微任务队列是空的以后,再执行一个宏任务,执行完后再检查有没有微任务,然后再执行宏任务队列的下一个宏任务

async和await是promise的语法糖,await后面执行的代码相当于(resolvemreject)=>{}

await的下一句相当于then,放入微任务队列

node事件循环

LIBUV专注异步IO的库,为node开发

WORKER THREADS线程池

阻塞IO和非阻塞IO

如果希望对一个文件进行操作,呢么需要通过文件描述符打开这个文件

js实际上不能对文件进行操作,任何程序的文件操作都是通过系统1调用(操作系统的文件系统)

实际上对文件的操作,是一个操作系统的系统调用(IO系统,IO是输入、输出)

操作系统提供了两种调用文件系统的方式:阻塞式调用和非阻塞式调用

阻塞式调用:调用结果返回前,当前线程处于阻塞状态,(阻塞状态CPU是不会分配时间片的),调用线程只有在调用结果以后才会继续执行

非阻塞式调用:调用执行以后,当前线程不会停止执行,会立即返回一个记过然后执行后续代码。只需要过一段时间来检查有没有结果返回,不停判断,轮询,轮询十分消耗性能

采用了非阻塞式的操作

socket通信,文件读写的IO操作

libuv提供了一个线程池,thread pool

线程池会负责所有相关操作,并且通过轮询或者其他方式等待结果

当获取结果以后,就可以将对应回调放到事件循环(某一个事件队列)中

事件循环就可以负责接管后续工作,告知js应用程序执行对应回调函数

阻塞和非阻塞,同步和异步的区别

阻塞和被阻塞是针对被调用者来说的

同步和异步是针对调用者来说的

事件循环

无论是文件IO,数据库IO,网咯IO,定时器,子进程,在完成对应操作以后,都会将结果和回调函数放到事件循环(任务队列)中

事件循环会不断的从任务队列中取出对应事件(回调函数)执行

node中一次完整的事件循环Tick分为很多阶段

定时器(Timers):本阶段已执行定时器回调函数

待定回调(Pending Callback):对某些系统操作,如TCP错误类型执行回调,比如TCP连接时收到ECONNREFUSED

idle,prepare:仅系统内部使用

轮询(Poll):检索新的IO事件,执行与IO相关的回调

检测:setImmediate()回调函数在这里执行1

关闭的回调函数:一些关闭的回调函数,如socket.on(‘close’,()=>{})

node的微任务和宏任务

微任务:promise.then,process.nextTick,queueMicrotask

宏任务:定时器,IO事件,seiImmediate,close事件

执行一次循环就是一次tick

node的事件循环比浏览器事件循环要复杂些

1

setImediata相比于定时器偶尔会早执行,和双方推入任务队列的顺序无关

涉及到了libuv底层的机制

额外知识:发布nom包

首先在npmjs.com注册账号

命令行执行npm login

输入账号和密码,密码是不可见的

npm publish

Stream/流

当我们从一个文件中读取数据时,文件的二进制(字节)数据会源源不断的被读取到我们程序中

而这一连串的字节就是流

流式连续字节的一种表现形式和抽象概念

流式可读可写的

文件读写时可以使用readFile和writeFile方式读写文件,那么为什么需要流呢

直接读写文件的方式,无法控制细节操作

比如从什么位置开始读?读到什么位置,一次性读取多少个字节

读到某个位置后,暂停读取,某个时刻恢复读取等

或者这个文件非常大,视频文件之类,一次性全部读取不合适

所有的流都是eventEmitter的实例

node中有四种基本流类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const fs = require('fs')

const reader = fs.createReadStream('./foo.txt',{
flag
encoding
fd文件描述符
mode文件权限
start开始读取位置
end结尾位置
highWaterMark每次读多少
... //很多属性看官网
})

//数据读取过程
reader.on('data',(data)=>{
console.log(data)
reader.pause() //暂停读取
setTimeout(()=>{
reader.resume() //恢复读取
},1000)
})

reader.on('open',()=>{
conosle.log('文件被打开')
})

reader.on('close',()=>{
conosle.log('文件被关闭')
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const fs = require('fs')

const writer = fs.createWriteStream('./bar,txt',{
flags:'a',
start
... //很多属性看官网
})

写入
writer.write('写入的数据'//可以是buffer,(err)=>{
if(err){
console.log(err)
return
}
console.log('写入成功')
})

writer.close() //关闭,但是真实开发很少
一般使用writer.end('1'//写入最后东西,然后关闭)

写入是可以一直写入writer.writer......
writer.on('close',()=>{
console.log('文件被关闭')
})

pipe方法

1
2
3
4
5
6
7
8
9
10
11
fs.readFile('',(err,data)=>{
fs,writeFile('./',data,()=>{

})
})

stream通过pipe的读写
const reader = fs.createReadStream('./')
const writer = fs.createWriteStream('./')
reader.pipe(writer)
writer.close()

loading……