14個 JavaScript 代碼優(yōu)化技巧

JavaScript 已經(jīng)成為有史以來最受歡迎的編程語言之一搜锰。從 W3Tech 的數(shù)據(jù)來看蛋叼,全世界將近 96%的網(wǎng)站都在使用它狈涮。關(guān)于 Web 有一個關(guān)鍵的事實是鸭栖,你無法控制訪問網(wǎng)站的用戶所用設(shè)備晕鹊。當(dāng)用戶訪問你的網(wǎng)站時,使用的可能是高端設(shè)備也可能是低端設(shè)備晓锻,網(wǎng)絡(luò)連接條件也有好有差砚哆。這意味著你必須盡可能優(yōu)化自己的網(wǎng)站,以滿足任何用戶的需求纷铣。

附帶提一下关炼,請共享和重用你的 JS 組件儒拂,以在高質(zhì)量代碼(寫起來需要花費時間)和合理的交付時間之間保持適當(dāng)?shù)钠胶馍АD憧梢允褂?Bit 等流行工具將任何項目中的組件(普通 JS命雀、TS、React撵儿、Vue 等)共享到 Bit 的組件中心淀歇,用不了多大功夫匈织。

1缀匕、刪除未使用的代碼和功能

你的應(yīng)用程序包含的代碼越多乡小,就需要將更多的數(shù)據(jù)傳輸?shù)娇蛻舳寺印g覽器也需要更多時間來分析和解釋代碼。

有時,你可能打包了很多根本用不到的功能牵辣。最好只在開發(fā)環(huán)境中保留這些額外的代碼择浊,而不要將其推送到生產(chǎn)環(huán)境中逾条,以免給客戶端的瀏覽器增加負(fù)擔(dān)。

要不斷問自己担孔,某個功能或代碼段是否是必要的糕篇。

你可以手動移除未使用的代碼拌消,也可以使用 Uglify 或谷歌的 Closure Compiler 之類的工具刪除它們墩崩。你還可以使用一種被稱為搖樹優(yōu)化的技術(shù)從應(yīng)用程序中刪除未使用的代碼侯勉。Webpack 這類打包軟件提供了這種技術(shù)址貌,詳情可以參考這里:

https://www.infoq.cn/article/dcKcJiT8aeEBNZbdotFF

如果要刪除未使用的 npm 軟件包余舶,可以使用命令 npm prune锹淌,詳細(xì)信息參考 NPM 文檔赂摆。

https://docs.npmjs.com/cli-commands/prune.html

2、盡可能的緩存

緩存可以減少延遲和網(wǎng)絡(luò)流量政恍,從而減少了顯示資源表示所需的時間篙耗,以提高網(wǎng)站的速度和性能。緩存可以借助 Cache API 或 HTTP caching 來實現(xiàn)宪赶。你可能想知道內(nèi)容更改時會發(fā)生什么宗弯。當(dāng)滿足某些條件(例如發(fā)布新內(nèi)容)時,上述緩存機制能夠處理和重新生成緩存搂妻。

3蒙保、避免內(nèi)存泄漏

作為一種高級語言,JS 會負(fù)責(zé)一些底層管理工作欲主,例如內(nèi)存管理邓厕。垃圾回收是大多數(shù)編程語言共有的過程。用外行術(shù)語來說扁瓢,垃圾收集就是收集并釋放已分配給對象邑狸,但目前尚未在程序的任何部分中使用的內(nèi)存。在 C 這樣的編程語言中涤妒,開發(fā)人員必須使用 malloc() 和 dealloc() 函數(shù)來處理內(nèi)存分配和釋放操作她紫。

雖然在 JavaScript 中垃圾回收是自動執(zhí)行的,但在某些情況下它也不是完美的茄菊。在 JavaScript ES6 中,引入了 Map 和 Set 及其“weaker”的同級對象。被稱為 WeakMap 和 WeakSet 的“較弱”對應(yīng)項持有對對象的“弱”引用增淹。它們使未引用的值能夠被垃圾回收,從而防止內(nèi)存泄漏拳喻。你可以在此處了解有關(guān) WeakMaps 的更多信息:

https://blog.bitsrc.io/understanding-weakmaps-in-javascript-6e323d9eec81

4码撰、盡早打破循環(huán)

超大循環(huán)肯定會耗費很多的時間颊亮,所以你應(yīng)該盡早打破這些超大循環(huán)。你可以用 break 關(guān)鍵字和 continue 關(guān)鍵字來做這件事偿渡,從而編寫更高效的代碼适揉。

在下面的示例中,如果你沒有從循環(huán)中 break,則你的代碼將循環(huán)運行 1000000000?(10億)次票彪,顯然會過載的。

letarr =?newArray(1000000000).fill('----');

arr[970] =?'found';

for(leti =?0; i < arr.length; i++) {

if(arr[i] ===?'found') {

console.log("Found");

break;

}

}

在下面的示例中桶蝎,如果你在循環(huán)不符合你的條件時沒有 continue,則你仍將運行該函數(shù) 1000000000 次。我們僅在數(shù)組元素處于偶數(shù)位置時處理它。這將循環(huán)執(zhí)行減少了近一半。

letarr =?newArray(1000000000).fill('----');

arr[970] =?'found';

for(leti =?0; i < arr.length; i++) {

if(i%2!=0){

continue;

};

process(arr[i]);

}

你可以在此處詳細(xì)了解循環(huán)和性能的關(guān)系:

https://www.oreilly.com/library/view/high-performance-javascript/9781449382308/ch04.html

5、最小化變量計算的次數(shù)

為了減少計算變量的次數(shù),可以使用閉包。通俗來說,JavaScript 中的閉包使你可以從內(nèi)部函數(shù)訪問外部函數(shù)作用域恃疯。每次創(chuàng)建函數(shù)(不調(diào)用)時都會創(chuàng)建閉包。內(nèi)部函數(shù)將有權(quán)訪問外部作用域的變量,即使在返回外部函數(shù)之后也是如此。

我們來看兩個例子鹤耍。這些示例均來自 Bret 的博客杆怕。

functionfindCustomerCity(name){

consttexasCustomers = ['John',?'Ludwig',?'Kate'];

constcaliforniaCustomers = ['Wade',?'Lucie','Kylie'];

returntexasCustomers.includes(name) ??'Texas':

californiaCustomers.includes(name) ??'California':?'Unknown';

};

如果你多次調(diào)用上面的函數(shù),那么每次都會創(chuàng)建一個新對象。每次調(diào)用時,變量 texasCustomers 和 californiaCustomers 都會導(dǎo)致不必要的內(nèi)存重分配。

functionfindCustomerCity()?{

consttexasCustomers = ['John',?'Ludwig',?'Kate'];

constcaliforniaCustomers = ['Wade',?'Lucie','Kylie'];

returnname=>texasCustomers.includes(name) ??'Texas':

californiaCustomers.includes(name) ??'California':?'Unknown';

};

letcityOfCustomer = findCustomerCity();

cityOfCustomer('John');//Texas

cityOfCustomer('Wade');//California

cityOfCustomer('Max');//Unknown

在上面的示例中,借助于閉包,返回到變量 cityOfCustomer 的內(nèi)部函數(shù)可以訪問外部函數(shù) findCustomerCity() 的常量。而且,每當(dāng)以傳遞的名稱作為參數(shù)調(diào)用內(nèi)部函數(shù)時,都無需再次實例化常量虱颗。要了解關(guān)于閉包的更多信息畦粮,建議你閱讀 Prashant 的博客文章:

https://medium.com/@prashantramnyc/javascript-closures-simplified-d0d23fa06ba4

6、盡量減少 DOM 訪問

與其他 JavaScript 語句相比对蒲,訪問 DOM 的速度很慢蝠咆。如果你對 DOM 進行更改碑韵,觸發(fā)了布局的重新繪制辙纬,那么就得等好一陣子了。

為了減少訪問 DOM 元素的次數(shù)溉知,請先訪問一次,然后將其用作局部變量恒傻。完成需求后注簿,請一定將其設(shè)置為 null 來移除該變量的值山上。這將防止內(nèi)存泄漏寄摆,因為這會觸發(fā)垃圾回收過程割择。

7、壓縮文件

通過壓縮方法(例如 Gzip)可以減小 JavaScript 文件的大小叹侄。較小的文件會提升你的網(wǎng)站性能昨登,因為瀏覽器只需下載較小的資產(chǎn)即可。

這類壓縮手段最多可以減少 80%的文件大小尿褪。在此處閱讀有關(guān)壓縮的更多信息:

https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer#text_compression_with_gzip

8臼闻、縮小最終代碼

有人認(rèn)為縮小和壓縮是相同的乓搬,其實不然。在壓縮中衙傀,我們使用特殊算法來改變文件的輸出大小盅弛;在縮小時钱骂,我們需要刪除 JavaScript 文件中的注釋和多余的空格叔锐。可以在網(wǎng)上找到許多工具和軟件包來幫助完成這一過程见秽∮淅樱縮小已成為頁面優(yōu)化的標(biāo)準(zhǔn)做法,也是前端優(yōu)化的主要步驟之一解取。

縮小可以讓文件大小最多減少 60%步责。你可以在此處閱讀有關(guān)縮小的更多信息:

https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer#minification_preprocessing_context-specific_optimizations

9、使用 Throttle 和 Debounce

我們可以使用 Throttle(節(jié)流)和 Debounce(防抖)這兩種技術(shù)來嚴(yán)格控制代碼需要處理事件的次數(shù)禀苦。

節(jié)流是指定函數(shù)可以超時的最大次數(shù)蔓肯。例如,“每 1000 毫秒最多執(zhí)行一次 onkeyup 事件函數(shù)”振乏。也就是說哪怕你每秒敲 20 個鍵蔗包,該事件每秒也只會觸發(fā)一次。這將減少代碼的負(fù)擔(dān)慧邮。

另一方面调限,防抖是指定自上次執(zhí)行相同函數(shù)以來再次運行該函數(shù)的最短持續(xù)時間。換句話說误澳,“上次調(diào)用函數(shù)后過最少 600 毫秒才執(zhí)行此函數(shù)”旧噪。要了解有關(guān)節(jié)流和防抖的更多信息,這里有一篇快速入門:

https://css-tricks.com/the-difference-between-throttling-and-debouncing/

你可以實現(xiàn)自己的防抖和節(jié)流函數(shù)脓匿,也可以從 Lodash 和 Underscore 之類的庫中導(dǎo)入它們淘钟。

10、避免使用 Delete 關(guān)鍵字

delete 關(guān)鍵字用于從對象中刪除屬性陪毡。這個關(guān)鍵字的性能表現(xiàn)不怎么好米母,預(yù)計它將在未來的更新中修復(fù)。

或者毡琉,你可以簡單地將不需要的屬性設(shè)置為 undefined铁瞒。

constobject= {name:"Jane Doe", age:43};

object.age = undefined;

你還可以使用 Map 對象,Bret 認(rèn)為它的 delete 方法會更快桅滋。

11慧耍、使用異步代碼防止線程阻塞

你應(yīng)該知道 JavaScript 默認(rèn)情況下是同步的和單線程的。但是在某些情況下丐谋,你的代碼需要很大的計算量芍碧。代碼本質(zhì)上是同步的,意味著一段代碼運行時將阻止其他代碼語句運行号俐,直到前者完成執(zhí)行為止泌豆。這會降低整體性能。

但是我們可以通過異步代碼來避免這種情況吏饿。異步代碼以前以回調(diào)的形式編寫踪危,但是 ES6 引入了一種處理異步代碼的新樣式蔬浙。這種新樣式被稱為 Promise。你可以在 MDN 的官方文檔中了解有關(guān)回調(diào)和 Promise 的更多信息贞远。

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing

可是等等……

JavaScript 默認(rèn)情況下是同步的畴博,并且也是單線程的。

如何在單個線程上運行異步代碼呢蓝仲?這是很多人感到困惑的地方绎晃。做到這一點,主要依賴運行在瀏覽器后臺的 JavaScript 引擎杂曲。JavaScript 引擎是執(zhí)行 JavaScript 代碼的計算機程序或解釋器庶艾。JavaScript 引擎可以用多種語言編寫。例如擎勘,支持 Chrome 瀏覽器的 V8 引擎是用 C++ 編寫的咱揍,而支持 Firefox 瀏覽器的 SpiderMonkey 引擎是用 C 和 C++ 編寫的。

這些 JavaScript 引擎可以在后臺處理任務(wù)棚饵。根據(jù) Brian 的說法煤裙,調(diào)用棧可以識別 Web API 的函數(shù)噪漾,并將其交給瀏覽器處理硼砰。瀏覽器完成這些任務(wù)后,它們將返回并作為回調(diào)被推上堆棧欣硼。

你可能想知道 Node.js 是怎么做這些工作的题翰,畢竟它沒有瀏覽器的幫助。實際上诈胜,支持 Chrome 的那個 V8 引擎也是 Node.js 背后的支撐豹障。這里有 Salil 的一篇很棒的博客文章,解釋了 Node 生態(tài)系統(tǒng)中的這一過程焦匈。

https://medium.com/better-programming/is-node-js-really-single-threaded-7ea59bcc8d64

12血公、使用代碼拆分

如果你有使用 Google Light House 的經(jīng)驗,肯定會熟悉一種稱為“first contentful paint”的指標(biāo)缓熟。它是 Lighthouse 報告的 Performance 部分中跟蹤的六個指標(biāo)之一累魔。

First Contentful Paint(FCP)衡量用戶轉(zhuǎn)到你的頁面后瀏覽器渲染第一段 DOM 內(nèi)容所花費的時間。頁面上的圖像够滑、非白色<canvas>元素和 SVG 被視為 DOM 內(nèi)容垦写;iframe 內(nèi)部不包含任何內(nèi)容。

獲得更高的 FCP 分?jǐn)?shù)的最佳方法之一是使用代碼拆分版述。代碼拆分是一種在傳輸開始時僅將必要的模塊發(fā)送給用戶的技術(shù)梯澜。通過減小最初發(fā)送的載荷大小,這將極大地影響 FCP 分?jǐn)?shù)渴析。

流行的模塊打包器(例如 webpack)可為你提供代碼拆分功能晚伙。你還可以利用原生 ES 模塊來單獨加載各個模塊。你可以在此處詳細(xì)了解有關(guān)原生 ES 模塊的信息俭茧。

https://blog.bitsrc.io/understanding-es-modules-in-javascript-a28fec420f73

13咆疗、使用 async 和 defer

在現(xiàn)代網(wǎng)站中,腳本比 HTML 更為密集母债,其大小更大且消耗更多的處理時間午磁。默認(rèn)情況下,瀏覽器必須等待腳本下載和執(zhí)行完畢后毡们,再處理頁面的其余部分迅皇。

于是笨重的腳本可能會阻止網(wǎng)頁的加載。為了避免這種情況衙熔,JavaScript 為我們提供了兩種分別稱為 async 和 defer 的技術(shù)登颓。你只需將這些屬性添加到<script>標(biāo)記中即可。

Async 會讓瀏覽器在不影響渲染的情況下加載腳本红氯。換句話說框咙,頁面不會等待 async 腳本,而是先處理和顯示內(nèi)容痢甘。

Defer 是讓瀏覽器在渲染完成后加載腳本喇嘱。如果同時指定它們兩者,則 async 在現(xiàn)代瀏覽器上更優(yōu)先塞栅,而支持 defer 但不支持 async 的老式瀏覽器將回退為 defer者铜。

這兩個屬性可以幫助你大幅減少頁面加載時間。我強烈建議你閱讀 Flavio 的這篇博客文章放椰。

https://flaviocopes.com/javascript-async-defer/

14王暗、在后臺運行 CPU 密集型任務(wù)

可以使用 Web Worker 在后臺線程中運行腳本。如果你有一些高強度的任務(wù)庄敛,可以將它們分配給 Web Worker俗壹,這些 WebWorker 可以在不干擾用戶界面的情況下運行它們。創(chuàng)建后藻烤,Web Worker 可以將消息發(fā)布到該代碼指定的事件處理程序來與 JavaScript 代碼通信绷雏,反之亦然。

要了解有關(guān) Web Worker 的更多信息怖亭,建議你閱讀 MDN 文檔:

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

感謝閱讀涎显,歡迎評論,編程愉快兴猩!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末期吓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子倾芝,更是在濱河造成了極大的恐慌讨勤,老刑警劉巖箭跳,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異潭千,居然都是意外死亡谱姓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門刨晴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屉来,“玉大人,你說我怎么就攤上這事狈癞∏芽浚” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵蝶桶,是天一觀的道長慨绳。 經(jīng)常有香客問我,道長莫瞬,這世上最難降的妖魔是什么儡蔓? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮疼邀,結(jié)果婚禮上喂江,老公的妹妹穿的比我還像新娘。我一直安慰自己旁振,他們只是感情好获询,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拐袜,像睡著了一般吉嚣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹬铺,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天尝哆,我揣著相機與錄音,去河邊找鬼甜攀。 笑死秋泄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的规阀。 我是一名探鬼主播恒序,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谁撼!你這毒婦竟也來了歧胁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喊巍,沒想到半個月后屠缭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡玄糟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年勿她,在試婚紗的時候發(fā)現(xiàn)自己被綠了袄秩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阵翎。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖之剧,靈堂內(nèi)的尸體忽然破棺而出郭卫,到底是詐尸還是另有隱情,我是刑警寧澤背稼,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布贰军,位于F島的核電站,受9級特大地震影響蟹肘,放射性物質(zhì)發(fā)生泄漏词疼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一帘腹、第九天 我趴在偏房一處隱蔽的房頂上張望贰盗。 院中可真熱鬧,春花似錦阳欲、人聲如沸舵盈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秽晚。三九已至,卻和暖如春筒愚,著一層夾襖步出監(jiān)牢的瞬間赴蝇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工巢掺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留句伶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓址遇,卻偏偏與公主長得像熄阻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子倔约,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355