关于浏览器架构:深入了解现代网络浏览器第-4-部分

27次阅读

共计 4270 个字符,预计需要花费 11 分钟才能阅读完成。

前言

本文是进击的大葱对 Mario Kosaka 写的 inside look at modern web browser 系列文章的翻译。这里的翻译不是指直译,而是联合集体的了解将作者想表白的意思表达出来,而且会尽量补充一些相干的内容来帮忙大家更好地了解。

达到合成线程的输出

这篇文章是探索 Chrome 外部工作原理的 四集系列文章 中的最初一篇了。在上一篇文章中,咱们探讨了一下浏览器渲染页面的过程以及学习了一些对于合成线程的常识,在本篇文章中,咱们要看一下当用户在网页上输出内容的时候,合成线程(compositor)做了些什么来保障晦涩的用户体验的。

从浏览器的角度来看输出事件

当你听到“输出事件”(input events)的时候,你可能只会想到用户在文本框中输出内容或者对页面进行了点击操作,可是从浏览器的角度来看的话,输出其实代表着来自于用户的任何手势动作(gesture)。所以用户 滚动页面 触碰屏幕 以及 挪动鼠标 等操作都能够看作来自于用户的输出事件。

当用户做了一些诸如触碰屏幕的手势动作时,浏览器过程(browser process)是第一个能够接管到这个事件的中央。可是浏览器过程只能晓得用户的手势动作产生在什么中央而不晓得如何解决,这是因为标签内(tab)的内容是由页面的渲染过程(render process)负责的。因而浏览器过程会将事件的类型(如touchstart)以及坐标(coordinates)发送给渲染过程。为了能够正确地解决这个事件,渲染过程会找到事件的指标对象(target)而后运行这个事件绑定的监听函数(listener)。

图 1:点击事件从浏览器过程路由到渲染过程

合成线程接管到输出事件

在上一篇文章中,咱们查看了合成线程是如何通过合并页面曾经光栅化好的层来保障晦涩滚动体验(scroll smoothly)的。如果以后页面不存在任何用户事件的监听器(event listener),合成线程齐全不须要主线程的参加就能创立一个新的合成帧来响应事件。可是如果页面有一些事件监听器(event listeners)呢?合成线程是如何判断出这个事件是否须要路由给主线程解决的呢?

图 2:视口悬停在页面图层上

理解非疾速滚动区域 – non-fast scrollable region

因为页面的 JavaScript 脚本是在主线程(main thread)中运行的,所以当一个页面被合成的时候,合成线程会将页面那些注册了事件监听器的区域标记为“非疾速滚动区域”(Non-fast Scrollable Region)。因为晓得了这些信息,当用户事件产生在这些区域时,合成线程会将输出事件发送给主线程来解决。如果输出事件不是产生在非疾速滚动区域,合成线程就毋庸主线程的参加来合成一个新的帧。

图 3:非疾速滚动区域有用户事件产生时的示意图

当你写事件监听器的时候留点心眼

Web 开发的一个常见的模式是事件委托(event delegation)。因为事件会冒泡,你能够给顶层的元素绑定一个事件监听函数来作为其所有子元素的事件委托者,这样子节点的事件就能够对立被顶层的元素解决了。因而你可能看过或者写过相似于上面的代码:

document.body.addEventListener('touchstart', event => {if (event.target === area) {event.preventDefault()
  }
})

只用一个事件监听器就能够服务到所有的元素,乍一看这种写法还是挺实惠的。可是,如果你从浏览器的角度去看一下这段代码,你会发现下面给 body 元素绑定了事件监听器后其实是将整个页面都标记为一个非疾速滚动区域,这就意味着即便你页面的某些区域压根就不在乎是不是有用户输出,当用户输出事件产生时,合成线程每次都会告知主线程并且会期待主线程解决完它才干活。因而这种状况下合成线程就丢失提供晦涩用户体验的能力了(smooth scrolling ability)。

图 4:当整个页面都是非疾速滚动区域时页面的事件处理示意图

为了加重这种状况的产生,您能够为事件监听器传递 passive:true 选项。这个选项会通知浏览器您仍要在主线程中侦听事件,可是合成线程也能够持续合成新的帧。

document.body.addEventListener('touchstart', event => {if (event.target === area) {event.preventDefault()
    }
 }, {passive: true});

passive:false,浏览器执行完回调函数才晓得有没有调用 preventDefault,如果没有调用preventDefault,再去执行默认行为,就是滚动,这样就会造成滚动不晦涩。
passive:true,就是通知浏览器不会调用preventDefault,浏览器间接执行滚动就行,不必思考回调函数了。这时,即便你在回调函数里调用preventDefault 也不会失效。
为了防止这一问题,大部分浏览器(SafariInternet Explorer 除外)将文档级节点 WindowDocumentDocument.body 上的 wheelmousewheeltouchstarttouchmove 事件的 passive 默认值更改为 true。如此,事件监听器便不能取消事件,也不会在用户滚动页面时阻止页面出现。
还是要揭示大家,在你不须要调用 preventDefault 的时候,监听 scroll 或者 touchmove,将passive 设置为true

preventDefault 与浏览器

对于一个滚动事件mousewheel(非标准)/touch(挪动端事件)/wheel(规范)来说,浏览器须要先去调用他的回调函数,而后再去执行默认行为(在这里是滚动)。然而执行回调函数是须要工夫的,如果回调函数中放了一个耗时的循环,它就会期待这个循环执行完再去滚动,那么页面体现就是滑动滚轮后的一段时间页面才会滚动,会有很显著的提早感。

除此之外,因为执行回调函数须要浏览器的主线程,因而当主线程忙着执行其余工作时就会来不及执行回调函数,页面同样会呈现提早感。

应用 passive 改善滚屏性能

查找事件的指标对象(event target)

当合成线程向主线程发送输出事件时,主线程要做的第一件事是通过命中测试(hit test)去找到事件的指标对象(target)。具体的命中测试流程是遍历在渲染流水线中生成的绘画记录(paint records)来找到输出事件呈现的 x, y 坐标下面描述的对象是哪个。

图 5:主线程通过遍历绘画记录来确定在 x,y 坐标上的是哪个对象

最小化发送给主线程的事件数

上一篇文章中咱们有说过显示器的刷新频率通常是一秒钟 60 次以及咱们能够通过让 JavaScript 代码的执行频率和屏幕刷新频率保持一致来实现页面的平滑动画成果(smooth animation)。对于用户输出来说,触摸屏个别一秒钟会触发 60 到 120 次点击事件,而鼠标个别则会每秒触发 100 次事件,因而输出事件的触发频率其实远远高于咱们屏幕的刷新频率。

如果每秒将诸如 touchmove 这种间断被触发的事件发送到主线程 120 次,因为屏幕的刷新速度相对来说比较慢,它可能会触发适量的点击测试以及 JavaScript 代码的执行。

图 6:事件吞没了屏幕刷新的时间轴,导致页面很卡顿

为了最大水平地缩小对主线程的过多调用,Chrome 会合并间断事件(例如 wheelmousewheelmousemovepointermovetouchmove),并将调度提早到下一个requestAnimationFrame 之前。

图 7:和之前雷同的事件轴,可是这次事件被合并并提早调度了

任何诸如 keydownkeyupmouseupmousedowntouchstarttouchend等绝对不怎么频繁产生的事件都会被立刻派送给主线程。

应用 getCoalesecedEvents 来获取帧内(intra-frame)事件

对于大多数 web 利用来说,合并事件应该曾经足够用来提供很好的用户体验了,然而,如果你正在构建的是一个依据用户的 touchmove 坐标来进行绘图的利用的话,合并事件可能会使页面画的线不够顺畅和间断。在这种状况下,你能够应用鼠标事件的 getCoalescedEvents 来获取被合成的事件的详细信息。

图 8:右边是顺畅的触摸手势,左边是事件合成后不那么间断的手势

window.addEventListener('pointermove', event => {const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

下一步

这本系列的文章中,咱们以 Chrome 浏览器为例子探讨了浏览器的外部工作原理。如果你之前素来没有想过为什么 DevTools 举荐你在事件监听器中应用 passive:true 选项或者在 script 标签中写 async 属性的话,我心愿这个系列的文章能够给你一些对于浏览器为什么须要这些信息来提供更快更晦涩的用户体验的起因。

学习如何掂量性能

不同网站的性能调整可能会有所不同,你要本人掂量本人网站的性能并确定最适宜晋升你的网站性能的计划。你能够查看 Chrome DevTools 团队的一些教程来学习如何能力掂量本人网站的性能。

为你的站点增加 Feature Policy

如果你想更进一步,你能够理解一下 Feature Policy 这个新的 Web 平台性能,这个性能能够在你构建我的项目的时候提供一些爱护让您的应用程序具备某些行为并避免你犯下谬误。例如,如果你想确保你的利用代码不会阻塞页面的解析(parsing),你能够在同步脚本策略(synchronius scripts policy)中运行你的利用。具体做法是将 sync-script 设置为 ’none’,这样那些会阻塞页面解析的 JavaScript 代码会被禁止执行。这样做的益处是防止你的代码阻塞页面的解析,而且浏览器毋庸放心解析器(parser)暂停。

总结

以上就是所有和浏览器架构和运行原理相干的内容了,咱们当前在开发 web 利用的时候,不应该只思考代码的优雅性,还要多多从浏览器是如何解析运行咱们的代码的方面进行思考,从而为用户提供更好的用户体验。

正文完
 0