JavaScript 是一種通過解釋執行的高級編程語言,同時也是一門動態、弱類型的直譯腳本語言,適合面向對象(基于原型)和函數式的編程風格。
  直譯語言可以直接在解釋器中運行,而與直譯語言相對應的編譯語言(例如 C++),要先將代碼編譯為機器碼,然后才能運行。
  不過直譯語言有一個弱點,就是如果有一條不能運行,那么下面的也不能運行了。
  JavaScript 主要運行在一個內置 JavaScript 解釋器的客戶端中(例如 Web 瀏覽器),能夠處理復雜的計算,操控文檔的內容、樣式和行為。
  能在客戶端完成的操作(例如輸入驗證、日期計算等)盡量都由 JavaScript 完成,這樣就能減少與服務器的通信,降低服務器的負載。
  JavaScript 作為現今前端開發最為核心的一部分,處理的不好就很容易影響網頁性能和用戶體驗。
  因此有必要了解一些 JavaScript 的代碼優化。本文所用的示例代碼已上傳至[Github](https://github.com/pwstrick/pe)。
## 一、代碼優化
  完整的JavaScript由3部分組成,如下所列:
* ECMAScript,定義了該語言的語法和語義。
* DOM(Document Object Model)即文檔對象模型,處理文檔內容的編程接口。
* BOM(Browser Object Model)即瀏覽器對象模型,獨立于內容與瀏覽器進行交互的接口。
  對其優化,也會圍繞這 3 部分展開。
**1)相等運算符**
  相等(==)和全等(===)這兩個運算符都用來判斷兩個操作數是否相等,但它們之間有一個最大的區別。
  就是“==”允許在比較中進行類型轉換,而“===”禁止類型轉換。
  各種類型之間進行相等比較時,執行的類型轉換是不同的,在 ECMAScript 5 中已經定義了具體的轉換規則。
  下表將規則以表格的形式展示,第一列表示左邊操作數“X”的類型,第一行表示右邊操作數“Y”的類型。
  在表格中,Number() 函數用簡寫 N() 表示,ToPrimitive() 函數用簡寫 TP() 表示。
| X == Y | 數字 | 字符串 | 布爾值 | null | undefined | 對象 |
| --- | --- | --- | --- | --- | --- | --- |
| 數字 | | N(Y) | N(Y) | 不等 | 不等 | TP(Y) |
| 字符串 | N(X) | | N(Y) | 不等 | 不等 | TP(Y) |
| 布爾值 | N(X) | N(X) | | N(X) | N(X) | N(X) |
| null | 不等 | 不等 | N(Y) | 相等 | 相等 | TP(Y) |
| undefined | 不等 | 不等 | N(Y) | 相等 | 相等 | TP(Y) |
| 對象 | TP(X) | TP(X) | N(Y) | TP(X) | TP(X) |
  當判斷對象和非對象是否相等時,會先讓對象執行 ToPrimitive 抽象操作,再進行相等判斷。
  ToPrimitive 抽象操作就是先檢查是否有 valueOf() 方法,如果有并且返回基本類型的值,就用它的返回值;如果沒有就改用 toString() 方法,再用它的返回值。
  由于相等會進行隱式的類型轉換,因此會出現很多不確定性,[ESLint](https://eslint.org/)的規則會建議將其替換成全等。
  順帶說一句,弱類型有著代碼簡潔、靈活性高等優勢,但它的可讀性差、不夠嚴謹等劣勢也非常突出。
  在編寫有點規模的項目時,推薦使用強類型的[TypeScript](https://www.typescriptlang.org/zh/),以免發生不可預測的錯誤。
**2)位運算**
  在內存中,數字都是按二進制存儲的,位運算就是直接對更低層級的二進制位進行操作。
  由于位運算不需要轉成十進制,因此處理速度非常快。
  常見的運算符包括按位與(&)、按位或(|)、按位異或(^)、按位非(~)、左移(>)等。
  位運算常用于取代純數學操作,例如對 2 取模(digit%2)判斷偶數與奇數、數字交換,如下所示。
~~~
if (digit & 1) {
// 奇數(odd)
} else {
// 偶數(even)
}
// 數字交換
a = a^b;
b = b^a;
a = a^b;
~~~
  位掩碼技術是使用單個數字的每一位來判斷選項是否成立。注意,每項值都是 2 的冪,如下所示。
~~~
const OPTION_A = 1, OPTION_B = 2, OPTION_C = 4, OPTION_D = 8, OPTION_E = 16;
//用按位或運算創建一個數字來包含多個設置選項
const options = OPTION_A | OPTION_C | OPTION_D;
//接下來可以用按位與操作來判斷給定的選項是否可用
//選項A是否在列表中
if(options & OPTION_A) {
//...
}
~~~
  用按位左移(>),例如 digit \* 2 可以替換成 digit << 2。
  位運算的應用還有很多,此處只做拋磚引玉。
  順便說一句,推薦在 JavaScript 中使用原生方法,例如數學計算就調用[Math](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math)中的方法。
  當年,jQuery 為了抹平瀏覽器之間的 DOM 查詢,自研了一款 CSS 選擇器引擎([sizzle](https://github.com/jquery/sizzle)),源碼在 2500 多行。
  而現在,瀏覽器內置了[querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)和[querySelectorAll()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)選擇器方法,若使用基礎功能,完全可以替代 sizzle。
  瀏覽器和規范的不斷發展,使得原生方法也越來越完善,在性能方面也在越做越好。
**3)存儲**
  在早期,網頁的數據存儲都是通過 Cookie 完成的,不過 Cookie 最初的作用是保持 HTTP 請求的狀態。
  隨著網頁交互的復雜度越來越高,它的許多缺陷也暴露了出來,例如:
1. 每個 HTTP 請求都會帶上 Cookie 信息,增加了 HTTP 首部的內容,如果網站訪問量巨大,將會很影響帶寬。
2. Cookie 不適合存儲一些隱私敏感信息(例如用戶名、密碼等),因為 Cookie 會在網絡中傳遞,很容易被劫持,劫持后可以偽造請求,執行一些危險操作(例如刪除或修改信息)。
3. Cookie 的大小被瀏覽器限制在 4KB 左右,只能存儲一點簡單的信息,不能應對復雜的存儲需求,例如緩存表單信息、數據同步等。
  為了解決這些問題,HTML5 引入了 Web 存儲:本地存儲([local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage))和會話存儲([session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage))。
  它們的存儲容量,一般在 2.5M 到 10M 之間(大部分是 5M),在 Chrome DevTools 的 Application 面板可以查看當前網頁所存儲的內容。
  它不會作為請求報文中的額外信息傳遞給服務器,因此比較容易實現網頁或應用的離線化。
  若存儲的數據比較大,那么就需要[IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API),這是一個嵌入在瀏覽器中的事務數據庫,但不像關系型數據庫使用固定列。
  而是一種基于 JavaScript 對象的數據庫,類似于 NoSQL。
**4)虛擬 DOM**
  在瀏覽器中,DOM 和 JavaScript 是兩個獨立的模塊,在 JavaScript 中訪問 DOM 就好比穿過要收費的跨海大橋。
  訪問次數越多,過橋費越貴,最直接的優化方法就是減少過橋次數,虛擬 DOM 的優化思路與此類似。
  所謂虛擬DOM(Virtual DOM),其實就是構建在真實 DOM 之上的一層抽象。
  它先將 DOM 元素映射成內存中的 JavaScript 對象(即通過 React.createElement() 得到的 React 元素),形成一棵 JavaScript 對象樹。
  再用算法找出新舊虛擬 DOM 之間的差異,隨后只更新真實 DOM 中需要變化的節點,而不是將整棵 DOM 樹重新渲染一遍,過程參考下圖。
:-: 
  虛擬 DOM 還有一大亮點,那就是將它與其他渲染器配合能夠集成到指定的終端。
  例如將 React 元素映射成對應的原生控件,既可以用 react-dom 在 Web 端渲染,還可以使用 react-native 在手機端渲染。
**5)Service Worker**
  Service Worker 是瀏覽器和服務器之間的代理服務器,可攔截網站所有請求,根據自定義條件采取適當的動作,例如讀取響應緩存、將請求轉發給服務器、更新緩存的資源等。
:-: 
  Service Worker 運行在主線程之外,提供更細粒度的緩存管理,雖然無法訪問 DOM,但增加了離線緩存的能力。
  當前,正在使用 Service Worker 技術的網站有[google](https://www.google.com.hk/)、[微博](https://m.weibo.cn/)等。
  每個 Service Worker 都有一個獨立于 Web 頁面的生命周期,如下圖所示,其中 Cache API 是指[CacheStorage](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage),可對緩存進行增刪改查。
:-: 
  在主線程中注冊 Service Worker 后,觸發 install 事件,安裝 Service Worker 并且解析和執行 Service Worker 文件(常以 sw.js 命名)。
  當 install 事件回調成功時,觸發 activate 事件,開始激活 Service Worker,然后監聽指定作用域中頁面的資源請求,監聽邏輯記錄在 fetch 事件中。
  接下來用一個例子來演示 Service Worker 的使用,首先在 load 事件中注冊 Service Worker,如下所示。
  因為注冊的腳本是運行在主線程中的,為了避免影響首屏渲染,遂將其移動到 load 事件中。
~~~
window.addEventListener("load", () => {
// 注冊一個 sw.js,通知瀏覽器為該頁面分配一塊內存,然后就會進入安裝階段
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("service worker 注冊成功");
})
.catch((err) => {
console.log("servcie worker 注冊失敗");
});
});
~~~
  sw.js 是一個 Service Worker 文件,將其放置在根目錄中,這樣就能監控整個項目的頁面。若放置在其他位置,則需要配置 scope 參數,如下所示。
~~~
navigator.serviceWorker.register("/assets/js/sw.js", { scope: '/' })
~~~
  但是在訪問 sw.js 時會報錯(如下所示),需要給它增加 Service-Worker-Allowed 首部,然后才能在整個域中工作,默認只能在其所在的目錄和子目錄中工作。
~~~
The path of the provided scope ('/') is not under the max scope allowed ('/assets/js/').
Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope
~~~
  在 sw.js 中注冊了 install 和 fetch 事件(如下所示),都是比較精簡的代碼,caches 就是 CacheStorage,提供了 open()、match()、addAll() 等方法。
  在 then() 方法中,當存在 response 參數時,就直接將其作為響應返回,它其實就是一個[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)實例。
~~~
// 安裝
self.addEventListener("install", e => {
e.waitUntil(
caches.open("resource").then(cache => {
cache.addAll(["/assets/js/demo.js"]).then(() => {
console.log("資源都已獲取并緩存");
}).catch(error => {
console.log('緩存失敗:', error);
});
})
);
});
// 攔截
self.addEventListener("fetch", e => {
e.respondWith(
caches.match(e.request).then(response => {
// 響應緩存
if (response) {
console.log("fetch cache");
return response;
}
return fetch(e.request);
})
);
});
~~~
  運行網頁后,在 Chrome DevTools 的 Application 面板中的 Service Workers 菜單中,就能看到注冊成功的 Service Worker,如下圖所示。
:-: 
  在 Cache Storage 菜單中,就能看到添加的緩存資源,如下圖所示。
:-: 
  關閉頁面,再次打開,查看 demo.js 的網絡請求,size 那列顯示的就不是文件尺寸,而是 Service Worker,如下圖所示。
  worker.html 沒有進行緩存,所以將請求轉發給服務器。
:-: 
  2022 年 HTTP Archive 預估網站中 Service Worker 的[使用率](https://almanac.httparchive.org/en/2022/pwa#service-worker-usage)在桌面端和移動端分別有 1.63% 和 1.81%,從數據中可知,使用率并不高。
  雖然 Service Worker 的兼容性除了 IE 之外,主流的瀏覽器都已兼容,但是在實際使用中,還是要慎重。
  首先得做到引入 Service Worker 后帶來某些方面的性能提升,但不能讓另一些方面的性能降低,需要考慮成本和收益。
  其次網頁的運行不能依賴 Service Worker,它的作用只是錦上添花,而不是業務必須的。
  對于不能運行 Service Worker 的瀏覽器,也要確保網頁的呈現和交互都是正常的。
## 二、函數優化
  函數(function)就是一段可重復使用的代碼塊,用于完成特定的功能,能被執行任意多次。
  它是一個 Function 類型的對象,擁有自己的屬性和方法。
  JavaScript 是一門函數式編程語言,它的函數既是語法也是值,能作為參數傳遞給一個函數,也能作為一個函數的結果返回。
  與函數相關的優化有許多,本文選取其中的 3 種進行講解。
**1)記憶函數**
  記憶函數是指能夠緩存先前計算結果的函數,避免重復執行不必要的復雜計算,是一種用空間換時間的編程技巧。
  具體的實施可以有多種寫法,例如創建一個緩存對象,每次將計算條件作為對象的屬性名,計算結果作為對象的屬性值。
  下面的代碼用于判斷某個數是否是質數,在每次計算完成后,就將計算結果緩存到函數的自有屬性 digits 內。
  質數又叫素數,是指一個大于1的自然數,除了1和它本身外,不能被其它自然數整除的數。
~~~
function prime(number) {
if (!prime.digits) {
prime.digits = {}; //緩存對象
}
if (prime.digits[number] !== undefined) {
return prime.digits[number];
}
var isPrime = false;
for (var i = 2; i < number; i++) {
if (number % i == 0) {
isPrime = false;
break;
}
}
if (i == number) {
isPrime = true;
}
return (prime.digits[number] = isPrime);
}
prime(87);
prime(17);
console.log(prime.digits[87]); //false
console.log(prime.digits[17]); //true
~~~
**2)惰性模式**
  惰性模式用于減少每次代碼執行時的重復性分支判斷,通過對對象重定義來屏蔽原對象中的分支判斷。
  惰性模式按觸發時機可分為兩種,第一種是在文件加載后立即執行對象方法來重定義。
  在早期為了統一 IE 和其他瀏覽器之間注冊事件的語法,通常會設計一個兼容性函數,下面示例采用的是第一種惰性模式。
~~~
var A = {};
A.on = (function (dom, type, fn) {
if (dom.addEventListener) {
return function (dom, type, fn) {
dom.addEventListener(type, fn, false);
};
} else if (dom.attachEvent) {
return function (dom, type, fn) {
dom.attachEvent("on" + type, fn);
};
} else {
return function (dom, type, fn) {
dom["on" + type] = fn;
};
}
})(document);
~~~
  第二種是當第一次使用方法對象時來重定義,同樣以注冊事件為例,采用第二種惰性模式,如下所示。
~~~
A.on = function (dom, type, fn) {
if (dom.addEventListener) {
A.on = function (dom, type, fn) {
dom.addEventListener(type, fn, false);
};
} else if (dom.attachEvent) {
A.on = function (dom, type, fn) {
dom.attachEvent("on" + type, fn);
};
} else {
A.on = function (dom, type, fn) {
dom["on" + type] = fn;
};
}
//執行重定義on方法
A.on(dom, type, fn);
};
~~~
**3)節流和防抖**
  節流(throttle)是指預先設定一個執行周期,當調用動作的時刻大于等于執行周期則執行該動作,然后進入下一個新周期,示例如下。
~~~
function throttle(fn, wait) {
let start = 0;
return () => {
const now = +new Date();
if (now - start > wait) {
fn();
start = now;
}
};
}
~~~
  適用于 mousemove、resize 和 scroll 事件。之前做過一個內部系統的表格,希望在左右滾動時能將第一列固定在最左邊。
  為了讓操作能更流暢,在 scroll 事件中使用了節流技術,如下圖所示。
:-: 
  值得一提的是,在未來會有一個[scrollend](https://developer.chrome.com/blog/scrollend-a-new-javascript-event/)事件,專門監聽滾動的結束,待到瀏覽器支持,就可以不用再大費周章的節流了。
  防抖(debounce)是指當調用動作 n 毫秒后,才會執行該動作,若在這 n 毫秒內又調用此動作則將重新計算執行時間,示例如下。
~~~
function debounce(fn, wait) {
let start = null;
return () => {
clearTimeout(start);
start = setTimeout(fn, wait);
};
}
~~~
  適用于文本輸入的 keydown 和 keyup 兩個事件,常應用于文本框自動補全。
  節流與防抖最大的不同的地方就是在計算最后執行時間的方式上,著名的開源工具庫[underscore](https://underscorejs.org/)中有內置了兩個方法。
**4)定時器**
  2023-11-20 為了不讓一些復雜的 JavaScript 任務阻塞主線程,就需要將其讓出主線程的控制權,即停止執行,可以通過定時器實現。
  當函數運行時間太長時,可以把它拆分成一系列更小的步驟(任務分解),把每個獨立的方法放到定時器中回調(異步執行)。
  簡單的示例如下,其中 arguments.callee 是指當前正在執行的函數,但是注意,arguments.callee 已被棄用。
~~~
const tasks = [openDocumnet, writeText, closeDocument, updateUI];
setTimeout(function() {
//執行下一個任務
const task = tasks.shift();
task();
//檢查是否還有其他任務
if (tasks.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
~~~
## 三、內存優化
  JavaScript 并沒有提供像 C 語言那樣底層的[內存管理](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management)函數,例如 malloc() 和 free()。
  而是在創建變量(對象,字符串等)時自動分配內存,并且在不使用它們時自動釋放,釋放過程稱為垃圾回收。
  雖然垃圾回收器很智能,但是若處理不當,還是有可能發生內存泄漏的。
**1)垃圾回收器**
  Node.js 是一個基于 V8 引擎的 JavaScript 運行時環境,而 Node.js 中的垃圾回收器(GC)其實就是 V8 的垃圾回收器。
  這么多年來,V8 的[垃圾回收器](https://www.cnblogs.com/strick/p/16300059.html)(Garbage Collector,簡寫GC)從一個全停頓(Stop-The-World),慢慢演變成了一個更加并行,并發和增量的垃圾回收器。
  本節內容參考了 V8 團隊分享的文章:[Trash talk: the Orinoco garbage collector](https://v8.dev/blog/trash-talk)。
  在垃圾回收中有一個重要術語:代際假說(The Generational Hypothesis),這個假說不僅僅適用于 JavaScript,同樣適用于大多數的動態語言,Java、Python 等。
  代際假說表明很多對象在內存中存在的時間很短,即從垃圾回收的角度來看,很多對象在分配內存空間后,很快就變得不可訪問。
  在 V8 中,會將堆分為兩塊不同的區域:新生代(Young Generation)和老生代(Old Generation)。
  新生代中存放的是生存時間短的對象,大小在 1~ 8M之間;老生代中存放的生存時間久的對象。
  對于這兩塊區域,V8 會使用兩個不同的垃圾回收器:
* 副垃圾回收器(Scavenger)主要負責新生代的垃圾回收。如果經過垃圾回收后,對象還存活的話,就會從新生代移動到老生代。
* 主垃圾回收器(Full Mark-Compact)主要負責老生代的垃圾回收。
  無論哪種垃圾回收器,都會有一套共同的工作流程,定期去做些任務:
1. 標記活動對象和非活動對象,前者是還在使用的對象,后者是可以進行垃圾回收的對象。
2. 回收或者重用被非活動對象占據的內存,就是在標記完成后,統一清理那些被標記為可回收的對象。
3. 整理內存碎片(不連續的內存空間),這一步是可選的,因為有的垃圾回收器不會產生內存碎片。
  V8 為新生代采用 Scavenge 算法,會將內存空間劃分成兩個區域:對象區域(From-Space)和空閑區域(To-Space)。
  副垃圾回收器在清理新生代時:
* 會先將所有的活動對象移動(evacuate)到連續的一塊空閑內存中(這樣能避免內存碎片)。
* 然后將兩塊內存空間互換,即把 To-Space 變成 From-Space。
* 接著為了新生代的內存空間不被耗盡,對于兩次垃圾回收后還活動的對象,會把它們移動到老生代,而不是 To-Space。
* 最后是更新引用已移動的原始對象的指針。上述幾步都是交錯進行,而不是在不同階段執行。
  主垃圾回收器負責老生代的清理,而在老生代中,除了新生代中晉升的對象之外,還有一些大的對象也會被分配到此處。
  主垃圾回收器采用了 Mark-Sweep(標記清除)和 Mark-Compact(標記整理)兩種算法,其中涉及三個階段:標記(marking),清除(sweeping)和整理(compacting)。
1. 在標記階段,會從一組根元素開始,遞歸遍歷這組根元素。其中根元素包括執行堆棧和全局對象,瀏覽器環境下的全局對象是 window,Node.js 環境下是 global。
2. 在清除階段,會將非活動對象占用的內存空間添加到一個叫空閑列表的數據結構中。
3. 在整理階段,會讓所有活動的對象都向一端移動,然后直接清理掉那一端邊界以外的內存。
**2)內存泄漏**
  內存泄漏(memory leak)是計算機科學中的一種資源泄漏,主因是程序的內存管理失當,因而失去對一段已分配內存的控制。
  程序繼續占用已不再使用的內存空間,或是存儲器所存儲對象無法透過執行代碼而訪問,令內存資源空耗,簡單地說就是內存無法被垃圾回收。
  下面會羅列幾種內存泄漏的場景:
  第一種是全局變量,它不會被自動回收,而是會常駐在內存中,因為它總能被垃圾回收器訪問到。
  第二種是閉包(closure),當一個函數能夠訪問和操作另一個函數作用域中的變量時,就會構成一個閉包,即使另一個函數已經執行結束,但其變量仍然會被存儲在內存中。
  如果引用閉包的函數是一個全局變量或某個可以從根元素追溯到的對象,那么就不會被回收,以后不再使用的話,就會造成內存泄漏。
  第三種是事件監聽,如果對某個目標重復注冊同一個事件,并且沒有移除,那么就會造成內存泄漏。
  第四種是緩存,當緩存中的對象屬性越來越多時,長期存活的概率就越大,垃圾回收器也不會清理,部分不需要的對象就會造成內存泄漏。
  在實際開發中,曾遇到過第三種內存泄漏,如下圖所示,內存一直在升。
:-: 
  要分析內存泄漏,首先需要下載堆快照(\*.heapsnapshot文件),然后在 Chrome DevTools 的 Memory 面板中載入,可以看到下圖內容。
:-: 
  在將堆快照做縝密的分析后發現,請求的 ma.gif 地址中的變量不會釋放,其內容如下圖所示。
:-: 
  仔細查看代碼后,發現在為外部的 queue 對象反復注冊一個 error 事件,如下所示。
~~~
import queue from "../util/queue";
router.get("/ma.gif", async (ctx) => {
queue.on('error', function( err ) {
logger.trace('handleMonitor queue error', err);
});
});
~~~
  將這段代碼去除后,內存就恢復了平穩,沒有出現暴增的情況,如下圖所示。
:-: 
## 總結
  本文首先分析了相等和全等兩個運算符的差異,然后再介紹了幾種位運算的巧妙用法。
  再介紹了目前主流的幾種 Web 存儲,以及虛擬 DOM 解決的問題,并且講解了 Service Worker 管理緩存的過程。
  在第二節中主要分析了三種函數優化,分別是記憶函數、惰性模式、節流和防抖。
  其中節流和防抖在實際項目中有著廣泛的應用,很多知名的庫也都內置這兩個函數。
  最后講解了 V8 對內存的管理,包括垃圾回收,以及用一個實例演示了內存泄漏后簡單的排查過程。
*****
> 原文出處:
[博客園-前端性能精進](https://www.cnblogs.com/strick/category/2267607.html)
[知乎專欄-前端性能精進](https://www.zhihu.com/column/c_1610941255021780992)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎閱讀。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020