序
大家是從什么時候接觸性能優化的呢?
第一時間想到的又是什么呢?雅虎軍規 ? 高性能javascript ?
性能優化沒有標準答案,我們只能不斷地把從搜索引擎和書中的知識付諸實踐,這個程序是漫長且艱辛的
本文為總結記錄學習修言大佬小冊,感興趣的同學可以購買支持正版
一切從一道面試題開始
從輸入 URL 到頁面加載完成,發生了什么?
概括來說,分為5步
- DNS把url決議成IP
- 客戶端通過Ip和服務端建立tcp連接
- 客戶端發起http請求
- 服務端處理http請求并回傳回應
- 客戶端拿到回應并渲染頁面
每一步都可以說的很細,這5個程序就是提高前端性能的根本的切入點
關于第一第二步,我們前端能做的非常有限,理解為主,
DNS 決議花時間,能不能盡量減少決議次數或者把決議前置?能——瀏覽器 DNS 快取和 DNS prefetch
TCP 每次的三次握手都急死人,有沒有解決方案?有——長連接、預連接、接入 SPDY 協議
為了知其所以然(應對深究的面試官),我們了解下DNS的決議程序:
- 瀏覽器先檢查自身快取中有沒有被決議過的這個域名對應的ip地址,如果有,決議結束
- 如果瀏覽器快取沒有命中,瀏覽器會檢查作業系統快取中有沒有對應的已決議過的結果,而作業系統也有一個域名決議的程序,在windows中可通過c盤里一個叫hosts的檔案來設定,如果你在這里指定了一個域名對應的ip地址,那瀏覽器會首先使用這個ip地址,但是這種作業系統級別的域名決議規程也被很多黑客利用,通過修改你的hosts檔案里的內容把特定的域名決議到他指定的ip地址上,造成所謂的域名劫持,所以在windows7中將hosts檔案設定成了readonly,防止被惡意篡改,
- 如果至此還沒有命中域名,才會真正的請求本地域名服務器(LDNS)來決議這個域名,這臺服務器一般在你的城市的某個角落,距離你不會很遠,并且這臺服務器的性能都很好,一般都會快取域名決議結果,大約80%的域名決議到這里就完成了,
- 如果LDNS仍然沒有命中,就直接跳到Root Server 域名服務器請求決議
- 根域名服務器回傳給LDNS一個所查詢域的主域名服務器(gTLD Server,國際頂尖域名服務器,如.com .cn .org等)地址
- 此時LDNS再發送請求給上一步回傳的gTLD
- 接受請求的gTLD查找并回傳這個域名對應的Name Server的地址,這個Name Server就是網站注冊的域名服務器
- Name Server根據映射關系表找到目標ip,回傳給LDNS
- LDNS快取這個域名和對應的ip
- LDNS把決議的結果回傳給用戶,用戶根據TTL值快取到本地系統快取中,域名決議程序至此結束
HTTP 連接這一層面的優化才是我們網路優化的核心,
- 減少http請求次數
- 減少單次請求花費的時間
減少h t tp請求次數就必定會增加單次請求的開銷,兩點怎么權衡?
網路篇
減少單次請求花費的時間
webpack 性能優化
最常見操作就是資源的壓縮和合并,該操作最常見的工具就是webpack, 所以問題就指向了webpack的性能瓶頸
構建程序時間太長
打包體積太大
從 webpack v4.0.0 開始,可以不用引入一個組態檔,然而,webpack 仍然還是高度可配置的,
不要讓loader做太多事
以 babel-loader 為例:
下面直接貼webpack官網的描述
babel-loader 很慢!
確保轉譯盡可能少的檔案,你可能使用
/\.js$/來匹配,這樣也許會去轉譯node_modules目錄或者其他不需要的源代碼,要排除
node_modules,參考檔案中的loaders配置的exclude選項,你也可以通過使用
cacheDirectory選項,將 babel-loader 提速至少兩倍, 這會將轉譯的結果快取到檔案系統中,
babel 對一些公共方法使用了非常小的輔助代碼,比如
_extend, 默認情況下會被添加到每一個需要它的檔案中你可以引入 babel runtime 作為一個獨立模塊,來避免重復引入,
下面的配置禁用了 babel 自動對每個檔案的 runtime 注入,而是引入
babel-plugin-transform-runtime并且使所有輔助代碼從這里參考,
rules: [
// 'transform-runtime' 插件告訴 babel 要參考 runtime 來代替注入,
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader?cacheDirectory=true',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/transform-runtime']
}
}
}
]
不要放過第三方庫
打包第三方依賴推薦 DllPlugin
DllPlugin 是基于 Windows 元件(dll)的思想被創作出來的,這個插件會把第三方庫單獨打包到一個檔案中,這個檔案就是一個單純的依賴庫,這個依賴庫不會跟著你的業務代碼一起被重新打包,只有當依賴自身發生版本變化時才會重新打包
Dll.config.js
// 以一個基于 React 的簡單專案為例,我們的 dll 的組態檔可以撰寫如下
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 依賴的庫陣列
vendor: [
'prop-types',
'babel-polyfill',
'react',
'react-dom',
'react-router-dom',
]
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
new webpack.DllPlugin({
// DllPlugin的name屬性需要和libary保持一致
name: '[name]_[hash]',
path: path.join(__dirname, 'dist', '[name]-manifest.json'),
// context需要和webpack.config.js保持一致
context: __dirname,
}),
],
}
運行這個組態檔,我們的 dist 檔案夾里會出現這樣兩個檔案:
- vendor-manifest.json: 用于描述每個第三方庫對應的具體路徑
- vendor.js: 我們第三方庫打包的結果
Webpack.config.js
const path = require('path');
const webpack = require('webpack')
module.exports = {
mode: 'production',
// 編譯入口
entry: {
main: './src/index.js'
},
// 目標檔案
output: {
path: path.join(__dirname, 'dist/'),
filename: '[name].js'
},
// dll相關配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest就是我們第一步中打包出來的json檔案
manifest: require('./dist/vendor-manifest.json'),
})
]
}
Happypack——將 loader 由單行程轉為多行程
webpack由于是node撰寫的,node是單執行緒的,就算此刻存在多個任務,你也只能排隊一個接一個地等待處理, 為了充分利用多核cpu的資源,根據cpu的核數,我們可以fork多個行程, Happypack就是為此而生
const HappyPack = require('happypack')
// 手動創建行程池
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
module.exports = {
module: {
rules: [
...
{
test: /\.js$/,
// 問號后面的查詢引數指定了處理這類檔案的HappyPack實體的名字
loader: 'happypack/loader?id=happyBabel',
...
},
],
},
plugins: [
...
new HappyPack({
// 這個HappyPack的“名字”就叫做happyBabel,和樓上的查詢引數遙相呼應
id: 'happyBabel',
// 指定行程池
threadPool: happyThreadPool,
loaders: ['babel-loader?cacheDirectory']
})
],
}
這樣就可以并發處理多個任務
可視化打包后各個包的體積
webpack-bundle-analyzer
大家可以點進去看一下,它將創建一個互動式treemap可視化你的包的內容,這樣你就可以知道哪些包是引起你打包體積過大的罪魁禍首
洗掉冗余代碼
tree shaking
tree shaking 是一個術語,通常用于描述移除 JavaScript 背景關系中的未參考代碼(dead-code),它依賴于 ES2015 模塊系統中的靜態結構特性,例如
import和export,這個術語和概念實際上是興起于 ES2015 模塊打包工具 rollup,新的 webpack 4 正式版本,擴展了這個檢測能力,通過
package.json的"sideEffects"屬性作為標記,向 compiler 提供提示,表明專案中的哪些檔案是 "pure(純的 ES2015 模塊)",由此可以安全地洗掉檔案中未使用的部分,
如果所有代碼都不包含副作用,我們就可以簡單地將該屬性標記為 false,來告知 webpack,它可以安全地洗掉未用到的 export 匯出,
{
"name": "your-project",
"sideEffects": false
}
如果你的代碼確實有一些副作用,那么可以改為提供一個陣列:
陣列方式支持相關檔案的相對路徑、絕對路徑和 glob 模式,它在內部使用 micromatch,
注意,任何匯入的檔案都會受到 tree shaking 的影響,這意味著,如果在專案中使用類似 css-loader 并匯入 CSS 檔案,則需要將其添加到 side effect 串列中,以免在生產模式中無意中將它洗掉:
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js"
"*.css"
]
}
Tree-Shaking 的針對性很強,它更適合用來處理模塊級別的冗余代碼
UglifyJsPlugin
// 在壓縮程序中對碎片化的冗余代碼(如 console 陳述句、注釋等)進行自動化洗掉
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin({
// 允許并發
parallel: true,
// 開啟快取
cache: true,
compress: {
// 洗掉所有的console陳述句
drop_console: true,
// 把使用多次的靜態值自動定義為變數
reduce_vars: true,
},
output: {
// 不保留注釋
comment: false,
// 使輸出的代碼盡可能緊湊
beautify: false
}
}),
]
}
按需加載
比如路由的懶加載,tab組件的懶加載
Gzip
gzip的基礎是DEFLATE,DEFLATE是LZ77與哈夫曼編碼的一個組合體,DEFLATE最初是作為LZW以及其它受專利保護的資料壓縮演算法的替代版本而設計的,當時那些專利限制了compress以及其它一些流行的歸檔工具的應用
開啟gzip壓縮,只需要在請求頭中加上
accept-encoding:gzip
壓縮后通常能幫我們減少回應 70% 左右的大小
Gzip 壓縮背后的原理,是在一個文本檔案中找出一些重復出現的字串、臨時替換它們,從而使整個檔案變小,根據這個原理,檔案中代碼的重復率越高,那么壓縮的效率就越高,使用 Gzip 的收益也就越大,反之亦然
圖片優化
圖片優化就是在圖片體積和圖片質量之間做權衡
前置知識: 在計算機中,像素用二進制數來表示, n位二進制可以表示2^n種顏色,
所以二進制位數越多,可表示的顏色種類就越多,成像效果越細膩,圖片體積越大
我們需要在不同的業務場景選擇合適的圖片型別
JPEG/JPG
體積小有損壓縮不支持透明加載快
當我們把圖片體積壓縮至原有體積的 50% 以下時,JPG 仍然可以保持住 60% 的品質,此外,JPG 格式以 24 位存盤單個圖,可以呈現多達2^24 = 16,777,216 種顏色,足以應對大多數場景下對色彩的要求
使用場景
JPG 圖片經常作為大的背景圖、輪播圖或 Banner 圖出現,使用 JPG 呈現大圖,既可以保住圖片的質量,又不會帶來令人頭疼的圖片體積,是當下比較推崇的一種方案,
PNG-8/PNG-24
體積大無損壓縮支持透明質量高
8和24代表2進制位數
Png-8: 2^8 = 256中顏色
Png-24: 2^24 = 16,777,216 種顏色
追求極致的顯示效果,不在意圖片大小的可以選擇png-24
實踐中,如果png-8沒有帶來視覺可辨別的色彩缺陷,考慮到體積,一般使用png-8
使用場景
考慮到 PNG 在處理線條和顏色對比度方面的優勢,我們主要用它來呈現小的 Logo、顏色簡單且對比強烈的圖片或背景等,
性能方面堪稱業界楷模的淘寶首頁頁面上的 Logo,無論大小,都是 PNG 格式
SVG
體積小不失真文本檔案兼容性好
SVG(可縮放矢量圖形)是一種基于 XML 語法的影像格式,它和本文提及的其它圖片種類有著本質的不同:SVG 對影像的處理不是基于像素點,而是是基于對影像的形狀描述,
優勢
- SVG 與 PNG 和 JPG 相比,檔案體積更小,可壓縮性更強,
- 作為矢量圖,它最顯著的優勢在于圖片可無限放大而不失真, 這使得 SVG 即使是被放到視網膜螢屏上,也可以一如既往地展現出較好的成像品質——1 張 SVG 足以適配 n 種解析度
- SVG 是文本檔案,我們既可以像寫代碼一樣定義 SVG,把它寫在 HTML 里、成為 DOM 的一部分,也可以把對圖形的描述寫入以 .svg 為后綴的獨立檔案, 這使得 SVG 檔案可以被非常多的工具讀取和修改,具有較強的靈活性
劣勢
- 渲染成本高
- 相比其他圖片格式,學習成本高,因為它是可編程的
使用場景
小圖示
Base64
文本檔案依賴編碼小圖示解決方案
Base64 并非一種圖片格式,而是一種編碼方式,Base64 和雪碧圖一樣,是作為小圖示解決方案而存在的,在了解 Base64 之前,我們先來了解一下雪碧圖
雪碧圖
影像精靈(sprite,意為精靈),被運用于眾多使用大量小圖示的網頁應用之上,它可取影像的一部分來使用,使得使用一個影像檔案替代多個小檔案成為可能,相較于一個小圖示一個影像檔案,單獨一張圖片所需的 HTTP 請求更少,對記憶體和帶寬更加友好,
前端使用background-position 來獲取不同位置的圖示
Base64 圖片的出現,也是為了減少加載網頁圖片時對服務器的請求次數,從而提升網頁性能,Base64 是作為雪碧圖的補充而存在的,
使用場景
小logo, 小icon
- 圖片的實際尺寸很小(大家可以觀察一下掘金頁面的 Base64 圖,幾乎沒有超過 2kb 的)
- 圖片無法以雪碧圖的形式與其它小圖結合(合成雪碧圖仍是主要的減少 HTTP 請求的途徑,Base64 是雪碧圖的補充)
- 圖片的更新頻率非常低(不需我們重復編碼和修改檔案內容,維護成本較低)
劣勢
Base64 編碼后,圖片大小會膨脹為原檔案的 4/3(這是由 Base64 的編碼原理決定的),
如果我們把大圖也編碼到 HTML 或 CSS 檔案中,后者的體積會明顯增加,即便我們減少了 HTTP 請求,也無法彌補這龐大的體積帶來的性能開銷,得不償失
base64編碼工具推薦
webpack 的 url-loader , limit引數表示 指定檔案的最大大小,以位元組為單位,只有8192byte一下大小的圖片才會進行base64編碼
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
],
},
};
WebP
年輕的全能型選手
它于 2010 年被提出, 是 Google 專為 Web 開發的一種旨在加快圖片加載速度的圖片格式,它支持有損壓縮和無損壓縮
與 PNG 相比,WebP 無損影像的尺寸縮小了 26%,在等效的 SSIM 質量指數下,WebP 有損影像比同類 JPEG 影像小 25-34%,
- 最大的缺陷就是兼容性
- WebP 還會增加服務器的負擔——和編碼 JPG 檔案相比,編碼同樣質量的 WebP 檔案會占用更多的計算資源
應用場景
要使用webP我們就必須為不兼容的瀏覽器進行降級處理
我們可以看看淘寶是怎么做的
在谷歌瀏覽器打開淘寶,打開控制臺,搜索.webp
其中一個img的src是這樣的
img.alicdn.com/imgextra/i3/6000000002336/O1CN019A9rll1T7vqVs4Wur_!!6000000002336-0-octopus.jpg_400x400q90.jpg_.webp
在safari中打開淘寶,打開控制臺,查看同一張圖片的src
img.alicdn.com/imgextra/i3/6000000002336/O1CN019A9rll1T7vqVs4Wur_!!6000000002336-0-octopus.jpg_400x400q90.jpg
淘寶是會根據瀏覽器的型號來判斷是否支持webp,不支持就把.webp后綴切換成.jpg
更靈活的方案
把判斷邏輯交給后端,服務器根據 請求頭的 Accept 欄位 來判斷是否支持webp, 否則回傳原圖(jpg),
這樣做的好處是當webp的兼容性發生變化時,前端不用修改判斷是否支持webp的代碼
存盤篇
通過網路獲取內容既速度緩慢又開銷巨大,較大的回應需要在客戶端與服務器之間進行多次往返通信,這會延遲瀏覽器獲得和處理內容的時間,還會增加訪問者的流量費用,因此,快取并重復利用之前獲取的資源的能力成為性能優化的一個關鍵方面,
瀏覽器快取機制
按斬訓取資源時請求的優先級排序:
- Memory Cache
- Service Worker Cache
- HTTP Cache
- Push Cache
Memory Cache(記憶體快取)
是指存在記憶體中的快取,從優先級上來說,它是瀏覽器最先嘗試去命中的一種快取,從效率上來說,它是回應速度最快的一種快取,
記憶體快取是快的,也是“短命”的,它和渲染行程“生死相依”,當行程結束后,也就是 tab 關閉以后,記憶體里的資料也將不復存在
資源存不存記憶體,瀏覽器秉承的是“節約原則”,我們發現,Base64 格式的圖片,幾乎永遠可以被塞進 memory cache,這可以視作瀏覽器為節省渲染開銷的“自保行為”;此外,體積不大的 JS、CSS 檔案,也有較大地被寫入記憶體的幾率——相比之下,較大的 JS、CSS 檔案就沒有這個待遇了,記憶體資源是有限的,它們往往被直接甩進磁盤
Service Worker Cache
Service Worker 是一種獨立于主執行緒之外的 Javascript 執行緒,它脫離于瀏覽器表單,因此無法直接訪問 DOM,這樣獨立的個性使得 Service Worker 的“個人行為”無法干擾頁面的性能,這個“幕后作業者”可以幫我們實作離線快取、訊息推送和網路代理等功能,我們借助 Service worker 實作的離線快取就稱為 Service Worker Cache,
Service Worker 的生命周期包括 install、active、working三個階段,一旦 Service Worker 被 install,它將始終存在,只會在 active 與 working 之間切換,除非我們主動終止它,這是它可以用來實作離線存盤的重要先決條件,
如何使用 service worker(必須以 https 協議為前提)
新建test.js檔案
// Service Worker會監聽 install事件,我們在其對應的回呼里可以實作初始化的邏輯
self.addEventListener('install', event => {
event.waitUntil(
// 考慮到快取也需要更新,open內傳入的引數為快取的版本號
// caches 為瀏覽器的API
// CacheStorage {}
// __proto__: CacheStorage
// delete: ? delete()
// has: ? has()
// keys: ? keys()
// match: ? match()
// open: ? open()
// constructor: ? CacheStorage()
// Symbol(Symbol.toStringTag): "CacheStorage"
// __proto__: Object
caches.open('test-v1').then(cache => {
return cache.addAll([
// 此處傳入指定的需快取的檔案名
'/test.html',
'/test.css',
'/test.js'
])
})
)
})
// Service Worker會監聽所有的網路請求,網路請求的產生觸發的是fetch事件,我們可以在其對應的監聽函式中實作對請求的攔截,進而判斷是否有對應到該請求的快取,實作從Service Worker中取到快取的目的
self.addEventListener('fetch', event => {
event.respondWith(
// 嘗試匹配該請求對應的快取值
caches.match(event.request).then(res => {
// 如果匹配到了,呼叫Server Worker快取
if (res) {
return res;
}
// 如果沒匹配到,向服務端發起這個資源請求
return fetch(event.request).then(response => {
if (!response || response.status !== 200) {
return response;
}
// 請求成功的話,將請求快取起來,
caches.open('test-v1').then(function(cache) {
cache.put(event.request, response);
});
return response.clone();
});
})
);
});
在專案代碼入口j s檔案中加入
window.navigator.serviceWorker.register('/test.js').then(
function () {
console.log('注冊成功')
}).catch(err => {
console.error("注冊失敗")
})
HTTP Cache(重點)
HTTP 快取是我們日常開發中最為熟悉的一種快取機制,它又分為強快取和協商快取,優先級較高的是強快取,在命中強快取失敗的情況下,才會走協商快取,
強快取
強快取是利用 http 頭中的 Expires 和 Cache-Control兩個欄位來控制的,強快取中,當請求再次發出時,瀏覽器會根據其中的 expires 和 cache-control 判斷目標資源是否“命中”強快取,若命中則直接從快取中獲取資源,不會再與服務端發生通信,
命中強快取的情況下,回傳的 HTTP 狀態碼為200 (from disk cache)
Cache-Control 的 max-age 配置項相對于 expires 的優先級更高,當 Cache-Control 與 expires 同時出現時,我們以 Cache-Control 為準,
Cache-Control
cache-control: max-age=31536000
max-age表示的有效時長, 單位是秒,表示該資源31536000秒內是有效的
public 與 private
public 與 private 是針對資源是否能夠被代理服務快取而存在的一組對立概念,
如果我們為資源設定了 public,那么它既可以被瀏覽器快取,也可以被代理服務器快取;如果我們設定了 private,則該資源只能被瀏覽器快取,private 為默認值,但多數情況下,public 并不需要我們手動設定
no-store與no-cache
no-cache 繞開了瀏覽器:我們為資源設定了 no-cache 后,每一次發起請求都不會再去詢問瀏覽器的快取情況,而是直接向服務端去確認該資源是否過期(不走強快取),
no-store 比較絕情,顧名思義就是不使用任何快取策略,在 no-cache 的基礎上,它連服務端的快取確認也繞開了,只允許你直接向服務端發送請求、并下載完整的回應,
Expires
expires: Wed, 11 Sep 2019 16:12:18 GMT
expires 設定的是資源到期時間(服務器時間)
接下來如果我們試圖再次向服務器請求資源,瀏覽器就會先對比本地時間和 expires 的時間戳,如果本地時間小于 expires 設定的過期時間,那么就直接去快取中取這個資源,由于時間戳是服務器來定義的,而本地時間的取值卻來自客戶端,因此 expires 的作業機制對客戶端時間與服務器時間之間的一致性提出了極高的要求,若服務器與客戶端存在時差,將帶來意料之外的結果,
協商快取
協商快取機制下,瀏覽器需要向服務器去詢問快取的相關資訊,進而判斷是重新發起請求、下載完整的回應,還是從本地獲取快取的資源,
如果服務端提示快取資源未改動(Not Modified),資源會被重定向到瀏覽器快取,這種情況下網路請求對應的狀態碼是 304
Etag 在感知檔案變化上比 Last-Modified 更加準確,優先級也更高,當 Etag 和 Last-Modified 同時存在時,以 Etag 為準.
Last-Modified
首次請求時,回應頭里會攜帶Last-Modified欄位,像這樣
Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT
再次請求相同的資源,請求頭里會帶上If-Modified-Since欄位,
值是上一次請求回應頭的Last-Modified的值
If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT
服務器接收到這個時間戳后,會比對該時間戳和資源在服務器上的最后修改時間是否一致,從而判斷資源是否發生了變化,如果發生了變化,就會回傳一個完整的回應內容,并在 回應頭 中添加新的 Last-Modified 值;否則,回傳如上圖的 304 回應,回應頭 不會再添加 Last-Modified 欄位,
弊端
- 我們編輯了檔案,但檔案的內容沒有改變,服務端并不清楚我們是否真正改變了檔案,它仍然通過最后編輯時間進行判斷,因此這個資源在再次被請求時,會被當做新資源,進而引發一次完整的回應——不該重新請求的時候,也會重新請求
- 當我們修改檔案的速度過快時(比如花了 100ms 完成了改動),由于 If-Modified-Since 只能檢查到以秒為最小計量單位的時間差,所以它是感知不到這個改動的——該重新請求的時候,反而沒有重新請求了
為了解決這樣的問題,Etag 作為 Last-Modified 的補充出現了
Etag
Etag 是由服務器為每個資源生成的唯一的標識字串,這個標識字串是基于檔案內容編碼的,只要檔案內容不同,它們對應的 Etag 就是不同的,反之亦然,因此 Etag 能夠精準地感知檔案的變化,
首次請求時,回應頭里會攜帶ETag欄位,像這樣
ETag: W/"2a3b-1602480f459"
再次請求相同的資源,請求頭里會帶上If-Modified-Since欄位,
值是上一次請求回應頭的ETag的值
If-None-Match: W/"2a3b-1602480f459"
服務器會拿If-None-Match的值和服務器資源的當前標識對比,相同回傳304, 不同回傳新的資源并帶上新的Etag
Etag 的生成程序需要服務器額外付出開銷,會影響服務端的性能,這是它的弊端,因此啟用 Etag 需要我們審時度勢,正如我們剛剛所提到的——Etag 并不能替代 Last-Modified,它只能作為 Last-Modified 的補充和強化存在,
本地存盤
Cookie
Cookie 的本職作業并非本地存盤,而是“維持狀態”,
在 Web 開發的早期,人們亟需解決的一個問題就是狀態管理的問題:HTTP 協議是一個無狀態協議,服務器接收客戶端的請求,回傳一個回應,故事到此就結束了,服務器并沒有記錄下關于客戶端的任何資訊,那么下次請求的時候,如何讓服務器知道“我是我”呢?
在這樣的背景下,Cookie 應運而生,
學習中,持續更新,,,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/220581.html
標籤:其他
上一篇:NTP時鐘服務器(北斗衛星授時)同步區塊鏈分布式系統
下一篇:BigInt
