前端性能優(yōu)化23條建議(2020)
性能優(yōu)化是把雙刃劍,有好的一面也有壞的一面。好的一面就是能提升網(wǎng)站性能,壞的一面就是配置麻煩,或者要遵守的規(guī)則太多。并且某些性能優(yōu)化規(guī)則并不適用所有場景,需要謹慎使用,請讀者帶著批判性的眼光來閱讀本文。
1. 減少 HTTP 請求
一個 HTTP 請求過程:
一個 HTTP 請求需要經(jīng)歷以上過程,接下來看一個具體的例子:
這是一個 HTTP 請求,請求的文件大小為 28.4KB。
名詞解釋:
- Queueing: 在請求隊列中的時間。
- Stalled: 從TCP 連接建立完成,到真正可以傳輸數(shù)據(jù)之間的時間差,此時間包括代理協(xié)商時間。
- Proxy negotiation: 與代理服務(wù)器連接進行協(xié)商所花費的時間。
- DNS Lookup: 執(zhí)行DNS查找所花費的時間,頁面上的每個不同的域都需要進行DNS查找。
- Initial Connection / Connecting: 建立連接所花費的時間,包括TCP握手/重試和協(xié)商SSL。
- SSL: 完成SSL握手所花費的時間。
- Request sent: 發(fā)出網(wǎng)絡(luò)請求所花費的時間,通常為一毫秒的時間。
- Waiting(TFFB): TFFB 是發(fā)出頁面請求到接收到應(yīng)答數(shù)據(jù)第一個字節(jié)的時間總和,它包含了 DNS 解析時間、 TCP 連接時間、發(fā)送 HTTP 請求時間和獲得響應(yīng)消息第一個字節(jié)的時間。
- Content Download: 接收響應(yīng)數(shù)據(jù)所花費的時間。
從這個例子可以看出,真正下載數(shù)據(jù)的時間占比為 13.05 / 204.16 = 6.39%,文件越小,這個比例越小,文件越大,比例就越高。這就是為什么要建議將多個小文件合并為一個大文件,從而減少 HTTP 請求次數(shù)的原因。
參考資料:
2. 使用 HTTP2
HTTP1.x 客戶端需要使用多個連接才能實現(xiàn)并發(fā)和縮短延遲;HTTP1.x 不會壓縮請求和響應(yīng)標頭,從而導(dǎo)致不必要的網(wǎng)絡(luò)流量;HTTP1.x 不支持有效的資源優(yōu)先級,致使底層 TCP 連接的利用率低下等等。
HTTP2 是對之前 HTTP 標準的擴展,它通過支持標頭字段壓縮和在同一連接上進行多個并發(fā)交換,讓應(yīng)用更有效地利用網(wǎng)絡(luò)資源,減少感知的延遲時間。具體來說,它可以對同一連接上的請求和響應(yīng)消息進行交錯發(fā)送并為 HTTP 標頭字段使用有效編碼。
HTTP2 還允許為請求設(shè)置優(yōu)先級,讓更重要的請求更快速地完成,從而進一步提升性能。
HTTP2 支持了多路復(fù)用,HTTP 連接變得十分廉價,之前為了節(jié)省連接數(shù)所采用的類似于「資源合并、資源內(nèi)聯(lián)」等優(yōu)化手段不再需要了。多路復(fù)用可以在一個 TCP 連接上建立大量 HTTP 連接,也就不存在 HTTP 連接數(shù)限制了,HTTP1.x 中常見的「靜態(tài)域名」優(yōu)化策略不但用不上了,還會帶來負面影響,需要去掉。另外,HTTP2 的頭部壓縮功能也能大幅減少 HTTP 協(xié)議頭部帶來的開銷。但是,要等HTTP1.x 完全退出舞臺還需要一段時間。
現(xiàn)在有很多網(wǎng)站已經(jīng)開始使用 HTTP2 了,例如知乎:
其中 h2 是指 HTTP2 協(xié)議,http/1.1 則是指 HTTP1.1 協(xié)議。
參考資料:
3. 使用服務(wù)端渲染
客戶端渲染: 獲取 HTML 文件,根據(jù)需要下載 JavaScript 文件,運行文件,生成 DOM,再渲染。
服務(wù)端渲染:服務(wù)端返回 HTML 文件,客戶端只需解析 HTML。
- 優(yōu)點:首屏渲染快,SEO 好。
- 缺點:配置麻煩。
參考資料:
4. 靜態(tài)資源使用 CDN
內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)是一組分布在多個不同地理位置的 Web 服務(wù)器。我們都知道,當服務(wù)器離用戶越遠時,延遲越高。CDN 就是為了解決這一問題,在多個位置部署服務(wù)器,讓用戶離服務(wù)器更近,從而縮短請求時間。
參考資料:
5. 將 CSS 放在文件頭部,JavaScript 文件放在底部
所有放在 head 標簽里的 CSS 和 JS 文件都會堵塞渲染。如果這些 CSS 和 JS 需要加載和解析很久的話,那么頁面就空白了。所以 JS 文件要放在底部,等 HTML 解析完了再加載 JS 文件。
那為什么 CSS 文件還要放在頭部呢?
因為先加載 HTML 再加載 CSS,會讓用戶第一時間看到的頁面是沒有樣式的、“丑陋”的,為了避免這種情況發(fā)生,就要將 CSS 文件放在頭部了。
另外,JS 文件也不是不可以放在頭部,只要給 script 標簽加上 defer 屬性就可以了,異步下載,延遲執(zhí)行。
6. 使用字體圖標 iconfont 代替圖片圖標
字體圖標就是將圖標制作成一個字體,使用時就跟字體一樣,可以設(shè)置屬性,例如 font-size、color 等等,非常方便。并且字體圖標是矢量圖,不會失真。還有一個優(yōu)點是生成的文件特別小。
7. 善用緩存,不重復(fù)加載相同的資源
為了避免用戶每次訪問網(wǎng)站都得請求文件,我們可以通過添加 Expires 頭來控制這一行為。Expires 設(shè)置了一個時間,只要在這個時間之前,瀏覽器都不會請求文件,而是直接使用緩存。
不過這樣會產(chǎn)生一個問題,當文件更新了怎么辦?怎么通知瀏覽器重新請求文件?
可以通過更新頁面中引用的 CSS 鏈接地址,讓瀏覽器主動放棄緩存,加載新資源。
具體做法是把 CSS 地址的修改與文件內(nèi)容關(guān)聯(lián)起來,也就是說,只有文件內(nèi)容變化,才會導(dǎo)致相應(yīng) CSS 地址的變更,從而實現(xiàn)文件級別的精確緩存控制。什么東西與文件內(nèi)容相關(guān)呢?我們會很自然的聯(lián)想到利用數(shù)據(jù)摘要要算法對文件求摘要信息,摘要信息與文件內(nèi)容一一對應(yīng),就有了一種可以精確到單個文件粒度的緩存控制依據(jù)了。
參考資料:
8. 壓縮文件
壓縮文件可以減少文件下載時間,讓用戶體驗性更好。
得益于 webpack 和 node 的發(fā)展,現(xiàn)在壓縮文件已經(jīng)非常方便了。
在 webpack 可以使用如下插件進行壓縮:
- JavaScript:UglifyPlugin
- CSS :MiniCssExtractPlugin
- HTML:HtmlWebpackPlugin
其實,我們還可以做得更好。那就是使用 gzip 壓縮。可以通過向 HTTP 請求頭中的 Accept-Encoding 頭添加 gzip 標識來開啟這一功能。當然,服務(wù)器也得支持這一功能。
gzip 是目前最流行和最有效的壓縮方法。舉個例子,我用 Vue 開發(fā)的項目構(gòu)建后生成的 app.js 文件大小為 1.4MB,使用 gzip 壓縮后只有 573KB,體積減少了將近 60%。
附上 webpack 和 node 配置 gzip 的使用方法。
下載插件
- npm install compression-webpack-plugin --save-dev
- npm install compression
webpack 配置
- const CompressionPlugin = require('compression-webpack-plugin');
- module.exports = {
- plugins: [new CompressionPlugin()],
- }
node 配置
- const compression = require('compression')
- // 在其他中間件前使用
- app.use(compression())
9. 圖片優(yōu)化
(1). 圖片延遲加載
在頁面中,先不給圖片設(shè)置路徑,只有當圖片出現(xiàn)在瀏覽器的可視區(qū)域時,才去加載真正的圖片,這就是延遲加載。對于圖片很多的網(wǎng)站來說,一次性加載全部圖片,會對用戶體驗造成很大的影響,所以需要使用圖片延遲加載。
首先可以將圖片這樣設(shè)置,在頁面不可見時圖片不會加載:
- <img data-src="https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4">
等頁面可見時,使用 JS 加載圖片:
- const img = document.querySelector('img')
- imgimg.src = img.dataset.src
這樣圖片就加載出來了,完整的代碼可以看一下參考資料。
參考資料:
(2). 響應(yīng)式圖片
響應(yīng)式圖片的優(yōu)點是瀏覽器能夠根據(jù)屏幕大小自動加載合適的圖片。
通過 picture 實現(xiàn)
- <picture>
- <source srcset="banner_w1000.jpg" media="(min-width: 801px)">
- <source srcset="banner_w800.jpg" media="(max-width: 800px)">
- <img src="banner_w800.jpg" alt="">
- </picture>
通過 @media 實現(xiàn)
- @media (min-width: 769px) {
- .bg {
- background-image: url(bg1080.jpg);
- }
- }
- @media (max-width: 768px) {
- .bg {
- background-image: url(bg768.jpg);
- }
- }
(3). 調(diào)整圖片大小
例如,你有一個 1920 * 1080 大小的圖片,用縮略圖的方式展示給用戶,并且當用戶鼠標懸停在上面時才展示全圖。如果用戶從未真正將鼠標懸停在縮略圖上,則浪費了下載圖片的時間。
所以,我們可以用兩張圖片來實行優(yōu)化。一開始,只加載縮略圖,當用戶懸停在圖片上時,才加載大圖。還有一種辦法,即對大圖進行延遲加載,在所有元素都加載完成后手動更改大圖的 src 進行下載。
(4). 降低圖片質(zhì)量
例如 JPG 格式的圖片,100% 的質(zhì)量和 90% 質(zhì)量的通常看不出來區(qū)別,尤其是用來當背景圖的時候。我經(jīng)常用 PS 切背景圖時, 將圖片切成 JPG 格式,并且將它壓縮到 60% 的質(zhì)量,基本上看不出來區(qū)別。
除此之外,網(wǎng)上還有很多在線壓縮圖片的網(wǎng)站,大家可以自行搜索。
(5). 盡可能利用 CSS3 效果代替圖片
有很多圖片使用 CSS 效果(漸變、陰影等)就能畫出來,這種情況選擇 CSS3 效果更好。因為代碼大小通常是圖片大小的幾分之一甚至幾十分之一。
10. 通過 webpack 按需加載 JavaScript 代碼
懶加載或者按需加載,是一種很好的優(yōu)化網(wǎng)頁或應(yīng)用的方式。這種方式實際上是先把你的代碼在一些邏輯斷點處分離開,然后在一些代碼塊中完成某些操作后,立即引用或即將引用另外一些新的代碼塊。這樣加快了應(yīng)用的初始加載速度,減輕了它的總體體積,因為某些代碼塊可能永遠不會被加載。
如果你使用腳手架來構(gòu)建項目,一般配置起來非常簡單,具體細節(jié)可看一下 webpack 文檔。
參考資料:
11. 減少重繪重排
瀏覽器渲染過程
- 解析HTML生成DOM樹。
- 解析CSS生成CSSOM規(guī)則樹。
- 將DOM樹與CSSOM規(guī)則樹合并在一起生成渲染樹。
- 遍歷渲染樹開始布局,計算每個節(jié)點的位置大小信息。
- 將渲染樹每個節(jié)點繪制到屏幕。
重排
當改變 DOM 元素位置或大小時,會導(dǎo)致瀏覽器重新生成渲染樹,這個過程叫重排。
重繪
當重新生成渲染樹后,就要將渲染樹每個節(jié)點繪制到屏幕,這個過程叫重繪。不是所有的動作都會導(dǎo)致重排,例如改變字體顏色,只會導(dǎo)致重繪。記住,重排會導(dǎo)致重繪,重繪不會導(dǎo)致重排 。
重排和重繪這兩個操作都是非常昂貴的,因為 JavaScript 引擎線程與 GUI 渲染線程是互斥,它們同時只能一個在工作。
什么操作會導(dǎo)致重排?
- 添加或刪除可見的 DOM 元素
- 元素位置改變
- 元素尺寸改變
- 內(nèi)容改變
- 瀏覽器窗口尺寸改變
如何減少重排重繪?
- 用 JavaScript 修改樣式時,最好不要直接寫樣式,而是替換 class 來改變樣式。
- 如果要對 DOM 元素執(zhí)行一系列操作,可以將 DOM 元素脫離文檔流,修改完成后,再將它帶回文檔。推薦使用隱藏元素(display:none)或文檔碎片(DocumentFragement),都能很好的實現(xiàn)這個方案。
12. 使用事件委托
事件委托利用了事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。所有用到按鈕的事件(多數(shù)鼠標事件和鍵盤事件)都適合采用事件委托技術(shù), 使用事件委托可以節(jié)省內(nèi)存。
- <ul>
- <li>蘋果</li>
- <li>香蕉</li>
- <li>鳳梨</li>
- </ul>
- // good
- document.querySelector('ul').onclick = (event) => {
- const target = event.target
- if (target.nodeName === 'LI') {
- console.log(target.innerHTML)
- }
- }
- // bad
- document.querySelectorAll('li').forEach((e) => {
- e.onclick = function() {
- console.log(this.innerHTML)
- }
- })
13. 注意程序的局部性
一個編寫良好的計算機程序常常具有良好的局部性,它們傾向于引用最近引用過的數(shù)據(jù)項附近的數(shù)據(jù)項,或者最近引用過的數(shù)據(jù)項本身,這種傾向性,被稱為局部性原理。有良好局部性的程序比局部性差的程序運行得更快。
局部性通常有兩種不同的形式:
- 時間局部性:在一個具有良好時間局部性的程序中,被引用過一次的內(nèi)存位置很可能在不遠的將來被多次引用。
- 空間局部性 :在一個具有良好空間局部性的程序中,如果一個內(nèi)存位置被引用了一次,那么程序很可能在不遠的將來引用附近的一個內(nèi)存位置。
時間局部性示例
- function sum(arry) {
- let i, sum = 0
- let len = arry.length
- for (i = 0; i < len; i++) {
- sum += arry[i]
- }
- return sum
- }
在這個例子中,變量sum在每次循環(huán)迭代中被引用一次,因此,對于sum來說,具有良好的時間局部性
空間局部性示例
具有良好空間局部性的程序
- // 二維數(shù)組
- function sum1(arry, rows, cols) {
- let i, j, sum = 0
- for (i = 0; i < rows; i++) {
- for (j = 0; j < cols; j++) {
- sum += arry[i][j]
- }
- }
- return sum
- }
空間局部性差的程序
- // 二維數(shù)組
- function sum2(arry, rows, cols) {
- let i, j, sum = 0
- for (j = 0; j < cols; j++) {
- for (i = 0; i < rows; i++) {
- sum += arry[i][j]
- }
- }
- return sum
- }
看一下上面的兩個空間局部性示例,像示例中從每行開始按順序訪問數(shù)組每個元素的方式,稱為具有步長為1的引用模式。
如果在數(shù)組中,每隔k個元素進行訪問,就稱為步長為k的引用模式。
一般而言,隨著步長的增加,空間局部性下降。
這兩個例子有什么區(qū)別?區(qū)別在于第一個示例是按行掃描數(shù)組,每掃描完一行再去掃下一行;第二個示例是按列來掃描數(shù)組,掃完一行中的一個元素,馬上就去掃下一行中的同一列元素。
數(shù)組在內(nèi)存中是按照行順序來存放的,結(jié)果就是逐行掃描數(shù)組的示例得到了步長為 1 引用模式,具有良好的空間局部性;而另一個示例步長為 rows,空間局部性極差。
性能測試
運行環(huán)境:
- cpu: i5-7400
- 瀏覽器: chrome 70.0.3538.110
對一個長度為9000的二維數(shù)組(子數(shù)組長度也為9000)進行10次空間局部性測試,時間(毫秒)取平均值,結(jié)果如下:
所用示例為上述兩個空間局部性示例
步長為 1 | 步長為 9000 |
---|---|
124 | 2316 |
從以上測試結(jié)果來看,步長為 1 的數(shù)組執(zhí)行時間比步長為 9000 的數(shù)組快了一個數(shù)量級。
總結(jié):
- 重復(fù)引用相同變量的程序具有良好的時間局部性
- 對于具有步長為 k 的引用模式的程序,步長越小,空間局部性越好;而在內(nèi)存中以大步長跳來跳去的程序空間局部性會很差
參考資料:
14. if-else 對比 switch
當判斷條件數(shù)量越來越多時,越傾向于使用 switch 而不是 if-else。
- if (color == 'blue') {
- } else if (color == 'yellow') {
- } else if (color == 'white') {
- } else if (color == 'black') {
- } else if (color == 'green') {
- } else if (color == 'orange') {
- } else if (color == 'pink') {
- }
- switch (color) {
- case 'blue':
- break
- case 'yellow':
- break
- case 'white':
- break
- case 'black':
- break
- case 'green':
- break
- case 'orange':
- break
- case 'pink':
- break
- }
像以上這種情況,使用 switch 是最好的。假設(shè) color 的值為 pink,則 if-else 語句要進行 7 次判斷,switch 只需要進行一次判斷。
從可讀性來說,switch 語句也更好。從使用時機來說,當條件值大于兩個的時候,使用 switch 更好。
不過,switch 只能用于 case 值為常量的分支結(jié)構(gòu),而 if-else 更加靈活。
15. 查找表
當條件語句特別多時,使用 switch 和 if-else 不是最佳的選擇,這時不妨試一下查找表。查找表可以使用數(shù)組和對象來構(gòu)建。
- switch (index) {
- case '0':
- return result0
- case '1':
- return result1
- case '2':
- return result2
- case '3':
- return result3
- case '4':
- return result4
- case '5':
- return result5
- case '6':
- return result6
- case '7':
- return result7
- case '8':
- return result8
- case '9':
- return result9
- case '10':
- return result10
- case '11':
- return result11
- }
可以將這個 switch 語句轉(zhuǎn)換為查找表
- const results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]
- return results[index]
如果條件語句不是數(shù)值而是字符串,可以用對象來建立查找表
- const map = {
- red: result0,
- green: result1,
- }
- return map[color]
16. 避免頁面卡頓
60fps 與設(shè)備刷新率
目前大多數(shù)設(shè)備的屏幕刷新率為 60 次/秒。因此,如果在頁面中有一個動畫或漸變效果,或者用戶正在滾動頁面,那么瀏覽器渲染動畫或頁面的每一幀的速率也需要跟設(shè)備屏幕的刷新率保持一致。
其中每個幀的預(yù)算時間僅比 16 毫秒多一點 (1 秒/ 60 = 16.66 毫秒)。但實際上,瀏覽器有整理工作要做,因此您的所有工作需要在 10 毫秒內(nèi)完成。如果無法符合此預(yù)算,幀率將下降,并且內(nèi)容會在屏幕上抖動。 此現(xiàn)象通常稱為卡頓,會對用戶體驗產(chǎn)生負面影響。
假如你用 JavaScript 修改了 DOM,并觸發(fā)樣式修改,經(jīng)歷重排重繪最后畫到屏幕上。如果這其中任意一項的執(zhí)行時間過長,都會導(dǎo)致渲染這一幀的時間過長,平均幀率就會下降。假設(shè)這一幀花了 50 ms,那么此時的幀率為 1s / 50ms = 20fps,頁面看起來就像卡頓了一樣。
對于一些長時間運行的 JavaScript,我們可以使用定時器進行切分,延遲執(zhí)行。
- for (let i = 0, len = arry.length; i < len; i++) {
- process(arry[i])
- }
假設(shè)上面的循環(huán)結(jié)構(gòu)由于 process() 復(fù)雜度過高或數(shù)組元素太多,甚至兩者都有,可以嘗試一下切分。
- const todo = arry.concat()
- setTimeout(() => {
- process(todo.shift())
- if (todo.length) {
- setTimeout(arguments.callee, 25)
- } else {
- callback(arry)
- }
- }, 25)
如果有興趣了解更多,可以查看一下高性能JavaScript第 6 章和高效前端:Web高效編程與優(yōu)化實踐第 3 章。
參考資料:
17. 使用 requestAnimationFrame 來實現(xiàn)視覺變化
從第 16 點我們可以知道,大多數(shù)設(shè)備屏幕刷新率為 60 次/秒,也就是說每一幀的平均時間為 16.66 毫秒。在使用 JavaScript 實現(xiàn)動畫效果的時候,最好的情況就是每次代碼都是在幀的開頭開始執(zhí)行。而保證 JavaScript 在幀開始時運行的唯一方式是使用 requestAnimationFrame。
- /**
- * If run as a requestAnimationFrame callback, this
- * will be run at the start of the frame.
- */
- function updateScreen(time) {
- // Make visual updates here.
- }
- requestAnimationFrame(updateScreen);
如果采取 setTimeout 或 setInterval 來實現(xiàn)動畫的話,回調(diào)函數(shù)將在幀中的某個時點運行,可能剛好在末尾,而這可能經(jīng)常會使我們丟失幀,導(dǎo)致卡頓。
參考資料:
18. 使用 Web Workers
Web Worker 使用其他工作線程從而獨立于主線程之外,它可以執(zhí)行任務(wù)而不干擾用戶界面。一個 worker 可以將消息發(fā)送到創(chuàng)建它的 JavaScript 代碼, 通過將消息發(fā)送到該代碼指定的事件處理程序(反之亦然)。
Web Worker 適用于那些處理純數(shù)據(jù),或者與瀏覽器 UI 無關(guān)的長時間運行腳本。
創(chuàng)建一個新的 worker 很簡單,指定一個腳本的 URI 來執(zhí)行 worker 線程(main.js):
- var myWorker = new Worker('worker.js');
- // 你可以通過postMessage() 方法和onmessage事件向worker發(fā)送消息。
- first.onchange = function() {
- myWorker.postMessage([first.value,second.value]);
- console.log('Message posted to worker');
- }
- second.onchange = function() {
- myWorker.postMessage([first.value,second.value]);
- console.log('Message posted to worker');
- }
在 worker 中接收到消息后,我們可以寫一個事件處理函數(shù)代碼作為響應(yīng)(worker.js):
- onmessage = function(e) {
- console.log('Message received from main script');
- var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
- console.log('Posting message back to main script');
- postMessage(workerResult);
- }
onmessage處理函數(shù)在接收到消息后馬上執(zhí)行,代碼中消息本身作為事件的data屬性進行使用。這里我們簡單的對這2個數(shù)字作乘法處理并再次使用postMessage()方法,將結(jié)果回傳給主線程。
回到主線程,我們再次使用onmessage以響應(yīng)worker回傳的消息:
- myWorker.onmessage = function(e) {
- result.textContent = e.data;
- console.log('Message received from worker');
- }
在這里我們獲取消息事件的data,并且將它設(shè)置為result的textContent,所以用戶可以直接看到運算的結(jié)果。
不過在worker內(nèi),不能直接操作DOM節(jié)點,也不能使用window對象的默認方法和屬性。然而你可以使用大量window對象之下的東西,包括WebSockets,IndexedDB以及FireFox OS專用的Data Store API等數(shù)據(jù)存儲機制。
參考資料:
19. 使用位操作
JavaScript 中的數(shù)字都使用 IEEE-754 標準以 64 位格式存儲。但是在位操作中,數(shù)字被轉(zhuǎn)換為有符號的 32 位格式。即使需要轉(zhuǎn)換,位操作也比其他數(shù)學(xué)運算和布爾操作快得多。
取模
由于偶數(shù)的最低位為 0,奇數(shù)為 1,所以取模運算可以用位操作來代替。
- if (value % 2) {
- // 奇數(shù)
- } else {
- // 偶數(shù)
- }
- // 位操作
- if (value & 1) {
- // 奇數(shù)
- } else {
- // 偶數(shù)
- }
取反
- ~~10.12 // 10
- ~~10 // 10
- ~~'1.5' // 1
- ~~undefined // 0
- ~~null // 0
位掩碼
- const a = 1
- const b = 2
- const c = 4
- const options = a | b | c
通過定義這些選項,可以用按位與操作來判斷 a/b/c 是否在 options 中。
- // 選項 b 是否在選項中
- if (b & options) {
- ...
- }
20. 不要覆蓋原生方法
無論你的 JavaScript 代碼如何優(yōu)化,都比不上原生方法。因為原生方法是用低級語言寫的(C/C++),并且被編譯成機器碼,成為瀏覽器的一部分。當原生方法可用時,盡量使用它們,特別是數(shù)學(xué)運算和 DOM 操作。
21. 降低 CSS 選擇器的復(fù)雜性
(1). 瀏覽器讀取選擇器,遵循的原則是從選擇器的右邊到左邊讀取。
看個示例
- #block .text p {
- color: red;
- }
- 查找所有 P 元素。
- 查找結(jié)果 1 中的元素是否有類名為 text 的父元素
- 查找結(jié)果 2 中的元素是否有 id 為 block 的父元素
(2). CSS 選擇器優(yōu)先級
- 內(nèi)聯(lián) > ID選擇器 > 類選擇器 > 標簽選擇器
根據(jù)以上兩個信息可以得出結(jié)論。
- 選擇器越短越好。
- 盡量使用高優(yōu)先級的選擇器,例如 ID 和類選擇器。
- 避免使用通配符 *。
最后要說一句,據(jù)我查找的資料所得,CSS 選擇器沒有優(yōu)化的必要,因為最慢和慢快的選擇器性能差別非常小。
參考資料:
22. 使用 flexbox 而不是較早的布局模型
在早期的 CSS 布局方式中我們能對元素實行絕對定位、相對定位或浮動定位。而現(xiàn)在,我們有了新的布局方式 flexbox,它比起早期的布局方式來說有個優(yōu)勢,那就是性能比較好。
下面的截圖顯示了在 1300 個框上使用浮動的布局開銷:
然后我們用 flexbox 來重現(xiàn)這個例子:
現(xiàn)在,對于相同數(shù)量的元素和相同的視覺外觀,布局的時間要少得多(本例中為分別 3.5 毫秒和 14 毫秒)。
不過 flexbox 兼容性還是有點問題,不是所有瀏覽器都支持它,所以要謹慎使用。
各瀏覽器兼容性:
- Chrome 29+
- Firefox 28+
- Internet Explorer 11
- Opera 17+
- Safari 6.1+ (prefixed with -webkit-)
- Android 4.4+
- iOS 7.1+ (prefixed with -webkit-)
參考資料:
23. 使用 transform 和 opacity 屬性更改來實現(xiàn)動畫
在 CSS 中,transforms 和 opacity 這兩個屬性更改不會觸發(fā)重排與重繪,它們是可以由合成器(composite)單獨處理的屬性。
參考資料: