记得很久之前看过谷歌官方有这么样的声明:如果一个页面的加载时间从 1 秒增加到3 秒,那么用户跳出的概率将增加 32%。
但是早在 2012 年,亚马逊就计算出了,页面加载速度一旦下降一秒钟,每年就会损失 16 亿美元的销售额。于是,这篇文章就想聊聊有没有方法来解决这种问题。
那么,是什么影响了页面的加载速度,导致用户跳出?
其中有一个大的因素就是我们的应用用到了很多的第三方库。
那么,有没有一种一举两得的方法,我即可以保留使用的第三方脚本,又可以保证页面的加载速度?
其实,我们知道 JavaScript 本质上是一种单线程语言,只运行一个事件循环。这意味着一次只执行一条语句。由于这一限制,当试图运行自己的代码以及任何第三方脚本时,它们必须在同一线程中执行。这意味着由于处理能力的限制,它们会减慢主线程和彼此的速度,也会减慢彼此的速度。
根据谷歌的说法,添加第三方脚本后,有一些潜在的问题会产生,我列举了以下几点:
api
(例如 document.write()
),对用户体验是有害的。window.onload
的执行,例如使用 async或 defer
。这些问题都可以通过谷歌浏览器的 Analytics 工具检测出来。
现在,有一个改善第三方脚本的工具,能帮助我们的应用减少大量的第三方脚本,也是本篇文章要说的主角—— Partytown
。
Partytown 是一个 JavaScript 库,可以让你的第三方脚本交给 web worker
来处理,以消除他们可能对你的网站产生的性能影响。为了抵消上述第三方脚本的负面影响,Partytown 打算做以下事情:
DOM setter /getter
批处理到组更新中,减少来自第三方脚本的布局抖动。简单地说,Partytown 添加了一个 worker 线程来允许在主线程和 worker 线程中执行。
要理解 Partytown,首先要了解现代网络浏览器使用的一些技术:
传统上,主线程和 worker 线程之间的通信必须是异步的:因为为了让两个线程通信,它们不能使用阻塞调用。Partytown 则不同。它允许从 web worker 执行的代码同步访问 DOM。这样做的好处是第三方脚本可以继续按照它们的编码方式工作。
如下图所示,运行在代理全局变量的 web worker 中的代码使用同步 XHR 使异步操作同步化。这将被 service worker 拦截,主线程值将通过 postMessage
检索并发送回来。
你可以很容易地将它添加到任何站点,并使用 type="text/partytown"
标记任何你想要加载在 web worker 中的脚本。
需要注意的是,Partytown 并不会自动将所有脚本转移到 web worker上,而是采用了一种可选择的方法。最好的情况是,开发人员可以准确地选择哪些脚本应该使用Partytown,而所有其他脚本将保持不变。
Partytown可以使用任何 HTML 页面,不需要特定的框架。然而,为了让它更容易在各种框架或服务中使用,可以为它们的生态系统制作插件/包装器。
只有当特定脚本具有 type="text/ Partytown "
属性时,才会启用 Partytown。
<script type="text/partytown" src="third-parth.js"></script>
上面这段脚本执行时会产生几个事件:
<script/>
标签上的 type="text/partytown"
属性,脚本不能在主线程上运行。“onfetch”
处理程序来拦截特定的请求。document
的每次调用都是阻塞的。任何你添加 type="text/partytown"
的脚本都会在默认情况下加载到 web worker 中,但是可以完全访问全局变量。' type="text/partytown" '
属性做两件事:
[type="text/ Partytown "]
属性的元素。你会注意到,当一个脚本在web worker中执行后,它会得到一个更新的 type
属性: type="text/partytown-x"
。// run in the worker
fetch('/track', {
body: JSON.stringify({
url: window.location.href // run on the main thread
})
})
我们使用 JavaScript Proxy 向 worker 线程提供主线程全局变量,拦截它们并转发给主线程:
self.window = new Proxy({
get(key) {
return getFromMainThread(key)
}
})
这里是最好的部分是: 使用一个同步 XHR 请求来阻塞 worker 线程,并从主线程检索所需的值:
function getFromMainThread(prop) {
request.open('POST', '/proxytown', false)
request.send(null)
request.send(JSON.stringify({prop}))
return JSON.parse(request.reponseText)
}
现在我们可以使用 service worker 来拦截 /proxytown
请求,向主线程异步postMessage
以获取所需的值并返回它:
self.addEventListener('fetch', event=>{
if(request.url === '/proxytown') {
event.respondWith(new Promise(async resolve=>{
resolve(await getFromMainThread(event.request.json()))
}))
}
})
好了!你现在可以无缝地将一系列第三方脚本放到 web worker 中运行,从而消除主线程的性能成本。如果感兴趣,可以用一用。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。