浏览器是浏览器, 正不正经我不知道。

浏览器是什么(百度百科)

浏览器是用来检索、展示以及传递Web信息资源的应用程序。Web信息资源由统一资源标识符( Uniform Resource Identifier,URI)所标记,它是一张网页、一张图片、一段视频或者任何在Web上所呈现的内容。使用者可以借助超级链接( Hyperlinks),通过浏览器浏览互相关联的信息。
组成部分包括地址栏, 菜单栏, 选项卡, 页面窗口, 状态栏等。

什么是内核

对于操作系统来说,内核是操作系统的核心,是第一层基于硬件的软件扩充,提供最核心最基础的服务。应用程序通过内核进行系统调用来使用计算机的硬件,内核代码简洁高效,基本没有bug(由于是最底层的服务,一点微小的错误也会造成整个系统崩溃); 对于浏览器来说,同样存在浏览器内核,与操作系统内核相似,浏览器内核需要提供API给浏览器开发者使用,同时提供最核心的功能(如加载和渲染网页,调用操作系统所提供的服务);对浏览器厂商来说,高效使用和开发浏览器内核是核心问题;对于web开发者来说,理解浏览器内核的基本机制,才能开发出高性能的web应用。

有哪些内核

Trident

代表产品为Internet Explorer,又称其为IE内核。Trident(又称为MSHTML),是微软开发的一种排版引擎。使用Trident渲染引擎的浏览器有:IE、傲游、世界之窗浏览器、Avant、腾讯TT、Netscape 8、NetCaptor、Sleipnir、GOSURF、GreenBrowser和KKman等。

Gecko

代表作品为Mozilla Firefox。Gecko是一套开放源代码的、以C++编写的网页排版引擎,是最流行的排版引擎之一,仅次于Trident。使用它的最著名浏览器有Firefox、Netscape6至9。

WebKit

代表作品有Safari、Chrome。WebKit是一个开源项目,包含了来自KDE项目和苹果公司的一些组件,主要用于Mac OS系统,它的特点在于源码结构清晰、渲染速度极快。缺点是对网页代码的兼容性不高,导致一些编写不标准的网页无法正常显示。

Presto

代表作品Opera。Presto是由Opera Software开发的浏览器排版引擎,供Opera 7.0及以上使用。它取代了旧版Opera 4至6版本使用的Elektra排版引擎,包括加入动态功能,例如网页或其部分可随着DOM及Script语法的事件而重新排版。

查看用户代理

1、打开控制台
2、输入navigator.userAgent

UserAgent的作用

  • 判断浏览器类型,采用兼容方案
  • 判断是否为移动端
  • 标识H5容器,方便调用H5容器特定接口
  • 要注意userAgent伪装成本很低,不要过于依赖

网页内容组成

  • doctype:提供浏览器的html版本信息
  • head:html头部
    • meta:元数据信息
      • charset:此特性声明当前文档所使用的字符编码
      • http-equiv:客户端行为,如渲染模式,缓存等
      • name[keywords]:搜索引擎关键字
      • name[description]:搜索引擎描述
      • name[viewport]:浏览器视口设置
  • link
  • script:需要在body前完成加载或运行的脚本
  • body:html实体
  • script:需要在body解析时加载或运行的脚本

浏览器渲染

浏览器的渲染总共分成两部分

  • 加载:加载渲染所必须的html代码
  • 渲染:将html绘制成图像结果

加载

资源加载机制

分为三类:
1、特定资源加载器: 针对每种资源类型的特定加载器, 仅加载某一种资源, 对应设计模式中的单例模式
2、缓存资源加载器: 与常规的缓存逻辑相同, 特定加载器先通过缓存资源加载器来查找是否有缓存资源, 如果在资源缓存池中存在缓存资源, 则取出以便使用, 若不存在, 发送请求给网络模块
3、通用资源加载器: 由于加载资源太多属于网络请求, 而网络请求的逻辑是可以被特定资源加载器所共享的, 所以通用资源加载器只负责通过网络获得目标资源的数据, 但是不负责进一步解析

资源缓存(可以在浏览器的 network 加载项里面看到)

1、page cache: 页面缓存
2、memory cache: 内存缓存
3、disk cache: 磁盘缓存

预先加载

1、dns 预取 (比如请求头里面的 header rel=”dns-prefech” href=”” 去异步加载资源)
2、资源预取
3、TCP preconnect

如何提高加载速度

1、合并请求: nginx 模块, sprite 雪碧图, (减少连接数(合并请求) (资源合并))
2、缓存, from cache(memory dist), localStorage, 本地缓存策略,http 头(结合业务 )
3、tcp 网络链接优化: tcp 调优, keep-alive
4、硬件: 加大带宽, 使用 cdn(对象存储)
5、资源大小: gzip, webp, image 压缩, cookie 体积
6、预加载: 多个 cdn 域名(加速), dns 预取, 异步读取 js

网络栈
  • 确定请求类型,协议
  • 判断是否需要建立网络连接
  • 建立http事务
  • 建立tcp socket连接
  • 套接字连接

渲染

html 解释器

解释过程资源的变换:

1
字节流 -> 字符流 -> tokens -> 节点 -> dom 树

流程

1
词法分析 -> XSSauditor -> 语法分析 -> 生成dom树

词法分析

通过HTMLTokenizer来进行词法分析词法分析的任务是对输入字节流进行逐字扫描,根据构词规则识别单词和符号,词法分析器的主要接口是nextToken()函数,调用者只需要将字符串传入,就会得到一个词语。

注意,在这里并不涉及标签类型信息,这是之后语法分析的工作

组成渲染引擎的重要组件(webkit)

  • html解释器: 解释html文本的解释器,html文本 => dom 树
  • css解释器: 遇到级联样式的时候, 需要使用级联样式表解释器为dom对象计算出样式信息
  • javascript解释器: 遇到js代码时, 使用js解释器, 使得js代码具有调用dom接口和cssom接口的能力(也就是操作dom)
  • 布局: 结合css, 计算出dom对象的大小位置信息
  • 绘图: 经过布局计算的dom绘制成图像

浏览器的fps

fps和帧数的关系

  • fps:全称为Frames Per Second,即 每一秒的帧数;
  • 帧:指显示器显示的每一张画面;

通常在浏览器上看到的各种各样的动画特效,都是由一帧一帧组成的,可以将 fps 理解为动画播放的速度。fps 越大,动画越流畅。

刷新率hz

屏幕的刷新率 是指 屏幕每秒能够显示图像的次数,单位为 hz(赫兹)。

一般浏览器的 fps 为 60。当然,如果你的显示器的刷新率能够达到 144 hz 的话,浏览器的 fps 可以达到 144 fps, fps 的值受限于屏幕的刷新率,即 fps 的值小于等于屏幕刷新率的值。

以 Chromium 的 Blink 引擎为例分析:

  • 一个渲染帧内 commit 的多次 DOM 改动会被合并渲染;
  • 耗时 JS 会造成丢帧;
  • 渲染帧间隔为 16ms 左右;
  • 避免耗时脚本、交错读写样式以保证流畅的渲染。

渲染帧的流程

渲染帧是指浏览器一次完整绘制过程,帧之间的时间间隔是 DOM 视图更新的最小间隔。 由于主流的屏幕刷新率都在 60Hz,那么渲染一帧的时间就必须控制在 16ms 才能保证不掉帧。 也就是说每一次渲染都要在 16ms 内页面才够流畅不会有卡顿感。 这段时间内浏览器需要完成如下事情:

  • 脚本执行(JavaScript):脚本造成了需要重绘的改动,比如增删 DOM、请求动画等
  • 样式计算(CSS Object Model):级联地生成每个节点的生效样式。
  • 布局(Layout):计算布局,执行渲染算法
  • 重绘(Paint):各层分别进行绘制(比如 3D 动画)
  • 合成(Composite):合成各层的渲染结果

📢 耗时 JS 会造成丢帧

JavaScript 在并发编程上一个重要特点是“Run To Completion”。在事件循环的一次 Tick 中, 如果要执行的逻辑太多会一直阻塞下一个 Tick,所有异步过程都会被阻塞。 一个流畅的页面中,JavaScript 引擎中的执行队列可能是这样的:

1
执行 JS -> 空闲 -> 绘制(16ms)-> 执行 JS -> 空闲 -> 绘制(32ms)-> ...

如果在某个时刻有太多 JavaScript 要执行,就会丢掉一次帧的绘制:

1
执行很多 JS...(20ms)-> 空闲 -> 绘制(32ms)-> ...

demo: 阻塞dom渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="msg"></div>
<script>
let preTime = Date.now();
let i = 0;
let mdom = document.querySelector('#msg');
while(true) {
let nowTime = Date.now()
if(nowTime - preTime > 1000) {
if (i++ >=5) {
break;
}
mdom.innerText +='hardy!\n'
console.log(i)
preTime = nowTime;
}
}
</script>

很明显每秒都写一次dom,但是5秒之后才会全部渲染出来,说明耗时脚本阻塞了渲染。

测量渲染帧间隔

用最新版本浏览器提供的requestAnimationFrame,这个 API 可以请求浏览器在下一个渲染帧执行某个回调,于是测量渲染间隔就很方便了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let preTime = Date.now()
let count = 0

const nextFrame = () => {
requestAnimationFrame(() => {
count++
if (count % 20 === 0) {
let time = (Date.now() - preTime) / count
let ms = Math.round(time * 1000) / 1000
let fps = Math.round(100000 / ms) / 100
console.log(`count: ${count}\t${ms}ms/frame\t${fps}fps`)
}
nextFrame()
})
}
nextFrame()

运行效果
运行效果

每次 requestAnimationFrame 回调执行时发起下一个 requestAnimationFrame,统计一段时间即可得到渲染帧间隔,以及 fps。知道了浏览器需要在 16ms 内完成整个JS->Style->Layout->Paint->Composite 流程,那么基于此有哪些页面渲染的优化建议, 后面更~

requestAnimationFrame, requestIdleCallback

在react(v16.8)源码中,有这样的两个api requestAnimationFramerequestIdleCallback

分别看一下mdn上这两个方法的定义:

1、requestAnimationFrame

1
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

2、requestIdleCallback:

1
2
3
window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。

你可以在空闲回调函数中调用requestIdleCallback(),以便在下一次通过事件循环之前调度另一个回调。

这两个 api 与 浏览器的 fps 存在着关系。

Chromium架构(chrome的开源版本,激进的版本,平常用的chrome是稳定的版本)

除去webkit内核完成的功能,浏览器的工作有哪些?

现代浏览器的工作

  • 资源管理:
  • 多页面管理:也就是多个标签页的管理
  • 插件和拓展:如flash,chrome拓展程序
  • 账户和同步
  • 安全机制
  • 多系统支持

进程和线程

  • 进程:操作系统对一个正在运行的程序的抽象。你可以查看进程占用CPU,内存的大小。
  • 线程: 组成进程的执行单位
  • 进程通讯:进程间传输数据(交换信息) (linux 和 window 不一样 )
  • 线程同步:
IPC是什么, 这是一张IPC通讯(进程间的通讯)
IPC是什么, 这是一张IPC通讯(进程间的通讯)

Chromium架构主要进程

  • Browser 进程: 主进程, 负责浏览器界面, 页面管理等
  • Renderer 进程: 渲染进程
  • NPAPI 插件进程
  • GPU 进程: 当 GPU 硬件加速打开时会创建

Chromium各进程介绍

  • 父进程:Browser,负责管理整个浏览器的状态,运行UI,消息派发(如申请文件资源)。
  • 子进程:
    • Render,负责渲染网页,于Webkit(Blink)交互,向外(Browser、GPU)发送请求。
    • GPU,当GPU硬件加速时会打开

多进程架构的目的

  • 职责分离,故障范围小
  • 隔离性
  • 性能

javascript 中的进程和线程

js 是严格的单线程操作, 所以不会像 java 一样存在线程不安全的情况, 一般在操作的时候也是单独的进程(比如 node, 如果是用 pm2 创建的进程实际上是父子的关系, pm2 就是守护进程)

现代浏览器格局

  • IE: Trident,IE8的javascript引擎是Jscript, IE9开始用Chakra
  • Firfox: Gecko, Javascript引擎SpiderMonkey
  • Safari: Webkit, 渲染引擎以及Javascript解析引擎均是从KDE的KHTML以及KJS引擎衍生而来
  • Chrome: Blink, Javascript引擎V8
  • Opera: 早期是Presto, 现在改成Blink与V8

国内浏览器大部分都是基于chromium以及IE核的外壳型浏览器

浏览器的事件轮询

浏览器缓存机制

浏览器安全

🏹 几个思考:

  • 通过响应的内容,我们可以看到内容中还存在许多外联资源,浏览器是如何处理的?

    1
    不同的外链资源,webkit中右不同的资源加载器,当浏览器解析到URL地址时,调用特定的资源加载器,如果不是特殊资源(js),加载过程不会阻碍渲染过程
  • 著名的优化:“css放在头部,js放在尾部”,为什么?

    1
    一般来说css资源不会阻碍渲染过程,但是js资源在浏览器会阻碍渲染过程的进行,如果放在头部,渲染过程会暂停,造成“白屏”,但现代浏览器的优化已经做得很好了,所以当渲染被阻塞时,浏览器会开启新的线程继续渲染。
  • 浏览器在渲染之前或之后还要做哪些事情?

    1
    渲染之前需要加载资源,渲染之后在DOM或者CSS变化后,重新进行布局计算和重渲染操作。
  • 移动端的浏览器和PC端的浏览器是否相同?

    1
    功能基本相同,但所运行的操作系统不同,渲染机制有些差异