個經常被忽視的網站性能瓶頸發生在處理級聯樣式表和隨后在網頁的文檔對象模型中應用 CSS 選擇器。為了加快網頁的渲染速度并改善最終用戶體驗,請考慮以下五個 CSS 性能優化技巧。
1. 使用內聯樣式
如果給定樣式僅用于單個特定頁面元素(例如圖像滑塊或輪播)并且該組件僅用于站點中的少數頁面,請使用內聯樣式而不是通用加載的 CSS 文件。這不僅會減少外部樣式表的大小,還會減少在不使用該組件的頁面上發生的 CSS 選擇器評估的數量。
2. 使用特定的樣式
不要將樣式應用于通用選擇器、后代選擇器和頂級 HTML 元素。這樣做會觸發許多布爾評估。相反,通過選擇更細粒度的元素(例如單個類樣式)來優化 CSS 性能。
3. 使用 WebComponents 優化 CSS
WebComponents 是自包含的,減少了對共享 CSS 和 JavaScript 的需求,WebComponents 框架是一種相對較新的基于標準的方法,用于創建可重用組件,其中 JavaScript 和樣式是自包含和隔離的。當你將組件所需的樣式保留在全局共享的 CSS 文件之外時,你不會看到對站點的其他區域的性能影響,因為未使用的樣式應用程序會不必要地消耗時鐘周期。
4. 拆分你的 CSS 文件
如果你的許多 CSS 文件是針對特定瀏覽器或設備定制的,請將這些樣式分解為多個 CSS 文件。在運行時,僅加載必要的那些。
這個 CSS 性能優化技巧將需要一些客戶端 JavaScript 或服務器端處理來完成,但對用戶的好處將值得付出額外的努力。
例如,如果移動和桌面呈現顯著不同,這可能會在每種設備類型上將文檔對象模型 (DOM) 評估的數量減少 50%。
5. 減小 DOM 的大小
用于拆分 CSS 文件的技巧也可用于減小 DOM 的大小。
另一個 CSS 性能優化技巧類似于將 CSS 文件拆分為多個特定于設備的文件。相反,你還可以縮小網頁本身的大小,減少 HTML 中 DOM 元素的數量,從而加快客戶端渲染速度。
但是,處理 CSS 的問題不僅在于需要應用于頁面的樣式數量,還在于實際頁面上的 DOM 元素數量。許多采用響應式設計的現代網頁會在每個頁面請求上加載 HTML 源代碼,以實現移動和桌面體驗。相反,使用客戶端 JavaScript 甚至服務器端技術來確保不會將多余的 HTML 加載到永遠不會使用它的頁面上。這可以顯著減少 DOM 大小以及頁面需要經過的樣式表評估次數。
樣式表是現代網站開發人員最好的朋友,但它們通常會帶來隱藏的性能成本。遵循這五個 CSS 性能優化建議,減少 CSS 選擇器成為網站性能瓶頸的可能性。想往前端發展的小伙伴建議參加Web前端培訓來學習前端技術,有系統規范的課程,有經驗豐富的專業講師面授指導教學,能在短時間內學有所成。
了解更多
文已經過原作者 EthicalAds 授權翻譯。
作為開發人員,我們一直在尋找讓我們的代碼更快更好的方法。但在此之前,編寫高性能代碼需要做三件事:
記住這一點
任何傻瓜都可以編寫計算機可以理解的代碼,優秀的程序員編寫人類可以理解的代碼。- 丁·福勒
我們來看看如何使 JavaScript代碼運行得更快。
延遲算法將計算延遲到需要執行時才執行,然后生成結果。
const someFn = () => {
doSomeOperation()
return () => {
doExpensiveOperation()
}
}
const t = someArray.filter((x) => checkSomeCondition(x)).map((x) => someFn(x))
// 現在,如果有需要在執行
t.map((x) => t())
最快的代碼是未執行的代碼,所以盡量延遲執行。
JavaScript 使用原型繼承,JS 中所有對象都是Object的實例。
MDN說:
嘗試訪問對象的屬性時,不僅會在對象上搜索該屬性,還會在對象的原型,原型的原型等上搜索該屬性,直到找到匹配屬性名或原型鏈的末端。
對于每個屬性,JavaScript引擎都必須遍歷整個對象鏈,直到找到匹配項。如果使用不當,這會占用大量資源,并影響應用程序的性能。
所以不要這樣:
const name = userResponse.data.user.firstname + userResponse.data.user.lastname
而是這樣做:
const user = userResponse.data.user
const name = user.firstname + user.lastname
使用臨時變量來保存鏈接的屬性,而不是遍歷訪問整條鏈。
在上述情況下,userResponse可能不是對象,如果是對象,它的屬性 user 也可能不是對象。所以,在獲取值時要進行檢查:
let name = ''
if (userResponse) {
const data = userResponse.data
if (data && data.user) {
const user = data.user
if (user.firstname) {
name += user.firstname
}
if (user.lastname) {
name += user.firstname
}
}
}
這太啰嗦了。代碼越多,bug 就越明顯。我們能把它縮小嗎?當然,可以使用 JS 中可選的鏈接、解構賦值來優化它。
const user = userResponse?.data?.user
const { firstname = '', lastname = ''} = user
const name = firstname + lastname
是不是很靈活地,簡短?不要使用這個要注意,Babel 會按照以下方式進行轉換:
'use strict'
var _userResponse, _userResponse$data
var user =
(_userResponse = userResponse) === null || _userResponse === void 0
? void 0
: (_userResponse$data = _userResponse.data) === null ||
_userResponse$data === void 0
? void 0
: _userResponse$data.user
var _user$firstname = user.firstname,
firstname = _user$firstname === void 0 ? '' : _user$firstname,
_user$lastname = user.lastname,
lastname = _user$lastname === void 0 ? '' : _user$lastname
var name = firstname + lastname
當使用轉譯時,確保你選擇了一個更適合你的用例的。
數字很奇怪,ECMAScript將數字標準化為64位浮點值,也稱為雙精度浮點或Float64表示形式。
如果 JS 引擎以Float64表示形式存儲數字,則將導致巨大的性能低下。JS 引擎對數字進行抽象,使其行為與Float64完全匹配。與float64運算相比,JS 引擎執行整數運算的速度要快得多。
有時,我們認為像下面這樣寫法可讀比較好:
const maxWidth = '1000'
const minWidth = '100'
const margin = '10'
getWidth = () => ({
maxWidth: maxWidth - margin * 2,
minWidth: minWidth - margin * 2,
})
如果getWidth函數被多次調用,那么每次調用它時都會計算它的值。上面的計算并不是什么大問題,因此我們不會注意到任何性能影響。
但是總的來說,運行時的求值的數量越少,性能就越好。
// maxWidth - (margin * 2)
const maxWidth = '980'
// minWidth - (margin * 2)
const minWidth = '80'
const margin = '10'
getWidth = () => ({
maxWidth,
minWidth,
})
如果要檢查多個條件時,可以使用Map代替 switch/if-else條件。在Map中查找元素的性能比對switch和if-else條件快得多。
switch (day) {
case 'monday':
return 'workday'
case 'tuesday':
return 'workday'
case 'wednesday':
return 'workday'
case 'thursday':
return 'workday'
case 'friday':
return 'workday'
case 'saturday':
return 'funday'
case 'sunday':
return 'funday'
}
// or this
if (
day === 'monday' ||
day === 'tuesday' ||
day === 'wednesday' ||
day === 'thursday' ||
day === 'friday'
)
return 'workday'
else return 'funday'
上面可以使用 Map 來代替
const m = new Map([
['monday','workday'],
['tuesday', 'workday'],
['wednesday', 'workday'],
['thursday', 'workday'],
['friday', 'workday'],
['saturday', 'funday'],
['sunday', 'funday']
];
return m.get(day);
在 React組件中,這種寫法還是很常見的。
export default function UserList(props) {
const { users } = props
if (users.length) {
return <UserList />
}
return <EmptyUserList />
}
在這里,我們在沒有用戶時渲染<EmptyUserList />否則渲染<UserList />。有大部分人認為,我們首先處理所有空的的情況,然后再處理有數據的情況。對于任何讀過它的人來說都更清楚,而且效率更高。也就是說,以下代碼比上一個代碼更有效。
export default function UserList(props) {
const { users } = props
if (!users.length) {
return <EmptyUserList />
}
// some resource intensive operation
return <UserList />
}
當然 users.length 一直有值的話,就使用第一種情況。
JavaScript是解釋型和編譯型語言。為了產生更有效的二進制文件,編譯器需要類型信息。但是,作為一種動態類型化的語言會使編譯器難以進行。
編譯器在編譯熱代碼(多次執行的代碼)時進行一些假設并優化代碼。編譯器花費一些時間來生成此優化的代碼。當這些假設失敗時,編譯器必須丟棄優化的代碼,并退回到解釋的執行方式。這是耗時且昂貴的。
作者:EthicalAds 譯者:前端小智 來源: sendilkumarn
原文:https://sendilkumarn.com/blog/javascript-optimization/
在前端開發中,我們往往會把性能問題當做編碼過程的一個重要考慮因素,如何寫出高質量代碼是我們每個程序員追求的高境界。
今天這篇文章我們一起來看看如何寫出一些高效的Javascript代碼,提高執行效率吧。
Javascript
在Javascript中提供了with關鍵字,它可以將代碼執行的作用域設定到一個特定的作用域中。
使用with語句可以減少代碼量,例如以下的一些與location有關的執行語句。
不使用with
當使用with語句后,以上的代碼可以優化成以下的方式。
使用with
我們可以看出,在代碼編寫上,如果使用with語句,代碼塊中就會包含一個獨立的作用域,根據作用域鏈的思想,每次執行會優先尋找該作用域下的變量與方法。
使用with語句的一個最大問題是:Javascript引擎無法優化with代碼塊。
拿上面那段代碼來說,Javascript引擎無法判斷with代碼塊內部的search是局部變量還是location對象的一個屬性,因此只要Javascript引擎遇到with關鍵字,就會放棄優化其中的代碼塊,造成性能下降。
因此我們得出的結論是:不要因為節省代碼量而使用with關鍵字。
雖然不推薦使用全局變量,但是全局變量也有它的好處,最主要是寫法簡單,所有信息可以直接從window尋找。
但是其弊端也是很明顯的,總結如下。
由于作用域鏈的存在,代碼執行會優先從當前作用域尋找變量,如果引用全局變量,總是需要尋找到window對象才可以找到,非常耗時。
全局作用域中定義的變量會一直存在于內存中,會導致占用的內存越來越大,影響性能。
變量污染,當定義的全局變量越來越多,可能自己定義的變量名已經記不清了,后面的變量覆蓋前面的值很可能發生,造成變量沖突。
下面我們通過一個簡單的例子來驗證使用全局變量會造成性能下降。
例子的主要功能是,執行一個for循環,每次拼接一個簡單的字符,通過console.time()和console.timeEnd()方法測試執行的時間。
執行代碼
我們將上述代碼復制到Chrome控制臺中執行,為了證實結果不是偶然的,我們執行三次,得到的結果如下所示。
結果1
結果2
結果3
通過上述的結果,可以看出在不使用全局變量的情況下,執行時間要遠遠低于使用全局變量的。
因此得出的結論是:在萬不得已的情況下,不要使用全局變量。
在拼接字符串時,我們總是會習慣性的使用'+'運算符拼接多個字符串。
累計使用'+'
這種寫法的可讀性比較好,而且能夠減少很多的代碼量,在寫法上非常常見。
但是如果考慮到性能問題,這種寫法卻不是最好的,這是為什么呢?
因為使用連續的'+'運算符,每使用一次都會生成一個臨時變量去存儲字符串,直到最后一次才會將結果賦值給變量a。
比如上述的代碼,會生成一個臨時變量保存字符串'xy',然后拼接到變量a的后面,最后才賦值給a。
而如果不使用累加形式拼接字符串,就不會出現這樣的情況。
我們將上面的代碼改寫成以下形式。
改寫形式
這種寫法在執行時不會生成臨時變量,而是將每次更新的值直接賦給變量a,這樣的話性能會有所提升。
因此結論是:代碼不復雜的情況下,盡量減少連續使用'+'連接符。
使用過Javascript定時器的同學都知道,setTimeout和setInterval方法可以傳入函數,也可以傳入字符串。
但是我們推薦傳入函數的方式,如果傳入字符串的話會影響性能。這是因為當你傳入字符串的時候,它會被傳進構造函數,并且重新調用另一個函數,影響執行過程。
如果傳入函數的話,雖然在延遲一定的時間后才會執行,但是會被Javascript引擎優化,從而帶來性能的提升。
例如以下是傳入字符串的寫法。
傳如字符串
可以改寫為以下傳入函數的寫法。
傳入函數
Javascript中提升性能的方法還有很多,文章中只是列舉了在編寫代碼過程中可以注意的問題。后面我還會整理與DOM操作有關的性能優化的文章,敬請關注~
*請認真填寫需求信息,我們會在24小時內與您取得聯系。