最好的資源最佳化就是不載入資源。緩存也是最見效的優化手段。說實話,雖然說客戶端快取發生在瀏覽器端,但快取主要還是服務端來控制,與我們前端關係並不是很大。但還是有必要了解一下。
快取包含服務端快取和客戶端緩存,本文只談客戶端快取。所謂客戶端快取主要是http快取。 http快取主要分為強制快取和協商快取。
Expires(http1.0)
在http1.0中使用Expires來做強制快取。 Exprires的值為服務端傳回的資料到期時間。當再次請求時的請求時間小於返回的此時間,則直接使用快取資料。但由於服務端時間和客戶端時間可能有誤差,這也將導致快取命中的誤差。
Cache-Control
Cache-Control有很多屬性,不同的屬性代表的意義也不同。
private:客戶端可以快取
#public:客戶端和代理伺服器都可以快取
max-age=t:快取內容將在t秒後失效
no-cache:需要使用協商快取來驗證快取資料
no-store:所有內容都不會快取。
瀏覽器第一次要求資料時,伺服器會將快取標識與資料一起回應給客戶端,客戶端將它們備份到快取中。再次請求時,客戶端會將快取中的識別傳送給伺服器,伺服器根據此標識判斷。若未失效,返回304狀態碼,瀏覽器拿到此狀態碼就可以直接使用快取資料了。
Last-Modified
伺服器在回應請求時,會告訴瀏覽器資源的最後修改時間
if-Modified-Since
瀏覽器再次請求伺服器的時候,請求頭會包含此字段,後面跟著在快取中獲得的Last-Modified(最後修改時間)。服務端收到此要求頭髮現有if-Modified-Since,則與被要求資源的最後修改時間進行對比,如果大於被要求資源最後修改時間則返回304,瀏覽器從快取獲取資源。如果小於被要求資源最後修改時間,則傳回200,並傳回最新資源,瀏覽器從服務端取得最新的資源,並快取。
Etag
伺服器產生的每個資源的唯一識別字串
If -None-Match
再次請求伺服器時,瀏覽器的請求封包頭會包含此字段,後面的值為在快取中取得的識別。伺服器接收到次訊息後發現If-None-Match則與被要求資源的唯一識別進行比較。如果相同,表示資源沒有被修改過,返回304,瀏覽器從快取取得資源,如果不同說明資源被修改過,則傳回200,並傳回最新資源,瀏覽器從服務端取得最新資源,並快取。
Last-Modified與ETag是可以一起使用的,伺服器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最後才決定是否回傳304。
如果使用前端打包工具,可以在打包檔案時候在為檔案新增版本號碼或hash值,同樣可以區分資源是否過期。
使用CDN託管靜態資源
可以藉助gulp、webpack等打包工具對js、css等檔案合併與壓縮
圖片懶載入、按需加載,當捲動到圖片視覺區域才去載入圖片
如果瀏覽器FPS到達60,就會顯得比較流暢,大多數顯示器的刷新頻率是60Hz,瀏覽器會自動依照這個頻率刷新動畫。
依照FPS等於60來計算,平均一幀的時間為1000ms/60 = 16.7ms,所以每次渲染時間不能超過16ms,如果超過這個時間就會出現丟幀、卡頓現象。
可以在chrome瀏覽器開發者工具中的Timeline中查看更新率,可以查看所有幀率耗時情況以及某一幀的執行情況。 Timeline的使用教學:https://segmentfault.com/a/11...
為了確保正常的FPS,有些渲染效能最佳化還是有必要的。以下所介紹的都是有關渲染優化的策略。
盡量使用css3來做動畫
#總所周知,css的效能要比js快,所以能使用css,盡量不用js來實現
避免使用setTimeout或setInterval,盡量使用requestAnimationFrame來做動畫或高頻Dom操作。
因為setTimeout和setInterval無法保證callback函數的執行時機,很可能在幀結束的時候執行,從而導致丟幀,但是requestAnimationFrame可以保證callback函數在每幀動畫開始的時執行
requestAnimationFrame的中文MDN位址:https://developer.mozilla.org...
複雜的運算運算使用Web Workers
#如果有需要複雜的資料操作,例如對一個有一個元素的陣列遍歷求和,那麼Web Workers在適合不過了。
Web Workers可以讓JavaScript腳本在後台執行緒(類似建立一個子執行緒),而後台執行緒不會影響到主執行緒中的頁面。不過,使用Web Workers建立的執行緒是不能操作DOM樹。
有關Web Workers的更多可以查看MDN詳解:https://developer.mozilla.org...
css放在頭部,js放在尾部。
讀過前面js運作機制的應該知道頁面渲染是怎樣一個過程,不再贅述了。 css放在頭部會避免生成html樹之後重新佈局的閃屏現象,js一般對頁面的影響較大,一般放在尾部最後執行。
事件防手震(debounce)與節流(throttle)
針對高頻觸發的事件(mousemove、scroll)等事件,如果不加以控制會在短時間內觸發很多事件。
函數防手震是指頻繁觸發的情況下,只有足夠的空閒時間,才會執行程式碼一次。場景:註冊時郵箱的輸入框,隨著使用者的輸入,即時判斷郵箱格式是否正確,當第一次輸入事件觸發,設定定時:在800ms之後執行檢查。假如只過了100ms,上次的定時還沒執行,此時清除定時,重新定時800ms。直到最近一次的輸入,後面沒有緊鄰的輸入了,這最近一次的輸入定時計時結束,終於執行了檢查代碼。
const filter = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; $("#email").on("keyup",checkEmail()); function checkEmail(){ let timer=null; return function (){ clearTimeout(timer); timer=setTimeout(function(){ console.log('执行检查'); },800); } }
函數節流是指一定時間內js方法只跑一次。就是本來一秒要執行100次的變成一秒執行10次。
場景:函數節流應用的實際場景,多數在監聽頁面元素滾動事件的時候會用到。
var canRun = true; document.getElementById("throttle").onscroll = function(){ if(!canRun){ // 判断是否已空闲,如果在执行中,则直接return return; } canRun = false; setTimeout(function(){ console.log("函数节流"); canRun = true; }, 300); };
Dom操作
前端開發人員都知道Do操作是非常耗時的(曾經親測過30*30的表格遍歷新增樣式)。所以盡量避免頻繁的Dom操作,如果避免不了就盡量對DOm操作做優化。
1.:缓存Dom查询,比如通过getElementByTagName('p')获取Dom集,而不是逐个获取。 2: 合并Dom操作,使用createDocumentFragment()
var frag = document.createDocumentFragment() for (i<10) { var li = document.createElement('li') frag.appendChild(li) } document.body.appendChild(frag)
3: 使用React、Vue等框架的虚拟dom(原理目前还不明白),可以更快的实现dom操作。
盡量避免重繪(rePaint)和回流(reFlow)
如果使用js修改元素的顏色或背景色就會觸發重繪,重繪的開銷還是比較昂貴的,因為瀏覽器會在某一個DOM元素的視覺效果改變後去check這個DOM元素內的所有節點。
如果修改元素的尺寸和位置就會發生回流,回流開銷更大,它會在某一個DOM元素的位置改變後觸發,而且它會重新計算所有元素的位置和在頁面中的佔有的面積,這樣的話將會引起頁面某一個部分甚至整個頁面的重新渲染。
css3硬體加速
瀏覽器渲染時,會分成兩個圖層:普通圖層和複合圖層。
普通文件流內可以理解為一個複合圖層,absolute、fixed佈局雖然可以脫離普通文檔流,但它仍然屬於普通圖層,不會啟動硬體加速。上面說的重繪(rePaint)和回流(reFlow)說的就是普通圖層上的重繪和回流。
複合圖層會啟動硬體加速。和普通圖層不在同一個圖層,所以複合圖層不會影響普通圖層,如果一個元素被提升到複合圖層,再操作該元素時,就不會引起普通圖層的重繪和回流,從而提升渲染效能。
如何啟動硬體加速:
1.使用translate3d和translateZ
webkit-transform: translateZ(0); -moz-transform: translateZ(0); -ms-transform: translateZ(0); -o-transform: translateZ(0); transform: translateZ(0); webkit-transform: translate3d(0,0,0); -moz-transform: translate3d(0,0,0); -ms-transform: translate3d(0,0,0); -o-transform: translate3d(0,0,0); transform: translate3d(0,0,0);
2.使用opacity
需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态
3.使用will-chang属性
这个属性比较不常用,一般配合opacity与translate使用
针对webkit浏览器,启用硬件加速有些时候可能会导致浏览器频繁闪烁或抖动,可以使用下面方法消除:
-webkit-backface-visibility:hidden; -webkit-perspective:1000;
如果使用硬件加速,请使用z-index配合使用, 因为如果这个元素添加了硬件加速,并且index层级比较低, 那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的), 会默认变为复合层渲染,如果处理不当会极大的影响性能
避免强制同步布局和布局抖动
浏览器渲染过程为:js/css(javascript) > 计算样式(style) > 布局(layout) > 绘制(paint) > 渲染合并图层(Composite)
JavaScript:JavaScript实现动画效果,DOM元素操作等。
Style(计算样式):确定每个DOM元素应该应用什么CSS规则。
Layout(布局):计算每个DOM元素在最终屏幕上显示的大小和位置。
Paint(绘制):在多个层上绘制DOM元素的的文字、颜色、图像、边框和阴影等。
Composite(渲染层合并):按照合理的顺序合并图层然后显示到屏幕上。
在js中如果读取style属性的某些值就会让浏览器强行进行一次布局、计算,然后再返回值,比如:
offsetTop, offsetLeft, offsetWidth, offsetHeight scrollTop/Left/Width/Height clientTop/Left/Width/Height width,height 请求了getComputedStyle(), 或者 IE的 currentStyle
所以,如果强制浏览器在执行JavaScript脚本之前先执行布局过程,这就是所谓的强制同步布局。
比如下面代码:
requestAnimationFrame(logBoxHeight); // 先写后读,触发强制布局 function logBoxHeight() { // 更新box样式 box.classList.add('super-big'); // 为了返回box的offersetHeight值 // 浏览器必须先应用属性修改,接着执行布局过程 console.log(box.offsetHeight); } // 先读后写,避免强制布局 function logBoxHeight() { // 获取box.offsetHeight console.log(box.offsetHeight); // 更新box样式 box.classList.add('super-big'); }
在JavaScript脚本运行的时候,它能获取到的元素样式属性值都是上一帧画面的,都是旧的值。因此,如果你在当前帧获取属性之前又对元素节点有改动,那就会导致浏览器必须先应用属性修改,结果执行布局过程,最后再执行JavaScript逻辑。
如果连续多次强制同步布局,就会导致布局抖动
比如下面代码:
function resizeAllParagraphsToMatchBlockWidth() { for (var i = 0; i < paragraphs.length; i++) { paragraphs[i].style.width = box.offsetWidth + 'px'; } } 作者:SylvanasSun 链接:https://juejin.im/post/59da456951882525ed2b706d 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
我们知道浏览器是一帧一帧的刷新页面的,对于每一帧,上一帧的布局信息都是已知的。
强制布局就是使用js强制浏览器提前布局,比如下面代码:
// bed 每次循环都要去获取left ,就会发生一次回流 function logBoxHeight() { box.style.left += 10 console.log(box.style.left) } // goog var width = box.offsetWidth; function resizeAllParagraphsToMatchBlockWidth() { for (var i = 0; i < paragraphs.length; i++) { // Now write. paragraphs[i].style.width = width + 'px'; } }
DOMContentLoaded与Load
DOMContentLoaded 事件触发时,仅当DOM加载完成才触发DOMContentLoaded,此时样式表,图片,外部引入资源都还没加载。而load是等所有的资源加载完毕才会触发。
1. 解析HTML结构。 2. 加载外部脚本和样式表文件。 3. 解析并执行脚本代码。 4. DOM树构建完成。//DOMContentLoaded 5. 加载图片等外部文件。 页面加载完毕。//load
视觉优化
等待加载时间可以合理使用loading gif动图一定程度上消除用户等待时间的烦躁感
代码对性能的影响可大可小,但是养成一个良好的写代码习惯和高质量的代码,会潜移默化的提高性能,同时也能提高自己的水平。废话不多说,直接看我总结的部分要点(因为这一部分知识点太多,需要大家写代码的时候多多总结)。
避免全局查找
访问局部变量会比访问全局变量快,因为js查找变量的时候现在局部作用局查找,找不到在逐级向上找。
// bad function f () { for (...){ console.log(window.location.href) } } //good function f () { var href = window.location.href for (...){ console.log(href) } }
循环技巧
// bed for(var i = 0; i < array.length; i++){ .... } // good for(var i = 0, len = array.length; i < len; i++){ .... } // 不用每次查询长度
不要使用for in 遍历数组
for in是最慢的,其他的都差不多,其中直接使用for循环是最快的。for in只是适合用来遍历对象。
使用+''代替String()吧变量转化为字符串
var a = 12 //bad a = String(a) // good var a = 12 a = a + ''
这个还有很多类似的,比如使用*1代替parseInt()等都是利用js的弱类型,其实这样对性能提升不是很大,网上有人测试过,进行十几万次变量转换,才快了零点几秒。
删除dom
删除dom元素要删除注册在该节点上的事件,否则就会产生无法回收的内存,在选择removeChild和innerHTML=''二者之间尽量选择后者,据说removeChild有时候无法有效的释放节点(具体原因不明)
使用事件代理处理事件
任何可以冒泡的事件都可以在节点的祖先节点上处理,这样对于子节点需要绑定相同事件的情况就不用分别给每个子节点添加事件监听,而是都提升到祖先节点处理。
通过js生成的dom对象必须append到页面中
在IE下,js创建的额dom如果没有添加到页面,这部分内存是不会被回收的
避免与null比较
可以使用下面方法替换与null比较
1.如果该值为引用类型,则使用instanceof检查其构造函数
2.如果该值为基本类型,使用typeof检查类型
尽量使用三目运算符代替if else
if(a>b){num = a} else{num = b} // 可以替换为 num = a > b ? a : b
当判断条件大于3中情况时,使用switch代替if
因为switch的执行速度比if要快,也别是在IE下,速度大约是if的两倍。
相关推荐:
以上是Js前端效能優化總結的詳細內容。更多資訊請關注PHP中文網其他相關文章!