為什么要進行性能優(yōu)化
57%的?戶更在乎??在3秒內是否完成加載
52%的在線?戶認為??打開速度影響到他們對?站的忠實度
每慢1秒造成?? PV 降低11%,?戶滿意度也隨之降低降低16%
近半數(shù)移動?戶因為在10秒內仍未打開??從?放棄。
性能優(yōu)化學徒工
雅?軍規(guī)踐?
html數(shù)量控制
能盡量用CSS解決的就用CSS解決。(陰影,漸變)壓縮,合并,MD5,CDN
接下來請大家思考一個問題,為什么CDN對于前端這么重要?

-
還有一個很重要的點就是離線緩存
打開谷歌控制臺的application
application
localStorage存儲本地數(shù)據(jù)
//需求是什么?
//假設我們需要請求a.xx3322.js
//在本地存儲localstorage存儲key為a.js,對應的value是a.xx3322.js
//key為a.xx3322.js,對應的value就是我們的目標js代碼
//所以就不需要<script src = "a.xx3322.js">
//可以用下面代碼來實現(xiàn)
//webpack打包出來
var res = {
"a.js":"a.xx3322.js"
}
function activePage(){
for(let item of res){
const js = localStorage[item.key]
//如果本地沒有發(fā)請求,再發(fā)一次請求緩存
//本地存在的話,就要判斷一下當前的版本號
//更新我們的資源
if(js == item.value){
eval(js)
}else{
fetch(item.value).then(function(res){
localStorage['a.js'] = "a.xx3322.js";
localStorage['a.xx3322.js'] = res;
})
}
}
}
activePage();
現(xiàn)在我們可以使用basket.js來實現(xiàn)上述代碼的功能
https://github.com/addyosmani/basket.js
http://www.wenjiangs.com/article/basket-js.html
使用basket.js這個庫就能輕松管理了。
簡單來說 Basket.js 是一個腳本緩存器,使用本地儲存 localStorage 緩存 JavaScript 文件,如果腳本以前在本地緩存過,那么他將會被快速的加載到頁面中,如果沒有緩存過,那么就使用 XHR 異步加載到頁面中
HTML5 規(guī)范建議存儲限額為 5MB 的本地存儲,但瀏覽器可以實現(xiàn)他們自己的配額,如果他們希望。如果超出了配額,瀏覽器可能無法在緩存中存儲項目。如果發(fā)生這種情況,Bask.js 將從最舊的緩存中刪除條目,然后重試。有些像 Opera 這樣的瀏覽器會要求用戶在超過設定閾值時增加配額。
配合使用前端離線緩存方案 localForage
緩存策略
緩存的優(yōu)先級
cache-control > expire > etag > last-modified

他們的關系面試一定會問到。
回去需要用nginx體現(xiàn)一下
網(wǎng)站協(xié)議
HTTP2協(xié)議
HTTP2協(xié)議的特點:
使用二進制格式傳輸,更高效、更緊湊。
TTP 2.0 中所有加強性能的核心點在于此。在之前的 HTTP 版本中,我們是通過文本的方式傳輸數(shù)據(jù)。在 HTTP 2.0 中引入了新的編碼機制,所有傳輸?shù)臄?shù)據(jù)都會被分割,并采用二進制格式編碼。-
對報頭壓縮,降低開銷。
在 HTTP 1.X 中,我們使用文本的形式傳輸 header,在 header 攜帶 cookie 的情況下,可能每次都需要重復傳輸幾百到幾千的字節(jié)。在 HTTP 2.0 中,使用了 HPACK 壓縮格式對傳輸?shù)?header 進行編碼,減少了 header 的大小。并在兩端維護了索引表,用于記錄出現(xiàn)過的 header ,后面在傳輸過程中就可以傳輸已經(jīng)記錄過的 header 的鍵名,對端收到數(shù)據(jù)后就可以通過鍵名找到對應的值。
-
多路復用,一個網(wǎng)絡連接實現(xiàn)并行請求。
在 HTTP 2.0 中,有兩個非常重要的概念,分別是幀(frame)和流(stream)。幀代表著最小的數(shù)據(jù)單位,每個幀會標識出該幀屬于哪個流,流也就是多個幀組成的數(shù)據(jù)流。
多路復用,就是在一個 TCP 連接中可以存在多條流。換句話說,也就是可以發(fā)送多個請求,對端可以通過幀中的標識知道屬于哪個請求。通過這個技術,可以避免 HTTP 舊版本中的隊頭阻塞問題,極大的提高傳輸性能。
HTTP/2對同?域名下所有請求都是基于流,也就是說同?域名不管訪問多少?件,也只建??路連接。同樣Apache的最?連接數(shù)為300,因為有了這個新特性,最?的并發(fā)就可以提升到300,?原來提升了6倍?。ū緃ttp1.x的話每個用戶可能就會占據(jù)5-6個請求)
-
服務器主動推送,減少請求的延遲 。
在 HTTP 2.0 中,服務端可以在客戶端某個請求后,主動推送其他資源。可以想象以下情況,某些資源客戶端是一定會請求的,這時就可以采取服務端 push 的技術,提前給客戶端推送必要的資源,這樣就可以相對減少一點延遲時間。當然在瀏覽器兼容的情況下你也可以使用 prefetch 。
默認使用加密。
小字為先
性能優(yōu)化其實就可以用這四個字來概括,“小字為先” ,也就是將大的東西變小
渲染中性能優(yōu)化
重繪
先來了解一下開發(fā)者工具下中的隱藏技能
控制臺下面有個rendering的選項(如果沒有的話可以在上面的performance右邊的工具欄選項選擇more tools中的rendering進行添加)

Paint Flashing 高亮顯示網(wǎng)頁中需要被重繪的部分。
Layer Borders 顯示Layer邊界。
FPS Meter 每一秒的幀細節(jié),幀速率的分布信息和GPU的內存使用情況。

Scrolling Performance Issues 分析鼠標滾動時的性能問題,會顯示使屏幕滾動變慢的區(qū)域。
Emulate CSS Media 仿真CSS媒體類型,查看不同的設備上CSS樣式效果,可能的媒體類型選項有print、screen。
將paint flashing選項打勾之后點擊刷新的時候可以看到頁面綠了一下,正是因為這是網(wǎng)頁中需要被重繪的部分。
上個代碼(重點部分)
<div class="container">
<div class="ball" id="ball">
</div>
</div>
<script>
var ball = document.getElementById('ball');
ball.classList.add('ball');
ball.classList.add('ball-running');
</script>
<style>
.container{
position: relative;
min-height: 400px;
}
.ball{
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: blueviolet;
border-radius: 50%;
box-shadow: 0 0 5px rgba(245, 172, 172, 0.75)
}
.ball-running{
animation: run-around 4s infinite;
}
@keyframes run-around {
0%{
top: 0;
left: 0;
}
25%{
top: 0;
left: 200px;
}
50%{
top: 200px;
left: 200px;
}
75%{
top: 200px;
left: 0;
}
}
</style>
上面主要就是實現(xiàn)一個小球運動的效果,當我們已經(jīng)在rendering里面選中了paint flashing的時候,我們可以看到小球運動起來的效果是外面包裹著一層綠色,說明這是要進行重繪的區(qū)域

使用Chrome DevTools的performance面板可以記錄和分析頁面在運行時的所有活動。
https://www.cnblogs.com/xiaohuochai/p/9182710.html

loading:加載時間scripting:腳本執(zhí)行時間rendering:重排時間painting:重繪時間idle:空閑時間,網(wǎng)站性能越好,空閑時間越長
網(wǎng)站的渲染流程
將上面的圖從summary切換到event log

event log按照時間先后來排序
可以看到網(wǎng)站的渲染流程是這樣的:
- 獲取DOM進行分層
- 對每個圖層節(jié)點進行樣式的計算 Recalculate Style
- 為每個對應的節(jié)點圖形和位置 重排Layout
- 對每個節(jié)點進行繪制并添加到圖層位圖中 Paint
(并不是每個圖層都會GPU進行參與)
只有Composite Layers才會讓GPU參與 - 將這個位圖上傳至GPU 旋轉、縮放、偏移、修改透明
所以渲染過程總的來說是這樣的:Layout -》 Paint -》 Composite Layers
我們說DOM會進行分層,那么什么元素會獨立成層呢?
根元素、position、transfrom、半透明元素、CSS濾鏡、Video 、Overflow
我們說GPU跑起來會比CPU快,那么哪些元素屬性會讓GPU參與進來呢?
CSS3D、Video、Webgl(https://github.com/lgwebdream/gpu.js)、CSS濾鏡、transfrom
CPU和GPU到底有什么區(qū)別呢?
https://www.zhihu.com/question/19903344
CPU即中央處理器,GPU即圖形處理器。其次,要解釋兩者的區(qū)別,要先明白兩者的相同之處:兩者都有總線和外界聯(lián)系,有自己的緩存體系,以及數(shù)字和邏輯運算單元。一句話,兩者都為了完成計算任務而設計。
總結一下:
相同之處:總線和外界聯(lián)系、緩存體系、數(shù)字和邏輯與預算單元、計算而生
不同之處:CPU主要負責和操作系統(tǒng)應用程序,GPU顯示數(shù)據(jù)相關
http://www.sohu.com/a/200435336_463987
還要推一推gpu.js這個庫
https://github.com/gpujs/gpu.js
GPU.js is a JavaScript Acceleration library for GPGPU (General purpose computing on GPUs) in JavaScript. GPU.js will automatically compile simple JavaScript functions into shader language and run them on the GPU. In case a GPU is not available, the functions will still run in regular JavaScript.
也就是說GPU.js會自動的將簡單的js函數(shù)翻譯成shader 語言并放在GPU上面運行他們。
像素管道
像素管道是網(wǎng)頁性能優(yōu)化的靈魂,讓我們來看看什么是像素管道

上圖就是像素管道,通常我們會使用JS修改一些樣式,隨后瀏覽器會進行樣式計算,然后進行布局,繪制,最后將各個圖層合并在一起完成整個渲染的流程,這期間的每一步都有可能導致頁面卡頓。
注意,并不是所有的樣式改動都需要經(jīng)歷這五個步驟。舉例來說:如果在JS中修改了元素的幾何屬性(寬度、高度等),那么瀏覽器需要需要將這五個步驟都走一遍。但如果您只是修改了文字的顏色,則布局(Layout)是可以跳過去的。
除了最后的合成,前面四個步驟在不同的場景下都可以被跳過。例如:CSS動畫就可以跳過JS運算,它不需要執(zhí)行JS。
通過錄制performance我們可以看到主線程的任務。
我們可以放大主線程從而精準的看到每一幀瀏覽器都執(zhí)行了哪些任務以及每個任務耗費了多長時間。如下圖所示:

我們如何改進上面的代碼呢,減少重繪呢?
使用transform來代替top和left
我們可以看到https://csstriggers.com/transform上面對于transform的描述是
Changing transform does not trigger any geometry changes or painting, which is very good. This means that the operation can likely be carried out by the compositor thread with the help of the GPU.
也就是說改變transform并不會觸發(fā)幾何圖形的更改或者重繪,transform的操作可以合成器線程在GPU的幫助下執(zhí)行。
css-triggers給出了不同的CSS屬性被更改后會觸發(fā)像素管道的那些步驟。
簡單來說,像素管道經(jīng)歷的步驟越多,渲染時間就越長,單個步驟內也可能因為某個原因而變得耗時很長。
將上面代碼的keyframes部分更改成:
0%{
transform: translate(0)
}
25%{
transform: translate(200px,0)
}
50%{
transform: translate(200px,200px)
}
75%{
transform: translate(0,200px)
}
我們此時看到小球在運動,但是已經(jīng)沒有綠色了,已經(jīng)沒有重排了??!

神奇?。?!
我們再來看看錄制的summary

我的天吶!真神奇。換了transforms每次運動就不用重繪重排了,都是合成層和GPU在操作了,而且只要GPU開啟,速度就會快很多。
https://csstriggers.com/這個網(wǎng)站拿好不送?。。?p>
總結
CSS動畫我們可以通過降低繪制區(qū)域并且使transform屬性來完成動畫,同時我們需要管理好圖層,因為繪制和圖層管理都需要成本,通常我們需要根據(jù)具體情況進行權衡并做出最好的選擇。
重排
什么會引起重排?
- 添加或者刪除元素的時候
- 元素的位置發(fā)生改變
- 元素的-webkit-box-sizing: border-box;不會讓我們的盒子發(fā)生太多的變化
如果用標準盒子模型的話,盒子越來越大 - 頁面初始化
- 內容變化(沒有撐開盒)
- js 讀取一下幾個值 offset、scroll、width、getComputerStyle
為什么js讀取的時候會引起重排?
var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
ele.style.padding = '5px';
乍一想,元素的樣式改變了三次,每次改變都會引起重排和重繪,所以總共有三次重排重繪過程,但是瀏覽器并不會這么笨,它會把三次修改“保存”起來(大多數(shù)瀏覽器通過隊列化修改并批量執(zhí)行來優(yōu)化重排過程),一次完成!但是,有些時候你可能會(經(jīng)常是不知不覺)強制刷新隊列并要求計劃任務立即執(zhí)行。獲取布局信息的操作會導致隊列刷新,比如:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
-
getComputedStyle() (currentStyle in IE)
將上面的代碼稍加修改,var ele = document.getElementById('myDiv'); ele.style.borderLeft = '1px'; ele.style.borderRight = '2px'; // here use offsetHeight // ... ele.style.padding = '5px';
因為offsetHeight屬性需要返回最新的布局信息,因此瀏覽器不得不執(zhí)行渲染隊列中的“待處理變化”并觸發(fā)重排以返回正確的值(即使隊列中改變的樣式屬性和想要獲取的屬性值并沒有什么關系),所以上面的代碼,前兩次的操作會緩存在渲染隊列中待處理,但是一旦offsetHeight屬性被請求了,隊列就會立即執(zhí)行,所以總共有兩次重排與重繪。所以盡量不要在布局信息改變時做查詢。
我們可以使用requestAnimationFrame,拆開來寫,就給了瀏覽器優(yōu)化的機會了。
var ele = document.getElementById('myDiv');
// here use offsetHeight
// ...
requestAnimationFrame(function(){
ele.style.padding = '5px';
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
})
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
requestAnimationFrame告訴瀏覽器——你希望執(zhí)行一個動畫,并且要求瀏覽器在下次重繪之前調用指定的回調函數(shù)更新動畫。該方法需要傳入一個回調函數(shù)作為參數(shù),該回調函數(shù)會在瀏覽器下一次重繪之前執(zhí)行
頁面加載性能優(yōu)化
必須知道的概念
TTFB(Time To First Byte ):?字節(jié)時間
FP(First Paint ):?次繪制,僅有?個div根節(jié)點。
FCP(First Contentful Paint): ?次有內容的繪制,包含??的基本框架,但沒有數(shù)據(jù)內容。
FMP(First Meaningful Paint):?次有意義的繪制,包含??所有元素及數(shù)據(jù)
TTI(Time To Interactive):可交互時間
Long tasks:超過了 50ms 的任務
SSR&&CSR:服務端渲染和客戶端渲染
Isomorphic JavaScript:同構化




類比于VUE

created 類比于 FP(首次繪制,只有一個app空節(jié)點)
mounted 類比于 FMP(頁面基本框架繪制完成)
Performance — 前端性能監(jiān)控利器
Performance是一個做前端性能監(jiān)控離不開的API,最好在頁面完全加載完成之后再使用,因為很多值必須在頁面完全加載之后才能得到。最簡單的辦法是在window.onload事件中讀取各種數(shù)據(jù)。
https://www.cnblogs.com/bldxh/p/6857324.html
https://cloud.tencent.com/developer/news/301840
從輸入url到用戶可以使用頁面的全過程時間統(tǒng)計,會返回一個PerformanceTiming對象,單位均為毫秒。

每一個performance.timing屬性都表示一個頁面事件(例如頁面發(fā)送了請求)或者頁面加載(例如當DOM開始加載),測量以毫秒的形式從1970年1月1日的午夜開始。結果為0表示該事件未發(fā)生(例如redirectEnd或者redirectStart等)

其中有個方法叫getEntries()
獲取所有資源請求的時間數(shù)據(jù),這個函數(shù)返回一個按startTime排序的對象數(shù)組,數(shù)組成員除了會自動根據(jù)所請求資源的變化而改變以外,還可以用mark(),measure()方法自定義添加,該對象的屬性中除了包含資源加載時間還有以下五個屬性。
name:資源名稱,是資源的絕對路徑或調用mark方法自定義的名稱
startTime:開始時間
duration:加載時間
entryType:資源類型,entryType類型不同數(shù)組中的對象結構也不同!具體見下
initiatorType:誰發(fā)起的請求,具體見下
話不多說,咱們來寫寫代碼。
<style>
body{
background-color: greenyellow;
}
</style>
const obsever = new PerformanceObserver((list)=>{
for(const entry of list.getEntries()){
console.log(entry.entryType);
console.log(entry.startTime);
console.log(entry.duration);
}
})
obsever.observe({entryTypes:['paint']});
結果如下:

或者可以直接通過window.performance.getEntriesByType("paint")就可以取得FP和FCP的值

FMP主要用來給頁面打點。
五分鐘擼一個前端性能監(jiān)控工具
http://web.jobbole.com/94938/
聊聊performance中的long task
什么是 long task?

https://www.itcodemonkey.com/article/10654.html
簡單而言,任何在瀏覽器中執(zhí)行超過 50 ms 的任務,都是 long task。
那么 long task這個時間是怎么得來的?
因為瀏覽器是單線程,這意味著同一時間主線程只能處理一個任務,如果一個任務執(zhí)行時間太長,瀏覽器就無法執(zhí)行其他任務,用戶就會感覺瀏覽器被卡死,因為他的輸入得不到任何響應。
為了100ms內能給出相應,將空閑周期執(zhí)行的任務限制在50ms意味著,即使用戶的輸入行為發(fā)生在任務剛執(zhí)行時。瀏覽器仍有50ms來相應用戶的輸入。
long task 會長時間占據(jù)主線程資源,進而阻礙了其他關鍵任務的執(zhí)行/響應,造成頁面卡頓。
常見場景如:
不斷計算 DOM 元素的大小、位置,并且根據(jù)結果對頁面進行 relayout;
一次性生成十分龐大的 DOM 元素,如大型表單;
1000000次的循環(huán)計算;
long task的基本屬性
Long Tasks API 定義了 PerformanceLongTaskTiming接口,用于描述 long task。
一般而言,name + attribution 就可以基本定位出 long task 的來源:
name:告訴我們來源是 <script/> 還是 <iframe/> ?self -> <script/>;same-origin-xxx + cross-origin-xxx -> <iframe/>
attribution:到底是哪個 <iframe/>?
如何使用?
const obsever = new PerformanceObserver((list)=>{
for(const entry of list.getEntries()){
console.log(entry.entryType);
console.log(entry.startTime);
console.log(entry.duration);
console.log(JSON.stringify(entry.attribution))
}
})
obsever.observe({entryTypes:['longtask']});

還發(fā)現(xiàn)了一篇不錯的文章,前端性能優(yōu)化標準https://yq.aliyun.com/articles/598162
CSR SSR 預渲染 同構的優(yōu)點和缺點

NodeJs性能優(yōu)化
什么是內存泄漏?
不再用到的變量/內存,沒有及時釋放,就叫做內存泄漏。

內存泄漏的表現(xiàn)
隨著內存泄漏的增長,V8對垃圾收集器越來越具有攻擊性,這會使你的應用運行速度變慢。
內存泄漏可能觸發(fā)其他類型的失敗,可能會耗盡文件描述符,還可能會突然不能建立新的數(shù)據(jù)庫連接。
壓力測試尋找內存泄漏
https://www.cnblogs.com/ycyzharry/p/8372168.html
https://github.com/wg/wrk
wrk支持大多數(shù)類UNIX系統(tǒng),不支持windows。
還有更專業(yè)的JMeter
查找node內存泄漏工具
memwatch + heapdump
如果不發(fā)生特別大的內存泄漏問題,這兩個工具是不會跳出來的。
memwatch.on('leak',function(info){
var file = './tmp/heapsnapshot';
heapdump.writeSnapshot(file,function(err){
if(err)console.log(err);
else console.error('Wrote snapshot',file);
})
})
//通過Diff的方式找到真正的元兇
var hd = new memwatch.HeapDiff();
var diff = hd.end()
//一個狀態(tài)時間發(fā)射器
memwatch.on('stats',function(stats){
//數(shù)據(jù)包括
usage_trend(使用趨勢)
current_base(當前基數(shù))
estimated_base(預期基數(shù))
num_full_gc(完整的垃圾回收次數(shù))
num_inc_gc(增長的垃圾回收次數(shù))
heap_compactions(內存壓縮次數(shù))
min(最小)
max(最大)
})
http://www.linkdata.se/sourcecode/memwatch/ memwatch的源代碼下載地址
Nodejs編碼規(guī)范
慎用內存緩存
函數(shù)內的變量是可以隨著函數(shù)執(zhí)行被回收的,但是全局不行。所以避免使用對象作為緩存,可以移步到Redis等。
Redis 是完全開源免費的,遵守BSD協(xié)議,是一個高性能的key-value數(shù)據(jù)庫。
Redis 與其他 key - value 緩存產(chǎn)品有以下三個特點:
Redis支持數(shù)據(jù)的持久化,可以將內存中的數(shù)據(jù)保存在磁盤中,重啟的時候可以再次加載進行使用。
Redis不僅僅支持簡單的key-value類型的數(shù)據(jù),同時還提供list,set,zset,hash等數(shù)據(jù)結構的存儲。
Redis支持數(shù)據(jù)的備份,即master-slave模式的數(shù)據(jù)備份。
http://www.runoob.com/redis/redis-intro.html
關于隊列消費不及時
比如我們用log4來收集日志,如果日志的產(chǎn)生速度大于文件寫入的速度。就容易產(chǎn)生內存泄漏。訪問已經(jīng)結束了,服務器的log4日志還在不停的寫。
解決方式:
監(jiān)控隊列的長度一旦堆積就報警或者拒絕新的要求。
所以的異步調用都有超時回調,一旦達到時間調用未得到結果就報警。
關于閉包
node如果有閉包從而產(chǎn)生內存泄露服務器就很容易掛,相對于在瀏覽器的js的閉包,node的閉包的處理顯得更加重要。
解決方式:
- weakmap可以立即回收某個變量。
let b = new Object()
let wm = new Weakmap()
wm.set(b,new Array(5*1024*1024))
b = null
- perf_hooks(性能鉤子)
是nodejs中的一個api。
The Performance Timing API provides an implementation of the W3C Performance Timeline specification. The purpose of the API is to support collection of high resolution performance metrics. This is the same Performance API as implemented in modern Web browsers.
提供的功能就類似于JS中的那個new PerformanceObserver
const obs = new PerformanceObserver((items) => {
console.log(items.getEntries()[0].duration);
performance.clearMarks();
});
obs.observe({ entryTypes: ['measure'] });
performance.mark('A');
doSomeLongRunningProcess(() => {
performance.mark('B');
performance.measure('A to B', 'A', 'B');
});
詳細內容參照http://nodejs.cn/api/perf_hooks.html
總結
對于nodejs應用的測試
- node --inspect app.js
- chrome://inspect/#devices
- 沒經(jīng)過壓力測試的代碼只完成10%
- 準確計算QPS未雨綢繆
- 合理利用壓力測試工具
- 注意緩存隊列及其他耗時較長的代碼
- 開發(fā)健壯的NodeJs應用
