浏览器运行 JS 的顺序,你真的掌握吗?

本文深入探讨了浏览器中的进程与线程概念,重点解析了渲染进程中的JavaScript单线程执行机制,包括宏任务与微任务的事件循环过程。通过实例解释了JavaScript如何处理异步任务,如AJAX请求,以及任务队列和事件表的角色。了解这些机制有助于优化前端性能和解决代码执行问题。
摘要由CSDN通过智能技术生成

前言

不知道你有没有遇到过类似这样的问题,某些代码乱序执行或样式的更改后不生效?你是不是曾经把代码包在 setTimeout 里面来解决类似的问题?是不是这种方式不太可靠?然后你就不断调试 timeout 值以至于看起来好像没问题了?接下来我们将一起来看一下这其中到底发生了什么。

进程和线程

我们先来区分一下进程和线程

  • 进程是 cpu 资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是 cpu 调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

通俗地讲:进程就像是一个有边界的生产厂间,而线程就像是厂间内的一个个员工,可以自己做自己的事情,也可以相互配合做同一件事情,所以一个进程可以创建多个线程

理解了进程与线程了区别后,接下来对浏览器进行一定程度上的认识

浏览器的多进程

它主要包括以下进程:

  • 浏览器是多进程的
  • 浏览器之所以能够运行,是因为系统给它的进程分配了资源(CPU、内存)
  • 简单点理解,每打开一个 Tab 页,就相当于创建了一个独立的浏览器进程。

渲染进程(浏览器内核)

对于普通的前端操作来说,最重要的是渲染进程,页面的渲染,JS 的执行,事件的循环,都在这个进程内进行。接下来我们重点分析这个进程。注意:浏览器的渲染进程是多线程的。 接下来看看它都包含了哪些线程(列举一些主要常驻线程):

单线程的 JS

JavaScript 单线程指的是浏览器中负责解释和执行 JavaScript 代码的只有一个主线程,即为 JS 引擎线程,每次只能做一件事。

我们知道一个 AJAX 请求,主线程在等待它响应的同时是会去做其它事的,浏览器先在事件表注册 AJAX 的回调函数,响应回来后回调函数被添加到任务队列中等待执行,不会造成线程阻塞,所以说 JS 处理 AJAX 请求的方式是异步的。

JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,其主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。假设同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,那就乱套了。 我们可以来看下面的例子:

function foo() {
    bar()
    console.log('foo')
}

function bar() {
    baz()
    console.log('bar')
}

function baz() {
    console.log('baz')
}

foo()

// baz
// bar
// foo
function foo() {
    console.log('foo');
}

setTimeout(() => {
    console.log('setTimeout');
}, 0);

foo();

// foo
// setTimeout: 0s

任务队列

任务队列是指一个事件的队列(消息队列),先进先出的数据结构,排在前面的事件,优先被主线程读取。只要执行栈上任务一清空,就会被主线程读取,任务队列上首位的事件就自动进入主线程。

任务又分成两种,一种是同步任务,另一种是异步任务。

  • 同步任务,在主线程形成一个执行栈,排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  • 异步任务,不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

另外事件循环中的异步任务队列有两种:macroTask(宏任务)队列和 microTask(微任务)队列。

宏任务队列

  • DOM 事件
  • XHR
  • 定时器(setTimeout / setInterval / setImmediate)

可以通过 setTimeout(func) 即可将 func 添加到宏任务队列中(使用场景:将计算耗时长的任务切分成小块,以便于浏览器有空处理用户事件,以及显示耗时进度)

微任务队列

  • Promise 事件
  • MutationObserver
  • process.nextTick 事件(Node.js)

可以通过 queueMicrotask(func) 将 func 添加到微任务队列中。

事件循环

JS 主线程循环往复地从任务队列(callback queue / task queue)中读取任务,执行任务,其中运行机制称为事件循环(event loop)。

在事件表 Web APIs 中会注册各类事件线程处理各种事件,然后将处理好的回调事件放入对应的任务队列(宏任务、微任务)中。如果执行栈里的任务执行完成,即执行栈为空的时候(JS 引擎线程空闲),事件触发线程才会从任务队列取出一个任务(即异步的回调函数)放入执行栈中执行。具体的流程可以参考下图:

事件循环.png

运行机制

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后 GUI 线程接管渲染
  • 渲染完毕后,JS 线程继续接管,开始下一个宏任务(从事件队列中获取)

事件循环流程图.png

 

总结

以上详细讲解了浏览器器运行JS的顺序,学会之后无论对你今后工作上还是面试上都会有所帮助。

链接:https://pan.baidu.com/s/1_4PIUb-Yl68aTW9Bw95iJA 
提取码:tnav 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值