CDN(Content Delivery Network,內(nèi)容分發(fā)網(wǎng)絡(luò))是指一種通過(guò)互聯(lián)網(wǎng)互相連接的電腦網(wǎng)絡(luò)系統(tǒng),利用最靠近每位用戶的服務(wù)器,更快、更可靠地將音樂(lè)、圖片、視頻、應(yīng)用程序及其他文件發(fā)送給用戶,來(lái)提供高性能、可擴(kuò)展性及低成本的網(wǎng)絡(luò)內(nèi)容傳遞給用戶。
典型的CDN系統(tǒng)由下面三個(gè)部分組成:
CDN一般會(huì)用來(lái)托管Web資源(包括文本、圖片和腳本等),可供下載的資源(媒體文件、軟件、文檔等),應(yīng)用程序(門戶網(wǎng)站等)。使用CDN來(lái)加速這些資源的訪問(wèn)。
(1)在性能方面,引入CDN的作用在于:
(2)在安全方面,CDN有助于防御DDoS、MITM等網(wǎng)絡(luò)攻擊:
除此之外,CDN作為一種基礎(chǔ)的云服務(wù),同樣具有資源托管、按需擴(kuò)展(能夠應(yīng)對(duì)流量高峰)等方面的優(yōu)勢(shì)。
CDN和DNS有著密不可分的聯(lián)系,先來(lái)看一下DNS的解析域名過(guò)程,在瀏覽器輸入 www.test.com 的解析過(guò)程如下:
(1) 檢查瀏覽器緩存
(2)檢查操作系統(tǒng)緩存,常見的如hosts文件
(3)檢查路由器緩存
(4)如果前幾步都沒(méi)沒(méi)找到,會(huì)向ISP(網(wǎng)絡(luò)服務(wù)提供商)的LDNS服務(wù)器查詢
(5)如果LDNS服務(wù)器沒(méi)找到,會(huì)向根域名服務(wù)器(Root Server)請(qǐng)求解析,分為以下幾步:
.com
?,?.cn
?,?.org
?等的地址,該例子中會(huì)返回 ?.com
?的地址.test
?的地址www.test.com
?的地址CDN的工作原理:
(1)用戶未使用CDN緩存資源的過(guò)程:
(2)用戶使用CDN緩存資源的過(guò)程:
如果緩存服務(wù)器沒(méi)有用戶想要的內(nèi)容,那么緩存服務(wù)器就會(huì)向它的上一級(jí)緩存服務(wù)器請(qǐng)求內(nèi)容,以此類推,直到獲取到需要的資源。最后如果還是沒(méi)有,就會(huì)回到自己的服務(wù)器去獲取資源。
CNAME(意為:別名):在域名解析中,實(shí)際上解析出來(lái)的指定域名對(duì)應(yīng)的IP地址,或者該域名的一個(gè)CNAME,然后再根據(jù)這個(gè)CNAME來(lái)查找對(duì)應(yīng)的IP地址。
懶加載也叫做延遲加載、按需加載,指的是在長(zhǎng)網(wǎng)頁(yè)中延遲加載圖片數(shù)據(jù),是一種較好的網(wǎng)頁(yè)性能優(yōu)化的方式。在比較長(zhǎng)的網(wǎng)頁(yè)或應(yīng)用中,如果圖片很多,所有的圖片都被加載出來(lái),而用戶只能看到可視窗口的那一部分圖片數(shù)據(jù),這樣就浪費(fèi)了性能。
如果使用圖片的懶加載就可以解決以上問(wèn)題。在滾動(dòng)屏幕之前,可視化區(qū)域之外的圖片不會(huì)進(jìn)行加載,在滾動(dòng)屏幕時(shí)才加載。這樣使得網(wǎng)頁(yè)的加載速度更快,減少了服務(wù)器的負(fù)載。懶加載適用于圖片較多,頁(yè)面列表較長(zhǎng)(長(zhǎng)列表)的場(chǎng)景中。
圖片的加載是由 src
引起的,當(dāng)對(duì) src
賦值時(shí),瀏覽器就會(huì)請(qǐng)求圖片資源。根據(jù)這個(gè)原理,我們使用HTML5 的 data-xxx
屬性來(lái)儲(chǔ)存圖片的路徑,在需要加載圖片的時(shí)候,將 data-xxx
中圖片的路徑賦值給 src
,這樣就實(shí)現(xiàn)了圖片的按需加載,即懶加載。
注意:data-xxx
中的 xxx
可以自定義,這里我們使用 data-src
來(lái)定義。
懶加載的實(shí)現(xiàn)重點(diǎn)在于確定用戶需要加載哪張圖片,在瀏覽器中,可視區(qū)域內(nèi)的資源就是用戶需要的資源。所以當(dāng)圖片出現(xiàn)在可視區(qū)域時(shí),獲取圖片的真實(shí)地址并賦值給圖片即可。
使用原生JavaScript實(shí)現(xiàn)懶加載:
知識(shí)點(diǎn):
(1)window.innerHeight
是瀏覽器可視區(qū)的高度 document.documentElement.clientHeight
(2)document.body.scrollTop || document.documentElement.scrollTop
是瀏覽器滾動(dòng)的過(guò)的距離
(3)imgs.offsetTop
是元素頂部距離文檔頂部的高度(包括滾動(dòng)條的距離)
(4)圖片加載條件:img.offsetTop < window.innerHeight + document.body.scrollTop;
圖示:
代碼實(shí)現(xiàn):
<div class="container">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
</div>
<script>
var imgs = document.querySelectorAll('img');
function lozyLoad(){
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var winHeight= window.innerHeight;
for(var i=0;i < imgs.length;i++){
if(imgs[i].offsetTop < scrollTop + winHeight ){
imgs[i].src = imgs[i].getAttribute('data-src');
}
}
}
window.onscroll = lozyLoad();
</script>
這兩種方式都是提高網(wǎng)頁(yè)性能的方式,兩者主要區(qū)別是一個(gè)是提前加載,一個(gè)是遲緩甚至不加載。懶加載對(duì)服務(wù)器前端有一定的緩解壓力作用,預(yù)加載則會(huì)增加服務(wù)器前端壓力。
當(dāng)渲染樹中部分或者全部元素的尺寸、結(jié)構(gòu)或者屬性發(fā)生變化時(shí),瀏覽器會(huì)重新渲染部分或者全部文檔的過(guò)程就稱為回流。
下面這些操作會(huì)導(dǎo)致回流:
在觸發(fā)回流(重排)的時(shí)候,由于瀏覽器渲染頁(yè)面是基于流式布局的,所以當(dāng)觸發(fā)回流時(shí),會(huì)導(dǎo)致周圍的DOM元素重新排列,它的影響范圍有兩種:
當(dāng)頁(yè)面中某些元素的樣式發(fā)生變化,但是不會(huì)影響其在文檔流中的位置時(shí),瀏覽器就會(huì)對(duì)元素進(jìn)行重新繪制,這個(gè)過(guò)程就是重繪。
下面這些操作會(huì)導(dǎo)致回流:
注意: 當(dāng)觸發(fā)回流時(shí),一定會(huì)觸發(fā)重繪,但是重繪不一定會(huì)引發(fā)回流。
減少回流與重繪的措施:
table
?布局, 一個(gè)小的改動(dòng)可能會(huì)使整個(gè) ?table
?進(jìn)行重新布局documentFragment
?,在它上面應(yīng)用所有DOM操作,最后再把它添加到文檔中display: none
?,操作結(jié)束后再把它顯示出來(lái)。因?yàn)樵赿isplay屬性為none的元素上進(jìn)行的DOM操作不會(huì)引發(fā)回流和重繪。瀏覽器針對(duì)頁(yè)面的回流與重繪,進(jìn)行了自身的優(yōu)化——渲染隊(duì)列
瀏覽器會(huì)將所有的回流、重繪的操作放在一個(gè)隊(duì)列中,當(dāng)隊(duì)列中的操作到了一定的數(shù)量或者到了一定的時(shí)間間隔,瀏覽器就會(huì)對(duì)隊(duì)列進(jìn)行批處理。這樣就會(huì)讓多次的回流、重繪變成一次回流重繪。
上面,將多個(gè)讀操作(或者寫操作)放在一起,就會(huì)等所有的讀操作進(jìn)入隊(duì)列之后執(zhí)行,這樣,原本應(yīng)該是觸發(fā)多次回流,變成了只觸發(fā)一次回流。
對(duì)于如何優(yōu)化動(dòng)畫,我們知道,一般情況下,動(dòng)畫需要頻繁的操作DOM,就就會(huì)導(dǎo)致頁(yè)面的性能問(wèn)題,我們可以將動(dòng)畫的 position
屬性設(shè)置為 absolute
或者 fixed
,將動(dòng)畫脫離文檔流,這樣他的回流就不會(huì)影響到頁(yè)面了。
MDN中對(duì) documentFragment
的解釋:
DocumentFragment,文檔片段接口,一個(gè)沒(méi)有父對(duì)象的最小文檔對(duì)象。它被作為一個(gè)輕量版的 Document使用,就像標(biāo)準(zhǔn)的document一樣,存儲(chǔ)由節(jié)點(diǎn)(nodes)組成的文檔結(jié)構(gòu)。與document相比,最大的區(qū)別是DocumentFragment不是真實(shí) DOM 樹的一部分,它的變化不會(huì)觸發(fā) DOM 樹的重新渲染,且不會(huì)導(dǎo)致性能等問(wèn)題。
當(dāng)我們把一個(gè) DocumentFragment 節(jié)點(diǎn)插入文檔樹時(shí),插入的不是 DocumentFragment 自身,而是它的所有子孫節(jié)點(diǎn)。在頻繁的DOM操作時(shí),我們就可以將DOM元素插入DocumentFragment,之后一次性的將所有的子孫節(jié)點(diǎn)插入文檔中。和直接操作DOM相比,將DocumentFragment 節(jié)點(diǎn)插入DOM樹時(shí),不會(huì)觸發(fā)頁(yè)面的重繪,這樣就大大提高了頁(yè)面的性能。
防抖函數(shù)的應(yīng)用場(chǎng)景:
節(jié)流函數(shù)的適?場(chǎng)景:
函數(shù)防抖的實(shí)現(xiàn):
function debounce(fn, wait) {
var timer = null;
return function() {
var context = this,
args = [...arguments];
// 如果此時(shí)存在定時(shí)器的話,則取消之前的定時(shí)器重新記時(shí)
if (timer) {
clearTimeout(timer);
timer = null;
}
// 設(shè)置定時(shí)器,使事件間隔指定事件后執(zhí)行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
函數(shù)節(jié)流的實(shí)現(xiàn):
// 時(shí)間戳版
function throttle(fn, delay) {
var preTime = Date.now();
return function() {
var context = this,
args = [...arguments],
nowTime = Date.now();
// 如果兩次時(shí)間間隔超過(guò)了指定時(shí)間,則執(zhí)行函數(shù)。
if (nowTime - preTime >= delay) {
preTime = Date.now();
return fn.apply(context, args);
}
};
}
// 定時(shí)器版
function throttle (fun, wait){
let timeout = null
return function(){
let context = this
let args = [...arguments]
if(!timeout){
timeout = setTimeout(() => {
fun.apply(context, args)
timeout = null
}, wait)
}
}
}
(1)BMP是無(wú)損的、既支持索引色也支持直接色的點(diǎn)陣圖。這種圖片格式幾乎沒(méi)有對(duì)數(shù)據(jù)進(jìn)行壓縮,所以BMP格式的圖片通常是較大的文件。
(2)GIF是無(wú)損的、采用索引色的點(diǎn)陣圖。采用LZW壓縮算法進(jìn)行編碼。文件小,是GIF格式的優(yōu)點(diǎn),同時(shí),GIF格式還具有支持動(dòng)畫以及透明的優(yōu)點(diǎn)。但是GIF格式僅支持8bit的索引色,所以GIF格式適用于對(duì)色彩要求不高同時(shí)需要文件體積較小的場(chǎng)景。
(3)JPEG是有損的、采用直接色的點(diǎn)陣圖。JPEG的圖片的優(yōu)點(diǎn)是采用了直接色,得益于更豐富的色彩,JPEG非常適合用來(lái)存儲(chǔ)照片,與GIF相比,JPEG不適合用來(lái)存儲(chǔ)企業(yè)Logo、線框類的圖。因?yàn)橛袚p壓縮會(huì)導(dǎo)致圖片模糊,而直接色的選用,又會(huì)導(dǎo)致圖片文件較GIF更大。
(4)PNG-8是無(wú)損的、使用索引色的點(diǎn)陣圖。PNG是一種比較新的圖片格式,PNG-8是非常好的GIF格式替代者,在可能的情況下,應(yīng)該盡可能的使用PNG-8而不是GIF,因?yàn)樵谙嗤膱D片效果下,PNG-8具有更小的文件體積。除此之外,PNG-8還支持透明度的調(diào)節(jié),而GIF并不支持。除非需要?jiǎng)赢嫷闹С郑駝t沒(méi)有理由使用GIF而不是PNG-8。
(5)PNG-24是無(wú)損的、使用直接色的點(diǎn)陣圖。PNG-24的優(yōu)點(diǎn)在于它壓縮了圖片的數(shù)據(jù),使得同樣效果的圖片,PNG-24格式的文件大小要比BMP小得多。當(dāng)然,PNG24的圖片還是要比JPEG、GIF、PNG-8大得多。
(6)SVG是無(wú)損的矢量圖。SVG是矢量圖意味著SVG圖片由直線和曲線以及繪制它們的方法組成。當(dāng)放大SVG圖片時(shí),看到的還是線和曲線,而不會(huì)出現(xiàn)像素點(diǎn)。這意味著SVG圖片在放大時(shí),不會(huì)失真,所以它非常適合用來(lái)繪制Logo、Icon等。
(7)WebP是谷歌開發(fā)的一種新圖片格式,WebP是同時(shí)支持有損和無(wú)損壓縮的、使用直接色的點(diǎn)陣圖。從名字就可以看出來(lái)它是為Web而生的,什么叫為Web而生呢?就是說(shuō)相同質(zhì)量的圖片,WebP具有更小的文件體積?,F(xiàn)在網(wǎng)站上充滿了大量的圖片,如果能夠降低每一個(gè)圖片的文件大小,那么將大大減少瀏覽器和服務(wù)器之間的數(shù)據(jù)傳輸量,進(jìn)而降低訪問(wèn)延遲,提升訪問(wèn)體驗(yàn)。目前只有Chrome瀏覽器和Opera瀏覽器支持WebP格式,兼容性不太好。
對(duì)于 Loader 來(lái)說(shuō),影響打包效率首當(dāng)其沖必屬 Babel 了。因?yàn)?Babel 會(huì)將代碼轉(zhuǎn)為字符串生成 AST,然后對(duì) AST 繼續(xù)進(jìn)行轉(zhuǎn)變最后再生成新的代碼,項(xiàng)目越大,轉(zhuǎn)換代碼越多,效率就越低。當(dāng)然了,這是可以優(yōu)化的。
首先我們優(yōu)化 Loader 的文件搜索范圍
module.exports = {
module: {
rules: [
{
// js 文件才使用 babel
test: /\.js$/,
loader: 'babel-loader',
// 只在 src 文件夾下查找
include: [resolve('src')],
// 不會(huì)去查找的路徑
exclude: /node_modules/
}
]
}
}
對(duì)于 Babel 來(lái)說(shuō),希望只作用在 JS 代碼上的,然后 node_modules
中使用的代碼都是編譯過(guò)的,所以完全沒(méi)有必要再去處理一遍。
當(dāng)然這樣做還不夠,還可以將 Babel 編譯過(guò)的文件緩存起來(lái),下次只需要編譯更改過(guò)的代碼文件即可,這樣可以大幅度加快打包時(shí)間
loader: 'babel-loader?cacheDirectory=true'
受限于 Node 是單線程運(yùn)行的,所以 Webpack 在打包的過(guò)程中也是單線程的,特別是在執(zhí)行 Loader 的時(shí)候,長(zhǎng)時(shí)間編譯的任務(wù)很多,這樣就會(huì)導(dǎo)致等待的情況。
HappyPack 可以將 Loader 的同步執(zhí)行轉(zhuǎn)換為并行的,這樣就能充分利用系統(tǒng)資源來(lái)加快打包效率了
module: {
loaders: [
{
test: /\.js$/,
include: [resolve('src')],
exclude: /node_modules/,
// id 后面的內(nèi)容對(duì)應(yīng)下面
loader: 'happypack/loader?id=happybabel'
}
]
},
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory'],
// 開啟 4 個(gè)線程
threads: 4
})
]
DllPlugin 可以將特定的類庫(kù)提前打包然后引入。這種方式可以極大的減少打包類庫(kù)的次數(shù),只有當(dāng)類庫(kù)更新版本才有需要重新打包,并且也實(shí)現(xiàn)了將公共代碼抽離成單獨(dú)文件的優(yōu)化方案。DllPlugin的使用方法如下:
// 單獨(dú)配置在一個(gè)文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 想統(tǒng)一打包的類庫(kù)
vendor: ['react']
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].dll.js',
library: '[name]-[hash]'
},
plugins: [
new webpack.DllPlugin({
// name 必須和 output.library 一致
name: '[name]-[hash]',
// 該屬性需要與 DllReferencePlugin 中一致
context: __dirname,
path: path.join(__dirname, 'dist', '[name]-manifest.json')
})
]
}
然后需要執(zhí)行這個(gè)配置文件生成依賴文件,接下來(lái)需要使用 DllReferencePlugin
將依賴文件引入項(xiàng)目中
// webpack.conf.js
module.exports = {
// ...省略其他配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest 就是之前打包出來(lái)的 json 文件
manifest: require('./dist/vendor-manifest.json'),
})
]
}
在 Webpack3 中,一般使用 UglifyJS
來(lái)壓縮代碼,但是這個(gè)是單線程運(yùn)行的,為了加快效率,可以使用 webpack-parallel-uglify-plugin
來(lái)并行運(yùn)行 UglifyJS
,從而提高效率。
在 Webpack4 中,不需要以上這些操作了,只需要將 mode
設(shè)置為 production
就可以默認(rèn)開啟以上功能。代碼壓縮也是我們必做的性能優(yōu)化方案,當(dāng)然我們不止可以壓縮 JS 代碼,還可以壓縮 HTML、CSS 代碼,并且在壓縮 JS 代碼的過(guò)程中,我們還可以通過(guò)配置實(shí)現(xiàn)比如刪除 console.log
這類代碼的功能。
可以通過(guò)一些小的優(yōu)化點(diǎn)來(lái)加快打包速度
resolve.extensions
?:用來(lái)表明文件后綴列表,默認(rèn)查找順序是 ?['.js', '.json']
?,如果你的導(dǎo)入文件沒(méi)有添加后綴就會(huì)按照這個(gè)順序查找文件。我們應(yīng)該盡可能減少后綴列表長(zhǎng)度,然后將出現(xiàn)頻率高的后綴排在前面resolve.alias
?:可以通過(guò)別名的方式來(lái)映射一個(gè)路徑,能讓 Webpack 更快找到路徑module.noParse
?:如果你確定一個(gè)文件下沒(méi)有其他依賴,就可以使用該屬性讓 Webpack 不掃描該文件,這種方式對(duì)于大型的類庫(kù)很有幫助在開發(fā) SPA 項(xiàng)目的時(shí)候,項(xiàng)目中都會(huì)存在很多路由頁(yè)面。如果將這些頁(yè)面全部打包進(jìn)一個(gè) JS 文件的話,雖然將多個(gè)請(qǐng)求合并了,但是同樣也加載了很多并不需要的代碼,耗費(fèi)了更長(zhǎng)的時(shí)間。那么為了首頁(yè)能更快地呈現(xiàn)給用戶,希望首頁(yè)能加載的文件體積越小越好,這時(shí)候就可以使用按需加載,將每個(gè)路由頁(yè)面單獨(dú)打包為一個(gè)文件。當(dāng)然不僅僅路由可以按需加載,對(duì)于 loadash
這種大型類庫(kù)同樣可以使用這個(gè)功能。
按需加載的代碼實(shí)現(xiàn)這里就不詳細(xì)展開了,因?yàn)殍b于用的框架不同,實(shí)現(xiàn)起來(lái)都是不一樣的。當(dāng)然了,雖然他們的用法可能不同,但是底層的機(jī)制都是一樣的。都是當(dāng)使用的時(shí)候再去下載對(duì)應(yīng)文件,返回一個(gè) Promise
,當(dāng) Promise
成功以后去執(zhí)行回調(diào)。
Scope Hoisting 會(huì)分析出模塊之間的依賴關(guān)系,盡可能的把打包出來(lái)的模塊合并到一個(gè)函數(shù)中去。
比如希望打包兩個(gè)文件:
// test.js
export const a = 1
// index.js
import { a } from './test.js'
對(duì)于這種情況,打包出來(lái)的代碼會(huì)類似這樣:
[
/* 0 */
function (module, exports, require) {
//...
},
/* 1 */
function (module, exports, require) {
//...
}
]
但是如果使用 Scope Hoisting ,代碼就會(huì)盡可能的合并到一個(gè)函數(shù)中去,也就變成了這樣的類似代碼:
[
/* 0 */
function (module, exports, require) {
//...
}
]
這樣的打包方式生成的代碼明顯比之前的少多了。如果在 Webpack4 中你希望開啟這個(gè)功能,只需要啟用 optimization.concatenateModules
就可以了:
module.exports = {
optimization: {
concatenateModules: true
}
}
Tree Shaking 可以實(shí)現(xiàn)刪除項(xiàng)目中未被引用的代碼,比如:
// test.js
export const a = 1
export const b = 2
// index.js
import { a } from './test.js'
對(duì)于以上情況,test
文件中的變量 b
如果沒(méi)有在項(xiàng)目中使用到的話,就不會(huì)被打包到文件中。
如果使用 Webpack 4 的話,開啟生產(chǎn)環(huán)境就會(huì)自動(dòng)啟動(dòng)這個(gè)優(yōu)化功能。
?webpack優(yōu)化前端性能是指優(yōu)化webpack的輸出結(jié)果,讓打包的最終結(jié)果在瀏覽器運(yùn)?快速?效。
更多建議: