详解大型网站的前端性能优化思路
每个参与过开发企业级web应用的前端工程师或许都曾思考过前端性能优化方面的问题,经验丰富的工程师对于前端性能优化方法耳濡目染,基本都能一一列举出来,这些性能优化原则大概是在7年前提出的,对于web性能优化至今都有非常重要的指导意义。
对于构建大型web应用的团队来说,要坚持贯彻这些优化原则并不是一件十分容易的事,因为优化原则中很多要求与工程管理相违背,比如“把css放在头部”和“把js放在尾部”这两条原则,我们不能让整个团队的工程师在写样式和脚本引用的时候都去修改同一份的页面文件,这会严重影响团队成员间并行开发的效率,尤其是在团队有版本管理的情况下,每天要花大量的时间进行代码修改合并,这项成本是难以接受的。
因此在前端工程界,总会看到周期性的性能优化工作,辛勤的前端工程师们每到月圆之夜就会倾巢出动根据优化原则做一次最佳实践。
今天,马海祥就从一个全新的视角来思考web性能优化与前端工程之间的关系,通过解读百度前端集成解决方案小组(F.I.S)在打造高性能前端架构并统一百度40多条前端产品线的过程中所经历的技术尝试,揭示前端性能优化在前端架构及开发工具设计层面的实现思路。
一、大型网站的性能优化原则及分类
先假设本文的读者是有前端开发经验的工程师,并对企业级web应用开发及性能优化有一定的思考,因此我不会重复介绍雅虎14条性能优化原则,如果您没有这些前续知识的,请移步这里来学习。
首先,我们把《雅虎十四条:网站前端网页优化的14条原则》中提到的优化点做一次梳理,如果按照优化方向分类可以得到这样一张表格:
目前,大多数前端团队可以利用压缩工具很容易做到“精简javascript”这条原则,同样的,也可以使用图片压缩工具对图像进行压缩,实现“图像优化”原则,这两条原则是对单个资源的处理,因此不会引起任何工程方面的问题。
很多团队也通过引入代码校验流程来确保实现“避免css表达式”和“避免重定向”原则;目前绝大多数互联网公司也已经开启了服务端的Gzip压缩,并使用CDN实现静态资源的缓存和快速访问;一些技术实力雄厚的前端团队甚至研发出了自动CSS Sprites工具,解决了CSS Sprites在工程维护方面的难题,使用“查找 - 替换”思路,我们似乎也可以很好的实现“划分主域”原则。
我们把以上这些已经成熟应用到实际生产中的优化手段去除掉,留下那些还没有很好实现的优化原则,再来回顾一下之前的性能优化分类:
诚然,不可否认现在有很多顶尖的前端团队可以将上述还剩下的优化原则也都一一解决,但业界大多数团队都还没能很好的解决这些问题,因此,接下来马海祥将就这些原则的解决方案做进一步的分析与讲解,从而为那些还没有进入前端工业化开发的团队提供一些基础技术建设意见,也借此机会与业界顶尖的前端团队在工业化工程化方向上交流一下彼此的心得。
二、静态资源版本更新与缓存
如上面第一条中的表格所示,在“缓存利用”分类中保留了“添加Expires头”和“配置ETag”两项,或许有些人会质疑,明明这两项只要配置了服务器的相关选项就可以实现,为什么说它们难以解决呢?
确实,开启这两项很容易,但开启了缓存后,我们的项目就开始面临另一个挑战:如何更新这些缓存?
相信大多数团队也找到了类似的答案,它和“高性能网站建设指南”关于“添加Expires头”所说的原则一样——修订文件名。
思路没错,但要怎么改变链接呢?变成什么样的链接才能有效更新缓存,又能最大限度避免那些没有修改过的文件缓存不失效呢?
先来看看现在一般前端团队的做法:
<script type="text/javascript" src="a.js?t=20130825"></script>
或者:
<script type="text/javascript" src="a.js?v=1.0.0"></script>
大家会采用添加query的形式修改链接,这样做是比较直观的解决方案,但在访问量较大的网站,这么做可能将面临一些新的问题。
通常一个大型的web应用几乎每天都会有迭代和更新,发布新版本也就是发布新的静态资源和页面的过程,以上述代码为例,假设现在线上运行着index.html文件,并且使用了线上的a.js资源。
index.html的内容为:
<script type="text/javascript" src="a.js?v=1.0.0"></script>
这次我们更新了页面中的一些内容,得到一个index.html文件,并开发了新的与之匹配的a.js资源来完成页面交互,新的index.html文件的内容因此而变成了:
<script type="text/javascript" src="a.js?v=1.0.1"></script>
好了,现在要开始将两份新的文件发布到线上去,可以看到,a.html和a.js的资源实际上是要覆盖线上的同名文件的,不管怎样,在发布的过程中,index.html和a.js总有一个先后的顺序,从而中间出现一段或大或小的时间间隔。
对于一个大型互联网应用来说即使在一个很小的时间间隔内,都有可能出现新用户访问,而在这个时间间隔中访问了网站的用户会发生什么情况呢?具体来说有以下2点:
1、如果先覆盖index.html,后覆盖a.js,用户在这个时间间隙访问,会得到新的index.html配合旧的a.js的情况,从而出现错误的页面。
2、如果先覆盖a.js,后覆盖index.html,用户在这个间隙访问,会得到旧的index.html配合新的a.js 的情况,从而也出现了错误的页面。
这就是为什么大型web应用在版本上线的过程中经常会较集中的出现前端报错日志的原因,也是一些互联网公司选择加班到半夜等待访问低峰期再上线的原因之一。
此外,由于静态资源文件版本更新是“覆盖式”的,而页面需要通过修改query来更新,对于使用CDN缓存的web产品来说,还可能面临CDN缓存攻击的问题,我们再来观察一下前面说的版本更新手段:
<script type="text/javascript" src="a.js?v=1.0.0"></script>
我们不难预测,a.js的下一个版本是“1.0.1”,那么就可以刻意构造一串这样的请求“a.js?v=1.0.1”、“a.js?v=1.0.2”、……,让CDN将当前的资源缓存为“未来的版本”。
这样当这个页面所用的资源有更新时,即使更改了链接地址,也会因为CDN的原因返回给用户旧版本的静态资源,从而造成页面错误,即便不是刻意制造的攻击,在上线间隙出现访问也可能导致区域性的CDN缓存错误。
此外,当版本有更新时,修改所有引用链接也是一件与工程管理相悖的事,至少我们需要一个可以“查找 - 替换”的工具来自动化的解决版本号修改的问题。
对付这个问题,马海祥觉得目前来说最优方案就是基于文件内容的hash版本冗余机制了,也就是说,我们希望工程师源码是这么写的:
<script type="text/javascript" src="a.js"></script>
但是线上代码是这样的:
<script type="text/javascript" src="a_8244e91.js"></script>
其中“82244e91”这串字符是根据a.js的文件内容进行hash运算得到的,只有文件内容发生变化了才会有更改,由于版本序列是与文件名写在一起的,而不是同名文件覆盖,因此不会出现上述说的那些问题,那么,这么做都有哪些好处呢?
1、线上的a.js不是同名文件覆盖,而是文件名+h ash的冗余,所以可以先上线静态资源,再上线html页面,不存在间隙问题。
2、遇到问题回滚版本的时候,无需回滚a.js,只须回滚页面即可。
3、由于静态资源版本号是文件内容的hash,因此所有静态资源可以开启永久强缓存,只有更新了内容的文件才会缓存失效,缓存利用率大增。
4、修改静态资源后会在线上产生新的文件,一个文件对应一个版本,因此不会受到构造CDN缓存形式的攻击。
虽然这种方案是相比之下最完美的解决方案,但它无法通过手工的形式来维护,因为要依靠手工的形式来计算和替换hash值并生成相应的文件将是一项非常繁琐且容易出错的工作,因此,我们需要借助工具,有了这样的思路,我们下面就来了解一下fis是如何完成这项工作的。
首先,之所以有这种工具需求,完全是因为web应用运行的根本机制决定的,web应用所需的资源是以字面的形式通知浏览器下载而聚合在一起运行的,这种资源加载策略使得web应用从本质上区别于传统桌面应用的版本更新方式,也是大型web应用需要工具处理的最根本原因。
为了实现资源定位的字面量替换操作,前端构建工具理论上需要识别所有资源定位的标记,其中包括:
1、css中的@import url(path)、background:url(path)、backgournd-image:url(path)、filter 中的src。
2、js中的自定义资源定位函数,在fis中我们将其规定为__uri(path)。
3、html中的<script src=”path”>、<link href=”path”>、<img src=”path”>、已经embed、audio、video、object等具有资源加载功能的标签。
为了工程上的维护方便,我们希望工程师在源码中写的是相对路径,而工具可以将其替换为线上的绝对路径,从而避免相对路径定位错误的问题(比如js中需要定位图片路径时不能使用相对路径的情况)。
fis有一个非常棒的资源定位系统,它是根据用户自己的配置来指定资源发布后的地址,然后由fis的资源定位系统识别文件中的定位标记,计算内容hash,并根据配置替换为上线后的绝对url路径。
要想实现具备hash版本生成功能的构建工具不是“查找 - 替换”这么简单的,我们考虑这样一种情况:
由于我们的资源版本号是通过对文件内容进行hash运算得到,如上图所示,index.html中引用的a.css文件的内容其实也包含了a.png的hash运算结果,因此我们在修改index.html中a.css的引用时,不能直接计算a.css的内容hash,而是要先计算出a.png的内容hash,替换a.css中的引用,得到了a.css的最终内容,再做hash运算,最后替换index.html中的引用。
这意味着构建工具需要具备“递归编译”的能力,这也是为什么fis团队不得不放弃gruntjs等task-based系统的根本原因,针对前端项目的构建工具必须是具备递归处理能力的,此外,由于文件之间的交叉引用等原因,fis构建工具还实现了构建缓存等机制,以提升构建速度。
在解决了基于内容hash的版本更新问题之后,我们可以将所有前端静态资源开启永久强缓存,每次版本发布都可以首先让静态资源全量上线,再进一步上线模板或者页面文件,再也不用担心各种缓存和时间间隙的问题了!
三、静态资源管理与模板框架
让我们再来看看前面的优化原则表还剩些什么:
很不幸,剩下的优化原则都不是使用工具就能很好实现的,或许有人会辩驳:“我用某某工具可以实现脚本和样式表合并”。
嗯,必须承认,使用工具进行资源合并并替换引用或许是一个不错的办法,但在大型web应用,这种方式有一些非常严重的缺陷,来看一个很熟悉的例子:
某个web产品页面有A、B、C三个资源:
工程师根据“减少 HTTP 请求”的优化原则合并了资源:
产品经理要求C模块按需出现,此时C资源已出现多余的可能:
C模块不再需要了,注释掉吧!但C资源通常不敢轻易剔除:
不知不觉中,性能优化变成了性能恶化……
事实上,使用工具在线下进行静态资源合并是无法解决资源按需加载的问题的,如果解决不了按需加载,则势必会导致资源的冗余。
此外,线下通过工具实现的资源合并通常会使得资源加载和使用的分离,比如在页面头部或配置文件中写资源引用及合并信息,而用到这些资源的html组件写在了页面其他地方,这种书写方式在工程上非常容易引起维护不同步的问题,导致使用资源的代码删除了,引用资源的代码却还在的情况。
因此,在工业上要实现资源合并至少要满足如下需求:
1、确实能减少HTTP请求,这是基本要求(合并)。
2、在使用资源的地方引用资源(就近依赖),不使用不加载(按需)。
3、虽然资源引用不是集中书写的,但资源引用的代码最终还能出现在页面头部(css)或尾部(js)。
4、能够避免重复加载资源(去重)。
将以上要求综合考虑,不难发现,单纯依靠前端技术或者工具处理的是很难达到这些理想要求的,现代大型web应用所展示的页面绝大多数都是使用服务端动态语言拼接生成的,有的产品使用模板引擎,比如:smarty、velocity,有的则干脆直接使用动态语言,比如:php、python,无论使用哪种方式实现,前端工程师开发的 html 绝大多数最终都不是以静态的 html 在线上运行的。
接下来马海祥会讲述一种新的模板架构设计,用以实现前面说到那些性能优化原则,同时满足工程开发和维护的需要,这种架构设计的核心思想就是:
考虑一段这样的页面代码:
<html>
<head>
<title>hello world</title>
<link rel="stylesheet" type="text/css" href="A.css">
<link rel="stylesheet" type="text/css" href="B.css">
<link rel="stylesheet" type="text/css" href="C.css">
</head>
<body>
<div>html of A</div>
<div>html of B</div>
<div>html of C</div>
</body>
</html>
根据资源合并需求中的第二项,我们希望资源引用与使用能尽量靠近,这样将来维护起来会更容易一些,因此,理想的源码是:
<html>
<head>
<title>hello world</title>
</head>
<body>
<link rel="stylesheet" type="text/css" href="A.css"><div>html of A</div>
<link rel="stylesheet" type="text/css" href="B.css"><div>html of B</div>
<link rel="stylesheet" type="text/css" href="C.css"><div>html of C</div>
</body>
</html>
当然,把这样的页面直接送达给浏览器用户是会有严重的页面闪烁问题的,所以我们实际上仍然希望最终页面输出的结果还是如最开始的截图一样,将css放在头部输出,这就意味着,页面结构需要有一些调整,并且有能力收集资源加载需求,那么我们考虑一下这样的源码:
<html>
<head>
<title>hello world</title>
<!--[CSS LINKS PLACEHOLDER]-->
</head>
<body>
{require name="A.css"}<div>html of A</div>
{require name="B.css"}<div>html of B</div>
{require name="C.css"}<div>html of C</div>
</body>
</html>
在页面的头部插入一个html注释“<!--[CSS LINKS PLACEHOLDER]-->”作为占位,而将原来字面书写的资源引用改成模板接口(require)调用,该接口负责收集页面所需资源,require接口实现非常简单,就是准备一个数组,收集资源引用,并且可以去重。
最后在页面输出的前一刻,我们将require在运行时收集到的“A.css”、“B.css”、“C.css”三个资源拼接成html标签,替换掉注释占位“<!--[CSS LINKS PLACEHOLDER]-->”,从而得到我们需要的页面结构(具体可查看马海祥博客《资深Web前端开发工程师教你如何优化CSS框架》的相关介绍)。
经过总结,我们发现模板层面只要实现三个开发接口,既可以比较完美的实现目前遗留的大部分性能优化原则,这三个接口分别是:
1、require(String id):收集资源加载需求的接口,参数是资源id。
2、widget(String template_id):加载拆分成小组件模板的接口,你可以叫它为load、component或者pagelet之类的,总之,我们需要一个接口把一个大的页面模板拆分成一个个的小部分来维护,最后在原来的大页面以组件为单位来加载这些小部件。
3、script(String code):收集写在模板中的 js 脚本,使之出现的页面底部,从而实现性能优化原则中的“将js放在页面底部”原则。
实现了这些接口之后,一个重构后的模板页面的源代码可能看起来就是这样的了:
<html>
<head>
<title>hello world</title>
<!--[CSS LINKS PLACEHOLDER]-->
{require name="jquery.js"}
{require name="bootstrap.css"}
</head>
<body>
{require name="A/A.css"}{widget name="A/A.tpl"}
{script}console.log('A loaded'){/script}
{require name="B/B.css"}{widget name="B/B.tpl"}
{require name="C/C.css"}{widget name="C/C.tpl"}
<!--[SCRIPTS PLACEHOLDER]-->
</body>
</html>
而最终在模板解析的过程中,资源收集与去重、页面script收集、占位符替换操作,最终从服务端发送出来的html代码为:
<html>
<head>
<title>hello world</title>
<link rel="stylesheet" type="text/css" href="bootstrap.css">
<link rel="stylesheet" type="text/css" href="A/A.css">
<link rel="stylesheet" type="text/css" href="B/B.css">
<link rel="stylesheet" type="text/css" href="C/C.css">
</head>
<body>
<div>html of A</div>
<div>html of B</div>
<div>html of C</div>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">console.log('A loaded');</script>
</body>
</html>
不难看出,我们目前已经实现了“按需加载”,“将脚本放在底部”,“将样式表放在头部”三项优化原则。
前面讲到静态资源在上线后需要添加hash戳作为版本标识,那么这种使用模板语言来收集的静态资源该如何实现这项功能呢?
答案是:静态资源依赖关系表,假设前面讲到的模板源代码所对应的目录结构为下图所示:
那么,我们可以使用工具扫描整个project目录,然后创建一张资源表,同时记录每个资源的部署路径,可以得到这样的一张表:
{
"res": {
"A/A.css": {
"uri": "/A/A_1688c82.css",
"type": "css"
},
"B/B.css": {
"uri": "/B/B_52923ed.css",
"type": "css"
},
"C/C.css": {
"uri": "/C/C_6dda653.css",
"type": "css"
},
"bootstrap.css": {
"uri": "bootstrap_08f2256.css",
"type": "css"
},
"jquery.js": {
"uri": "jquery_9155343.css",
"type": "js"
},
},
"pkg": {}
}
基于这张表,我们就很容易实现{require name=”id”}这个模板接口了,只须查表即可。
比如执行{require name=”jquery.js”},查表得到它的url是“/jquery_9151577.js”,声明一个数组收集起来就好了,这样,整个页面执行完毕之后,收集资源加载需求,并替换页面的占位符,即可实现资源的hash定位,得到:
<html>
<head>
<title>hello world</title>
<link rel="stylesheet" type="text/css" href="bootstrap_08f2256.css">
<link rel="stylesheet" type="text/css" href="A/A_1688c82.css">
<link rel="stylesheet" type="text/css" href="B/B_52923ed.css">
<link rel="stylesheet" type="text/css" href="C/C_6dda653.css">
</head>
<body>
<div>html of A</div>
<div>html of B</div>
<div>html of C</div>
<script type="text/javascript" src="jquery_9155343.js"></script>
<script type="text/javascript">console.log('A loaded');</script>
</body>
</html>
接下来,我们讨论如何在基于表的设计思想上是如何实现静态资源合并的,或许有些团队使用过combo服务,也就是我们在最终拼接生成页面资源引用的时候,并不是生成多个独立的link标签,而是将资源地址拼接成一个url路径,请求一种线上的动态资源合并服务,从而实现减少HTTP请求的需求,比如:
<html>
<head>
<title>hello world</title>
<link rel="stylesheet" type="text/css" href="/combo?files=bootstrap_08f2256.css,A/A_1688c82.css,B/B_52923ed.css,C/C_6dda653.css">
</head>
<body>
<div>html of A</div>
<div>html of B</div>
<div>html of C</div>
<script type="text/javascript" src="jquery_9155343.js"></script>
<script type="text/javascript">console.log('A loaded');</script>
</body>
</html>
这个“/combo?files=file1,file2,file3,…”的url请求响应就是动态combo服务提供的,它的原理很简单,就是根据get请求的files参数找到对应的多个文件,合并成一个文件来响应请求,并将其缓存,以加快访问速度。
这种方法很巧妙,有些服务器甚至直接集成了这类模块来方便的开启此项服务,这种做法也是大多数大型web应用的资源合并做法,但马海祥觉得它也存在一些缺陷:
1、浏览器有url长度限制,因此不能无限制的合并资源(具体可查看马海祥博客《如何以SEO的角度来优化网站的URL连接地址》的相关介绍)。
2、如果用户在网站内有公共资源的两个页面间跳转访问,由于两个页面的combo的url不一样导致用户不能利用浏览器缓存来加快对公共资源的访问速度。
对于上述第二条缺陷,可以举个例子来看说明:
(1)、假设网站有两个页面A和B;
(2)、A页面使用了a,b,c,d四个资源;
(3)、B页面使用了a,b,e,f四个资源;
(4)、如果使用combo服务,我们会得:
A页面的资源引用为:/combo?files=a,b,c,d
B页面的资源引用为:/combo?files=a,b,e,f
(5)、两个页面引用的资源是不同的url,因此浏览器会请求两个合并后的资源文件,跨页面访问没能很好的利用a、b这两个资源的缓存。
很明显,如果combo服务能聪明的知道A页面使用的资源引用为“/combo?files=a,b”和“/combo?files=c,d”,而B页面使用的资源引用为“/combo?files=a,b”,“/combo?files=e,f”就好了,这样当用户在访问A页面之后再访问B页面时,只需要下载B页面的第二个combo文件即可,第一个文件已经在访问A页面时缓存好了的。
基于这样的思考,在资源表上新增了一个字段,取名为“pkg”,就是资源合并生成的新资源,表的结构会变成:
{
"res": {
"A/A.css": {
"uri": "/A/A_1688c82.css",
"type": "css"
},
"B/B.css": {
"uri": "/B/B_52923ed.css",
"type": "css"
},
"C/C.css": {
"uri": "/C/C_6dda653.css",
"type": "css"
},
"bootstrap.css": {
"uri": "bootstrap_08f2256.css",
"type": "css"
},
"jquery.js": {
"uri": "jquery_9155343.css",
"type": "js"
},
},
"pkg": {
"p0": {
"uri": "/pkg/utils_b967346.css",
"type": "css",
"has": ["bootstrap.css", "A/A.css"]
},
"p1": {
"uri": "/pkg/others_0d4552a.css",
"type": "css",
"has": ["B/B.css", "C/C.css"]
}
}
}
相比之前的表,可以看到新表中多了一个pkg字段,并且记录了打包后的文件所包含的独立资源。
这样,我们重新设计一下{require name=”id”}这个模板接口:在查表的时候,如果一个静态资源有pkg字段,那么就去加载pkg字段所指向的打包文件,否则加载资源本身。
比如执行{require name=”bootstrap.css”},查表得知bootstrap.css被打包在了“p0”中,因此取出p0包的url的“/pkg/utils_b967346.css”,并且记录页面已加载了“bootstrap.css”和“A/A.css”两个资源,这样一来,之前的模板代码执行之后得到的html就变成了:
<html>
<head>
<title>hello world</title>
<link rel="stylesheet" type="text/css" href="pkg/utils_b967346.css">
<link rel="stylesheet" type="text/css" href="pkg/others_0d4552a.css">
</head>
<body>
<div>html of A</div>
<div>html of B</div>
<div>html of C</div>
<script type="text/javascript" src="jquery_9155343.js"></script>
<script type="text/javascript">console.log('A loaded');</script>
</body>
</html>
css资源请求数由原来的4个减少为2个,这样的打包结果是怎么来的呢?答案是配置得到的,我们来看一下带有打包结果的资源表的fis配置:
fis.config.set('pack', {
'pkg/util.css': [ 'bootstrap.css', 'A/A.css'],
'pkg/other.css': [ '**.css' ]
});
我们将“bootstrap.css”、“A/A.css”打包在一起,其他css另外打包,从而生成两个打包文件,当页面需要打包文件中的资源时,模块框架就会收集并计算出最优的资源加载结果,从而解决静态资源合并的问题。
这样做的原因是为了弥补combo在前面讲到的两点技术上的不足而设计的,但也不难发现这种打包策略是需要配置的,这就意味着维护成本的增加,但好在它有两个优势可以一定程度上弥补这个问题:
1、打包的资源只是原来独立资源的备份。打包与否不会导致资源的丢失,最多是没有合并的很好而已。
2、配置可以由工程师根据经验人工维护,也可以由统计日志生成,这为性能优化自适应网站设计提供了非常好的基础。
关于第二点,fis有这样辅助系统来支持自适应打包算法:
至此,我们通过基于表的静态资源管理系统和三个模板接口实现了几个重要的性能优化原则,现在我们再来回顾一下前面的性能优化原则分类表,剔除掉已经做到了的,看看还剩下哪些没做到的:
“拆分初始化负载”的目标是将页面一开始加载时不需要执行的资源从所有资源中分离出来,等到需要的时候再加载,工程师通常没有耐心去区分资源的分类情况,但我们可以利用组件化框架接口来帮助工程师管理资源的使用,还是从例子开始思考:
<html>
<head>
<title>www.mahaixiang.cn</title>
{require name="jquery.js"}
</head>
<body>
<button id="myBtn">Click Me</button>
{script}
$('#myBtn').click(function(){
var dialog = require('dialog/dialog.js');
dialog.alert('you catch me!');
});
{/script}
<!--[SCRIPTS PLACEHOLDER]-->
</body>
</html>
在fis给百度内部团队开发的架构中,如果这样书写代码,页面最终的执行结果会变成:
<html>
<head>
<title>www.mahaixiang.cn</title>
</head>
<body>
<button id="myBtn">Click Me</button>
<script type="text/javascript" src="/jquery_9151577.js"></script>
<script type="text/javascript" src="/dialog/dialog_ae8c228.js"></script>
<script type="text/javascript">
$('#myBtn').click(function(){
var dialog = require('dialog/dialog.js');
dialog.alert('you catch me!');
});
</script>
<!--[SCRIPTS PLACEHOLDER]-->
</body>
</html>
fis系统会分析页面中require(id)函数的调用,并将依赖关系记录到资源表对应资源的deps字段中,从而在页面渲染查表时可以加载依赖的资源。
但此时dialog.js是以script标签的形式同步加载的,这样会在页面初始化时出现资源的浪费,因此,fis团队提供了require.async的接口,用于异步加载一些资源,源码修改为:
<html>
<head>
<title>www.mahaixiang.cn</title>
{require name="jquery.js"}
</head>
<body>
<button id="myBtn">Click Me</button>
{script}
$('#myBtn').click(function() {
require.async('dialog/dialog.js', function( dialog ) {
dialog.alert('you catch me!');
});
});
{/script}
<!--[SCRIPTS PLACEHOLDER]-->
</body>
</html>
这样书写之后,fis系统会在表里以async字段来标准资源依赖关系是异步的,fis提供的静态资源管理系统会将页面输出的结果修改为:
<html>
<head>
<title>www.mahaixiang.cn</title>
</head>
<body>
<button id="myBtn">Click Me</button>
<script type="text/javascript" src="/jquery_9151577.js"></script>
<script type="text/javascript" src="/dialog/dialog_ae8c228.js"></script>
<script type="text/javascript">
$('#myBtn').click(function() {
require.async('dialog/dialog.js', function( dialog ) {
dialog.alert('you catch me!');
});
});
</script>
<!--[SCRIPTS PLACEHOLDER]-->
</body>
</html>
dialog.js不会在页面以script src的形式输出,而是变成了资源注册,这样,当页面点击按钮触发require.async执行的时候,async函数才会查表找到资源的url并加载它,加载完毕后触发回调函数。
到目前为止,我们又以架构的形式实现了一项优化原则(拆分初始化负载),回顾我们的优化分类表,现在仅有两项没能做到了:
剩下的两项优化原则要做到并不容易,真正可缓存的Ajax在现实开发中比较少见,而尽早刷新文档的输出的情况facebook在2010年的velocity上提到过,就是BigPipe技术,当时facebook团队还讲到了Quickling和PageCache两项技术,其中的PageCache算是比较彻底的实现Ajax可缓存的优化原则了。
fis团队也曾与某产品线合作基于静态资源表、模板组件化等技术实现了页面的PipeLine输出、以及Quickling和PageCache功能,但最终效果没有达到理想的性能优化预期,因此这两个方向尚在探索中,相信在不久的将来会有新的突破。
马海祥博客点评:
其实在前端开发工程管理领域还有很多细节值得探索和挖掘,提升前端团队生产力水平并不是一句空话,它需要我们能对前端开发及代码运行有更深刻的认识,对性能优化原则有更细致的分析与研究,马海祥一直致力于从架构而非经验的角度实现性能优化原则,解决前端工程师开发、调试、部署中遇到的工程问题,提供组件化框架,提高代码复用率,提供开发工具集,提升工程师的开发效率。
在前端工业化开发的所有环节均有可节省的人力成本,这些成本非常可观,相信现在很多大型互联网公司也都有了这样的共识。
本文发布于马海祥博客文章,如想转载,请注明原文网址摘自于https://www.mahaixiang.cn/znseo/1061.html,注明出处;否则,禁止转载;谢谢配合!您可能还会对以下这些文章感兴趣!
-
网站内部链接的策略规划
当站长们都在讨论外链的时候,很少有人注意到内链的作用,内链就是网站的内部的相互链接,我认为内链的最主要的作用在于提高搜索引擎索引效率和用户体验,增加PV,除此之外,由于是自己的网站,内链容易控制,有助于PR的传递推动网站排名,对于知名站点的SEO而言主要精……【查看全文】
-
独立博客网站该如何做站内SEO优化?
从广义的角度来看,我这篇文章的本身也是一种SEO的手段:既然进行SEO的终极目的还是有人来看,那么作为博客主要构成的内容部分肯定才是最重要的,所以就有“内容为王”的说法,如果你的博客内容非常精彩,你也就没必要做SEO这么无聊的玩意儿了;要是你的博客不但惨淡而且无聊……【查看全文】
-
大量重复URL被百度索引收录的解决方法
最近,很多SEO有这样的疑惑:索引量工具显示索引量数值很高,但流量总也上不去,也没有发现我们站内有低质内容,对此,马海祥找到了导致索引量高流量低的一个原因,并给出的解决方法,URL参数也叫URL query,是一个最复杂,最容易被忽视,最容易被妥协的问题,他是网站运营中……【查看全文】
-
百度收录网站文章的现状及原则依据
对于站长或SEO优化人员来说,如果给一个老站做关键词,只需要我们发一些高质量文章和一些外链就能带来很好的排名,网站前期的优化已经有人帮我们做完了。可如果我们做的是新站排名就十分困难,在我们设定好网站三大标签,设定好网站发展方向之后,首先就需要百度能收录网站的……【查看全文】
-
网页热力图对网站页面优化的帮助有哪些?
热力图是以特殊高亮的形式显示访客热衷的页面区域和访客所在的地区,其特点是,可以显示不可点击区域发生的事情,你将发现访客经常会点击那些不是链接的地方,也许你应该在那个地方放置一个资源链接。比如如果你发现人们总是在点击某个产品图片,你能想到的是,他们也许……【查看全文】
-
网站跳出率的分析方法及优化技巧
网站跳出率是指用户通过搜索引擎进行搜索关键词来到你的网站,只浏览了一个页面就离开与全部浏览数量的百分比。观察关键词的跳出率就可以得知用户对网站内容的认可,或者说你的网站是否对用户有吸引力,而网站的内容是否能够对用户有所帮助留住用户也直接可以在跳出率中……【查看全文】
-
网站URL路径该如何做SEO优化
搜索引擎作为一个程序,在互联网上抓取和识别网站唯一的标准就是:网站URL路径,我们想要把网站权重做起来,就要想办法让我们每一点权重集中起来,网站URL路径的优化就是网站路径的集权,通过把权重集中于一个主路径上,进而获取更好的搜索引擎的权重,路径优化是SEO优化的比……【查看全文】
-
如何利用SEO技术设置文章网页标题
SEO标题跟我们正文标题是完全不一样的,因为SEO标题的主要目的是为了做SEO优化而产生的,为了让用户在搜索引擎上面通过这个文章的想要表达的关键词搜索到,是为了引流而设置的标题,既然是为了SEO优化而作的,标题就不受字数的限制、不受语句通顺的限制、不受文章中心思……【查看全文】
-
论坛网站该如何做站内SEO优化
众所周知,网站SEO优化对一个网站的发展与生存至关重要,论坛也是一样,现在很多站长选择用论坛建站,因为比较方便,只要建设起来有了一定人气,每天的工作只是很轻松的管理帖子和用户,不需要太多的文章编辑和内容更新。我看过很多的论坛,很多站长朋友并不懂SEO,也并……【查看全文】
-
网站导航的优化方法和设置技巧
一说到网站导航,可能很多人的第一印象就是网站头部的那一排主导航栏,感觉没什么优化的必要。其实这是一个误区,网站导航不仅包括这个主导航,网站其它栏目、文章分类、当前的位置、返回首页、返回上一页等都是网站导航系统的一部分。凡是有助于方便用户浏览网站信息、……【查看全文】
-
数据库性能优化的方法
一个数据库系统的生命周期可以分成:设计、开发和成品三个阶段,在设计阶段进行数据库性能优化的成本最低,收益最大,在成品阶段进行数据库性能优化的成本最高,收益最小。一个系统的性能的提高,不单单是试运行或者维护阶段的性能调优,也不单单是开发阶段的事情,而是……【查看全文】
-
文章质量的高低是否取决于文章内容的多少?
一般用户在网上阅读内容时,注意力只能集中很短的时间。一般来说,读者在一个网站的文章里驻留的时间平均保持在96秒钟。这意味着你只有一分半钟的时间与读者沟通。因此,很多网站的经营者都会特意把内容的长度限制在扫几眼就能读懂的范围内,那么在网站里一篇文章的最佳……【查看全文】
-
网站关键词的选择和优化设置技巧
很多的站长打算建立一个新站之前,首先想的不是要先找关键词,而是先想着怎么上线,关键词等到网站上线正常之后,才慢慢的通过百度指数、下拉框、相关搜索及关键词查询工具进行寻找挑选,其实对于标准的新站SEO操作而言,这是一种大忌,尤其是现在搜索引挚普遍对新站都是非常……【查看全文】
-
多年的老网站突然不收录的原因是什么?
网站文章不收录是每一个站长都遇到过的问题之一,比如网站突然无缘无故就不收录了,或者是一些网站文章根本就不被收录。对于文章的不收录,我们首先要分为两种况,一种是新站,网站文章一直不收录;另一种是老站,突然之间网站停止了收录。如果你的网站也是老站,突然碰……【查看全文】
-
百度如何判断网站文章质量度的高与低
百度搜索引擎为了提高互联网的良性发展,对搜索算法的更新也是日新月异,不断的推出搜索新算法,要求网站运营需要符合用户体验,给用户提供有价值的内容,新算法给站长们带来的影响是很大的,但最终目的也都是为了发掘更多的优质文章。在SEO优化行业流行着这么一句话:“……【查看全文】
-
新手站长该如何做网站结构优化
一个好的网站架构,才能承担起SEO的排名,我们不仅要让用户对我们网站感兴趣,我们还需要让搜索引擎蜘蛛感兴趣,只有让搜索引擎蜘蛛感兴趣,才能经常来光顾我们的站点,才能收录我们的网站,这样我们的网站才能增强曝光率,合理的网站栏目结构,能正确表达网站的基本内容及其……【查看全文】
-
新网站该如何打造用户喜欢的内容
其实优质的网站内容,一定要站在用户的角度去思索,任何一个网站我们面对的真正客户就是活生生的实体他是人,如果你将文章针对搜索引擎,就意味着把人当机器了,这样你肯定写不出优质的文章,你的网站权重和排名肯定是在一个点上不断的徘徊,所以,发布用户喜欢的内容应……【查看全文】
-
原创内容就一定是优质内容吗?
原创内容就一定是优质内容吗?这个话题对博主或一些站长圈内的朋友来说,是一个老生常谈且百谈不厌的话题。随着搜索引擎算法不断的迭代更新、越来越智能化,对于优质内容的判断标准也是在不同阶段有不同的要求。现在很多站长或SEO人员一味的为了创造内容而创造内容,但又有多……【查看全文】
-
从网站流量公式来看如何提升网站流量
随着互联网的发展,出现了一群特殊的人,他们有一个共同的名字叫做“站长”,虽然他们各自经营的内容不一样,但是他们有一个共同的目标就是提升网站流量,只有提升流量才能提高网站的排名,从而创造效益。一个网站要想有更大的发展,首先要有流量,网站没有流量就没有人……【查看全文】
阅读:1004关键词: 网站流量 日期:2017-11-26 -
怎样使用网站热点图优化网页布局设计?
优秀网站都是通过细节性进行调整,在保持整体网站风格和样式,通过细微的差别体现出与众不同的方式,在网站结合热点图整合创意和设计,让网站跟访客形成良好的互动效果。有了热力图的科学依据,优化网页细节就可对症下药,避免了主观臆想和盲目改版,在点击行为集中和访……【查看全文】
分类目录
互联网更多>>
- 盘点2010年代这10年的重大网络安全事件 二十一世纪的第2个十年即将过去,在过去十年里有很多的重大网络安全事件发生,我们见证了过去十年,大量的数据……
- 移动互联网的后时代是小程序互联网吗? 中国移动互联网经历过去 10 年发展,近乎完美地将十亿计用户使用习惯培养起来,尤其是消费互联网,更是被开垦成……
- 如何收集和存储服务器运营的数据 随着数据的逐步完善和开放,互联网和企业都将建立起完善的大数据服务基础架构及商业化模式,从数据的存储、挖……
SEO优化 更多>>
-
快速收录上线公告
今天,百度搜索发布了一条最新公告,声称由于…… -
百度贴吧发帖的方法技巧
百度贴吧的引流效果毋庸置疑,这里的流量大的…… -
如何正确设置多样性的404页面?
404页面 就是当用户输入了错误的链接时,返回的…… -
网站点击率对关键词排名到底有没有
网站的总流量对排名并没有太大的直接影响,但…… -
淘宝客该如何做推广?
“淘宝客”是指帮助淘宝卖家推广商品赚取佣金…… -
Google搜索质量小组专业解答的25个SE
2013年对于众多站长和SEO可以说是最为波折的一年…… -
实例解析丰富网页摘要的三大标记格
丰富网页摘要英文名称为Rich Snippets,通俗的来讲…… -
医院网络部该如何开展网络营销推广
医疗行业的网络部是承担医院的网络营销和对外……