Buffer和浏览器的事件循环
Buffer
buffer与数据的二进制
计算机中所有内容,文字音频视频都是二进制来表示的
js很难表示二进制,可以使用node中的buffer,或者库Sharp,对buffer进行处理
buffer相当于一个存储了二进制的数组,数组中的每一项都可以保存八位二进制:00000000
八位二进制数字合在一起称作单元,称为一个字节
1byte = 8bit ,1kb = 1024byte,1m = 1024kb
buffer和字符串
一般情况下中文字符对应三个字节
1 | //创建方式1:已过期,不推荐 |
buffer和文件操作
1 | fs.readFile('./asset/xx.png',(err,data)=>{ |
sharp
可以在node中使用图片裁剪的插件
1 | const fs = require('fs'); |
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 | const fs = require('fs') |
写
1 | const fs = require('fs') |
pipe方法
1 | fs.readFile('',(err,data)=>{ |
loading……