小技术

传输层面的从来都是优化的核心点,而这个层面的优化要对浏览器有一个基本的认识,比如:

  • ① 网页自上而下的解析渲染,边解析边渲染,页面内CSS文件会阻塞渲染,异步CSS文件会导致回流

  • ② 浏览器在document下载结束会检测静态资源,新开线程下载(有并发上限),在带宽限制的条件下,无序并发会导致主资源速度下降,从而影响首屏渲染

  • ③ 浏览器缓存可用时会使用缓存资源,这个时候可以避免请求体的传输,对性能有极大提高

衡量性能的重要指标为首屏载入速度(指页面可以看见,不一定可交互),影响首屏的最大因素为请求,所以请求是页面真正的杀手,一般来说我们会做这些优化:

减少请求数

① 合并样式、脚本文件

② 合并背景图片

③ CSS3图标、Icon Font

降低请求量

① 开启GZip

② 优化静态资源,jQuery->Zepto、去除冗余代码

③ 图片无损压缩

④ 图片延迟加载

⑤ 减少Cookie携带

很多时候,我们也会采用类似“时间换空间、空间换时间”的做法,比如:

① 缓存为王,对更新较缓慢的资源&接口做缓存(浏览器缓存、localsorage、application cache这个坑多)

② 按需加载,先加载主要资源,其余资源延迟加载,对非首屏资源滚动加载

③ fake页技术,将页面最初需要显示Html&Css内联,在页面所需资源加载结束前至少可看,理想情况是index.html下载结束即展示(2G 5S内)

④ CDN

从工程的角度来看,上述优化点半数以上是重复的,一般在发布时候就直接使用项目构建工具做掉了,还有一些只是简单的服务器配置,开发时不需要关注。

可以看到,我们所做的优化都是在减少请求数,降低请求量,减小传输时的耗时,或者通过一个策略,优先加载首屏渲染所需资源,而后再加载交互所需资源(比如点击时候再加载UI组件)

解决冗余便抛开了历史的包袱,是前端优化的第一步也是比较难的一步,但模块拆分也将全站分成了很多小的模块,载入的资源分散会增加请求数;如果全部合并,会导致首屏加载不需要的资源,也会导致下一个页面不能使用缓存,如何做出合理的入口资源加载规则,如何合理的善用缓存,是前端优化的第二步。

资源缓存

① 浏览器缓存

② localstorage缓存

时间戳更新

只要服务器配置,浏览器本身便具有缓存机制,如果要使用浏览器机制作缓存,势必关心一个何时更新资源问题,我们一般是这样做的:

<script type="text/javascript" src="libs.js?t=20151025"></script>
这样做要求必须先发布js文件,才能发布html文件,否则读不到最新静态文件的。一个比较尴尬的场景是libs.js是框架团队甚至第三方公司维护的,和业务团队的index.html是两个团队,互相的发布是没有关联的,所以这两者的发布顺序是不能保证的,于是转向开始使用了MD5的方式。

MD5时代

为了解决以上问题我们开始使用md5码的方式为静态资源命名:

<script type="text/javascript" src="libs_md5_1234.js"></script>
每次框架更新便不做文件覆盖,直接生成一个唯一的文件名做增量发布,这个时候如果框架先发布,待业务发布时便已经存在了最新的代码;当业务先发布框架没有新的时,便继续沿用老的文件,一切都很美好,虽然业务开发偶尔会抱怨每次都要向框架拿MD5映射,直到框架一次BUG发生。

seed.js时代

突然一天框架发现一个全局性BUG,并且马上做出了修复,业务团队也马上发布上线,但这种事情出现第二次、第三次框架这边便压力大了,这个时候框架层面希望业务只需要引用一个不带缓存的seed.js,seed.js要怎么加载是他自己的事情:

<script type="text/javascript" src="seed.js"></script>
//seed.js需要按需加载的资源
<script src="libs_md5.js"></script>
<script src="main_md5.js"></script>
当然,由于js加载是顺序是不可控的,我们需要为seed.js实现一个最简单的顺序加载模块,映射什么的由构建工具完成,每次做覆盖发布即可,这样做的缺点是额外增加一个seed.js的文件,并且要承担模块加载代码的下载量。

localstorage缓存

也会有团队将静态资源缓存至localstorage中,以期做离线应用,但是我一般用它存json数据,没有做过静态资源的存储,想要尝试的朋友一定要做好资源更新的策略,然后localstorage的读写也有一定损耗,不支持的情况还需要做降级处理,这里便不多介绍。

CSS性能让JavaScript变慢?

当一个元素的外观的可见性visibility发生改变的时候,重绘(repaint)也随之发生,但是不影响布局。类似的例子包括:outline, visibility, or background color。根据Opera浏览器,重绘的代价是高昂的,因为浏览器必须验证DOM树上其他节点元素的可见性。而回流更是性能的关键因为其变化涉及到部分页面(或是整个页面)的布局。一个元素的回流导致了其所有子元素以及DOM中紧随其后的祖先元素的随后的回流。

<body>
<div class="error">
    <h4>我的组件</h4>
    <p><strong>错误:</strong>错误的描述…</p>
    <h5>错误纠正</h5>
    <ol>
        <li>第一步</li>
        <li>第二步</li>
    </ol>
</div>
</body>

在上面的HTML片段中,对该段落(<p>标签)回流将会引发强烈的回流,因为它是一个子节点。这也导致了祖先的回流(div.error和body – 视浏览器而定)。此外,h5和ol也会有简单的回流,因为其在DOM中在回流元素之后。就Opera而言,大部分的回流将导致页面的重新渲染。

既然它们对性能影响如此可怕,那什么会导致回流呢?
不幸的是,很多的东西,其中一些还与CSS的书写特别相关。

调整窗口大小(Resizing the window)
改变字体(Changing the font)
增加或者移除样式表(Adding or removing a stylesheet)
内容变化,比如用户在input框中输入文字(Content changes, such as a user typing text in
an input box)
激活 CSS 伪类,比如 :hover (IE 中为兄弟结点伪类的激活)(Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling))
操作 class 属性(Manipulating the class attribute)
脚本操作 DOM(A script manipulating the DOM)
计算 offsetWidth 和 offsetHeight 属性(Calculating offsetWidth and offsetHeight)
设置 style 属性的值 (Setting a property of the style attribute)

回流与重绘

  1. 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

  2. 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

注意:回流必将引起重绘,而重绘不一定会引起回流。

var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘

document.body.appendChild(document.createTextNode('abc!'));
说到这里大家都知道回流比重绘的代价要更高,回流的花销跟render tree有多少节点需要重新构建有关系,假设你直接操作body,比如在body最前面插入1个元素,会导致整个render tree回流,这样代价当然会比较高,但如果是指body后面插入1个元素,则不会影响前面元素的回流。

如何减少回流、重绘

减少回流、重绘其实就是需要减少对render tree的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有:

  1. 直接改变className,如果动态改变样式,则使用cssText(考虑没有优化的浏览器)
// 不好的写法
var left = 1;
var top = 1;
el.style.left = left + "px";
el.style.top = top + "px";
// 比较好的写法
el.className += " className1";
 
// 比较好的写法
el.style.cssText += "; 
left: " + left + "px; 
top: " + top + "px;"

2.不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存

// 别这样写,大哥
for(循环) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
 
// 这样写好点
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style; 
for (循环) { 
left += 10; 
top += 10; 
s.left = left + "px"; 
s.top = top + "px"; 
}

JS 模块化

参考链接

按需加载

自己实现也不难,主要思路就是有个config配置需要动态加载js库的path,然后在运行时,由你的加载器,动态的创 <script src='js_Path'/>标签,并把把它append到<head/>中。你的加载器能自适应市面上主流的几种规范即可,比如global var,AMD,Commonjs等。

关于浏览器缓存

浏览器的资源请求,如果使用了缓存基本上是两种情况

status code: 200 ok
status code: 304 Not Modified

If-Modified-Since是标准的HTTP请求头标签,它对应的就是服务器发送过来的该文件的最后服务器修改时间。
在发送http请求的时候,就会将这个最后修改的时间发送到服务器,然后服务器会将这个时间与服务器上当前文件最后修改时间进行比对,如果时间一致,方式如下:
(1).那么返回HTTP状态码304(不返回文件内容),客户端接到之后,就直接把本地缓存文 件显示到浏览器中。
(2).如果时间不一致(说明服务器有了更新的文件),就返回HTTP状态码200和新的文件内容,客户端接到之后,会丢弃旧文件,把新文件 缓存起来,并显示到浏览器中。

参考链接

问题1不一定,读取过的文件在http header设置了expire(http 1.0) / max-age(http 1.1),在正常浏览时,如未超时,并且浏览器也有缓存时,会直接从浏览器缓存取出,但如果你在当前页面按刷新按钮(F5)时,有的浏览器会再次向服务器发出请求,有些浏览器不会。

问题2CSS是一次渲染全部后再接着做其它工作,重复的图片URL,在没缓存的情况下,只会发出一次请求,如果相同的图片URL已在页面或内存中,不会再次请求。

问题3浏览器缓存是存在浏览器本地的文件,304是HTTP状态码,服务器用来标识这个文件没修改,不返回内容,浏览器在接收到个状态码后,会使用浏览器已缓存的文件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • Web前端技术由html、css和 javascript 三大部分构成,是一个庞大而复杂的技术体系,其复杂程度不低...
    WEB攻程狮阅读 1,827评论 2 116
  • Web前端技术由html、css和 javascript三大部分构成,是一个庞大而复杂的技术体系,其复杂程度不低于...
    Hebborn_hb阅读 262评论 0 1
  • 姓名:岳沁 学号:17101223458 转载自:http://www.cnblogs.com/fsyz/p/80...
    丘之心阅读 440评论 0 0
  • 心里想什么,就会发现身边各种各样的事儿都会扑面而来,毫无征兆。 比如在最近一两个月,无限爸爸每天都跟我说要有目标,...
    无限妈妈阅读 130评论 1 0