英文 | https://blog.bitsrc.io/14-javascript-code-optimization-tips-for-front-end-developers-a44763d3a0da
譯文 |?https://github.com/xitu/gold-miner/blob/master/article/2020/14-javascript-code-optimization-tips-for-front-end-developers.mdJavaScript 已經(jīng)成為當(dāng)下最流行的編程語言之一。根據(jù) W3Tech,全世界幾乎 96% 的網(wǎng)站都在使用它。關(guān)于網(wǎng)站,你需要知道的最關(guān)鍵的一點是,你無法控制訪問你網(wǎng)站的用戶的硬件設(shè)備規(guī)格。訪問你的網(wǎng)站的終端用戶也許使用了高端或低端的設(shè)備,用著好的或差的網(wǎng)絡(luò)連接。這意味著你必須確保你的網(wǎng)站是盡可能優(yōu)化的,你能夠滿足任何用戶的要求。這里有一些技巧,可以幫助你更好地優(yōu)化 JavaScript 代碼,從而提高性能。順便提一下,為了共享和復(fù)用 JS 組件,需要在高質(zhì)量代碼(需要花時間)和合理交付時間之間保持正確的平衡。你可以使用流行的工具例如 Bit (Github),去共享組件(vanilla JS, TS, React, Vue 等)到 Bit 的 component hub,而不浪費(fèi)太多時間。1、刪除不使用的代碼和功能
程序包含越多的代碼,給客戶端傳遞的數(shù)據(jù)就越多。瀏覽器也需要更多的時間去解析和編譯代碼。有時,代碼里也許會包含完全未使用到的功能,最好只將這些額外的代碼保留在開發(fā)環(huán)境中,并且不要把它們留到生產(chǎn)環(huán)境中,因為無用的代碼可能會增加客戶端瀏覽器的負(fù)擔(dān)。經(jīng)常問自己那個函數(shù)、特性或代碼是否是必需的。你可以手動的刪掉無用的代碼,也可以用工具 Uglify 或 谷歌開發(fā)的 Closure Compiler 幫你刪。你甚至可以使用一種叫做 tree shaking 的技術(shù)來刪除程序中未使用的代碼。例如打包工具 Webpack 就提供了它。你可以在 這里 了解更多關(guān)于 tree shaking 信息。還有,如果你想刪掉未使用的 npm 包,你可以輸入命令 npm prune 。閱讀 NPM 文檔 了解更多。2、?盡可能緩存
緩存通過減少等待時間和網(wǎng)絡(luò)請求提高了網(wǎng)站的速度和性能,因此減少了展示資源的時間。可以借助于 緩存 API 或 HTTP 緩存 實現(xiàn)它。你也許好奇當(dāng)內(nèi)容改變時發(fā)生了什么。上述緩存機(jī)制能夠在滿足某些條件(如發(fā)布新內(nèi)容)時處理和重新生成緩存。3、避免內(nèi)存泄漏
作為一種高級語言,JS 負(fù)責(zé)幾個低級別的管理,比如內(nèi)存管理。對于大多數(shù)編程語言來說,垃圾回收是一個常見的過程。通俗地說,垃圾回收就是簡單地收集和釋放,那些已經(jīng)分配給對象,但目前又不被程序任一部分使用的內(nèi)存。在像 C 這樣的編程語言中,開發(fā)者必須使用 malloc() 和 dealloc() 函數(shù)來處理內(nèi)存分配和回收。盡管垃圾回收是 JavaScript 自動執(zhí)行的,但在某些情況下,它可能并不完美。在 JavaScript ES6 中,Map 和 Set 與它們的“weaker”兄弟元素一起被引入?!皐eaker”對應(yīng)著 WeakMap 和 WeakSet,持有的是每個鍵對象的“弱引用”。它們允許對未引用的值進(jìn)行垃圾收集,從而防止內(nèi)存泄漏。了解更多關(guān)于 WeakMaps 的信息。4、盡早跳出循環(huán) Try to Break Out of Loops Early
執(zhí)行循環(huán)在代碼量大的循環(huán)中肯定會消耗大量寶貴的時間,這就是為什么要盡早打破循環(huán)的原因。你可以使用 break 關(guān)鍵字和continue 關(guān)鍵字跳出循環(huán)。編寫最有效的代碼是開發(fā)者們的責(zé)任。在下面的例子中,如果你不在循環(huán)中使用 break ,你的代碼將運(yùn)行循環(huán) 1000000000 次,顯然是超出負(fù)荷的。let arr = new Array(1000000000).fill('----');arr[970] = 'found';for (let i = 0; i < arr.length; i++) { if (arr[i] === 'found') { console.log("Found"); break; }}
在下面的例子中,當(dāng)不滿足條件時如果你不使用 continue,那么將執(zhí)行函數(shù) 1000000000 次。而我們只處理了位于偶數(shù)位置的數(shù)組元素,就將循環(huán)執(zhí)行減少了近一半。let arr = new Array(1000000000).fill('----');arr[970] = 'found';for (let i = 0; i < arr.length; i++) { if(i%2!=0){ continue; }; process(arr[i]);}
你可以在 這里 了解更多關(guān)于循環(huán)和性能。5、最小化變量的計算次數(shù)
要減少計算變量的次數(shù),可以使用閉包。JavaScript 中的閉包允許你從內(nèi)部函數(shù)訪問外部函數(shù)作用域。每次創(chuàng)建一個函數(shù)時都會創(chuàng)建閉包——但不調(diào)用。內(nèi)部函數(shù)可以訪問外部作用域的變量,即使外部函數(shù)已經(jīng)調(diào)用結(jié)束。讓我們看兩個例子,看看這是怎么回事。這些例子的靈感來自 Bret 的博客。function findCustomerCity(name) { const texasCustomers = ['John', 'Ludwig', 'Kate']; const californiaCustomers = ['Wade', 'Lucie','Kylie'];
return texasCustomers.includes(name) ? 'Texas' : californiaCustomers.includes(name) ? 'California' : 'Unknown';};
如果我們多次調(diào)用上述函數(shù),每次都會創(chuàng)建一個新對象。對于每個調(diào)用,不會將內(nèi)存重新分配給變量 texasCustometrs 和 californiaCustomers。通過使用帶有閉包的解決方案,我們只能實例化變量一次。讓我們看看下面的例子。function findCustomerCity() { const texasCustomers = ['John', 'Ludwig', 'Kate']; const californiaCustomers = ['Wade', 'Lucie','Kylie'];
return name => texasCustomers.includes(name) ? 'Texas' : californiaCustomers.includes(name) ? 'California' : 'Unknown';};
let cityOfCustomer = findCustomerCity();
cityOfCustomer('John');cityOfCustomer('Wade');cityOfCustomer('Max');
上述例子中,在閉包的幫助下,返回給變量 cityOfCustomer 的內(nèi)部函數(shù)可以訪問外部函數(shù) findCustomerCity() 的常量。并且當(dāng)調(diào)用內(nèi)部函數(shù)并傳參 name 時,不需要再次實例化這些常量。如果想要對閉包有更多了解,我建議你瀏覽Prashant的這篇博客。6、最小化 DOM 的訪問
與其他 JavaScript 語句相比,訪問 DOM 要慢一些。如果你要操作 DOM,從而觸發(fā)重繪布局,那么操作會變得相當(dāng)緩慢。要減少訪問 DOM 元素的次數(shù),請訪問它一次,并將其作為局部變量使用。當(dāng)需求完成時,確保通過將變量設(shè)置為 null 來刪除該變量的值。這將防止內(nèi)存泄漏,因為它允許垃圾回收。7、壓縮文件
通過使用諸如 Gzip 之類的壓縮方法,可以減小 JavaScript 文件的大小。這些較小的文件將提升網(wǎng)站性能,因為瀏覽器只需要下載較小的資源。這些壓縮可以減少多達(dá) 80% 的文件大小。在這里了解更多關(guān)于 壓縮。8、縮小你的最終代碼
有些人認(rèn)為縮小和壓縮是一樣的。但卻相反,它們是不同的。在壓縮中,使用特殊的算法來改變輸出文件的大小。但在縮小中,需要刪除 JavaScript 文件中的注釋和額外的空格。這個過程可以在網(wǎng)上找到的許多工具和軟件包的幫助下完成??s小已經(jīng)成為頁面優(yōu)化的標(biāo)準(zhǔn)實踐和前端優(yōu)化的主要組成部分。縮小可以減少你的文件大小高達(dá) 60%。在這里了解更多關(guān)于 縮小。9、使用節(jié)流 throttle 和防抖 debounce
通過使用這兩種技術(shù),我們可以嚴(yán)格執(zhí)行代碼需要處理事件的次數(shù)。節(jié)流是指函數(shù)在指定時間內(nèi)被調(diào)用的最大次數(shù)。例如,“最多每 1000 毫秒執(zhí)行一次 onkeyup 事件函數(shù)”。這意味著如果你每秒輸入 20 個鍵,該事件將每秒只觸發(fā)一次。這將減少代碼的加載。另一方面,防抖是指函數(shù)在上次觸發(fā)后再次觸發(fā)要間隔的最短時間。換句話說,“僅當(dāng)經(jīng)過 600 毫秒而沒有調(diào)用該函數(shù)時才執(zhí)行該函數(shù)”。這將意味著,你的函數(shù)將不會被調(diào)用,直到 600 毫秒后,最后一次執(zhí)行相同的函數(shù)。要了解更多關(guān)于節(jié)流和防抖的知識,這里有一個快速閱讀。你可以實現(xiàn)自己的防抖和節(jié)流函數(shù),也可以從 Lodash 和 Underscore 等庫導(dǎo)入它們。10、避免使用 delete 關(guān)鍵字
delete 關(guān)鍵字用于從對象中刪除屬性。關(guān)于這個 delete 關(guān)鍵字的性能,已經(jīng)有一些爭議。你可以在 此處 和 [此處](stackoverflow.com/questions/4… propertieses-in-js-in-v8/44008788) 中查看它們。這個問題有望在未來的更新中得到解決。As an alternative, you can simply to set the unwanted property as undefined.
另一種選擇是,你可以直接將將不想要的屬性設(shè)置為 undefined。const object = {name:"Jane Doe", age:43};object.age = undefined;
你還可以使用 Map 對象,因為根據(jù) Bret,Map 的 delete 方法被認(rèn)為更快。11、使用異步代碼防止線程阻塞
你應(yīng)該知道 JavaScript 是同步的,也是單線程的。但是在某些情況下,可能會花費(fèi)大量的時間來執(zhí)行一段代碼。在本質(zhì)上同步意味著,這段代碼將阻止其他代碼語句的運(yùn)行,直到它完成執(zhí)行,這會降低代碼的整體性能。但其實,我們可以通過實現(xiàn)異步代碼來避免這種情況。異步代碼以前是以回調(diào)的形式編寫的,但是在 ES6 中引入了一種處理異步代碼的新風(fēng)格。這種新風(fēng)格被稱為 promises。你可以在 MDN 的官方文檔 中了解更多關(guān)于回調(diào)和 promises 的信息。JavaScript默認(rèn)是同步的,也是單線程的。為什么在單一線程上運(yùn)行,還能運(yùn)行異步代碼?這是很多人感到困惑的地方。這要歸功于瀏覽器外殼下運(yùn)行的 JavaScript 引擎。JavaScript 引擎是執(zhí)行 JavaScript 代碼的計算機(jī)程序或解釋器。JavaScript 引擎可以用多種語言編寫。例如,支持 Chrome 瀏覽器的 V8 引擎是用 c++ 編寫的,而支持 Firefox 瀏覽器的 SpiderMonkey 引擎是用 C 和 c++ 編寫的。這些 JavaScript 引擎可以在后臺處理任務(wù)。根據(jù) Brian,調(diào)用棧識別 Web API 的函數(shù),并將它們交給瀏覽器處理。一旦瀏覽器處理完成這些任務(wù),它們將返回并作為回調(diào)推到堆棧上。你有時可能想知道,Node.js 在沒有瀏覽器幫助的情況下是如何運(yùn)行的。事實上,為 Chrome 提供動力的 V8 引擎同樣也為 Node.js 提供動力。下面是一篇由 Salil 撰寫的非常棒的博客文章:Node.js真的是單線程嗎,它解釋了節(jié)點生態(tài)系統(tǒng)上的這個過程。12、使用代碼分割
如果你有使用 Google Light House 的經(jīng)驗,你就會熟悉一個叫做“first contentful paint”的度量。它是 Lighthouse 報告的性能部分跟蹤的六個指標(biāo)之一。First Contentful Paint(FCP)測量用戶導(dǎo)航到頁面后瀏覽器渲染 DOM 第一個內(nèi)容所花費(fèi)的時間。頁面上的圖像、非白色 獲得更高 FCP 分?jǐn)?shù)的最好方法之一是使用代碼分割。代碼分割是一種在開始時只向用戶發(fā)送必要模塊的技術(shù)。減少最初傳輸?shù)挠行?nèi)容的大小,會顯著地影響 FCP 得分。流行的模塊打包工具(如 webpack)提供了代碼分割功能。你可以在原生 ES 模塊的幫助下,加載各個模塊。你可以閱讀更多關(guān)于原生 ES 模塊的 詳細(xì)信息。13、使用異步 async 和延遲 defer
在現(xiàn)代網(wǎng)站中,腳本比 HTML 更密集,它們的尺寸更大,消耗更多的處理時間。默認(rèn)情況下,瀏覽器必須等待腳本下載、執(zhí)行,然后處理頁面的其余部分。