浏览器相关知识点

588 阅读6分钟

一、事件循环

事实上,事件循环可以理解成我们编写的js和浏览器或者Node之间的一个桥梁

浏览器的事件循环是一个我们编写的Js代码和浏览器API调用(setTimeout/AJAX/监听事件等)的一个桥梁

桥梁之间他们通过回调函数进行沟通

Node的事件循环是一个我们编写的js代码和系统调用(file system /netWork等 )之间的一个桥梁

桥梁之间他们通过回调函数进行沟通

二、进程和线程

线程和进程是操作系统中的两个概念:

进程(process):计算机已经运行的程序

线程(thread):操作系统能够运行调度的最小单位

直观解释:

进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程)

线程:每一个进程中,都会启动一个线程用来执行程序中的代码,这个线程被称之为主线程

所以我们也可以说进程是线程的容器

例子解释:操作系统类似于一个工厂,工厂中里有很多车间,这个车间就是进程,每个车间可能有一个以上的工人在工厂,这个工厂就是线程

三、多进程多线程开发

cpu的运行速度非常快,它可以快速的在多个进程之间迅速的切换,当我们的进程中的线程获取到时间片时,就可以快速执行我们编写的代码,对于用户来说是感受不到这种快速的切换的

但是js的代码执行是在一个单独的进程中进行的,不能进行耗时的操作,不然会进行阻塞

那么传入的一个定时器函数,会在什么时候被执行呢?

事实上,setTimeout是调用了web api ,在合适的时机,会将timer函数加入到一个时间队列中,

事件队列中的函数,会被放入到调用栈中,在调用栈中被执行

四、浏览器的事件循环

每一个回调函数就是一个任务(macrotask 宏任务 macrotask queue 宏任务队列 )

Promise().then((data)=>{}) 里面的data就是微任务(microtask) microqueue微任务队列

五、宏任务和微任务

\

但是事件循环中并非只维护这一个队列,事实上是有两个队列:

宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听等

微任务队列 (microtask queue) :Pormise的then回调、Mutation Observer Api等

浏览器里面优先执行微任务队列(将微任务队列全部执行完之后),再执行宏任务队列(在执行任何一个宏任务之前,都会优先查看微任务队列是否有任务需要执行,也就是宏任务执行之前,必须保证微任务队列是空的,如果不为空,则优先执行微任务队列中的任务)

注意:在promise函数里面包裹的函数会被当作main script直接执行 不用放进到微任务队列里面,即await关键字后的函数

async、await是promise的一个语法塘

(1)我们可以将await关键字后面执行的代码,看作是包裹在(resolve、reject)=>(函数执行)中的代码;

(2)await的下一个语句,可以看作是then(res=>(函数执行))中的代码

六、Node的架构分析

浏览器中的EventLoop是根据H5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node是由libuv实现的。

libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,最主要是为了维护EVENT Loop

七、阻塞IO和非阻塞IO

如果我们希望在程序中对一个文件进行操作,那么我们就需要打开这个文件:通过文件描述符

事实上对文件的操作,是一个操作系统的系统调用(IO系统)

操作系统通常为我们提供了两种调用方式:阻塞式调用和非阻塞式调用

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

非阻塞式调用:调用执行之后,当前线程不会停止执行,只需要过一段时间来检查一个有没有结果返回即可

所以我们开发中的很多耗时操作,都是可以基于这样的 非阻塞式调用:

比如网络请求本身使用Socket通信,而Socket本身提供了select模型,也可以进行非阻塞方式的工作

比如文件读写的IO读写,我们可以使用操作系统提供的基于时间的回调机制

非阻塞IO的问题:我们并没有获取到需要读取(我们以读取为例)的结果

那么就意味着为了可以知道是否读取到了完整的数据,我们需要频繁的去确定读取到的数据是否是完整的

这个过程我们称之为轮训操作

libuv提供了一个线程池(Thread Pool)

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

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

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

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

阻塞和非阻塞是对于被调用者来说的,在我们这里就是系统调用,操作系统为我们提供了阻塞调用和非阻塞调用

同步和异步是对于调用者来说的,在我们这里就是自动的程序

八、Node事件循环的阶段

但是一次完整的事件循环Tick分成很多个阶段:

定时器(Timers):本阶段执行已经被setTimeout()和setInterval()的调度回调函数

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

idle,prepare:仅系统内部使用

轮询(Poll) 检索新的I/O事件;执行与I/O相关的回调

检测(check)SetImmediate()回调函数在这里执行

关闭的回调函数:一些关闭的回调函数,如:socket.on('close,...)

九、Node的微任务和宏任务

我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务

Timeout、setInterval、IO事件、setImmediate、close事件;

微任务(microtask):Promise的then回调、process.nextTick、queueMicrotask;