前端20個靈魂拷問 徹底搞明白你就是中級前端工程師 【下篇】

Peter譚金傑發表於2019-08-28

clipboard.png

不知不覺,已經來到了最後的下篇 其實我寫的東西你如果認真去看,跟著去寫,應該能有不少的收穫。

最近一些跨平臺技術,React-nativeflutter之類的,比較火。但是,我還是不準備把它們放進來,因為那是為做App而生,我想把Electron這個桌面端跨平臺的技術放進來。理由是什麼,後面說

這是上篇和中篇,如果你是第一次看這個系列文章,歡迎去從頭開始學習:

前端20個靈魂拷問 徹底搞明白你就是中級前端工程師 【上篇】
前端20個靈魂拷問 徹底搞明白你就是中級前端工程師 【中篇】

以及一些比較不錯的文章:

從零編寫一個React框架

我們為什麼要熟悉這些通訊協議

單頁面應用SPA原理

9102年:手寫一個React腳手架 【優化極致版】

效能優化不完全手冊

Electron跨平臺入門系列

上面的文章,gitHub上,都有對應的原始碼。

進入正題

一千個人眼裡有一千個哈姆雷特,我們做不到完美

每個人評判的標準不一樣,我們唯有拿出碾壓這個層級的能力的時候,才能堵住質疑者的嘴。當然,我們不做技術槓精,技術本身沒有好壞。不喜歡就不理會

最後問題,我準備如下內容:

前端的效能優化方向

從傳輸層面去優化的方向

預解析地址 首次請求解析地址如果沒有快取 那麼可能消耗60-120ms

效能優化不完全手冊這裡面有介紹

圖片描述

preload預請求必要內容,prefetch預請求可能需要內容

這種請求方式不會阻塞瀏覽器的解析,而且能將預請求的資源快取起來,而且可以設定crossorgin進行跨域資源的快取,不會推遲首屏的渲染時間,還會加快後面的載入時間,因為後面的本身需要的資源會直接從快取中讀取,而不會走網路請求。

使用 preload 前,在遇到資源依賴時進行載入:

clipboard.png

使用 preload 後,不管資源是否使用都將提前載入:

clipboard.png

可以看到,preload 的資源載入順序將被提前:

clipboard.png
使用 preload 後,Chrome 會有一個警告:

clipboard.png
preload 和 prefetch 混用的話,並不會複用資源,而是會重複載入。
若不確定資源是必定會載入的,則不要錯誤使用 preload,以免本末倒置,給頁面帶來更沉重的負擔。

clipboard.png

preload 載入頁面必需的資源如 CDN 上的字型檔案,與 prefetch 預測載入下一屏資料,興許是個不錯的組合。

preload和prefetch詳解 這篇文章寫得很棒 感謝作者

減少傳輸次數

部分圖片base64處理,然後使用雪碧圖。多張圖拼成一張傳輸

當然base64這個東西慎用,實際開發中它表現並那麼好

減少傳輸體積

例如後端返回資料:“該使用者沒有擁有許可權”

可以改成:0

約定優於配置的思想一定要有

使用probbuffer協議

ProtoBuffer是由谷歌研發的物件序列化和反序列化的開源工具

它的本質就是將一段資料序列化,轉變成二進位制形式傳輸

然後另外的伺服器端或者客戶端接受到之後 反序列化,轉換成對應的資料格式(json

好像還有人沒有聽說這個傳輸協議 其實它傳輸過程就是2進位制流的形式

用得最多的是和GRPC配合Go語言或者伺服器之間傳輸資料

例如IM應用,每個IM應用都是一個服務端 也是一個客戶端

那麼對於這種頻繁傳輸資料的時候,可以使用protobuffer傳輸協議

protobuffer下載

protobuffer有幾個優點:

1.平臺無關,語言無關,可擴充套件;
2.提供了友好的動態庫,使用簡單;
3.解析速度快,比對應的XML快約20-100倍;
4.序列化資料非常簡潔、緊湊,與XML相比,其序列化之後的資料量約為1/3到1/10。

protobuffer.js - 我們可以使用這個庫來解析

protobuf.js 提供了幾種方式來處理proto

直接解析,如protobuf.load("awesome.proto", function(err, root) {...})

轉化為JSONjs後使用,如protobuf.load("awesome.json", function(err, root) {...})

當然我們一般轉換成.js後使用

vue使用protobuffer 我這裡不做大篇介紹,因為有人完全用不到

程式碼層次優化:

封裝資料物件

可以用物件進行大資料封裝,儘量用物件key-value形式封裝

如果需要物件遍歷 其實也有很多種方法可以做到

用物件有個好處 就是資料量大起來但是需要查詢的時候會非常快

避免書寫耗時的同步程式碼

不管前端怎麼發展,js主執行緒是單執行緒,並且與GUI渲染執行緒互斥還是沒有變

為什麼?

因為js可以進行dom操作 為了防止在渲染過程出現dom操作而造成不可預見後果

現代框架的底層其實還是dom操作 並且直接的dom操作比資料驅動要快多!

例如:

for(let i=0; i< 100000; i++){
    console.log(i)
}

console.log(1)

for迴圈其實很快,但是走完這100000次迴圈的耗時,到列印出1,有可能超過100ms

那麼如果這個列印輸出1是一個使用者互動操作 就會讓使用者有了延遲卡頓的現象

所謂的卡,並不是電腦或者手機帶不動我們的程式碼,而是js執行緒和GUI渲染互斥造成的假象。(大部分是這情況,也有配置特別低的)

如果非要同步程式碼的場景?

那麼我建議ES6的非同步方案,或者改變實現方案,因為大部分效能方案優化是卡在這個點。

手機端白屏,持久化儲存等解決網路傳輸慢等方案

淘寶等task-slice 方案

淘寶task-slice方案

先不說這篇文章實現最終效果怎樣,但是這種思想在前端裡是可以大量使用的,Go語言裡就有切片

渲染任務分割後開啟效能除錯皮膚

clipboard.png

可以看到是一點點渲染出來的 也算是加快了首屏渲染吧!

圖片描述

切片佇列的核心程式碼:

function* sliceQueue({ sliceList, callback }) {
    let listOrNum = (isNum(sliceList) && sliceList) || (isArray(sliceList) && sliceList.length);
    for (let i = 0; i < listOrNum; ++i) {
        const start = performance.now();
        callback(i);
        while (performance.now() - start < 16.7) {
            yield;
        }
    }
}

跟我的React框架編寫的每幀清空渲染佇列有點類似:


/**
 * 佇列   先進先出 後進後出 ~
 * @param {Array:Object} setStateQueue  抽象佇列 每個元素都是一個key-value物件 key:對應的stateChange value:對應的元件
 * @param {Array:Component} renderQueue  抽象需要更新的元件佇列 每個元素都是Component
 */
const setStateQueue = [];
const renderQueue = [];


function defer(fn) {
  //requestIdleCallback的相容性不好,對於使用者互動頻繁多次合併更新來說,requestAnimation更有及時性高優先順序,requestIdleCallback則適合處理可以延遲渲染的任務~
  //   if (window.requestIdleCallback) {
  //     console.log('requestIdleCallback');
  //     return requestIdleCallback(fn);
  //   }
  //高優先順序任務
  return requestAnimationFrame(fn);
}

export function enqueueSetState(stateChange, component) {
  if (setStateQueue.length === 0) {
    //清空佇列的辦法是非同步執行,下面都是同步執行的一些計算
    defer(flush);
  }


...//dosomething 

}

從零編寫一個react框架

資料持久化儲存

PWA,漸進式web應用

將資料資源儲存在快取中,每次請求前判斷是否在Service Worker中,如果沒有再請求網路資源

PWA 的主要特點包括下面三點:

可靠 - 即使在不穩定的網路環境下,也能瞬間載入並展現
體驗 - 快速響應,並且有平滑的動畫響應使用者的操作
粘性 - 像裝置上的原生應用,具有沉浸式的使用者體驗,使用者可以新增到桌面

當使用者開啟我們站點時(從桌面 icon 或者從瀏覽器),通過 Service Worker 能夠讓使用者在網路條件很差的情況下也能瞬間載入並且展現。

Service Worker 是用 JavaScript 編寫的 JS 檔案,能夠代理請求,並且能夠操作瀏覽器快取,通過將快取的內容直接返回,讓請求能夠瞬間完成。開發者可以預儲存關鍵檔案,可以淘汰過期的檔案等等,給使用者提供可靠的體驗。

如果站點載入時間超過 3s,53% 的使用者會放棄等待。頁面展現之後,使用者期望有平滑的體驗,過渡動畫和快速響應。

為了保證首屏的載入,我們需要從設計上考慮,在內容請求完成之前,可以優先保證 App Shell 的渲染,做到和 Native App 一樣的體驗,App Shell 是 PWA 介面展現所需的最小資源。

文件寫得最好的,還是百度的lavas

clipboard.png

Service Worker生命週期分為這麼幾個狀態 安裝中, 安裝後, 啟用中, 啟用後, 廢棄

clipboard.png

安裝( installing ):這個狀態發生在 Service Worker 註冊之後,表示開始安裝,觸發 install 事件回撥指定一些靜態資源進行離線快取。

安裝後( installed ):Service Worker 已經完成了安裝,並且等待其他的 Service Worker 執行緒被關閉。

啟用( activating ):在這個狀態下沒有被其他的 Service Worker 控制的客戶端,允許當前的 worker 完成安裝,並且清除了其他的 worker 以及關聯快取的舊快取資源,等待新的 Service Worker 執行緒被啟用。

啟用後( activated ):在這個狀態會處理 activate 事件回撥 (提供了更新快取策略的機會)。並可以處理功能性的事件 fetch (請求)、sync (後臺同步)、push (推送)。

廢棄狀態 ( redundant ):這個狀態表示一個 Service Worker 的生命週期結束。

Service Worker本質,可以看成另外一個執行緒啟動,做為一箇中介軟體在發揮作用。

快取的資源都是可以在這裡看到

clipboard.png

Service Worker只能在localhost除錯中或者https中使用,因為它的許可權過於強大,可以攔截請求等。所以要確保安全,目前PWA並不成熟,瀏覽器相容性還是不那麼好,但是它用起來是真的很舒服

另外一種持久化儲存方案:

localStorage

clipboard.png

瀏覽器APIlocalStorage.getItem等...

有類似將js檔案快取寫入localStorage 然後通過與服務端對比版本號再決定是否更新js檔案

還有在進入首頁時,將詳情頁的模版先存入localStorage 當進入詳情頁時候直接取出,然後發請求,把請求回來的一小部分內容(比如圖片,渲染上去)

當然,還有種種的應用,騷操作。

最後是框架,現在的單頁面框架,其實很簡單。 每次更新頁面,diff對比差異後,更新差異部分。

精細化拆分元件 , 經常變和不經常變的分拆

精細化定製資料來源,最好做到單向資料流,只有一個資料改變可以影響重新渲染

並不是所有的都需要在shouldComponentUpdate中對比然後決定是否要更新

實踐證明 複用1000個元件渲染在頁面中

immutable去生成不可變資料對比

跟用PureComponent淺比較 後者會快很多很多

永遠別忘了js主執行緒和GUI渲染執行緒互斥。

合理手段減少重複渲染次數

如何優化你的超大型React應用
前端效能優化不完全手冊 - 很早前寫的文章

發現效能優化其實要寫的太多太多,但是,核心點在上面和文章裡了,特別是我的那個清空渲染佇列的程式碼,我決定能解決很大部分的效能瓶頸。

負載均衡,Nginxpm2配置

在理解Nginx的用途之前先了解正向代理、反向代理的概念:

正向代理:是一個位於客戶端和原始伺服器(origin server)之間的伺服器,為了從原始伺服器取得內容,客戶端向代理髮送一個請求並指定目標(原始伺服器),然後代理向原始伺服器轉交請求並將獲得的內容返回給客戶端。

反向代理:在計算機網路中,反向代理是代理伺服器的一種。它根據客戶端的請求,從後端的伺服器上獲取資源,然後再將這些資源返回給客戶端。與正向代理不同,正向代理作為一個媒介將網際網路上獲取的資源返回給相關聯的客戶端,而反向代理是在伺服器端作為代理使用,而不是客戶端。

PM2是一款非常好用的Node.js服務啟動容器。它可以讓你保持應用程式永遠執行,要重新載入它們無需停機(我是這麼理解的:PM2是一個監控工具)。

nginx是一款輕量化的web伺服器。相較於Apache具有佔有記憶體少,併發高等優勢。使用epoll模型,nginx的效率很高。並且可以熱升級。

Nginx與PM2的區別:

pm2是在應用層面單機的負載,nginx是多用於多機叢集的負載PM2 Cluster 是對單臺伺服器而言的,而 nginx 是對多臺伺服器而言的,它們可以很好的結合在一起。全篇看下來會發現,其實Nginx與PM2完全是不一樣的,兩者之間沒有很大的相同點讓人混淆。換一種更容易理解的說法是:nginx配置多站點(域名),pm2管理nodejs後臺程式

使用PM2永動機啟動Node.js專案,再使用nginx做反向代理,簡直完美。

因為node.js程式監聽的是伺服器埠,使用nginx做反向代理,就可以任意配置你的二級域名來訪問你的程式

這裡我們主要介紹nginx的負載模組

HTTP負載均衡模組(HTTP Upstream)

這個模組為後端的伺服器提供簡單的負載均衡(輪詢(round-robin)和連線IP(client IP))

如下例:

upstream backend  {
  server backend1.example.com weight=5;
  server backend2.example.com:8080;
  server unix:/tmp/backend3;
}
 
server {
  location / {
    proxy_pass  http://backend;
  }
}

Nginx的負載均衡演算法:

1.round robin(預設)
輪詢方式,依次將請求分配到各個後臺伺服器中,預設的負載均衡方式。
適用於後臺機器效能一致的情況。
掛掉的機器可以自動從服務列表中剔除。

2.weight
根據權重來分發請求到不同的機器中,指定輪詢機率,weight和訪問比率成正比,用於後端伺服器效能不均的情況。

例如:

upstream bakend {    
server 192.168.0.14 weight=10;    
server 192.168.0.15 weight=10;    
}  

3.IP_hash
根據請求者ip的hash值將請求傳送到後臺伺服器中,可以保證來自同一ip的請求被打到固定的機器上,可以解決session問題。

例如:

upstream bakend {    
ip_hash;    
server 192.168.0.14:88;    
server 192.168.0.15:80;    
}   

4.fair
根據後臺響應時間來分發請求,響應時間短的分發的請求多。

例如:

upstream backend {
server server1;
server server2;
fair;
}

5.url_hash
根據請求的urlhash值將請求分到不同的機器中,當後臺伺服器為快取的時候效率高。

例如:

在upstream中加入hash語句,server語句中不能寫入weight等其他的引數,hash_method是使用的hash演算法

upstream backend {    
server squid1:3128;    
server squid2:3128;    
hash $request_uri;    
hash_method crc32;    
}  

常見的負載均衡演算法
使用pm2:

npm install pm2 -g 


pm2 start app.js

clipboard.png

PM2 的主要特性

內建負載均衡(使用 Node cluster 叢集模組)
後臺執行
0 秒停機過載,我理解大概意思是維護升級的時候不需要停機.
具有 Ubuntu 和 CentOS 的啟動指令碼
停止不穩定的程式(避免無限迴圈)
控制檯檢測
提供 HTTP API
遠端控制和實時的介面 API ( Nodejs 模組,允許和 PM2 程式管理器互動 )


pm2常用命令

pm2的使用,讓我們避開了自己配置負載均衡,守護程式等一系列。但是高併發場景,Nginx和內建的負載均衡,僅僅只講到了皮毛,這裡只是入個門。

還剩下最後三個問題,我想寫得質量高一些,如果感覺寫得不錯可以點個贊,關注下。gitHub倉庫也歡迎去star~哦

歡迎加入我們的segmentFault前端交流群,因為群里人數比較多了,加我的個人微信:CALASFxiaotan 我會拉你進群

相關文章