如今浏览器能够实现的特性越来越多,并且网络逐渐向移动设备转移,使我们的前端代码更加紧凑,如何优化,就变得越来越重要了。
开发人员普遍会将他们的代码习惯优先于用户体验。但是很多很小的改变可以让用户体验有个飞跃提升,所以任何一点儿小小的优化都会提升你网站的性能。
前端给力的地方是可以有许多种简单的策略和代码习惯让我们可以保证最理想的前端性能。我们这个系列的主题就是要告诉你一些前端性能优化的最佳实践,只需要一分钟,就可以优化你现有的代码。
目 录
最佳实践1:使用DocumentFragments或innerHTML取代复杂的元素注入
最佳实践2:高频执行事件/方法的防抖
最佳实践3:网络存储的静态缓存和非必要内容优化
最佳实践4:使用异步加载,延迟加载依赖
最佳实践5:使用Array.prototype.join代替字符串连接
最佳实践6:尽可能使用CSS动画
最佳实践7:使用事件委托
最佳实践8:使用Data URI代替图片SRC
最佳实践9:使用媒体查询加载指定大小的背景图片
最佳实践10:使用索引对象
最佳实践11:控制DOM大小
最佳实践12:在繁重的执行上使用Web Workers
最佳实践13:链接CSS,避免使用@import
最佳实践14:在CSS文件中包含多种介质类型
最佳实践1:使用DocumentFragments或innerHTML取代复杂的元素注入
DOM操作在浏览器上是要付税的。尽管性能提升是在浏览器,DOM很慢,如果你没有注意到,你可能会察觉浏览器运行非常的慢。这就是为什么减少创建集中的DOM节点以及快速注入是那么的重要了。
现在假设我们页面中有一个<ul>元素,调用AJAX获取JSON列表,然后使用JavaScript更新元素内容。通常,程序员会这么写:
- var list = document.querySelector('ul');
- ajaxResult.items.forEach(function(item) {
- // 创建<li>元素
- var li = document.createElement('li');
- li.innerHTML = item.text;
- // <li>元素常规操作,例如添加class,更改属性attribute,添加事件监听等
- // 迅速将<li>元素注入父级<ul>中
- list.apppendChild(li);
- });
上面的代码其实是一个错误的写法,将<ul>元素带着对每一个列表的DOM操作一起移植是非常慢的。如果你真的想要 使用document.createElement,并且将对象当做节点来处理,那么考虑到性能问题,你应该使用DocumentFragement。
DocumentFragement 是一组子节点的“虚拟存储”,并且它没有父标签。在我们的例子中,将DocumentFragement想象成看不见的<ul>元素,在 DOM外,一直保管着你的子节点,直到他们被注入DOM中。那么,原来的代码就可以用DocumentFragment优化一下:
- var frag = document.createDocumentFragment();
- ajaxResult.items.forEach(function(item) {
- // 创建<li>元素
- var li = document.createElement('li');
- li.innerHTML = item.text;
- // <li>元素常规操作
- // 例如添加class,更改属性attribute,添加事件监听,添加子节点等
- // 将<li>元素添加到碎片中
- frag.appendChild(li);
- });
- // 最后将所有的列表对象通过DocumentFragment集中注入DOM
- document.querySelector('ul').appendChild(frag);
为DocumentFragment追加子元素,然后再将这个DocumentFragment加到父列表中,这一系列操作仅仅是一个DOM操作,因此它比起集中注入要快很多。
如果你不需要将列表对象当做节点来操作,更好的方法是用字符串构建HTML内容:
- var htmlStr = '';
- ajaxResult.items.forEach(function(item) {
- // 构建包含HTML页面内容的字符串
- htmlStr += '<li>' + item.text + '</li>';
- });
- // 通过innerHTML设定ul内容
- document.querySelector('ul').innerHTML = htmlStr;
这当中也只有一个DOM操作,并且比起DocumentFragment代码量更少。在任何情况下,这两种方法都比在每一次迭代中将元素注入DOM更高效。
最佳实践2:高频执行事件/方法的防抖
通常,开发人员会在有用户交互参与的地方添加事件,而往往这种事件会被频繁触发。想象一下窗口的resize事件或者是一个元素的onmouseover事件 - 他们触发时,执行的非常迅速,并且触发很多次。如果你的回调过重,你可能使浏览器死掉。
这就是为什么我们要引入防抖。
防抖可以限制一个方法在一定时间内执行的次数。以下代码是个防抖示例:
- // 取自 UnderscoreJS 实用框架
- function debounce(func, wait, immediate) {
- var timeout;
- return function() {
- var context = this, args = arguments;
- var later = function() {
- timeout = null;
- if (!immediate) func.apply(context, args);
- };
- var callNow = immediate && !timeout;
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- if (callNow) func.apply(context, args);
- };
- }
- // 添加resize的回调函数,但是只允许它每300毫秒执行一次
- window.addEventListener('resize', debounce(function(event) {
- // 这里写resize过程
- }, 300));
debounce方法返回一个方法,用来包住你的回调函数,限制他的执行频率。使用这个防抖方法,就可以让你写的频繁回调的方法不会妨碍用户的浏览器!
最佳实践3:网络存储的静态缓存和非必要内容优化
Web Storage的API曾经是Cookie API一个显著的进步,并且为开发者使用了很多年了。这个API是合理的,更大存储量的,而且是更为健全理智的。一种策略是去使用Session存储来存 储非必要的,更为静态的内容,例如侧边栏的HTML内容,从Ajax加载进来的文章内容,或者一些其他的各种各样的片断,是我们只想请求一次的。
我们可以使用JavaScript编写一段代码,利用Web Storage使这些内容加载更加简单:
- define(function() {
- var cacheObj = window.sessionStorage || {
- getItem: function(key) {
- return this[key];
- },
- setItem: function(key, value) {
- this[key] = value;
- }
- };
- return {
- get: function(key) {
- return this.isFresh(key);
- },
- set: function(key, value, minutes) {
- var expDate = new Date();
- expDate.setMinutes(expDate.getMinutes() + (minutes || 0));
- try {
- cacheObj.setItem(key, JSON.stringify({
- value: value,
- expires: expDate.getTime()
- }));
- }
- catch(e) { }
- },
- isFresh: function(key) {
- // 返回值或者返回false
- var item;
- try {
- item = JSON.parse(cacheObj.getItem(key));
- }
- catch(e) {}
- if(!item) return false;
- // 日期算法
- return new Date().getTime() > item.expires ? false : item.value;
- }
- }
- });
这个工具提供了一个基础的get和set方法,同isFresh方法一样,保证了存储的数据不会过期。调用方法也非常简单:
- require(['storage'], function(storage) {
- var content = storage.get('sidebarContent');
- if(!content) {
- // Do an AJAX request to get the sidebar content
- // ... and then store returned content for an hour
- storage.set('sidebarContent', content, 60);
- }
- });
现在同样的内容不会被重复请求,你的应用运行的更加有效。花一点儿时间,看看你的网站设计,将那些不会变化,但是会被不断请求的内容挑出来,你可以使用Web Storage工具来提升你网站的性能。