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

clipboard.png

不知不覺,已經來到了最後的下篇 其實我寫的東西你若是認真去看,跟着去寫,應該能有很多的收穫。

最近一些跨平臺技術,React-nativeflutter之類的,比較火。可是,我仍是不許備把它們放進來,由於那是爲作App而生,我想把Electron這個桌面端跨平臺的技術放進來。理由是什麼,後面說html

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

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

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

從零編寫一個React框架react

咱們爲何要熟悉這些通訊協議nginx

單頁面應用SPA原理git

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

性能優化不徹底手冊web

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 我會拉你進羣

相關文章
相關標籤/搜索