时间:2022-05-30 15:21:01 | 来源:网络营销
时间:2022-05-30 15:21:01 来源:网络营销
每个参与过开发企业级web应用的前端工程师或许都曾思考过前端性能优化方面的问题,经验丰富的工程师对于前端性能优化方法耳濡目染,基本都能一一列举出来,这些性能优化原则大概是在7年前提出的,对于web性能优化至今都有非常重要的指导意义。<script type="text/javascript" src="a.js?t=20130825"></script>或者:
<script type="text/javascript" src="a.js?v=1.0.0"></script>大家会采用添加query的形式修改链接,这样做是比较直观的解决方案,但在访问量较大的网站,这么做可能将面临一些新的问题。
<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总有一个先后的顺序,从而中间出现一段或大或小的时间间隔。
<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将当前的资源缓存为“未来的版本”。
<script type="text/javascript" src="a.js"></script>但是线上代码是这样的:
<script type="text/javascript" src="a_8244e91.js"></script>其中“82244e91”这串字符是根据a.js的文件内容进行hash运算得到的,只有文件内容发生变化了才会有更改,由于版本序列是与文件名写在一起的,而不是同名文件覆盖,因此不会出现上述说的那些问题,那么,这么做都有哪些好处呢?
<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>当然,把这样的页面直接送达给浏览器用户是会有严重的页面闪烁问题的,所以我们实际上仍然希望最终页面输出的结果还是如最开始的截图一样,将css放在头部输出,这就意味着,页面结构需要有一些调整,并且有能力收集资源加载需求,那么我们考虑一下这样的源码:
<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>
<html>在页面的头部插入一个html注释“<!--[CSS LINKS PLACEHOLDER]-->”作为占位,而将原来字面书写的资源引用改成模板接口(require)调用,该接口负责收集页面所需资源,require接口实现非常简单,就是准备一个数组,收集资源引用,并且可以去重。
<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>而最终在模板解析的过程中,资源收集与去重、页面script收集、占位符替换操作,最终从服务端发送出来的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>
<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>
{基于这张表,我们就很容易实现{require name=”id”}这个模板接口了,只须查表即可。
"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": {}
}
<html>接下来,我们讨论如何在基于表的设计思想上是如何实现静态资源合并的,或许有些团队使用过combo服务,也就是我们在最终拼接生成页面资源引用的时候,并不是生成多个独立的link标签,而是将资源地址拼接成一个url路径,请求一种线上的动态资源合并服务,从而实现减少HTTP请求的需求,比如:
<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>
<html>这个“/combo?files=file1,file2,file3,…”的url请求响应就是动态combo服务提供的,它的原理很简单,就是根据get请求的files参数找到对应的多个文件,合并成一个文件来响应请求,并将其缓存,以加快访问速度。
<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>
{相比之前的表,可以看到新表中多了一个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"]
}
}
}
<html>css资源请求数由原来的4个减少为2个,这样的打包结果是怎么来的呢?答案是配置得到的,我们来看一下带有打包结果的资源表的fis配置:
<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>
fis.config.set('pack', {我们将“bootstrap.css”、“A/A.css”打包在一起,其他css另外打包,从而生成两个打包文件,当页面需要打包文件中的资源时,模块框架就会收集并计算出最优的资源加载结果,从而解决静态资源合并的问题。
'pkg/util.css': [ 'bootstrap.css', 'A/A.css'],
'pkg/other.css': [ '**.css' ]
});
<html>在fis给百度内部团队开发的架构中,如果这样书写代码,页面最终的执行结果会变成:
<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>
<html>fis系统会分析页面中require(id)函数的调用,并将依赖关系记录到资源表对应资源的deps字段中,从而在页面渲染查表时可以加载依赖的资源。
<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>
<html>这样书写之后,fis系统会在表里以async字段来标准资源依赖关系是异步的,fis提供的静态资源管理系统会将页面输出的结果修改为:
<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>
<html>dialog.js不会在页面以script src的形式输出,而是变成了资源注册,这样,当页面点击按钮触发require.async执行的时候,async函数才会查表找到资源的url并加载它,加载完毕后触发回调函数。
<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>
关键词:性能,思路,大型