如今瀏覽器能夠?qū)崿F(xiàn)的特性越來越多,并且網(wǎng)絡(luò)逐漸向移動設(shè)備轉(zhuǎn)移,使我們的前端代碼更加緊湊,如何優(yōu)化,就變得越來越重要了。
開發(fā)人員普遍會將他們的代碼習(xí)慣優(yōu)先于用戶體驗。但是很多很小的改變可以讓用戶體驗有個飛躍提升,所以任何一點兒小小的優(yōu)化都會提升你網(wǎng)站的性能。
前端給力的地方是可以有許多種簡單的策略和代碼習(xí)慣讓我們可以保證最理想的前端性能。我們這個系列的主題就是要告訴你一些前端性能優(yōu)化的最佳實踐,只需要一分鐘,就可以優(yōu)化你現(xiàn)有的代碼。(本文內(nèi)容來自 極客標(biāo)簽 )
開發(fā)人員普遍會將他們的代碼習(xí)慣優(yōu)先于用戶體驗。但是很多很小的改變可以讓用戶體驗有個飛躍提升,所以任何一點兒小小的優(yōu)化都會提升你網(wǎng)站的性能。
前端給力的地方是可以有許多種簡單的策略和代碼習(xí)慣讓我們可以保證最理想的前端性能。我們這個系列的主題就是要告訴你一些前端性能優(yōu)化的最佳實踐,只需要一分鐘,就可以優(yōu)化你現(xiàn)有的代碼。(本文內(nèi)容來自 極客標(biāo)簽 )
目 錄 [ - ]
- 最佳實踐1:使用DocumentFragments或innerHTML取代復(fù)雜的元素注入
- 最佳實踐2:高頻執(zhí)行事件/方法的防抖
- 最佳實踐3:網(wǎng)絡(luò)存儲的靜態(tài)緩存和非必要內(nèi)容優(yōu)化
- 最佳實踐4:使用異步加載,延遲加載依賴
- 最佳實踐5:使用Array.prototype.join代替字符串連接
- 最佳實踐6:盡可能使用CSS動畫
- 最佳實踐7:使用事件委托
- 最佳實踐8:使用Data URI代替圖片SRC
- 最佳實踐9:使用媒體查詢加載指定大小的背景圖片
- 最佳實踐10:使用索引對象
- 最佳實踐11:控制DOM大小
- 最佳實踐12:在繁重的執(zhí)行上使用Web Workers
- 最佳實踐13:鏈接CSS,避免使用@import
- 最佳實踐14:在CSS文件中包含多種介質(zhì)類型
最佳實踐1:使用DocumentFragments或innerHTML取代復(fù)雜的元素注入
DOM操作在瀏覽器上是要付稅的。盡管性能提升是在瀏覽器,DOM很慢,如果你沒有注意到,你可能會察覺瀏覽器運行非常的慢。這就是為什么減少創(chuàng)建集中的DOM節(jié)點以及快速注入是那么的重要了。
現(xiàn)在假設(shè)我們頁面中有一個<ul>元素,調(diào)用AJAX獲取JSON列表,然后使用JavaScript更新元素內(nèi)容。通常,程序員會這么寫:
上面的代碼其實是一個錯誤的寫法,將<ul>元素帶著對每一個列表的DOM操作一起移植是非常慢的。如果你真的想要 使用document.createElement,并且將對象當(dāng)做節(jié)點來處理,那么考慮到性能問題,你應(yīng)該使用DocumentFragement。
DocumentFragement 是一組子節(jié)點的“虛擬存儲”,并且它沒有父標(biāo)簽。在我們的例子中,將DocumentFragement想象成看不見的<ul>元素,在 DOM外,一直保管著你的子節(jié)點,直到他們被注入DOM中。那么,原來的代碼就可以用DocumentFragment優(yōu)化一下:
為DocumentFragment追加子元素,然后再將這個DocumentFragment加到父列表中,這一系列操作僅僅是一個DOM操作,因此它比起集中注入要快很多。
如果你不需要將列表對象當(dāng)做節(jié)點來操作,更好的方法是用字符串構(gòu)建HTML內(nèi)容:
這當(dāng)中也只有一個DOM操作,并且比起DocumentFragment代碼量更少。在任何情況下,這兩種方法都比在每一次迭代中將元素注入DOM更高效。
現(xiàn)在假設(shè)我們頁面中有一個<ul>元素,調(diào)用AJAX獲取JSON列表,然后使用JavaScript更新元素內(nèi)容。通常,程序員會這么寫:
- var ?list?=?document.querySelector( 'ul' ); ??
- ajaxResult.items.forEach( function (item)?{ ??
- ???? //?創(chuàng)建<li>元素 ??
- ???? var ?li?=?document.createElement( 'li' ); ??
- ????li.innerHTML?=?item.text; ??
- ??
- ???? //?<li>元素常規(guī)操作,例如添加class,更改屬性attribute,添加事件監(jiān)聽等 ??
- ??
- ???? //?迅速將<li>元素注入父級<ul>中 ??
- ????list.apppendChild(li); ??
- });??
var list = document.querySelector('ul'); ajaxResult.items.forEach(function(item) { // 創(chuàng)建<li>元素 var li = document.createElement('li'); li.innerHTML = item.text; // <li>元素常規(guī)操作,例如添加class,更改屬性attribute,添加事件監(jiān)聽等 // 迅速將<li>元素注入父級<ul>中 list.apppendChild(li); });
上面的代碼其實是一個錯誤的寫法,將<ul>元素帶著對每一個列表的DOM操作一起移植是非常慢的。如果你真的想要 使用document.createElement,并且將對象當(dāng)做節(jié)點來處理,那么考慮到性能問題,你應(yīng)該使用DocumentFragement。
DocumentFragement 是一組子節(jié)點的“虛擬存儲”,并且它沒有父標(biāo)簽。在我們的例子中,將DocumentFragement想象成看不見的<ul>元素,在 DOM外,一直保管著你的子節(jié)點,直到他們被注入DOM中。那么,原來的代碼就可以用DocumentFragment優(yōu)化一下:
- var ?frag?=?document.createDocumentFragment(); ??
- ??
- ajaxResult.items.forEach( function (item)?{ ??
- ???? //?創(chuàng)建<li>元素 ??
- ???? var ?li?=?document.createElement( 'li' ); ??
- ????li.innerHTML?=?item.text; ??
- ??
- ???? //?<li>元素常規(guī)操作 ??
- ???? //?例如添加class,更改屬性attribute,添加事件監(jiān)聽,添加子節(jié)點等 ??
- ??
- ???? //?將<li>元素添加到碎片中 ??
- ????frag.appendChild(li); ??
- }); ??
- ??
- //?最后將所有的列表對象通過DocumentFragment集中注入DOM ??
- document.querySelector( 'ul' ).appendChild(frag);??
var frag = document.createDocumentFragment(); ajaxResult.items.forEach(function(item) { // 創(chuàng)建<li>元素 var li = document.createElement('li'); li.innerHTML = item.text; // <li>元素常規(guī)操作 // 例如添加class,更改屬性attribute,添加事件監(jiān)聽,添加子節(jié)點等 // 將<li>元素添加到碎片中 frag.appendChild(li); }); // 最后將所有的列表對象通過DocumentFragment集中注入DOM document.querySelector('ul').appendChild(frag);
為DocumentFragment追加子元素,然后再將這個DocumentFragment加到父列表中,這一系列操作僅僅是一個DOM操作,因此它比起集中注入要快很多。
如果你不需要將列表對象當(dāng)做節(jié)點來操作,更好的方法是用字符串構(gòu)建HTML內(nèi)容:
- var ?htmlStr?=? '' ; ??
- ??
- ajaxResult.items.forEach( function (item)?{ ??
- ???? //?構(gòu)建包含HTML頁面內(nèi)容的字符串 ??
- ????htmlStr?+=? '<li>' ?+?item.text?+? '</li>' ; ??
- }); ??
- ??
- //?通過innerHTML設(shè)定ul內(nèi)容 ??
- document.querySelector( 'ul' ).innerHTML?=?htmlStr;??
var htmlStr = ''; ajaxResult.items.forEach(function(item) { // 構(gòu)建包含HTML頁面內(nèi)容的字符串 htmlStr += '<li>' + item.text + '</li>'; }); // 通過innerHTML設(shè)定ul內(nèi)容 document.querySelector('ul').innerHTML = htmlStr;
這當(dāng)中也只有一個DOM操作,并且比起DocumentFragment代碼量更少。在任何情況下,這兩種方法都比在每一次迭代中將元素注入DOM更高效。
最佳實踐2:高頻執(zhí)行事件/方法的防抖
通常,開發(fā)人員會在有用戶交互參與的地方添加事件,而往往這種事件會被頻繁觸發(fā)。想象一下窗口的resize事件或者是一個元素的onmouseover事件 - 他們觸發(fā)時,執(zhí)行的非常迅速,并且觸發(fā)很多次。如果你的回調(diào)過重,你可能使瀏覽器死掉。
這就是為什么我們要引入防抖。
防抖可以限制一個方法在一定時間內(nèi)執(zhí)行的次數(shù)。以下代碼是個防抖示例:
debounce方法返回一個方法,用來包住你的回調(diào)函數(shù),限制他的執(zhí)行頻率。使用這個防抖方法,就可以讓你寫的頻繁回調(diào)的方法不會妨礙用戶的瀏覽器!
這就是為什么我們要引入防抖。
防抖可以限制一個方法在一定時間內(nèi)執(zhí)行的次數(shù)。以下代碼是個防抖示例:
- //?取自?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的回調(diào)函數(shù),但是只允許它每300毫秒執(zhí)行一次 ??
- window.addEventListener( 'resize' ,?debounce( function (event)?{ ??
- ??
- ???? //?這里寫resize過程 ??
- ??
- },?300));??
// 取自 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的回調(diào)函數(shù),但是只允許它每300毫秒執(zhí)行一次 window.addEventListener('resize', debounce(function(event) { // 這里寫resize過程 }, 300));
debounce方法返回一個方法,用來包住你的回調(diào)函數(shù),限制他的執(zhí)行頻率。使用這個防抖方法,就可以讓你寫的頻繁回調(diào)的方法不會妨礙用戶的瀏覽器!
最佳實踐3:網(wǎng)絡(luò)存儲的靜態(tài)緩存和非必要內(nèi)容優(yōu)化
Web Storage的API曾經(jīng)是Cookie API一個顯著的進(jìn)步,并且為開發(fā)者使用了很多年了。這個API是合理的,更大存儲量的,而且是更為健全理智的。一種策略是去使用Session存儲來存 儲非必要的,更為靜態(tài)的內(nèi)容,例如側(cè)邊欄的HTML內(nèi)容,從Ajax加載進(jìn)來的文章內(nèi)容,或者一些其他的各種各樣的片斷,是我們只想請求一次的。
我們可以使用JavaScript編寫一段代碼,利用Web Storage使這些內(nèi)容加載更加簡單:
這個工具提供了一個基礎(chǔ)的get和set方法,同isFresh方法一樣,保證了存儲的數(shù)據(jù)不會過期。調(diào)用方法也非常簡單:
現(xiàn)在同樣的內(nèi)容不會被重復(fù)請求,你的應(yīng)用運行的更加有效。花一點兒時間,看看你的網(wǎng)站設(shè)計,將那些不會變化,但是會被不斷請求的內(nèi)容挑出來,你可以使用Web Storage工具來提升你網(wǎng)站的性能。
我們可以使用JavaScript編寫一段代碼,利用Web Storage使這些內(nèi)容加載更加簡單:
- 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; ??
- ????????} ??
- ?????} ??
- });??
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; } } });
這個工具提供了一個基礎(chǔ)的get和set方法,同isFresh方法一樣,保證了存儲的數(shù)據(jù)不會過期。調(diào)用方法也非常簡單:
- 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);? ??
- ????} ??
- });??
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); } });
現(xiàn)在同樣的內(nèi)容不會被重復(fù)請求,你的應(yīng)用運行的更加有效。花一點兒時間,看看你的網(wǎng)站設(shè)計,將那些不會變化,但是會被不斷請求的內(nèi)容挑出來,你可以使用Web Storage工具來提升你網(wǎng)站的性能。
最佳實踐4:使用異步加載,延遲加載依賴
RequireJS
已經(jīng)迎來了異步加載和AMD格式的巨大浪潮。XMLHttpRequest(該對象可以調(diào)用AJAX)使得資源的異步加載變得流行起來,它允許無阻塞資源加載,并且使 onload 啟動更快,允許頁面內(nèi)容加載,而不需要刷新頁面。
我所用的異步加載器是John Hann的 curl 。curl加載器是基本的異步加載器,可以被配置,擁有很好的插件。以下是一小段curl的代碼:
你可能早就了解,異步加載可以大大提高萬展速度,但是我想在此說明的是,你要使用異步加載!使用了之后你可以看到區(qū)別,更重要的是,你的用戶可以看到區(qū)別。
當(dāng)你可以根據(jù)頁面內(nèi)容延遲加載依賴的時候,你就可以體會到異步加載的好處了。例如,你可以只加載Twitter,F(xiàn)acebook和Google Plus到應(yīng)用了名為social的CSS樣式的div元素中。“在加載前檢查是否需要”策略可以為我的用戶節(jié)省好幾KB的莫須有的加載。
我所用的異步加載器是John Hann的 curl 。curl加載器是基本的異步加載器,可以被配置,擁有很好的插件。以下是一小段curl的代碼:
- //?基本使用:??加載一部分AMD格式的模塊 ??
- curl([ 'social' ,? 'dom' ],? function (social,?dom)?{ ??
- ????dom.setElementContent( '.social-container' ,?social.loadWidgets()); ??
- }); ??
- ??
- //?定義一個使用Google?Analytics的模塊,該模塊是非AMD格式的 ??
- define([ "js!//google-analytics.com/ga.js" ],? function ()?{ ??
- ???? //?Return?a?simple?custom?Google?Analytics?controller ??
- ???? return ?{ ??
- ????????trackPageView:? function (href)?{ ??
- ????????????_gaq.push([ "_trackPageview" ,?url]); ??
- ????????}, ??
- ????????trackEvent:? function (eventName,?href)?{ ??
- ????????????_gaq.push([ "_trackEvent" ,? "Interactions" ,?eventName,? "" ,?href?||?window.location,? true ]); ??
- ????????} ??
- ????}; ??
- }); ??
- ??
- //?加載一個不帶回調(diào)方法的非AMD的js文件 ??
- curl([ 'js!//somesite.com/widgets.js' ]); ??
- ??
- //?將JavaScript和CSS文件作為模塊加載 ??
- curl([ 'js!libs/prism/prism.js' ,? 'css!libs/prism/prism.css' ],? function ()?{ ??
- ????Prism.highlightAll(); ??
- }); ??
- ??
- //?加載一個AJAX請求的URL ??
- curl([ 'text!sidebar.php' ,? 'storage' ,? 'dom' ],? function (content,?storage,?dom)?{ ??
- ????storage.set( 'sidebar' ,?content,?60); ??
- ????dom.setElementContent( '.sidebar' ,?content); ??
- });??
// 基本使用: 加載一部分AMD格式的模塊 curl(['social', 'dom'], function(social, dom) { dom.setElementContent('.social-container', social.loadWidgets()); }); // 定義一個使用Google Analytics的模塊,該模塊是非AMD格式的 define(["js!//google-analytics.com/ga.js"], function() { // Return a simple custom Google Analytics controller return { trackPageView: function(href) { _gaq.push(["_trackPageview", url]); }, trackEvent: function(eventName, href) { _gaq.push(["_trackEvent", "Interactions", eventName, "", href || window.location, true]); } }; }); // 加載一個不帶回調(diào)方法的非AMD的js文件 curl(['js!//somesite.com/widgets.js']); // 將JavaScript和CSS文件作為模塊加載 curl(['js!libs/prism/prism.js', 'css!libs/prism/prism.css'], function() { Prism.highlightAll(); }); // 加載一個AJAX請求的URL curl(['text!sidebar.php', 'storage', 'dom'], function(content, storage, dom) { storage.set('sidebar', content, 60); dom.setElementContent('.sidebar', content); });
你可能早就了解,異步加載可以大大提高萬展速度,但是我想在此說明的是,你要使用異步加載!使用了之后你可以看到區(qū)別,更重要的是,你的用戶可以看到區(qū)別。
當(dāng)你可以根據(jù)頁面內(nèi)容延遲加載依賴的時候,你就可以體會到異步加載的好處了。例如,你可以只加載Twitter,F(xiàn)acebook和Google Plus到應(yīng)用了名為social的CSS樣式的div元素中。“在加載前檢查是否需要”策略可以為我的用戶節(jié)省好幾KB的莫須有的加載。
最佳實踐5:使用Array.prototype.join代替字符串連接
有一種非常簡單的客戶端優(yōu)化方式,就是用Array.prototype.join代替原有的基本的字符連接的寫法。在上面的“最佳實踐1”中,我在代碼中使用了基本字符連接:
但是下面這段代碼中,我用了優(yōu)化:
也許你需要花上一點兒時間來看看這個數(shù)組是做什么用的,但是所有的用戶都從這個優(yōu)化中受益匪淺。
- htmlStr?+=? '<li>' ?+?item.text?+? '</li>' ;??
htmlStr += '<li>' + item.text + '</li>';
但是下面這段代碼中,我用了優(yōu)化:
- var ?items?=?[]; ??
- ??
- ajaxResult.items.forEach( function (item)?{ ??
- ???? //?構(gòu)建字符串 ??
- ????items.push( '<li>' ,?item.text,? '</li>' ); ??
- }); ??
- ??
- //?通過innerHTML設(shè)置列表內(nèi)容 ??
- document.querySelector( 'ul' ).innerHTML?=?items.join( '' );???
var items = []; ajaxResult.items.forEach(function(item) { // 構(gòu)建字符串 items.push('<li>', item.text, '</li>'); }); // 通過innerHTML設(shè)置列表內(nèi)容 document.querySelector('ul').innerHTML = items.join('');
也許你需要花上一點兒時間來看看這個數(shù)組是做什么用的,但是所有的用戶都從這個優(yōu)化中受益匪淺。
最佳實踐6:盡可能使用CSS動畫
網(wǎng)站設(shè)計對美觀特性和可配置元素動畫的大量需求,使得一些JavaScript類庫,如jQuery,MooTools大量的被使用。盡管現(xiàn)在瀏覽器支持CSS的transformation和keyframe所做的動畫,現(xiàn)在仍有很多人使用JavaScript制作動畫效果,但是實際上使用CSS動畫比起JavaScript驅(qū)動的動畫效率更高。CSS動畫同時需要更少的代碼。很多的CSS動畫是用GPU處理的,因此動畫本身很流暢,當(dāng)然你可以使用下面這個簡單的CSS強制使你的硬件加速:
tansform:transform(0,0,0)在不會影響其他動畫的同時將通話送入硬件加速。在不支持CSS動畫的情況下(IE8及以下版本的瀏覽器),你可以引入JavaScript動畫邏輯:
在上例中,ie-animations.js文件必須包含你自定義的jQuery代碼,用于當(dāng)CSS動畫在早期IE中不被支持的情況下,來替代CSS動畫完成動畫效果。完美的通過CSS動畫來優(yōu)化動畫,通過JavaScript來支持全局動畫效果。
- .myAnimation?{ ??
- ????animation:?someAnimation?1s; ??
- ????transform:?translate3d(0,?0,?0);? /*?強制硬件加速?*/ ??
- }??
.myAnimation { animation: someAnimation 1s; transform: translate3d(0, 0, 0); /* 強制硬件加速 */ }
tansform:transform(0,0,0)在不會影響其他動畫的同時將通話送入硬件加速。在不支持CSS動畫的情況下(IE8及以下版本的瀏覽器),你可以引入JavaScript動畫邏輯:
- <!--[ if ?低于IE8版本]> ??
- <script?src= "http://code.jquery.com/jquery-1.9.1.min.js" ></script> ??
- <script?src= "/js/ie-animations.js" ></script> ??
- <![endif]-->??
<!--[if 低于IE8版本]> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="/js/ie-animations.js"></script> <![endif]-->
在上例中,ie-animations.js文件必須包含你自定義的jQuery代碼,用于當(dāng)CSS動畫在早期IE中不被支持的情況下,來替代CSS動畫完成動畫效果。完美的通過CSS動畫來優(yōu)化動畫,通過JavaScript來支持全局動畫效果。
最佳實踐7:使用事件委托
想象一下,如果你有一個無序列表,里面有一堆<li>元素,每一個<li>元素都會在點擊的時候觸發(fā)一個行為。這個時候,你通常會在每一個元素上添加一個事件監(jiān)聽,但是如果當(dāng)這個元素或者你添加了監(jiān)聽的這個對象會被頻繁的移除添加呢?這個時候,你在移除添加元素的同時需要處理事件監(jiān)聽的移除和添加。這個時候,我們就需要引入事件委托了。
事件委托是在父級元素上添加一個事件監(jiān)聽,來替代在每一個子元素上添加事件監(jiān)聽。當(dāng)事件被觸發(fā)時,event.target會評估相應(yīng)的措施是否需要被執(zhí)行。下面我們給出了一個簡單的例子:
上面的例子是不可思議的簡單,當(dāng)事件發(fā)生的時候,它沒有輪詢父節(jié)點去尋找匹配的元素或選擇器,且它不支持基于選擇器的查詢(例如用class name,或者id來查詢)。所有的JavaScript框架提供了委托選擇器匹配。重點是,你避免了為每一個元素加載事件監(jiān)聽,而是在父元素上加一個事件監(jiān)聽。這樣大大的增加了效率,并且減少了很多維護(hù)!
事件委托是在父級元素上添加一個事件監(jiān)聽,來替代在每一個子元素上添加事件監(jiān)聽。當(dāng)事件被觸發(fā)時,event.target會評估相應(yīng)的措施是否需要被執(zhí)行。下面我們給出了一個簡單的例子:
- //?獲取元素,添加事件監(jiān)聽 ??
- document.querySelector( '#parent-list' ).addEventListener( 'click' ,? function (e)?{ ??
- ???? //?e.target?是一個被點擊的元素! ??
- ???? //?如果它是一個列表元素 ??
- ???? if (e.target?&&?e.target.tagName?==? 'LI' )?{ ??
- ???????? //?我們找到了這個元素,對他的操作可以寫在這里。 ??
- ????} ??
- });??
// 獲取元素,添加事件監(jiān)聽 document.querySelector('#parent-list').addEventListener('click', function(e) { // e.target 是一個被點擊的元素! // 如果它是一個列表元素 if(e.target && e.target.tagName == 'LI') { // 我們找到了這個元素,對他的操作可以寫在這里。 } });
上面的例子是不可思議的簡單,當(dāng)事件發(fā)生的時候,它沒有輪詢父節(jié)點去尋找匹配的元素或選擇器,且它不支持基于選擇器的查詢(例如用class name,或者id來查詢)。所有的JavaScript框架提供了委托選擇器匹配。重點是,你避免了為每一個元素加載事件監(jiān)聽,而是在父元素上加一個事件監(jiān)聽。這樣大大的增加了效率,并且減少了很多維護(hù)!
最佳實踐8:使用Data URI代替圖片SRC
提升頁面大小的效率,不僅僅是取決于使用精靈或是壓縮代碼,給定頁面的請求數(shù)量在前端性能中也占有了很不小的重量。減少請求可以讓你的網(wǎng)站加載更快,而其中一種減少頁面請求的方法就是用Data URI代替圖片的src屬性:
當(dāng)然頁面大小會增加(如果你的服務(wù)器使用適當(dāng)?shù)膅zip內(nèi)容,這個增加會很小),但是你減少了潛在的請求,同時也在過程中減少了服務(wù)器請求的數(shù)量。現(xiàn)在大多數(shù)瀏覽器都支持Data URI,在CSS中的背景骨片也可以使用Data URI,因此這個策略現(xiàn)在已經(jīng)可以在應(yīng)用層級,廣泛應(yīng)用。
- <!--?以前的寫法?--> ??
- <img?src= "/images/logo.png" ?/> ??
- ? ??
- <!--?使用data?URI的寫法?--> ??
- <img?src= "data:?image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAPAAA/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fH?...." ?/> ??
- ? ??
- <--?范例:??http: //davidwalsh.name/demo/data-uri-php.php?--> ??
<!-- 以前的寫法 --> <img src="/images/logo.png" /> <!-- 使用data URI的寫法 --> <img src="data: image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAPAAA/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fH ...." /> <-- 范例: http://davidwalsh.name/demo/data-uri-php.php -->
當(dāng)然頁面大小會增加(如果你的服務(wù)器使用適當(dāng)?shù)膅zip內(nèi)容,這個增加會很小),但是你減少了潛在的請求,同時也在過程中減少了服務(wù)器請求的數(shù)量。現(xiàn)在大多數(shù)瀏覽器都支持Data URI,在CSS中的背景骨片也可以使用Data URI,因此這個策略現(xiàn)在已經(jīng)可以在應(yīng)用層級,廣泛應(yīng)用。
最佳實踐9:使用媒體查詢加載指定大小的背景圖片
直到CSS @supports被廣泛支持,CSS媒體查詢的使用接近于CSS中寫邏輯控制。我們經(jīng)常用CSS媒體查詢來根據(jù)設(shè)備調(diào)整CSS屬性(通常根據(jù)屏幕寬度調(diào)整CSS屬性),例如根據(jù)不同的屏幕寬度來設(shè)置不同的元素寬度或者是懸浮位置。那么我們?yōu)槭裁床挥眠@種方式來改變背景圖片呢?
上面的代碼片段是為手機設(shè)備或是類似的移動設(shè)備加載一個較小尺寸的圖片,特別是需要一個特別小的圖片時(例如圖片的大小幾乎不可視)。
- /*?默認(rèn)是為桌面應(yīng)用加載圖片?*/ ??
- .someElement?{?background-image:?url(sunset.jpg);?} ??
- ? ??
- @media?only?screen?and?(max-width?:?1024px)?{ ??
- ????.someElement?{?background-image:?url(sunset-small.jpg);?} ??
- }??
/* 默認(rèn)是為桌面應(yīng)用加載圖片 */ .someElement { background-image: url(sunset.jpg); } @media only screen and (max-width : 1024px) { .someElement { background-image: url(sunset-small.jpg); } }
上面的代碼片段是為手機設(shè)備或是類似的移動設(shè)備加載一個較小尺寸的圖片,特別是需要一個特別小的圖片時(例如圖片的大小幾乎不可視)。
最佳實踐10:使用索引對象
這一篇,我們將講講使用索引對象檢索代替遍歷數(shù)組,提高遍歷速度。
AJAX和JSON一個最常見的使用案例是接收包含一組對象的數(shù)組,然后從這組數(shù)組中根據(jù)給定的值搜索對象。讓我們看一個簡單的例子,下面這個例子中,你從用戶接收一個數(shù)組,然后你可以根據(jù)username的值來搜索用戶對象:
上面這段代碼可以運行,但是并不是很有效,當(dāng)我們想要獲取一個用戶時,我們就要遍歷一次數(shù)組。那么更好的方法是創(chuàng)建一個新的對象,對每一個唯一的值建立一個索引,在上面這個例子中,用username作為索引,這個數(shù)組對象可以寫成:
現(xiàn)在當(dāng)你想要找一個用戶對象時,我們可以直接通過索引找到這個對象:
這樣的代碼寫起來更好一些,也很簡便,通過索引搜索比起遍歷整個數(shù)組更加快捷。
AJAX和JSON一個最常見的使用案例是接收包含一組對象的數(shù)組,然后從這組數(shù)組中根據(jù)給定的值搜索對象。讓我們看一個簡單的例子,下面這個例子中,你從用戶接收一個數(shù)組,然后你可以根據(jù)username的值來搜索用戶對象:
- function ?getUser(desiredUsername)?{ ??
- ???? var ?searchResult?=?ajaxResult.users.filter( function (user)?{ ??
- ???????? return ?user.username?==?desiredUsername; ??
- ????}); ??
- ? ??
- ???? return ?searchResult.length???searchResult[0]?:? false ; ??
- } ??
- ? ??
- //?根據(jù)用戶名獲取用戶 ??
- var ?davidwalsh?=?getUser( "davidwalsh" ); ??
- ? ??
- //?根據(jù)用戶名獲取另一個用戶. ??
- var ?techpro?=?getuser( "tech-pro" );??
function getUser(desiredUsername) { var searchResult = ajaxResult.users.filter(function(user) { return user.username == desiredUsername; }); return searchResult.length ? searchResult[0] : false; } // 根據(jù)用戶名獲取用戶 var davidwalsh = getUser("davidwalsh"); // 根據(jù)用戶名獲取另一個用戶. var techpro = getuser("tech-pro");
上面這段代碼可以運行,但是并不是很有效,當(dāng)我們想要獲取一個用戶時,我們就要遍歷一次數(shù)組。那么更好的方法是創(chuàng)建一個新的對象,對每一個唯一的值建立一個索引,在上面這個例子中,用username作為索引,這個數(shù)組對象可以寫成:
- var ?userStore?=?{}; ??
- ajaxResult.users.forEach( function (user)?{ ??
- ????userStore[user.username]?=?user; ??
- });??
var userStore = {}; ajaxResult.users.forEach(function(user) { userStore[user.username] = user; });
現(xiàn)在當(dāng)你想要找一個用戶對象時,我們可以直接通過索引找到這個對象:
- var ?davidwalsh?=?userStore.davidwalsh; ??
- var ?techpro?=?userStore[ "tech-pro" ];??
var davidwalsh = userStore.davidwalsh; var techpro = userStore["tech-pro"];
這樣的代碼寫起來更好一些,也很簡便,通過索引搜索比起遍歷整個數(shù)組更加快捷。
最佳實踐11:控制DOM大小
這一篇中,我們要說如何控制DOM的大小,來優(yōu)化前端性能。
DOM很慢是眾所周知的,使得網(wǎng)站變慢的罪魁禍?zhǔn)资谴罅康腄OM。想象一下,假如你有一個有著上千節(jié)點的DOM,在想象一下,使用querySelectorAll或者getElementByTagName,或者是其他以DOM為中心的搜索方式來搜索一個節(jié)點,即使是使用內(nèi)置方法,這也將是一個非常費力的過程。你要知道,多余的DOM節(jié)點會使其他的實用程序也變慢的。
我見過的一種情況,DOM的大小悄然增加,是在一個AJAX網(wǎng)站,它將所有的頁面都存在了DOM中,當(dāng)一個新的頁面通過AJAX被加載時,舊的頁面就會被存入隱藏的DOM節(jié)點。對于DOM的速度,將有災(zāi)難性的降低,特別是當(dāng)一個頁面是動態(tài)加載的。所以你需要一種更好的方法。
在這種情況下,當(dāng)頁面是通過AJAX加載的,并且以前的頁面是存儲在客戶端的,最好的方法就是將內(nèi)容通過String HTML存儲(將內(nèi)容從DOM中移除),然后使用事件委托來避免特定元素事件。這么做的同時,當(dāng)在客戶端緩存內(nèi)容的時候,可以避免大量的DOM生成。
通常控制DOM大小的技巧包括:
DOM很慢是眾所周知的,使得網(wǎng)站變慢的罪魁禍?zhǔn)资谴罅康腄OM。想象一下,假如你有一個有著上千節(jié)點的DOM,在想象一下,使用querySelectorAll或者getElementByTagName,或者是其他以DOM為中心的搜索方式來搜索一個節(jié)點,即使是使用內(nèi)置方法,這也將是一個非常費力的過程。你要知道,多余的DOM節(jié)點會使其他的實用程序也變慢的。
我見過的一種情況,DOM的大小悄然增加,是在一個AJAX網(wǎng)站,它將所有的頁面都存在了DOM中,當(dāng)一個新的頁面通過AJAX被加載時,舊的頁面就會被存入隱藏的DOM節(jié)點。對于DOM的速度,將有災(zāi)難性的降低,特別是當(dāng)一個頁面是動態(tài)加載的。所以你需要一種更好的方法。
在這種情況下,當(dāng)頁面是通過AJAX加載的,并且以前的頁面是存儲在客戶端的,最好的方法就是將內(nèi)容通過String HTML存儲(將內(nèi)容從DOM中移除),然后使用事件委托來避免特定元素事件。這么做的同時,當(dāng)在客戶端緩存內(nèi)容的時候,可以避免大量的DOM生成。
通常控制DOM大小的技巧包括:
- 使用:before和:after偽元素
- 延遲加載和呈現(xiàn)內(nèi)容
- 使用事件委托,更簡便的將節(jié)點轉(zhuǎn)換成字符串存儲
最佳實踐12:在繁重的執(zhí)行上使用Web Workers
這一篇我們將介紹Web Workder,一種可以將繁重操作移到獨立進(jìn)程的方法。
Web Workders在前段時間被引入流行的瀏覽器中,但是好像并沒有被廣泛應(yīng)用。Web Workers的主要功能是在一般瀏覽器執(zhí)行范圍外執(zhí)行繁重的方法。它不會訪問DOM,所以你必須傳入方法涉及的節(jié)點。
以下是一段Web Workder的示例代碼:
以上這段代碼是一個教你如何使用Web Worker在其他進(jìn)程中做一些繁重工作的簡單示例。它要執(zhí)行的是將一個圖片從普通顏色轉(zhuǎn)個灰度,因為這是一個比較繁重的過程,所以你可以將這個進(jìn)程提交給Web Worker,使你的瀏覽器負(fù)載不是很大。Data通過message事件傳回。
你可以仔細(xì)閱讀以下 MDN上關(guān)于Web Workder的使用 ,也許在你的網(wǎng)站上有一些功能可以移到其他的獨立進(jìn)程中去執(zhí)行。
Web Workders在前段時間被引入流行的瀏覽器中,但是好像并沒有被廣泛應(yīng)用。Web Workers的主要功能是在一般瀏覽器執(zhí)行范圍外執(zhí)行繁重的方法。它不會訪問DOM,所以你必須傳入方法涉及的節(jié)點。
以下是一段Web Workder的示例代碼:
- /*?使用Web?Worker?*/ ??
- //??啟動worker ??
- var ?worker?=? new ?Worker( "/path/to/web/worker/resource.js" ); ??
- worker.addEventListener( "message" ,? function (event)?{ ??
- ???? //?我們從web?worker獲取信息! ??
- }); ??
- ? ??
- //?指導(dǎo)Web?Worker工作! ??
- worker.postMessage({?cmd:? "processImageData" ,?data:?convertImageToDataUri(myImage)?}); ??
- ? ??
- /*??resource.js就是一個Web?workder?*/ ??
- self.addEventListener( "message" ,? function (event)?{ ??
- ???? var ?data?=?event.data; ??
- ? ??
- ???? switch ?(data.cmd)?{ ??
- ???????? case ? 'process' : ??
- ???????????? return ?processImageData(data.imageData); ??
- }); ??
- ? ??
- function ?processImageData(imageData)?{ ??
- ???? //?對圖像進(jìn)行操作 ??
- ???? //?例如將它改成灰度 ??
- ? ??
- ???? return ?newImageData; ??
- }??
/* 使用Web Worker */ // 啟動worker var worker = new Worker("/path/to/web/worker/resource.js"); worker.addEventListener("message", function(event) { // 我們從web worker獲取信息! }); // 指導(dǎo)Web Worker工作! worker.postMessage({ cmd: "processImageData", data: convertImageToDataUri(myImage) }); /* resource.js就是一個Web workder */ self.addEventListener("message", function(event) { var data = event.data; switch (data.cmd) { case 'process': return processImageData(data.imageData); }); function processImageData(imageData) { // 對圖像進(jìn)行操作 // 例如將它改成灰度 return newImageData; }
以上這段代碼是一個教你如何使用Web Worker在其他進(jìn)程中做一些繁重工作的簡單示例。它要執(zhí)行的是將一個圖片從普通顏色轉(zhuǎn)個灰度,因為這是一個比較繁重的過程,所以你可以將這個進(jìn)程提交給Web Worker,使你的瀏覽器負(fù)載不是很大。Data通過message事件傳回。
你可以仔細(xì)閱讀以下 MDN上關(guān)于Web Workder的使用 ,也許在你的網(wǎng)站上有一些功能可以移到其他的獨立進(jìn)程中去執(zhí)行。
最佳實踐13:鏈接CSS,避免使用@import
有時候,@import太好用以至于很難抗拒它的誘惑,但是為了減少令人抓狂的請求,你必須要拒絕它!最常見的用法是在一個"main"CSS文件中,沒有任何的內(nèi)容,只有@import規(guī)則。有時,多個@import規(guī)則往往會造成事件嵌套:
我們這樣寫CSS文件,在文件中多了兩個多余鏈接,因此會使頁面加載變慢。SASS可以讀取@import語句,鏈接CSS內(nèi)容到一個文件中,減少了多余的請求,控制了CSS文件的大小。
- //?主CSS文件(main.css) ??
- @ import ? "reset.css" ; ??
- @ import ? "structure.css" ; ??
- @ import ? "tutorials.css" ; ??
- @ import ? "contact.css" ; ??
- ? ??
- //?然后在tutorials.css文件中,會繼續(xù)有@import ??
- @ import ? "document.css" ; ??
- @ import ? "syntax-highlighter.css" ;??
// 主CSS文件(main.css) @import "reset.css"; @import "structure.css"; @import "tutorials.css"; @import "contact.css"; // 然后在tutorials.css文件中,會繼續(xù)有@import @import "document.css"; @import "syntax-highlighter.css";
我們這樣寫CSS文件,在文件中多了兩個多余鏈接,因此會使頁面加載變慢。SASS可以讀取@import語句,鏈接CSS內(nèi)容到一個文件中,減少了多余的請求,控制了CSS文件的大小。
最佳實踐14:在CSS文件中包含多種介質(zhì)類型
在上面第13個最佳實踐中我們說過,多個CSS文件可以通過@import規(guī)則合并到一起。但是很多程序員不知道的是,多種CSS介質(zhì)類型也可以合并到一個文件中。
對于文件的大小,什么時候必須合并介質(zhì),或是什么時候必須分開設(shè)定,CSS并沒有硬性規(guī)定,但是我會比較建議將所有的介質(zhì)合并,除非其中一個介質(zhì)所占的比例比起其他大了許多。少一個請求對于客戶端和服務(wù)器都將輕松不少,而且在大多數(shù)情況下,附贈的介質(zhì)類型相比主屏介質(zhì)類型要相對小很多。
- /*?以下全部寫在一個CSS文件中?*/ ??
- ? ??
- @media?screen?{ ??
- ???? /*?所有默認(rèn)的結(jié)構(gòu)設(shè)計和元素樣式寫在這里?*/ ??
- } ??
- ? ??
- @media?print?{ ??
- ???? /*?調(diào)整打印時的樣式?*/ ??
- } ??
- ? ??
- @media?only?screen?and?(max-width?:?1024px)?{ ??
- ???? /*?使用ipad或者移動電話時的樣式設(shè)定?*/ ??
- }??
/* 以下全部寫在一個CSS文件中 */ @media screen { /* 所有默認(rèn)的結(jié)構(gòu)設(shè)計和元素樣式寫在這里 */ } @media print { /* 調(diào)整打印時的樣式 */ } @media only screen and (max-width : 1024px) { /* 使用ipad或者移動電話時的樣式設(shè)定 */ }
對于文件的大小,什么時候必須合并介質(zhì),或是什么時候必須分開設(shè)定,CSS并沒有硬性規(guī)定,但是我會比較建議將所有的介質(zhì)合并,除非其中一個介質(zhì)所占的比例比起其他大了許多。少一個請求對于客戶端和服務(wù)器都將輕松不少,而且在大多數(shù)情況下,附贈的介質(zhì)類型相比主屏介質(zhì)類型要相對小很多。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
