Skip to content

Latest commit

 

History

History
129 lines (115 loc) · 6.67 KB

浏览器原理.md

File metadata and controls

129 lines (115 loc) · 6.67 KB

从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理

进程和线程

进程相当于一个工厂,线程相当于工厂里的工人 工厂之间相互独立,工厂内有一个或多个工人 工人之间共享空间,多个工人协作完成任务

  • 进程之间相互独立,拥有系统分配的独立的内存
  • 一个进程由一个或者多个线程组成,多个线程在进程中协作完成任务
  • 同一进程下的各个线程之间共享程序的内存空间(包括代码段、线程集、堆)

浏览器是多进程的

  • 浏览器是多进程的
  • 每打开一个tab页,就相当于创建了一个独立的浏览器进程
  1. Browser进程(浏览器主进程):
  • 负责界面显示,用户交互,如前进、后退等等
  • 负责页面管理、创建和销毁其他进程
  • 将render进程得到的bitmap绘制到用户界面上
  • 网络资源的管理、下载等
  1. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时,才创建
  2. GPU进程:用于3D绘制
  3. 浏览器渲染进程(浏览器内核)(render进程):默认每个tab一个,互不影响。页面渲染、脚本执行、事件处理等

浏览器内核(渲染进程)

  1. GUI渲染线程 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
  2. JS引擎线程 负责解析js脚本、运行代码。 一直在等待任务队列中任务的到来,一个render进程里,只有一个js线程
  3. 事件触发线程 用来控制事件循环。 当js引擎执行代码块如settimeout时,会将对应任务添加到事件线程中 当对应的事件符合触发条件时,该线程会把事件添加到待处理队列的队尾 js是单线程的,待处理队列中的事件要排队等待js线程处理(js线程空闲时执行)
  4. 定时触发器线程 setInterval与setTimeout所在线程 计时完毕后,添加到事件队列中,等待js引擎空闲的时候执行
  5. 异步http请求线程 xmlhttprequest连接后是通过浏览器新开一个线程请求 如果有回调函数,异步线程会产生状态变更事件,将回调放到事件队列中

浏览器内核中线程之间关系

GUI渲染线程与js线程互斥。

js可以修改dom,如果修改元素属性的同时渲染界面,渲染线程前后获得的元素数据可能不一致 js引擎执行时,GUI线程会被挂起 GUI更新会被保存在一个队列中,等js线程空闲时,立即执行

js阻碍页面加载

假设JS引擎正在进行巨量的计算,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行

浏览器渲染流程

前期工作

浏览器输入url,浏览器主进程接管,开一个下载线程,等待响应,获取内容,把内容交给浏览器渲染进程。

  1. 解析html,构建dom树
  2. 解析css构建render树(将CSS代码解析成树形的数据结构CSSOM,然后结合DOM合并成render树)
  3. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
  4. 绘制render树(paint),绘制页面像素信息
  5. 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。 css加载不会阻塞DOM树解析,但会阻塞render树渲染(因为render树需要css信息)

从Event Loop谈JS的运行机制

JS分为同步任务和异步任务 同步任务都在主线程上执行,形成一个执行栈 事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件 同步任务执行完毕,js引擎空闲,系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行

定时器线程

setInterval每隔一段时间推入一个事件,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了。 会导致定时器代码连续运行好几次,而之间没有间隔。 setTimeout在前一个定时器执行完前,不会向队列插入新的定时器,保证定时器间隔。

function myInterval(fn, ms) {
  const ref = {};
  const exec = () => {
    return setTimeout(() => {
      fn.apply(null);
      const timer = exec();
      ref.current = timer;
    }, ms);
  };
  ref.current = exec();
  list.add(ref);
  return ref;
}

function myClearInterval(ref) {
  clearTimeout(ref.current);
  list.delete(ref);
}

用setTimeout模拟setInterval

宏任务和微任务

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

回流(reflow)与重绘(repaint)

当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如::hover)
  • 查询某些属性或调用某些方法

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘

避免方法:

  • 将动画效果应用到position属性为absolute或fixed的元素上(让其脱离文档流)
  • 预先定义 class,然后修改 dom class 而不是直接操控 css
  • 不要使用table布局
  • 预先设定img的大小,以免载入图片之后大小变化导致 Reflow
  • 先为元素设置display: none,操作结束后再把它显示出来
  • 不要频繁操作样式

性能优化

懒加载 虚拟列表 小图用base64格式,多个图片整合到一起(雪碧图) cdn 服务端开启文件压缩功能 defer:加了defer属性,script标签可以放到任意地方,脚本会被延迟到整个页面都解析完毕后再运行。浏览器立即下载,但延迟执行 async:script下载会和页面解析并行执行,script下载完后,立即执行,多个async script脚本谁先加载完谁先执行