Skip to content

浏览器事件环

浏览器渲染原理与性能优化

参考链接:https://segmentfault.com/a/1190000012925872

区分进程和线程

应用 -》 进程 -》 线程

  • 一个应用可以有多个进程,一个进程可以有多个线程
  • 单线程与多线程,都是指在一个进程内的单和多
  • 进程是操作系统资源分配的基本单位,进程中包含线程。
  • 线程是由进程所管理的。为了提升浏览器的稳定性和安全性,浏览器采用了多进程模型。 官方一点解释:
  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程

浏览器是多进程的

浏览器都包含哪些进程?

浏览器中的(5个主要)进程

  • Browser进程:浏览器的主进程(负责协调、主控),只有一个。负责界面显示、用户交互、子进程管理,提供存储等。
  • 渲染进程(浏览器内核,Renderer进程):每个页卡都有单独的渲染进程,核心用于渲染页面,脚本执行,事件处理(内部是多线程的)
  • 网络进程:主要处理网络资源加载(HTML、CSS,、JS等)
  • GPU进程:3d绘制,提高性能
  • 插件进程: chrome中安装的一些插件

    每打开一个Tab页,就相当于创建了一个独立的浏览器进程。

浏览器多进程的优势

  • 多进程充分利用多核优势
  • 为了提升浏览器的稳定性和安全性,如果一个页卡奔溃了,不会影响到其他页卡,而且两个页卡之间不会互相影响

浏览器内核(渲染进程)

渲染进程包括哪些线程

  • GUI渲染线程
    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
    • 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
  • JS引擎线程
    • 也称为JS内核,负责解析Javascript脚本,运行代码(例如V8引擎)
    • 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
  • 事件触发线程
    • 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
    • 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添 加到事件线程中
    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
    • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
  • 定时触发器线程
    • setInterval与setTimeout所在线程
    • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
    • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
    • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
  • 异步http请求线程
    • 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
    • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

1、Browser进程和浏览器内核(Renderer进程)的通信过程

例子:

js
// 执行栈执行完之后再执行其他任务
setTimeout(() => {
    //宏任务
    console.log('ok1')
}, 1000)
setTimeout(() => {
    //宏任务
    console.log('ok2')
}, 0)
Promise.resolve().then((resolve, reject) => {
    //微任务
    console.log('then')
})

function a() {
    console.log('a')
    function b() {
        console.log('b')
        function c() {
            console.log('c')
        }
        c()
    }
    b()
}
a()

// a
// b
// c
// then
// ok2
// ok1

// 宏任务 [ok1,ok2]
//宏任务 script脚本 =》清空所有微任务 =》取出一个宏任务
//宏任务要求时间到了才会执行

微任务和GUI渲染

js
document.body.style.background = 'red'
console.log(1)
Promise.resolve().then(() => {
    console.log(2)
    document.body.style.background = 'yellow'
})
console.log(3)
//1 3 2
//微任务执行完毕之后才会取渲染页面,所以直接是'yellow'
js
document.body.style.background = 'red'
console.log(1)
setTimeout(() => {
    console.log(2)
    document.body.style.background = 'yellow'
}, 1000)
console.log(3)
//1 3 2 红变黄   渲染了两次,第一次走执行栈,页面为红,第二次取出一个宏任务,会变黄

2、事件任务

<body>
    <button id="button">点我</button>
</body>
<script>
    button.addEventListener('click',()=>{
        console.log('listener1');
        Promise.resolve().then(()=>console.log('micro task1'))
    })
    button.addEventListener('click',()=>{
        console.log('listener2');
        Promise.resolve().then(()=>console.log('micro task2'))
    })
    button.click(); // click1() click2()
    //button.click() listener1 listener2  micro task1  micro task2  立即执行,而不是一个个执行,不点就不会产生一个⌚事件线程,就同步了

    //宏任务是一个个执行
    //如果手动去点,则是一个个去执行,会产生事件线程  listener1    micro task1   listener2 micro task2
</script>

3、定时器任务

js
Promise.resolve().then(() => {
    console.log('Promise1')
    setTimeout(() => {
        console.log('setTimeout2')
    }, 0)
})
setTimeout(() => {
    console.log('setTimeout1')
    Promise.resolve().then(() => {
        console.log('Promise2')
    })
}, 0)
/*
    主栈代码:
    微任务  [promise1] 
    宏任务 [setTimeout1]

    1、执行第一个promise,则打印Promise1,setTimeout2进入宏任务队列
    此时微任务 [] 
    此时 宏任务 [setTimeout1,setTimeout2]
    2、取出一个宏任务执行,则打印setTimeout1,
    此时微任务队列:[promise2] 
    宏任务执行完毕之后,清空对应的微任务则打印Promise2
    3、此时任务队列中只有一个宏任务[setTimeout2]
    取出一个宏任务执行,则打印setTimeout2,
    所以执行结果是Promise1,setTimeout1,Promise2,setTimeout2
   */

4、任务执行面试题

js
// console.log(1);
async function async() {
    console.log(2)
    await console.log(3) //相当于Promise.resolve().then(()=>{console.log('4')})
    console.log(4)
}
setTimeout(() => {
    console.log(5)
}, 0)
const promise = new Promise((resolve, reject) => {
    console.log(6)
    resolve(7)
})
promise.then((res) => {
    console.log(res)
})
async() //立即执行
console.log(8)

//new Promise 会立即执行
//await console.log(3); await后面紧跟着的代码是同步代码,但是接下来的是微任务,相当于.then

主栈执行顺序,以及最终执行结果

js
1
//宏任务 [setTimeout5]
6
//微任务[then]
2 3
//微任务[then,4]
8
// 主栈执行完之后,清空微任务队列
7  4
//执行宏任务
5
//执行后的结果:1 6 2 3 8 7 4 5

5、MessageChannel 宏任务

js
let channel = new MessageChannel()
channel.port1.postMessage('ok')
channel.port2.onmessage = function (e) {
    console.log(e.data)
}
Promise.resolve().then((data) => {
    console.log(data)
})

//undefined
//ok
// 先走微任务再走宏任务

Released under the MIT License.