<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【優(yōu)化】1032- 騰訊課堂小程序性能極致優(yōu)化——綜合篇

          共 8129字,需瀏覽 17分鐘

           ·

          2021-08-01 11:00

          導(dǎo)語 | 如果你的小程序也遇到了性能問題,我們的實(shí)踐經(jīng)驗(yàn)也許可以給到你啟發(fā),我們從小程序的啟動、加載到交互都進(jìn)行了探索。順便說一句,這篇文章在騰訊內(nèi)部曾被小程序技術(shù)總監(jiān)打賞。

          1. 緣起

          事情,要從一個周末愜意的下午開始說起……

          那天,手機(jī)突然被喚醒,彈出多條微信消息。原來是這周末正在校園推廣的活動群發(fā)來的,想起之前大家有條不紊的開發(fā)進(jìn)度,和產(chǎn)品溝通的友好過程,應(yīng)該是活動反響不錯。

          現(xiàn)實(shí)是殘酷的:

          “我們的小程序打開慢成狗!”

          “這個 loading 加載的過程也太久了!”

          “滾動加載有點(diǎn)卡,而且很容易報錯……”

          看到的是最直接的控訴。

          看到用戶的錄屏,這幾個問題確實(shí)是有出現(xiàn),所以我們還是需要對小程序進(jìn)行一次主流程的性能優(yōu)化,三句控訴可以總結(jié)為3個點(diǎn):

          • 小程序啟動慢

          • 小程序請求慢

          • 小程序交互慢

          2.定位

          2.1. 啟動慢

          收到反饋后第一反應(yīng)是,用戶是不是網(wǎng)速太慢了,自己跑一遍,發(fā)現(xiàn)自己的手機(jī)跑起來都是沒問題的,灰常流暢,下意識的可能想錄個屏回復(fù)過去。

          不過有用戶錄屏在那,當(dāng)然不能這么草率,所以我們查了下管理后臺小程序在不同網(wǎng)絡(luò)下的大盤數(shù)據(jù):

          網(wǎng)絡(luò)啟動耗時
          總體3.6s
          WIFI3.5s
          4G3.9s
          2G/3G4.1s


          從統(tǒng)計看,總體3.7s的啟動耗時,網(wǎng)絡(luò)對于啟動耗時是會有影響的,但影響沒有很大,就算是2G-3G下跟大盤的數(shù)據(jù)對比也沒有慢很多,可見事情并不簡單。

          于是我們從另外一個維度來看一下大盤數(shù)據(jù):

          機(jī)型啟動耗時JS注入初次渲染
          總體3.6s0.29s 0.16s
          高端機(jī)2.9s0.19s0.06s
          中端機(jī)4.8s0.42s0.19s
          低端機(jī)7.9s0.72s0.43s

          從這里就可以看出問題來了,手機(jī)的性能對于小程序的啟動速度影響非常大,低端機(jī)相對高端機(jī)有2-3倍的差距,特別是渲染層甚至有5-6倍的差距,而且問題反饋的用戶所使用的手機(jī)也確實(shí)是中低端機(jī),但用戶使用什么手機(jī)我們也沒法控制,那這里有辦法去優(yōu)化嗎?

          針對這個問題,我們需要了解一下小程序的啟動過程,根據(jù)官方文檔的介紹,小程序的啟動可以分為下面幾個步驟:

          上圖描述了用戶點(diǎn)擊小程序開始到頁面開始請求數(shù)據(jù)的一個完整的冷啟動過程,而小程序初始化的過程(信息準(zhǔn)備、環(huán)境準(zhǔn)備)占用了比較長的時間,但這部分的工作是由微信客戶端來完成,開發(fā)者無法干預(yù),所以我們只能聚焦于后續(xù)的步驟(下載代碼包、注入代碼包、初次渲染)。

          根據(jù)官方文檔的介紹,這一部分的可優(yōu)化手段有:

          1. 減小代碼包體積

          2. 降低代碼復(fù)雜度

          3. 減少同步代碼接口調(diào)用

          4. 降低頁面結(jié)構(gòu)復(fù)雜度

          5. 減少自定義組件數(shù)量

          后面4條在技術(shù)上沒有特別好的限制手段,需要我們在 Code Review 的時候?qū)?fù)雜度和開銷大的接口調(diào)用進(jìn)行把關(guān),復(fù)雜度這里還可以借助如 CodeCC(騰訊內(nèi)部代碼檢查工具) 這類工具去進(jìn)行分析,減少自定義組件數(shù)量,這個是比較難以抉擇的,需要在代碼可讀性、可復(fù)用性之間做個取舍,不是本次優(yōu)化的重點(diǎn)。

          所以我們就重點(diǎn)關(guān)注的代碼包體積問題,通過我們的 CI 記錄可以收集到我們的總包大小:

          可以看到主包體積達(dá)到1949.71KB,接近了2M的極限了,在通過依賴分析后,發(fā)現(xiàn)除了一些是未使用到的模塊和組件外,很大一部分內(nèi)容是靜態(tài)資源,同時我們也在官方文檔中看到這樣一句話:

          小程序代碼包在下載時會使用 ZSTD 算法進(jìn)行壓縮,這些資源文件會占用大量代碼包體積,并且通常難以進(jìn)一步被壓縮,對于下載耗時的影響比代碼文件大得多。

          所以我們要減小代碼包的體積,最直接的方法就是將非必要的資源去除掉:

          • 對靜態(tài)資源進(jìn)行優(yōu)化,將非必要的靜態(tài)資源文件上傳到CDN

          • 對小程序的組件進(jìn)行依賴分析,過濾掉未使用的組件

          同時我們還關(guān)注到,有一些分包特別小,但是由于是普通分包,在打開這些頁面的時候還需要先下載主包,這里在包下載耗時上其實(shí)是有一些浪費(fèi)的,比較典型的就是 WebView 頁面,他們往往只需要對參數(shù)進(jìn)行處理,對于主包的依賴不是很強(qiáng),所以這里還有一個可以優(yōu)化的點(diǎn):

          • 對獨(dú)立性比較強(qiáng)的頁面進(jìn)行獨(dú)立分包,盡可能的減少包下載耗時

          2.2. 請求慢

          我們通過日志查到這個用戶的首頁數(shù)據(jù)請求返回會到3-4s,請求慢在正常情況下會有這么兩種情況:

          • 并發(fā)量突增導(dǎo)致服務(wù)器響應(yīng)慢

          • 用戶網(wǎng)速較慢導(dǎo)致發(fā)送請求和接收請求變慢

          我們通過日志統(tǒng)計發(fā)現(xiàn)用戶的訪問時間端,請求量跟平時保持一致,看大盤請求耗時的統(tǒng)計,也沒有產(chǎn)生大的波動:

          所以基本可以排除是后臺的問題,雖然大盤的數(shù)據(jù)在500ms左右,但是當(dāng)用戶網(wǎng)絡(luò)不好的情況下,這一塊要怎么保證呢?

          答案當(dāng)然是做提前拉取,當(dāng)用戶冷啟動的時候,我們可以使用小程序官方提供的數(shù)據(jù)預(yù)拉取能力提前拉取,從小程序的啟動耗時看,完全可以 cover 掉我們的接口請求耗時,可以讓小程序啟動成功后就直接渲染頁面。

          在熱啟動的情況下,請求慢主要體現(xiàn)在用戶交互時發(fā)生的請求和頁面切換時發(fā)生的請求,交互的情況我們下一節(jié)在分析,這里主要看頁面切換,從我們的統(tǒng)計數(shù)據(jù)來看,頁面切換的耗時大概在400ms左右,而其中能夠利用的時間大概是50ms-100ms


          route時間


          利用頁面切換的這個時間提前對頁面的數(shù)據(jù)進(jìn)行加載,可以減少用戶感觀上的數(shù)據(jù)請求時間,同時在第一次請求之后可以根據(jù)一定的策略對頁面的數(shù)據(jù)進(jìn)行緩存,從而可以達(dá)到二次進(jìn)入頁面秒開的效果。

          總結(jié)來看,請求慢的優(yōu)化手段有下面幾個,而且理論上效果都會很顯著:

          • 冷啟動開啟數(shù)據(jù)預(yù)拉取

          • 頁面路由切換時提前拉取數(shù)據(jù)

          • 對數(shù)據(jù)進(jìn)行緩存

          2.3. 交互慢

          先說一下這里的交互慢具體指什么,我們收到用戶反饋的現(xiàn)象是:用戶首屏順利加載出來之后,后續(xù)滾動加載和一些按鈕點(diǎn)擊的響應(yīng)非常慢并且很容易報錯。收到這個反饋后定位了很久,講道理如果是因?yàn)橛脩艟W(wǎng)絡(luò)問題導(dǎo)致的請求很慢,應(yīng)該所有的請求都會很慢,但是用戶表現(xiàn)出來的現(xiàn)象是后續(xù)的加載以及交互很慢,反而首屏還算正常。

          通過日志查詢,我們發(fā)現(xiàn)這個用戶的請求報錯都是請求超時,為什么超時會集中在交互加載這里呢?定位了一段時間后我們發(fā)現(xiàn)一個用戶的報錯都集中在首屏加載之后就立馬下滑或者點(diǎn)擊,如果過了一段時間再點(diǎn)擊又不會報錯。

          發(fā)現(xiàn)這個現(xiàn)象后,我們想到了官方文檔關(guān)于網(wǎng)絡(luò)使用說明的一個限制:

          wx.request、wx.uploadFile、wx.downloadFile的最大并發(fā)限制是 10 個

          再結(jié)合我們對于 wx.request 的封裝,請求超時的計時器是從調(diào)用 wx.request 的時候就開始了,如果請求并發(fā)超過了限制,那么就很容易出現(xiàn)請求超時,而當(dāng)我們從第一個業(yè)務(wù)接口請求到數(shù)據(jù)后就會進(jìn)行一系列的數(shù)據(jù)上報,包括 pv、組件曝光、關(guān)鍵鏈路打點(diǎn)等等,所以我們利用 Whistle 的 resDelay 方法,將我們的上報請求都延遲5000ms返回,果然就復(fù)現(xiàn)了用戶反饋的那種情況。

          找到問題之后也就明確了需要優(yōu)化的方向:

          • 保障與用戶體驗(yàn)相關(guān)的業(yè)務(wù)請求正常發(fā)送

          交互慢還有別的原因嗎?在繼續(xù)挖掘性能瓶頸的過程中,發(fā)現(xiàn)騰訊課堂小程序的課程詳情頁內(nèi)容非常多,有5-6屏的高度,而用戶只會關(guān)心首屏是不是更快的呈現(xiàn)出來,但是我們原本的處理方式是比較粗暴的,拿到詳情頁的數(shù)據(jù)之后對數(shù)據(jù)進(jìn)行處理,格式化成整個頁面所需要的數(shù)據(jù)之后一次性調(diào)用 this.setData 來更新頁面,所以如果要提升首屏速度,這里需要做的就是:

          • 頁面分步渲染

          2.4. 優(yōu)化點(diǎn)歸總

          再歸總一下需要優(yōu)化的點(diǎn)和方向:

          1. 啟動慢主要從優(yōu)化代碼包上下手:

            • 對靜態(tài)資源進(jìn)行優(yōu)化,將非必要的靜態(tài)資源文件上傳到CDN

            • 對小程序的組件進(jìn)行依賴分析,過濾掉未使用的組件

            • 對獨(dú)立性比較強(qiáng)的頁面進(jìn)行獨(dú)立分包,減少主包下載耗時

          2. 請求慢主要從預(yù)加載和緩存下手:

            • 冷啟動開啟數(shù)據(jù)預(yù)拉取

            • 頁面路由切換時提前拉取數(shù)據(jù)

            • 對數(shù)據(jù)進(jìn)行緩存

          3. 交互慢需要從發(fā)起請求和頁面渲染下手:

            • 保障與用戶體驗(yàn)相關(guān)的業(yè)務(wù)請求正常發(fā)送

            • 頁面分步渲染

          3. 優(yōu)化

          3.1. 啟動優(yōu)化

          3.1.1. 獨(dú)立分包

          由于用戶反饋主要是因?yàn)樾@推廣活動來的,而活動頁面我們是通過 WebView 內(nèi)嵌 H5 來承載的,而 WebView 頁面的啟動過程和小程序原生頁面還不太一樣:



          實(shí)際上 WebView 頁面只需要完善登錄態(tài)傳遞的功能,對于主包的依賴不是很大,而且這部分頁面更大的性能問題需要在 h5 那邊來優(yōu)化,所以我們第一時間對其進(jìn)行了獨(dú)立分包處理。

          最終的優(yōu)化效果還不錯,因?yàn)閱舆^程中不需要下載主包,啟動性能提升了30%。

          3.1.2. 靜態(tài)資源上CDN

          我們小程序構(gòu)成主要是由原生頁面 + kbone 頁面組成的,kbone 是采用的官方的方案,通過 webpack 構(gòu)建,有很多單獨(dú)打包靜態(tài)資源的方案。而我們的原生頁面是使用 gulp 進(jìn)行構(gòu)建的,原來主要的功能是將源碼中的 ts 轉(zhuǎn)成 js,同時對 css 文件通過 postcss 轉(zhuǎn)成 wxss,由于 wxss 不支持引用相對路徑,所以在 wxss 中引用的圖片和字體都是轉(zhuǎn)成 base64 的,然后對其余的文件如 json、wxml 文件則直接復(fù)制到產(chǎn)物中去。

          這樣的處理方式比較粗暴,通過 postcss 將 background-image 所引用的本地圖片都轉(zhuǎn)成 base64,還會導(dǎo)致很多圖片在項(xiàng)目中占用了2倍的體積。


          CI流程-優(yōu)化前


          所以我們首先需要將源碼下的靜態(tài)資源匹配到并單獨(dú)構(gòu)建出來,并且為了規(guī)避同名文件的問題,需要對資源打個 hashtag,我們這里需要用到一個 gulp 插件gulp-rev,這個插件可以對基于資源的內(nèi)容進(jìn)行 hash。


          CI流程-優(yōu)化后


          將圖片上到 CDN 后,把 css、js、json、wxml 中的引用路徑替換成 CDN 地址,具體的替換邏輯如下。


          CDN流程


          3.1.3. 過濾未使用組件

          隨著業(yè)務(wù)的迭代,不可避免的會有一些組件被廢棄了但是難以察覺,通過我們團(tuán)隊開發(fā)的小程序腳手架 imweb-miniprogram-cli 對頁面所使用到的組件進(jìn)行分析,可以把項(xiàng)目中未使用到的組件給過濾出來,不會打包到最終的產(chǎn)物中,大致的思路如下:


          組件依賴


          app.json開始,拿到小程序所配置的所有頁面和分包,通過檢查 App、頁面、分包中所使用的自定義組件來進(jìn)行收集,并且遞歸檢查自定義組件所使用的組件,如果檢測到有未使用的組件,也會給到提示,非常友好:


          組件過濾


          可以看到在我們的項(xiàng)目中就發(fā)現(xiàn)了好幾個未使用到的組件。

          3.2. 請求優(yōu)化

          3.2.1. 數(shù)據(jù)預(yù)拉取

          數(shù)據(jù)預(yù)拉取需要在小程序的管理后臺開啟,數(shù)據(jù)來源可以選擇開發(fā)者服務(wù)器或者云開發(fā),選擇開發(fā)者服務(wù)器的話會有一些限制,如果是直接填寫 CGI 地址,就只能拉取一種數(shù)據(jù),不夠靈活,而如果再搭建一個服務(wù)去做預(yù)拉取涉及到的工作量又會很大,所以我們選擇的是云開發(fā)的方式,大致流程如下圖:


          數(shù)據(jù)預(yù)拉取-大概


          當(dāng)小程序啟動的時候,微信客戶端會根據(jù)配置去拉取指定的云函數(shù),在云函數(shù)中通過 cl5 調(diào)用業(yè)務(wù)后臺的服務(wù)拉取到需要的數(shù)據(jù),拉取到后客戶端會將數(shù)據(jù)緩存在本地,當(dāng)小程序啟動成功后,在業(yè)務(wù)代碼中調(diào)用wx.getBackgroundFetchData就可以拿到預(yù)拉取的數(shù)據(jù),如果緩存數(shù)據(jù)拉到的是所需要的數(shù)據(jù)則可以直接渲染,如果不是則降級到業(yè)務(wù)中再拉一次接口。

          在云函數(shù)中可以拿到本次小程序啟動的pathquery參數(shù),所以我們可以根據(jù)這兩個參數(shù)來判斷本次預(yù)拉取需要調(diào)用業(yè)務(wù)后臺的哪個服務(wù),從而達(dá)到從不同的頁面啟動小程序都可以通過一個云函數(shù)預(yù)拉取到所需要的數(shù)據(jù)。

          const preFetchMap = {
            'pages/index/index': fetchIndex,
            'pages/course/course': fetchCourse,
          }

          // 云函數(shù)入口函數(shù)
          exports.main = async (event) => {
            const { path, query = '' } = event;
            const fetchFn = preFetchMap[path];

            if (fetchFn) {
              const res = await fetchFn(query);
              return res;
            }

            return {
              error: {
                event,
                retcode-1002,
                msg`${path}頁面未設(shè)置預(yù)拉取邏輯`
              }
            };
          };

          不過要注意的是,因?yàn)樾〕绦蜃陨碜隽撕芏喑跏蓟膬?yōu)化,有可能在小程序啟動后,預(yù)拉取的數(shù)據(jù)還沒有返回,所以我們做了進(jìn)一步的優(yōu)化,在業(yè)務(wù)拉取的過程中通過 wx.onBackgroundFetchData監(jiān)聽預(yù)拉取的返回,收到返回就直接渲染 ,盡可能的使用預(yù)拉取的數(shù)據(jù)來渲染首屏。


          數(shù)據(jù)預(yù)拉取


          3.2.2. 提前拉取 & 數(shù)據(jù)緩存

          前面已經(jīng)提到過,提前拉取就是要利用小程序切換頁面的空隙開始拉取數(shù)據(jù),從而在感官上較少數(shù)據(jù)請求的時間,整體的邏輯是通過封裝的跳轉(zhuǎn)邏輯,對應(yīng)的頁面添加不同的數(shù)據(jù)拉取邏輯,并將拉取的 promise 掛載在 App 上,當(dāng)頁面切換完成后優(yōu)先使用 App 上的 promise 來獲取數(shù)據(jù)。

          數(shù)據(jù)緩存則是在數(shù)據(jù)拉取成功后,將比較固定的數(shù)據(jù)通過 wx.setStorage 緩存在本地,當(dāng)?shù)诙吻袚Q到這個頁面時,先使用本地緩存的數(shù)據(jù)進(jìn)行渲染,后面再通過拉取的數(shù)據(jù)來進(jìn)行更新。


          提前拉取


          3.3. 交互優(yōu)化

          3.3.1. 業(yè)務(wù)請求保障

          保障業(yè)務(wù)請求的核心思路是讓業(yè)務(wù)請求優(yōu)先,我們封裝了一個 排隊請求模塊 ,通過對 wx.request API的攔截,將請求根據(jù)配置有個優(yōu)先級排序,低優(yōu)先級的會在請求并發(fā)數(shù)達(dá)到一定的閾值之后被推到等待隊列 WaitingQueue 中,留出足夠的通道給到高優(yōu)先級的業(yè)務(wù)請求。


          請求排隊


          3.3.2. 分步渲染

          這里的方案相信大家也能很好理解,主要是優(yōu)先處理首屏需要的數(shù)據(jù)并通過 setData 更新視圖,然后在處理其余的數(shù)據(jù)。但根據(jù)官方文檔的說明:

          setData 函數(shù)用于將數(shù)據(jù)從邏輯層發(fā)送到視圖層(異步),同時改變對應(yīng)的 this.data 的值(同步)。

          而小程序代碼執(zhí)行順序也遵循JS的事件循環(huán)機(jī)制,僅僅是在處理后的數(shù)據(jù)調(diào)用 setData ,然后繼續(xù)或者通過 Promise 處理下一步的話,并不能達(dá)到分步渲染的目的,而直接通過回調(diào)的方式在 setTimeout 中使用嵌套渲染,代碼的可讀性會變差,同時也不是很優(yōu)雅。我們的解決方式是利用 setTimeout 封裝了一個符合 Promise 標(biāo)準(zhǔn)的方法,從而可以像使用 Promise 那樣繼續(xù)分步渲染:



          4. 成果

          經(jīng)過這一系列的優(yōu)化,效果還是比較明顯的:

          4.1. 包大小

          在包大小方面:

          • 總包從9132.94KB減小到6736.42KB,減少了27%

          • 主包從1949.71KB減小到985.96KB,減少了49.5%

          從啟動耗時的數(shù)據(jù)看,下載耗時和JS注入耗時都有明顯的下降:


          啟動耗時


          再看打開耗時分布,可以看到3s內(nèi)打開的用戶比例有明顯增加,從56.26%增加到64.25%;


          打開分布


          4.2. 請求耗時

          數(shù)據(jù)預(yù)拉取,提前拉取,數(shù)據(jù)緩存在冷啟動和頁面切換時都起到了很不錯的效果:

          首頁請求速度從平均400ms下降到50ms,優(yōu)化了87.5%

          課詳頁的請求速度從平均800ms下降到90ms,優(yōu)化了88.75%

          而數(shù)據(jù)緩存讓二次訪問時頁面可以秒開:


          二次加載


          使用排隊請求之后,對網(wǎng)絡(luò)請求順序的干預(yù)效果還比較明顯,灰度用戶業(yè)務(wù)請求耗時平均有50-100ms,約15%的優(yōu)化

          同時我們通過分析耗時分別在80分位、50分位、20分位的效果發(fā)現(xiàn),請求耗時越長,優(yōu)化效果越明顯,也就是說在弱網(wǎng)情況下能夠更好的發(fā)揮作用。


          請求排隊結(jié)果


          4.3. 渲染

          使用分步渲染后,我們的頁面可以在處理完首屏的基礎(chǔ)數(shù)據(jù)之后就立即開始渲染了,由于我們的目錄結(jié)構(gòu)比較復(fù)雜,處理起來耗時比較長,所以第二部才處理目錄,實(shí)際的渲染效果如下圖:


          分步渲染


          首屏可以比原來提前100ms-150ms渲染出來。

          5. 總結(jié)

          我們本次的性能優(yōu)化對小程序啟動、請求、交互、渲染多個方面都進(jìn)行了性能的挖掘,是在對基礎(chǔ)庫版本要求不高的情況下能做到的極致了。

          以我們的核心頁面首頁和課程詳情頁來說:

          • 首頁冷啟動耗時開發(fā)者可干預(yù)的部分優(yōu)化大概是1300下載 + 300注入 + 170首渲 + 430請求 = 2200ms -> 750 + 245 + 170 + 50 = 1215ms,優(yōu)化了45%

          • 頁冷啟動耗時開發(fā)者可干預(yù)的部分優(yōu)化大概是1300下載 + 300注入 + 170首渲 + 790請求 = 2560ms -> 750 + 245 + 170 + 100 = 1265ms,優(yōu)化了50.5%

          • 頁面切換首次進(jìn)入詳情頁耗時從400路由 + 800請求 + 450處理 = 1650ms -> 400 + 720 + 300 = 1420ms,優(yōu)化了14%

          • 二次進(jìn)入詳情頁面幾乎看不到加載和渲染過程

          還有更多的優(yōu)化手段嗎?官方還提供了一些比較高級的功能,對基礎(chǔ)庫版本要求比較高的,例如:

          • 組件的按需注入和用時注入可以進(jìn)一步減小代碼包下載耗時,但是在我們發(fā)布時這個功能還有點(diǎn)問題,會導(dǎo)致首頁的自定義組件加載不出來,所以暫時沒有使用到。

          • 還可以使用2.11.1開始支持的初始渲染緩存,不必等到邏輯層初始化完畢,可以更早的開始渲染視圖。

          • 尚在試驗(yàn)階段的分包異步化,利用異步加載模塊的方式也可以減少代碼包的下載耗時和JS的注入耗時。

          利用這些能力可以在更多細(xì)節(jié)上進(jìn)行優(yōu)化,我們也將進(jìn)一步探索和跟進(jìn),如果你有更好的方案歡迎討論。




          這篇文章是騰訊課堂小程序優(yōu)化的綜合介紹,后面我們還有【網(wǎng)絡(luò)請求優(yōu)化】和【獨(dú)立分包與性能測試】兩個專項(xiàng)優(yōu)化,敬請期待。

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4. 正則 / 框架 / 算法等 重溫系列(16篇全)
          5. Webpack4 入門(上)|| Webpack4 入門(下)
          6. MobX 入門(上) ||  MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 120+ 篇原創(chuàng)文章

          瀏覽 30
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91爱爱爱爱 | 欧美成人精品网站 | 久久影院三级片 | 国产精品A片 | 狠狠躁夜夜躁人爽 |