当前位置 : 主页 > 编程语言 > 其它开发 >

浏览器工作原理与实践

来源:互联网 收集:自由互联 发布时间:2022-05-30
记录学习曲线,浏览器工作原理与实践 浏览器工作原理与实践 记录学习曲线: 2021年11月26日15:20:10学习了15个章节(1-13,29-30),耗时两个小时,共计15个章节 2021年11月29日18:00:08,完成
记录学习曲线,浏览器工作原理与实践 浏览器工作原理与实践

记录学习曲线:

  1. 2021年11月26日15:20:10学习了15个章节(1-13,29-30),耗时两个小时,共计15个章节

  2. 2021年11月29日18:00:08,完成了第二次学习。(14-17,31),学习了一个小时,共6个章节

  3. 2021年11月30日11:21:11,完成了第三次学习。(18-28)学习了两个小时。共计10个章节

  4. 2021年11月30日14:24:47,完成了第四次学习。(32-36)学习了一个小时,共计5个章节

  5. 完成,共计6个小时


宏观视角下的浏览器

Chrome是以一个非常快速的速度在进化,越来越多的业务和应用都逐渐转至浏览器来开发,身为开发人员,我们不能坐视不管,而应该紧跟其步伐,收获这波技术红利

仅仅打开了1个页面,为什么有4个进程?因为打开1个页面至少需要1个网络进程、1个浏览器进程、1个GPU进程以及1个渲染进程,共4个;如果打开的页面有运行插件的话,还需要再加上1个插件进程。

互联网中的数据是通过数据包来传输的。如果发送的数据很大,那么该数据就会被拆分为很多小数据包来传输。比如你现在听的音频数据,是拆分成一个个小的数据包来传输的,并不是一个大的文件一次传输过来的。

本篇文章是有很多分析问题的思路在里面的。所以在学习过程中,你也要学会提问,通过最终要做什么和现在有什么,去一步步分析并提出一些问题,让疑问带领着你去学习,抓住几个本质的问题就可以学透相关知识点,让你能站在更高维度去查看整体框架。希望它能成为你的一个学习技巧吧!

“在浏览器里,从输入URL到页面展示,这中间发生了什么?  这个过程可以大致描述为如下。

  • 首先,浏览器进程接收到用户输入的URL请求,浏览器进程便将该URL转发给网络进程。

  • 然后,在网络进程中发起真正的URL请求。

  • 接着网络进程接收到了响应头数据,便解析响应头数据,并将数据转发给浏览器进程。

  • 浏览器进程接收到网络进程的响应头数据之后,发送“提交导航(CommitNavigation)”消息到渲染进程;

  • 渲染进程接收到“提交导航”的消息之后,便开始准备接收HTML数据,接收数据的方式是直接和网络进程建立数据管道;

  • 最后渲染进程会向浏览器进程“确认提交”,这是告诉浏览器进程:“已经准备好接受和解析页面数据了”。

  • 浏览器进程接收到渲染进程“提交文档”的消息之后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态。

这其中,用户发出URL请求到页面开始解析的这个过程,就叫做导航

从输入URL到页面展示,这中间发生了什么?”这个问题,你知道怎么回答了吗?

beforeunload事件允许页面在退出之前执行一些数据清理操作,还可以询问用户是否要离开当前页面,比如当前页面可能有未提交完成的表单等情况,因此用户可以通过beforeunload事件来取消导航,让浏览器不再执行任何后续工作。

渲染流程 通常,我们编写好HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面(如下图所示),但是你知道它们是如何转化成页面的吗?

按照渲染的时间顺序,流水线可分为如下几个子阶段:构建DOM树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。内容比较多,我会用两篇文章来为你详细讲解这各个子阶段。接下来,在介绍每个阶段的过程中,你应该重点关注以下三点内容:

  • 开始每个子阶段都有其输入的内容

  • 然后每个子阶段有其处理过程

  • 最终每个子阶段会生成输出内容

理解了这三部分内容,能让你更加清晰地理解每个子阶段。

在HTML页面内容被提交给渲染引擎之后,渲染引擎首先将HTML解析为浏览器可以理解的DOM;然后根据CSS样式表,计算出DOM树所有节点的样式;接着又计算每个元素的几何坐标位置,并将这些信息保存在布局树中。

因为页面中有很多复杂的效果,如一些复杂的3D变换、页面滚动,或者使用z-indexing做z轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)

1637913614825

结合上图,一个完整的渲染流程大致可总结为如下:

  1. 渲染进程将HTML内容转换为能够读懂的DOM树结构。

  2. 渲染引擎将CSS样式表转化为浏览器可以理解的styleSheets,计算出DOM节点的样式。

  3. 创建布局树,并计算元素的布局信息。

  4. 对布局树进行分层,并生成分层树

  5. 为每个图层生成绘制列表,并将其提交到合成线程。

  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。

  7. 合成线程发送绘制图块命令DrawQuad给浏览器进程。

  8. 浏览器进程根据DrawQuad消息生成页面,并显示到显示器上。

通过这么多年的生活和工作经验来看,无论是做架构设计、产品设计,还是具体到代码的实现,甚至处理生活中的一些事情,能够把复杂问题简单化的人都是具有大智慧的。所以,在工作或生活中,你若想要简化遇到的问题,就要刻意地练习,练就抓住问题本质的能力,把那些复杂的问题简单化,从而最终真正解决问题。

浏览器中的JavaScript执行机制

JavaScript变量提升:因为在代码执行前会先被编译

所谓的变量提升,是指在JavaScript代码执行过程中,JavaScript引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个认值就是我们熟悉的undefined。

JavaScript引擎正是利用栈的这种结构来管理执行上下文的。在执行上下文创建好后,JavaScript引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行上下文栈,又称调用栈

HTTP网络协议

HTTP/2的解决方案可以总结为:一个域名只使用一个TCP长连接和消除队头阻塞问题

1638176349509

HTTP/2的一个核心特性是使用了多路复用技术,因此它可以通过一个TCP连接来发送多个URL请求。

多路复用技术能充分利用带宽,最大限度规避了TCP的慢启动所带来的问题,同时还实现了头部压缩、服务器推送等功能,使得页面资源的传输速度得到了大幅提升。在HTTP/1.1时代,为了提升并行下载效率,浏览器为每个域名维护了6个TCP连接;而采用HTTP/2之后,浏览器只需要为每个域名维护1个TCP持久连接,同时还解决了HTTP/1.1队头阻塞的问题。

我们首先分析了HTTP/2中所存在的一些问题,主要包括了TCP的队头阻塞、建立TCP连接的延时、TCP协议僵化等问题。

这些问题都是TCP的内部问题,因此要解决这些问题就要优化TCP或者“另起炉灶”创造新的协议。由于优化TCP协议存在着诸多挑战,所以官方选择了创建新的QUIC协议。

HTTP/3正是基于QUIC协议的,你可以把QUIC看成是集成了“TCP+HTTP/2的多路复用+TLS等功能”的一套协议。这是集众家所长的一个协议,从协议最底层对Web的文件传输做了比较彻底的优化,所以等生态相对成熟时,可以用来打造比现在的HTTP/2还更加高效的网络。

浏览器的页面循环系统

1638178035662

Chromium的官方源码

实践:浏览器页面是如何运行的

有了上面的基础知识之后,我们最后来看看浏览器的页面是如何运行的。

你可以打开开发者工具,点击“Performance”标签,选择左上角的“start porfiling and load page”来记录整个页面加载过程中的事件执行情况,如下图所示:

Performance页面

从图中可以看出,我们点击展开了Main这个项目,其记录了主线程执行过程中的所有任务。图中灰色的就是一个个任务,每个任务下面还有子任务,其中的Parse HTML任务,是把HTML解析为DOM的任务。值得注意的是,在执行Parse HTML的时候,如果遇到JavaScript脚本,那么会暂停当前的HTML解析而去执行JavaScript脚本。

至于Performance工具,在后面的章节中我们还会详细介绍,在这里你只需要建立一个直观的印象就可以了。

关于浏览器消息队列和事件循环总结

好了,今天就讲到这里,下面我来总结下今天所讲的内容。

  • 如果有一些确定好的任务,可以使用一个单线程来按照顺序处理这些任务,这是第一版线程模型。

  • 要在线程执行过程中接收并处理新的任务,就需要引入循环语句和事件系统,这是第二版线程模型。

  • 如果要接收其他线程发送过来的任务,就需要引入消息队列,这是第三版线程模型。

  • 如果其他进程想要发送任务给页面主线程,那么先通过IPC把任务发送给渲染进程的IO线程,IO线程再把任务发送给页面主线程。

  • 消息队列机制并不是太灵活,为了适应效率和实时性,引入了微任务。

基于消息队列的设计是目前使用最广的消息架构,无论是安卓还是Chrome都采用了类似的任务机制,所以理解了本篇文章的内容后,你再理解其他项目的任务机制也会比较轻松。

浏览器怎么实现setTimeout

要了解定时器的工作原理,就得先来回顾下之前讲的事件循环系统,我们知道渲染进程中所有运行在主线程上的任务都需要先添加到消息队列,然后事件循环系统再按照顺序执行消息队列中的任务。下面我们来看看那些典型的事件:

  • 当接收到HTML文档数据,渲染引擎就会将“解析DOM”事件添加到消息队列中,

  • 当用户改变了Web页面的窗口大小,渲染引擎就会将“重新布局”的事件添加到消息队列中。

  • 当触发了JavaScript引擎垃圾回收机制,渲染引擎会将“垃圾回收”任务添加到消息队列中。

  • 同样,如果要执行一段异步JavaScript代码,也是需要将执行任务添加到消息队列中。

以上列举的只是一小部分事件,这些事件被添加到消息队列之后,事件循环系统就会按照消息队列中的顺序来执行事件。

所以说要执行一段异步任务,需要先将任务添加到消息队列中。不过通过定时器设置回调函数有点特别,它们需要在指定的时间间隔内被调用,但消息队列中的任务是按照顺序执行的,所以为了保证回调函数能在指定时间内执行,你不能将定时器的回调函数直接添加到消息队列中。

在Chrome中除了正常使用的消息队列之外,还有另外一个消息队列,这个队列中维护了需要延迟执行的任务列表,包括了定时器和Chromium内部一些需要延迟执行的任务。所以当通过JavaScript创建一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列中。

Chromium中关于队列部分的源码。

使用setTimeout的一些注意事项
  1. 如果当前任务执行时间过久,会影响定时器任务的执行

  2. 如果setTimeout存在嵌套调用,那么系统会设置最短时间间隔为4毫秒

  3. 未激活的页面,setTimeout执行最小间隔是1000毫秒

  4. 延时执行时间有最大值

  5. 使用setTimeout设置的回调函数中的this不符合直觉

XMLHttpRequest运作机制

1638179247093

发起请求。

一切准备就绪之后,就可以调用xhr.send来发起网络请求了。你可以对照上面那张请求流程图,可以看到:渲染进程会将请求发送给网络进程,然后网络进程负责资源的下载,等网络进程接收到数据之后,就会利用IPC来通知渲染进程;渲染进程接收到消息之后,会将xhr的回调函数封装成任务并添加到消息队列中,等主线程循环系统执行到该任务的时候,就会根据相关的状态来调用对应的回调函数。

  • 如果网络请求出错了,就会执行xhr.onerror;

  • 如果超时了,就会执行xhr.ontimeout;

  • 如果是正常的数据接收,就会执行onreadystatechange来反馈相应的状态。

这就是一个完整的XMLHttpRequest请求流程,如果你感兴趣,可以参考下Chromium对XMLHttpRequest的实现,点击这里查看代码。

浏览器安全问题是前端工程师避不开的一道坎

XMLHttpRequest使用过程中的“坑”
  1. 跨域问题

  2. HTTPS混合内容的问题

微任务

微任务就是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。

微任务产生的时机和执行微任务队列的时机:

第一种方式是使用MutationObserver监控某个DOM节点,然后再通过JavaScript来修改这个节点,或者为这个节点添加、删除部分子节点,当DOM节点发生变化时,就会产生DOM变化记录的微任务。

第二种方式是使用Promise,当调用Promise.resolve()或者Promise.reject()的时候,也会产生微任务。

通常情况下,在当前宏任务中的JavaScript快执行完成时,也就在JavaScript引擎准备退出全局执行上下文并清空调用栈的时候,JavaScript引擎会检查全局执行上下文中的微任务队列,然后按照顺序执行队列中的微任务。WHATWG把执行微任务的时间点称为检查点。当然除了在退出全局执行上下文式这个检查点之外,还有其他的检查点,不过不是太重要,这里就不做介绍了。

如果在执行微任务的过程中,产生了新的微任务,同样会将该微任务添加到微任务队列中,V8引擎一直循环执行微任务队列中的任务,直到队列为空才算执行结束。也就是说在执行微任务过程中产生的新的微任务并不会推迟到下个宏任务中执行,而是在当前的宏任务中继续执行。

  • 微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。

  • 微任务的执行时长会影响到当前宏任务的时长。比如一个宏任务在执行过程中,产生了100个微任务,执行每个微任务的时间是10毫秒,那么执行这100个微任务的时间就是1000毫秒,也可以说这100个微任务让宏任务的执行时间延长了1000毫秒。所以你在写代码的时候一定要注意控制微任务的执行时长。

  • 在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。

监听DOM变化方法演变

虽然监听DOM的需求是如此重要,不过早期页面并没有提供对监听的支持,所以那时要观察DOM是否变化,唯一能做的就是轮询检测,比如使用setTimeout或者setInterval来定时检测DOM是否有改变。这种方式简单粗暴,但是会遇到两个问题:如果时间间隔设置过长,DOM 变化响应不够及时;反过来如果时间间隔设置过短,又会浪费很多无用的工作量去检查DOM,会让页面变得低效。

直到2000年的时候引入了Mutation Event,Mutation Event采用了观察者的设计模式,当DOM 有变动时就会立刻触发相应的事件,这种方式属于同步回调。

采用Mutation Event解决了实时性的问题,因为DOM一旦发生变化,就会立即调用JavaScript接口。但也正是这种实时性造成了严重的性能问题,因为每次DOM变动,渲染引擎都会去调用JavaScript,这样会产生较大的性能开销。比如利用JavaScript动态创建或动态修改50个节点内容,就会触发50次回调,而且每个回调函数都需要一定的执行时间,这里我们假设每次回调的执行时间是4毫秒,那么50次回调的执行时间就是200毫秒,若此时浏览器正在执行一个动画效果,由于Mutation Event触发回调事件,就会导致动画的卡顿。

也正是因为使用Mutation Event会导致页面性能问题,所以Mutation Event被反对使用,并逐步从Web标准事件中删除了。

为了解决了Mutation Event由于同步调用JavaScript而造成的性能问题,从DOM4开始,推荐使用 MutationObserver 来代替 Mutation Event。MutationObserver API 可以用来监视 DOM 的变化,包括属性的变化、节点的增减、内容的变化等。

那么相比较 Mutation Event,MutationObserver 到底做了哪些改进呢?

首先,MutationObserver将响应函数改成异步调用,可以不用在每次DOM变化都触发异步调用,而是等多次DOM变化后,一次触发异步调用,并且还会使用一个数据结构来记录这期间所有的DOM变化。这样即使频繁地操纵DOM,也不会对性能造成太大的影响。

我们通过异步调用和减少触发次数来缓解了性能问题,那么如何保持消息通知的及时性呢?如果采用setTimeout创建宏任务来触发回调的话,那么实时性就会大打折扣,因为上面我们分析过,在两个任务之间,可能会被渲染进程插入其他的事件,从而影响到响应的实时性。

这时候,微任务就可以上场了,在每次DOM节点发生变化的时候,渲染引擎将变化记录封装成微任务,并将微任务添加进当前的微任务队列中。这样当执行到检查点的时候,V8引擎就会按照顺序执行微任务了。

综上所述, MutationObserver采用了“异步+微任务”的策略。

  • 通过异步操作解决了同步操作的性能问题

  • 通过微任务解决了实时性的问题

Promise:使用Promise,告别回调函数

DOM/BOM API中新加入的API大多数都是建立在Promise上的,而且新的前端框架也使用了大量的Promise。 Promise已经成为现代前端的“水”和“电”,很是关键,所以深入学习Promise势在必行。

如果你想要学习一门新技术,最好的方式是先了解这门技术是如何诞生的,以及它所解决的问题是什么。了解了这些后,你才能抓住这门技术的本质。所以本文我们就来重点聊聊JavaScript引入Promise的动机,以及解决问题的几个核心关键点。

1638236822546

上图展示的是一个标准的异步编程模型,页面主线程发起了一个耗时的任务,并将任务交给另外一个进程去处理,这时页面主线程会继续执行消息队列中的任务。等该进程处理完这个任务后,会将该任务添加到渲染进程的消息队列中,并排队等待循环系统的处理。排队结束之后,循环系统会取出消息队列中的任务进行处理,并触发相关的回调操作。

这就是页面编程的一大特点:异步回调

封装异步代码,让处理流程变得线性 Promise:消灭嵌套调用和多次错误处理
  • Promise实现了回调函数的延时绑定

  • 需要将回调函数onResolve的返回值穿透到最外层

  • 错误“冒泡”技术

async/await:使用同步的方式去写异步代码

使用promise.then也是相当复杂,虽然整个请求流程已经线性化了,但是代码里面包含了大量的then函数,使得代码依然不是太容易阅读。基于这个原因,ES7 引入了async/await,这是JavaScript异步编程的一个重大改进,提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力,并且使得代码逻辑更加清晰。你可以参考下面这段代码:


async function foo(){

try{

let response1 = await fetch('https://www.geekbang.org')

console.log('response1')

console.log(response1)

let response2 = await fetch('https://www.geekbang.org/test')

console.log('response2')

console.log(response2)

}catch(err) {

console.error(err)

}

}

foo()

通过上面代码,你会发现整个异步处理的逻辑都是使用同步代码的方式来实现的,而且还支持try catch来捕获异常,这就是完全在写同步代码,所以是非常符合人的线性思维的。但是很多人都习惯了异步回调的编程思维,对于这种采用同步代码实现异步逻辑的方式,还需要一个转换的过程,因为这中间隐藏了一些容易让人迷惑的细节。

首先介绍生成器(Generator)是如何工作的,接着讲解Generator的底层实现机制——协程(Coroutine);又因为async/await使用了Generator和Promise两种技术,所以紧接着我们就通过Generator和Promise来分析async/await到底是如何以同步的方式来编写异步代码的。

生成器 VS 协程

生成器函数是一个带星号函数,而且是可以暂停执行和恢复执行的。

要搞懂函数为何能暂停和恢复,那你首先要了解协程的概念。协程是一种比线程更加轻量级的存在。 你可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,比如当前执行的是A协程,要启动B协程,那么A协程就需要将主线程的控制权交给B协程,这就体现在A协程暂停执行,B协程恢复执行;同样,也可以从B协程中启动A协程。通常,如果从A协程启动B协程,我们就把A协程称为B协程的父协程。

正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。 最重要的是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行) 。.

协程执行流程图

async/await

虽然生成器已经能很好地满足我们的需求了,但是程序员的追求是无止境的,这不又在ES7中引入了async/await,这种方式能够彻底告别执行器和生成器,实现更加直观简洁的代码。其实async/await技术背后的秘密就是Promise和生成器应用,往低层说就是微任务和协程应用。要搞清楚async和await的工作原理,我们就得对async和await分开分析。

Promise的编程模型依然充斥着大量的then方法,虽然解决了回调地狱的问题,但是在语义方面依然存在缺陷,代码中充斥着大量的then函数,这就是async/await出现的原因。

使用async/await可以实现用同步代码的风格来编写异步代码,这是因为async/await的基础技术使用了生成器和Promise,生成器是协程的实现,利用生成器能实现生成器函数的暂停和恢复。

另外,V8引擎还为async/await做了大量的语法层面包装,所以了解隐藏在背后的代码有助于加深你对async/await的理解。

async/await无疑是异步编程领域非常大的一个革新,也是未来的一个主流的编程风格。其实,除了JavaScript,Python、Dart、C#等语言也都引入了async/await,使用它不仅能让代码更加整洁美观,而且还能确保该函数始终都能返回Promise。

上面V8课程以及Promise,async和await等课程,懵懵懂懂的听不懂。

浏览器中的页面 Chrome开发者工具

Chrome开发者工具(简称DevTools)是一组网页制作和调试的工具,内嵌于Google Chrome 浏览器中

1638238331043

网络面板

网络面板由控制器、过滤器、抓图信息、时间线、详细列表和下载信息概要这6个区域构成

1638238370659

  1. 控制器

其中,控制器有4个比较重要的功能

  1. 过滤器

网络面板中的过滤器,主要就是起过滤功能。因为有时候一个页面有太多内容在详细列表区域中展示了,而你可能只想查看JavaScript文件或者CSS文件,这时候就可以通过过滤器模块来筛选你想要的文件类型。

  1. 抓图信息

抓图信息区域,可以用来分析用户等待页面加载时间内所看到的内容,分析用户实际的体验情况。比如,如果页面加载1秒多之后屏幕截图还是白屏状态,这时候就需要分析是网络还是代码的问题了。(勾选面板上的“Capture screenshots”即可启用屏幕截图。)

  1. 时间线

时间线,主要用来展示HTTP、HTTPS、WebSocket加载的状态和时间的一个关系,用于直观感受页面的加载过程。如果是多条竖线堆叠在一起,那说明这些资源被同时被加载。至于具体到每个文件的加载信息,还需要用到下面要讲的详细列表。

  1. 详细列表

这个区域是最重要的,它详细记录了每个资源从发起请求到完成请求这中间所有过程的状态,以及最终请求完成的数据信息。通过该列表,你就能很容易地去诊断一些网络问题。

详细列表是我们本篇文章介绍的重点,不过内容比较多,所以放到最后去专门介绍了。

  1. 下载信息概要

下载信息概要中,你要重点关注下DOMContentLoaded和Load两个事件,以及这两个事件的完成时间。

DOMContentLoaded,这个事件发生后,说明页面已经构建好DOM了,这意味着构建DOM所需要的HTML文件、JavaScript文件、CSS文件都已经下载完成了。

Load,说明浏览器已经加载了所有的资源(图像、样式表等)。

通过下载信息概要面板,你可以查看触发这两个事件所花费的时间。

单个资源的时间线

1638238740772

1638238785923

首先,页面中的资源是有优先级的,比如CSS、HTML、JavaScript等都是页面中的核心文件,所以优先级最高;而图片、视频、音频这类资源就不是核心资源,优先级就比较低。通常当后者遇到前者时,就需要“让路”,进入待排队状态。

优化时间线上耗时项

了解了时间线面板上的各项含义之后,我们就可以根据这个请求的时间线来实现相关的优化操作了。

1. 排队(Queuing)时间过久

排队时间过久,大概率是由浏览器为每个域名最多维护6个连接导致的。那么基于这个原因,你就可以让1个站点下面的资源放在多个域名下面,比如放到3个域名下面,这样就可以同时支持18个连接了,这种方案称为域名分片技术。除了域名分片技术外,我个人还建议你把站点升级到HTTP2,因为HTTP2已经没有每个域名最多维护6个TCP连接的限制了。

2. 第一字节时间(TTFB)时间过久

这可能的原因有如下:

  • 服务器生成页面数据的时间过久。对于动态网页来说,服务器收到用户打开一个页面的请求时,首先要从数据库中读取该页面需要的数据,然后把这些数据传入到模板中,模板渲染后,再返回给用户。服务器在处理这个数据的过程中,可能某个环节会出问题。

  • 网络的原因。比如使用了低带宽的服务器,或者本来用的是电信的服务器,可联通的网络用户要来访问你的服务器,这样也会拖慢网速。

  • 发送请求头时带上了多余的用户信息。比如一些不必要的Cookie信息,服务器接收到这些Cookie信息之后可能需要对每一项都做处理,这样就加大了服务器的处理时长。

对于这三种问题,你要有针对性地出一些解决方案。面对第一种服务器的问题,你可以想办法去提高服务器的处理速度,比如通过增加各种缓存的技术;针对第二种网络问题,你可以使用CDN来缓存一些静态文件;至于第三种,你在发送请求时就去尽可能地减少一些不必要的Cookie数据信息。

3. Content Download时间过久

如果单个请求的Content Download花费了大量时间,有可能是字节数太多的原因导致的。这时候你就需要减少文件大小,比如压缩、去掉源码中不必要的注释等方法。

Dom树

DOM树的解析流程介绍两块内容:第一个是在解析过程中遇到JavaScript脚本,DOM解析器是如何处理的?第二个是DOM解析器是如何处理跨站点资源的?

什么是DOM

从网络传给渲染引擎的HTML文件字节流是无法直接被渲染引擎理解的,所以要将其转化为渲染引擎能够理解的内部结构,这个结构就是DOM。DOM提供了对HTML文档结构化的表述。在渲染引擎中,DOM有三个层面的作用。

  • 从页面的视角来看,DOM是生成页面的基础数据结构。

  • 从JavaScript脚本视角来看,DOM提供给JavaScript脚本操作的接口,通过这套接口,JavaScript可以对DOM结构进行访问,从而改变文档的结构、样式和内容。

  • 从安全视角来看,DOM是一道安全防护线,一些不安全的内容在DOM解析阶段就被拒之门外了。

简言之,DOM是表述HTML的内部数据结构,它会将Web页面和JavaScript脚本连接起来,并过滤一些不安全的内容。

DOM树如何生成

在渲染引擎内部,有一个叫HTML解析器(HTMLParser)的模块,它的职责就是负责将HTML字节流转换为DOM结构。所以这里我们需要先要搞清楚HTML解析器是怎么工作的。

在开始介绍HTML解析器之前,我要先解释一个大家在留言区问到过好多次的问题:HTML解析器是等整个HTML文档加载完成之后开始解析的,还是随着HTML文档边加载边解析的?

在这里我统一解答下,HTML解析器并不是等整个文档加载完成之后再解析的,而是网络进程加载了多少数据,HTML解析器便解析多少数据。

那详细的流程是怎样的呢?网络进程接收到响应头之后,会根据响应头中的content-type字段来判断文件的类型,比如content-type的值是“text/html”,那么浏览器就会判断这是一个HTML类型的文件,然后为该请求选择或者创建一个渲染进程。渲染进程准备好之后,网络进程和渲染进程之间会建立一个共享数据的管道,网络进程接收到数据后就往这个管道里面放,而渲染进程则从管道的另外一端不断地读取数据,并同时将读取的数据“喂”给HTML解析器。你可以把这个管道想象成一个“水管”,网络进程接收到的字节流像水一样倒进这个“水管”,而“水管”的另外一端是渲染进程的HTML解析器,它会动态接收字节流,并将其解析为DOM。

1638239196312

1638239220101

由图可以看出,Tag Token又分StartTag 和 EndTag,比如<body>就是StartTag ,</body>就是EndTag,分别对于图中的蓝色和红色块,文本Token对应的绿色块。

至于后续的第二个和第三个阶段是同步进行的,需要将Token解析为DOM节点,并将DOM节点添加到DOM树中。

HTML解析器维护了一个Token栈结构,该Token栈主要用来计算节点之间的父子关系,在第一个阶段中生成的Token会被按照顺序压到这个栈中。具体的处理规则如下所示:

  • 如果压入到栈中的是StartTag Token,HTML解析器会为该Token创建一个DOM节点,然后将该节点加入到DOM树中,它的父节点就是栈中相邻的那个元素生成的节点。

  • 如果分词器解析出来是文本Token,那么会生成一个文本节点,然后将该节点加入到DOM树中,文本Token是不需要压入到栈中,它的父节点就是当前栈顶Token所对应的DOM节点。

  • 如果分词器解析出来的是EndTag标签,比如是EndTag div,HTML解析器会查看Token栈顶的元素是否是StarTag div,如果是,就将StartTag div从栈中弹出,表示该div元素解析完成。

通过分词器产生的新Token就这样不停地压栈和出栈,整个解析过程就这样一直持续下去,直到分词器将所有字节流分词完成。

通过上面的介绍,相信你已经清楚DOM是怎么生成的了。不过在实际生产环境中,HTML源文件中既包含CSS和JavaScript,又包含图片、音频、视频等文件,所以处理过程远比上面这个示范Demo复杂。不过理解了这个简单的Demo生成过程,我们就可以往下分析更加复杂的场景了。

JavaScript是如何影响DOM生成的

<script>标签之前,所有的解析流程还是和之前介绍的一样,但是解析到<script>标签时,渲染引擎判断这是一段脚本,此时HTML解析器就会暂停DOM的解析,因为接下来的JavaScript可能要修改当前已经生成的DOM结构。

其整个执行流程还是一样的,执行到JavaScript标签时,暂停整个DOM的解析,执行JavaScript代码,不过这里执行JavaScript时,需要先下载这段JavaScript代码。这里需要重点关注下载环境,因为JavaScript文件的下载过程会阻塞DOM解析,而通常下载又是非常耗时的,会受到网络环境、JavaScript文件大小等因素的影响。

渲染流水线:CSS如何影响首次加载时的白屏时间?

1638239661859

首先是发起主页面的请求,这个发起请求方可能是渲染进程,也有可能是浏览器进程,发起的请求被送到网络进程中去执行。网络进程接收到返回的HTML数据之后,将其发送给渲染进程,渲染进程会解析HTML数据并构建DOM。这里你需要特别注意下,请求HTML数据和构建DOM中间有一段空闲时间,这个空闲时间有可能成为页面渲染的瓶颈。

等DOM和CSSOM都构建好之后,渲染引擎就会构造布局树。布局树的结构基本上就是复制DOM树的结构,不同之处在于DOM树中那些不需要显示的元素会被过滤掉,如display:none属性的元素、head标签、script标签等。复制好基本的布局树结构之后,渲染引擎会为对应的DOM元素选择对应的样式信息,这个过程就是样式计算。样式计算完成之后,渲染引擎还需要计算布局树中每个元素对应的几何位置,这个过程就是计算布局。通过样式计算和计算布局就完成了最终布局树的构建。再之后,就该进行后续的绘制操作了。

这就是在渲染过程中涉及到CSS的一些主要流程。

1638239811800

1638239832906

影响页面展示的因素以及优化策略

前面我们为什么要花这么多文字来分析渲染流水线呢?主要原因就是渲染流水线影响到了首次页面展示的速度,而首次页面展示的速度又直接影响到了用户体验,所以我们分析渲染流水线的目的就是为了找出一些影响到首屏展示的因素,然后再基于这些因素做一些针对性的调整。

那么接下来我们就来看看从发起URL请求开始,到首次显示页面的内容,在视觉上经历的三个阶段。

  • 第一个阶段,等请求发出去之后,到提交数据阶段,这时页面展示出来的还是之前页面的内容。关于提交数据你可以参考前面《04 | 导航流程:从输入URL到页面展示,这中间发生了什么?》这篇文章。

  • 第二个阶段,提交数据之后渲染进程会创建一个空白页面,我们通常把这段时间称为解析白屏,并等待CSS文件和JavaScript文件的加载完成,生成CSSOM和DOM,然后合成布局树,最后还要经过一系列的步骤准备首次渲染。

  • 第三个阶段,等首次渲染完成之后,就开始进入完整页面的生成阶段了,然后页面会一点点被绘制出来。

影响第一个阶段的因素主要是网络或者是服务器处理这块儿,前面文章中我们已经讲过了,这里我们就不再继续分析了。至于第三个阶段,我们会在后续文章中分析,所以这里也不做介绍了。

现在我们重点关注第二个阶段,这个阶段的主要问题是白屏时间,如果白屏时间过久,就会影响到用户体验。为了缩短白屏时间,我们来挨个分析这个阶段的主要任务,包括了解析HTML、下载CSS、下载JavaScript、生成CSSOM、执行JavaScript、生成布局树、绘制页面一系列操作。

通常情况下的瓶颈主要体现在下载CSS文件、下载JavaScript文件和执行JavaScript

所以要想缩短白屏时长,可以有以下策略:

  • 通过内联JavaScript、内联CSS来移除这两种类型的文件下载,这样获取到HTML文件之后就可以直接开始渲染流程了。

  • 但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过webpack等工具移除一些不必要的注释,并压缩JavaScript文件。

  • 还可以将一些不需要在解析HTML阶段使用的JavaScript标记上async或者defer。

  • 对于大的CSS文件,可以通过媒体查询属性,将其拆分为多个不同用途的CSS文件,这样只有在特定的场景下才会加载特定的CSS文件。

通过以上策略就能缩短白屏展示的时长了,不过在实际项目中,总是存在各种各样的情况,这些策略并不能随心所欲地去引用,所以还需要结合实际情况来调整最佳方案。

分层,合成,分块
  • 首先我们介绍了显示器显示图像的原理,以及帧和帧率的概念,然后基于帧和帧率我们又介绍渲染引擎是如何实现一帧图像的。通常渲染引擎生成一帧图像有三种方式:重排、重绘和合成。其中重排和重绘操作都是在渲染进程的主线程上执行的,比较耗时;而合成操作是在渲染进程的合成线程上执行的,执行速度快,且不占用主线程。

  • 然后我们重点介绍了浏览器是怎么实现合成的,其技术细节主要可以使用三个词来概括:分层、分块和合成。

  • 最后我们还讲解了CSS动画比JavaScript动画高效的原因,以及怎么使用 will-change来优化动画或特效。

页面性能:如何系统地优化页面?

这里我们所谈论的页面优化,其实就是要让页面更快地显示和响应。由于一个页面在它不同的阶段,所侧重的关注点是不一样的,所以如果我们要讨论页面优化,就要分析一个页面生存周期的不同阶段。

通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段

  • 加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和JavaScript脚本。

  • 交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是JavaScript脚本。

  • 关闭阶段,主要是用户发出关闭指令后页面所做的一些清理操作。

1638240695554

通过前面文章的讲解,你应该已经知道了并非所有的资源都会阻塞页面的首次绘制,比如图片、音频、视频等文件就不会阻塞页面的首次渲染;而JavaScript、首次请求的HTML资源文件、CSS文件是会阻塞首次渲染的,因为在构建DOM的过程中需要HTML和JavaScript文件,在构造渲染树的过程中需要用到CSS文件。

我们把这些能阻塞网页首次渲染的资源称为关键资源。基于关键资源,我们可以继续细化出来三个影响页面首次渲染的核心因素。

第一个是关键资源个数。关键资源个数越多,首次页面的加载时间就会越长。比如上图中的关键资源个数就是3个,1个HTML文件、1个JavaScript和1个CSS文件。

第二个是关键资源大小。通常情况下,所有关键资源的内容越小,其整个资源的下载时间也就越短,那么阻塞渲染的时间也就越短。上图中关键资源的大小分别是6KB、8KB和9KB,那么整个关键资源大小就是23KB。

第三个是请求关键资源需要多少个RTT(Round Trip Time)。那什么是RTT呢? 在《02 | TCP协议:如何保证页面文件能被完整送达浏览器?》这篇文章中我们分析过,当使用TCP协议传输一个文件时,比如这个文件大小是0.1M,由于TCP的特性,这个数据并不是一次传输到服务端的,而是需要拆分成一个个数据包来回多次进行传输的。RTT就是这里的往返时延。它是网络中一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。通常1个HTTP的数据包在14KB左右,所以1个0.1M的页面就需要拆分成8个包来传输了,也就是说需要8个RTT。

总的优化原则就是减少关键资源个数,降低关键资源大小,降低关键资源的RTT次数

如何减少关键资源的个数?一种方式是可以将JavaScript和CSS改成内联的形式,比如上图的JavaScript和CSS,若都改成内联模式,那么关键资源的个数就由3个减少到了1个。另一种方式,如果JavaScript代码没有DOM或者CSSOM的操作,则可以改成async或者defer属性;同样对于CSS,如果不是在构建页面之前加载的,则可以添加媒体取消阻止显现的标志。当JavaScript标签加上了async或者defer、CSSlink 属性之前加上了取消阻止显现的标志后,它们就变成了非关键资源了。

如何减少关键资源的大小?可以压缩CSS和JavaScript资源,移除HTML、CSS、JavaScript文件中一些注释内容,也可以通过前面讲的取消CSS或者JavaScript中关键资源的方式。

如何减少关键资源RTT的次数?可以通过减少关键资源的个数和减少关键资源的大小搭配来实现。除此之外,还可以使用CDN来减少每次RTT时长。

在优化实际的页面加载速度时,你可以先画出优化之前关键资源的图表,然后按照上面优化关键资源的原则去优化,优化完成之后再画出优化之后的关键资源图表。

如何系统优化加载阶段和交互阶段的页面。

在加载阶段,核心的优化原则是:优化关键资源的加载速度,减少关键资源的个数,降低关键资源的RTT次数。

在交互阶段,核心的优化原则是:尽量减少一帧的生成时间。可以通过减少单次JavaScript的执行时间、避免强制同步布局、避免布局抖动、尽量采用CSS的合成动画、避免频繁的垃圾回收等方式来减少一帧生成的时长。

虚拟DOM:虚拟DOM和实际的DOM有何不同? 什么是虚拟DOM

在谈论什么是虚拟DOM之前,我们先来看看虚拟DOM到底要解决哪些事情。

  • 将页面改变的内容应用到虚拟DOM上,而不是直接应用到DOM上。

  • 变化被应用到虚拟DOM上时,虚拟DOM并不急着去渲染页面,而仅仅是调整虚拟DOM的内部状态,这样操作虚拟DOM的代价就变得非常轻了。

  • 在虚拟DOM收集到足够的改变时,再把这些变化一次性应用到真实的DOM上。

1638241290755

分析了直接操作DOM会触发渲染流水线的一系列反应,如果对DOM操作不当的话甚至还会触发强制同步布局和布局抖动的问题,这也是我们在操作DOM时需要非常小心谨慎的原因。

在此分析的基础上,我们介绍了虚拟DOM是怎么解决直接操作DOM所带来的问题以及React Fiber更新机制。

要聊前端框架,就绕不开设计模式,所以接下来我们又从双缓存和MVC角度分析了虚拟DOM。双缓存是一种经典的思路,应用在很多场合,能解决页面无效刷新和闪屏的问题,虚拟DOM就是双缓存思想的一种体现。而基于MVC的设计思想也广泛地渗透到各种场合,并且基于MVC又衍生出了很多其他模式(如MVP、MVVM等),不过万变不离其宗,它们的基础骨架都是基于MVC而来。站在MVC视角来理解虚拟DOM能让你看到更为“广阔的世界”。

渐进式网页应用(PWA):它究竟解决了Web应用的哪些问题?

在专栏开篇词中,我们提到过浏览器的三大进化路线:

第一个是应用程序Web化;

第二个是Web应用移动化;

第三个是Web操作系统化;

其中,第二个Web应用移动化是Google梦寐以求而又一直在发力的一件事,不过对于移动设备来说,前有本地App,后有移动小程序,想要浏览器切入到移动端是相当困难的一件事,因为浏览器的运行性能是低于本地App的,并且Google也没有类似微信或者Facebook这种体量的用户群体。

但是要让浏览器切入到移动端,让其取得和原生应用同等待遇可是Google的梦想,那该怎么做呢?

这就是我们本节要聊的PWA。那什么是PWA?PWA又是以什么方式切入到移动端的呢?

PWA,全称是Progressive Web App,翻译过来就是渐进式网页应用。根据字面意思,它就是“渐进式+Web应用”。对于Web应用很好理解了,就是目前我们普通的Web页面,所以PWA所支持的首先是一个Web页面。至于“渐进式”,就需要从下面两个方面来理解。

站在Web应用开发者来说,PWA提供了一个渐进式的过渡方案,让Web应用能逐步具有本地应用的能力。采取渐进式可以降低站点改造的代价,使得站点逐步支持各项新技术,而不是一步到位。

站在技术角度来说,PWA技术也是一个渐进式的演化过程,在技术层面会一点点演进,比如逐渐提供更好的设备特性支持,不断优化更加流畅的动画效果,不断让页面的加载速度变得更快,不断实现本地应用的特性。

从这两点可以看出来,PWA采取的是非常一个缓和的渐进式策略,不再像以前那样激进,动不动就是取代本地App、取代小程序。与之相反,而是要充分发挥Web的优势,渐进式地缩短和本地应用或者小程序的距离。

那么Web最大的优势是什么呢?我认为是自由开放,也正是因为自由和开放,所以大家就很容易对同一件事情达成共识,达成共识之后,一套代码就可以运行在各种设备之上了,这就是跨平台,这也恰恰是本地应用所不具备的。而对于小程序,倒是可以实现跨平台,但要让各家达成共识,目前来看,似乎还是非常不切实际的。

PWA的定义就是:它是一套理念,渐进式增强Web的优势,并通过技术手段渐进式缩短和本地应用或者小程序的距离。

我们先分析了PWA,它是由很多技术组成的一个理念,其核心思想是渐进式。对于开发者,它提供了非常温和的方式,让开发者将普通的站点逐步过渡到Web应用。对于技术本身而言,它是渐进式演进,逐渐将Web技术发挥到极致的同时,也逐渐缩小和本地应用的差距。在此基础上,我们又分析了PWA中的Service Worker的设计思路。

另外,PWA 还提供了 manifest.json 配置文件,可以让开发者自定义桌面的图标、显示名称、启动方式等信息,还可以设置启动画面、页面主题颜色等信息。关于manifest.json的配置还是比较简单的,详细使用教程网上有很多,这里我就不做介绍了。

添加桌面标、增加离线缓存、增加消息推送等功能是PWA走向设备的必备功能,但我认为真正决定PWA能否崛起的还是底层技术,比如页面渲染效率、对系统设备的支持程度、WebAssembly等,而这些技术也在渐进式进化过程中。所以未来如何,我们拭目以待。

WebComponent:像搭积木一样构建Web应用

首先,我们介绍了组件化开发是程序员的刚需,所谓组件化就是功能模块要实现高内聚、低耦合的特性。不过由于DOM和CSSOM都是全局的,所以它们是影响了前端组件化的主要元素。基于这个原因,就出现WebComponent,它包含自定义元素、影子DOM和HTML模板三种技术,使得开发者可以隔离CSS和DOM。在此基础上,我们还重点介绍了影子DOM到底是怎么实现的。

关于WebComponent的未来如何,这里我们不好预测和评判,但是有一点可以肯定,WebComponent也会采用渐进式迭代的方式向前推进,未来依然有很多坑需要去填。

浏览器安全

在没有安全保障的Web世界中,我们是没有隐私的,因此需要安全策略来保障我们的隐私和数据的安全。

什么是同源策略

如果两个URL的协议、域名和端口都相同,我们就称这两个URL同源

第一个,DOM层面。同源策略限制了来自不同源的JavaScript脚本对当前DOM对象读和写的操作。 当我们在InfoQ的页面中访问极客时间页面中的DOM时,页面抛出了如下的异常信息,这就是同源策略所发挥的作用。Blocked a frame with origin "https://www.infoq.cn" from accessing a cross-origin frame.

第二个,数据层面。同源策略限制了不同源的站点读取当前站点的Cookie、IndexDB、LocalStorage等数据。由于同源策略,我们依然无法通过第二个页面的opener来访问第一个页面中的Cookie、IndexDB或者LocalStorage等内容。你可以自己试一下,这里我们就不做演示了。

第三个,网络层面。同源策略限制了通过XMLHttpRequest等方式将站点的数据发送给不同源的站点。你还记得在《17 | WebAPI:XMLHttpRequest是怎么实现的?》这篇文章的末尾分析的XMLHttpRequest在使用过程中所遇到的坑吗?其中第一个坑就是在默认情况下不能访问跨域的资源。

同源策略会隔离不同源的DOM、页面数据和网络通信,进而实现Web页面的安全性。

不过鱼和熊掌不可兼得,要绝对的安全就要牺牲掉便利性,因此我们要在这二者之间做权衡,找到中间的一个平衡点,也就是目前的页面安全策略原型。总结起来,它具备以下三个特点:

页面中可以引用第三方资源,不过这也暴露了很多诸如XSS的安全问题,因此又在这种开放的基础之上引入了CSP来限制其自由程度。

使用XMLHttpRequest和Fetch都是无法直接进行跨域请求的,因此浏览器又在这种严格策略的基础之上引入了跨域资源共享策略,让其可以安全地进行跨域操作。

两个不同源的DOM是不能相互操纵的,因此,浏览器中又实现了跨文档消息机制,让其可以比较安全地通信。

什么是XSS攻击

XSS全称是Cross Site Scripting,为了与“CSS”区分开来,故简称XSS,翻译过来就是“跨站脚本”。XSS攻击是指黑客往HTML文件中或者DOM中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。最开始的时候,这种攻击是通过跨域来实现的,所以叫“跨域脚本”。但是发展到现在,往HTML文件中注入恶意代码的方式越来越多了,所以是否跨域注入脚本已经不是唯一的注入手段了,但是XSS这个名字却一直保留至今。

当页面被注入了恶意JavaScript脚本时,浏览器无法区分这些脚本是被恶意注入的还是正常的页面内容,所以恶意注入JavaScript脚本也拥有所有的脚本权限。下面我们就来看看,如果页面被注入了恶意JavaScript脚本,恶意脚本都能做哪些事情。

  • 可以窃取Cookie信息。恶意JavaScript可以通过“document.cookie”获取Cookie信息,然后通过XMLHttpRequest或者Fetch加上CORS功能将数据发送给恶意服务器;恶意服务器拿到用户的Cookie信息之后,就可以在其他电脑上模拟用户的登录,然后进行转账等操作。

  • 可以监听用户行为。恶意JavaScript可以使用“addEventListener”接口来监听键盘事件,比如可以获取用户输入的信用卡等信息,将其发送到恶意服务器。黑客掌握了这些信息之后,又可以做很多违法的事情。

  • 可以通过修改DOM伪造假的登录窗口,用来欺骗用户输入用户名和密码等信息。

  • 还可以在页面内生成浮窗广告,这些广告会严重地影响用户体验。

除了以上几种情况外,恶意脚本还能做很多其他的事情,这里就不一一介绍了。总之,如果让页面插入了恶意脚本,那么就相当于把我们页面的隐私数据和行为完全暴露给黑客了。

XSS攻击就是黑客往页面中注入恶意脚本,然后将页面的一些重要数据上传到恶意服务器。常见的三种XSS攻击模式是存储型XSS攻击、反射型XSS攻击和基于DOM的XSS攻击。

这三种攻击方式的共同点是都需要往用户的页面中注入恶意脚本,然后再通过恶意脚本将用户数据上传到黑客的恶意服务器上。而三者的不同点在于注入的方式不一样,有通过服务器漏洞来进行注入的,还有在客户端直接注入的。

针对这些XSS攻击,主要有三种防范策略,第一种是通过服务器对输入的内容进行过滤或者转码,第二种是充分利用好CSP,第三种是使用HttpOnly来保护重要的Cookie信息。

当然除了以上策略之外,我们还可以通过添加验证码防止脚本冒充用户提交危险操作。而对于一些不受信任的输入,还可以限制其输入长度,这样可以增大XSS攻击的难度。

什么是CSRF攻击

CSRF英文全称是Cross-site request forgery,所以又称为“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。简单来讲,CSRF攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事。

和XSS不同的是,CSRF攻击不需要将恶意代码注入用户的页面,仅仅是利用服务器的漏洞和用户的登录状态来实施攻击。 其最关键的一点是要能找到服务器的漏洞,所以说对于CSRF攻击我们主要的防护手段是提升服务器的安全性。

安全沙箱:页面和系统之间的隔离墙

首先我们分析了单进程浏览器在系统安全方面的不足,如果浏览器存在漏洞,那么黑客就有机会通过页面对系统发起攻击。

因此在设计现代浏览器的体系架构时,就考虑到这个问题了。于是,在多进程的基础之上引入了安全沙箱,有了安全沙箱,就可以将操作系统和渲染进程进行隔离,这样即便渲染进程由于漏洞被攻击,也不会影响到操作系统的。

由于渲染进程采用了安全沙箱,所以在渲染进程内部不能与操作系统直接交互,于是就在浏览器内核中实现了持久存储、网络访问和用户交互等一系列与操作系统交互的功能,然后通过IPC和渲染进程进行交互。

最后我们还分析了Chrome中最新的站点隔离功能。由于最初都是按照标签页来划分渲染进程的,所以如果一个标签页里面有多个不同源的iframe,那么这些iframe也会被分配到同一个渲染进程中,这样就很容易让黑客通过iframe来攻击当前渲染进程。而站点隔离会将不同源的iframe分配到不同的渲染进程中,这样即使黑客攻击恶意iframe的渲染进程,也不会影响到其他渲染进程的。

HTTPS:让数据传输更安全

由于HTTP的明文传输特性,在传输过程中的每一个环节,数据都有可能被窃取或者篡改,这倒逼着我们需要引入加密机制。于是我们在HTTP协议栈的TCP和HTTP层之间插入了一个安全层,负责数据的加密和解密操作。

我们使用对称加密实现了安全层,但是由于对称加密的密钥需要明文传输,所以我们又将对称加密改造成了非对称加密。但是非对称加密效率低且不能加密服务器到浏览器端的数据,于是我们又继续改在安全层,采用对称加密的方式加密传输数据和非对称加密的方式来传输密钥,这样我们就解决传输效率和两端数据安全传输的问题。

采用这种方式虽然能保证数据的安全传输,但是依然没办法证明服务器是可靠的,于是又引入了数字证书,数字证书是由CA签名过的,所以浏览器能够验证该证书的可靠性。

另外百看不如一试,我建议你自己亲手搭建一个HTTPS的站点,可以去freeSSL申请免费证书。链接我已经放在文中了:

中文:https://freessl.cn/

英文:https://www.freessl.com/

什么时候开始都不晚——沃尔舅·硕德
上一篇:Kafka最佳实践-Kafka常见的使用误区
下一篇:没有了
网友评论