前端面试题

Event Loop

一、进程与线程

1)运行以后的程序叫做进程
2)一个进程可以有多个线程
3)线程是最小的执行单元,进程至少由一个线程组成
4)线程与进程的调度由操作系统决定
5)单核CPU一次只能执行一个进程
6)有的内存是共享的;有的被加了互斥锁;有的只能给限定个数的线程使用(信号量)

参考资料

进程与线程的一个简单解释
进程和线程

二、浏览器与进程

以下以chrome为例

1)chrome是多进程的,每个tab标签代表一个进程。
2)进程的隔离性,可以防止一个进程挂断后,整个浏览器都挂掉。

参考资料

Chrome 为什么多进程而不是多线程?

三、Stack。栈和堆

1)数据结构(栈,后进先出)
2)代码运行方式(调用栈)
3)内存区域(stack栈,heap堆)

stack有三种不同的含义,需要根据不同的情况进行理解。这里重点记录下内存区域。

  •   系统会划分两种不同的内存空间,一种叫栈,一种叫堆。栈是有结构的,区块的大小是确定的。堆是没有结构的,数据任意存放。栈的寻址速度要快于堆。
  •   JS中原始数据类型(Undefined、Null、Boolean、Number、String)存放在栈中;引用数据类型由于占据空间大小不固定,存放在堆中,在栈中存储了指针,指针指向堆中该实体的起始地址。
  •   一般每个线程分配一个stack,每个进程分配一个heap,stack是线程独占的,heap是线程共享的。
  •   JS的方法运行完成后,方法中的局部变量会被回收,即相关栈会被清空,但是堆中的对象数据继续存在,直到系统垃圾清理机制将这块内存回收。一般内存泄露发生在堆,由于某些原因,无用的数据没有被回收。
参考资料

Stack的三种含义

四、Event Loop

1)JS是单线程的:单线程可以避免一个线程在修改DOM,另一个线程在删除这个DOM
2)Web Worker可以创建多个线程,但子线程受主线程控制且不得操作DOM
3)任务分为:同步任务与异步任务
  同步任务:在主线程上执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
  异步任务:不进入主线程进入“任务队列(task queue)”的任务,只能“任务队列”通知主线程这个任务可以执行了,该任务才会进入主线程执行。
  “任务队列”是【先进先出】的数据结构。

执行过程
  • 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  • 主线程之外,还存在一个“任务队列”(task queue)。只要异步任务有了运行结果,就在“任务队列”之中放置一个事件。
  • 一旦“执行栈”中的本轮循环中所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 主线程不断重复上面的第三步。

因为主线程从“任务队列”读取事件,这个过程是循环不断的,所以这种运行机制又称为Event Loop(事件循环)。

在这里插入图片描述
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在“任务队列”中加入了各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取“任务队列”,依次执行那些事件对应的回调函数。

定时器

setTimeout()只是将事件插入了“任务队列”,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,很可能要等很久,所以并没办法保证,回调函数一定会在setTimeout()指定的时间执行。

process.nextTick和setImmediate
  • process.nextTick在当前执行栈的尾部执行,即下一次Event Loop前执行,即它指定的任务总是发生在所有异步任务之前。
  • setImmediate和setTimout类似,在下一个Event Loop执行。
  • 多个process.nextTick语句总是在当前"执行栈"一次执行完,多个setImmediate可能则需要多次loop才能执行完。
process.nextTick(function A() {
  console.log(1);
  process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED

如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前"执行栈"执行。

setImmediate(function A() {
  console.log(1);
  setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0);

运行结果可能是1–TIMEOUT FIRED–2,也可能是TIMEOUT FIRED–1–2。

setImmediate(function A() {
  console.log(1);
  setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0);

process.nextTick(function foo() {
	console.log(10);
});

// 10
// TIMEOUT FIRED
// 1
// 2

如果在setTimeout和setImmediate中加入微任务,那么setTimeout或先于setImmediate执行。

setImmediate(function (){
  setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
  });

  setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
  }, 0);
});
// 1
// TIMEOUT FIRED
// 2

上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1–TIMEOUT FIRED–2,这时函数A一定在timeout前面触发。

node-v14.16.0 经测试,这段代码的运行结果也是不固定的可能是1–TIMEOUT FIRED–2,也可能是TIMEOUT FIRED–1–2

参考资料

JavaScript 运行机制详解:再谈Event Loop

五、宏任务与微任务

宏任务

 (macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
 浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task执行开始前,对页面进行重新渲染:
 (macro)task -> 渲染 -> (macro)task -> …
 即渲染发生在两次事件循环之间,而每次事件循环又包含执行栈的执行及执行栈从任务队列中拿去回调事件进行执行这两个过程

宏任务有哪些
  • script(整体代码)
  • setTimeout
  • setInterval
  • I/O
  • UI交互事件
  • postMessage
  • MessageChannel
  • setImmediate(Node.js 环境)
微任务

 microtask,可以理解是当前task执行结束后立即执行的任务,即任务队列里面的任务。
 在某个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。这也解释了JavaScript 运行机制详解:再谈Event Loop中写道的【“递归调用process.nextTick,将会没完没了”】。因为需要将宏任务执行期间产生的微任务都执行完,而微任务process.nextTick又产生了微任务process.nextTick,所以微任务就会一直被执行,而无法执行下一个宏任务。

微任务有哪些
  • Promise.then
  • Object.observe
  • MutationObserver
  • process.nextTick(Node.js 环境)

 微任务process.nextTick的优先级是所有微任务中最高的(也就是说,它指定的任务总是发生在所有异步任务之前【JavaScript 运行机制详解:再谈Event Loop】)。

 再者,当Promise有多个回调的时候,要先执行完所有的回调,才能执行process.nextTick。可以理解为微任务中,Promise有自己的任务队列,process.nextTick也有自己的任务队列,只有当当前的任务队列执行完后,才能执行其他的任务队列。【About Promise & process.nextTick

 任务队列里都是执行完的异步任务,不是注册异步任务到任务队列里。

运行机制

在事件循环中,每进行一次循环操作称为tick,每次tick的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值