Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 色噜噜狠狠色综合网图区,色屁屁www,欧美在线一区二区三区欧美

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          一篇文章帶你搞定JavaScript 性能調(diào)優(yōu)

          一篇文章帶你搞定JavaScript 性能調(diào)優(yōu)

          者:皮皮

          JavaScript 是單線程運(yùn)行的,所以在在執(zhí)行效率上并不是很高,隨著用戶體驗的日益重視,前端性能對用戶體驗的影響備受關(guān)注,但由于性能問題相對復(fù)雜,接下來我們來了解下JavaScript如何提高性能;

          從加載上優(yōu)化:合理放置腳本位置

          由于 JavaScript 的阻塞特性,在每一個<script>出現(xiàn)的時候,無論是內(nèi)嵌還是外鏈的方式,它都會讓頁面等待腳本的加載解析和執(zhí)行,并且<script>標(biāo)簽可以放在頁面的<head>或者<body>中,因此,如果我們頁面中的 css 和 js 的引用順序或者位置不一樣,即使是同樣的代碼,加載體驗都是不一樣的。示例如下:

          <!DOCTYPE html>
          <html>
              <head>
                  <meta charset="utf-8">
                  <meta name="viewport" content="width=device-width,initial-scale=1.0">
                  <title>js 引用的位置性能優(yōu)化</title>
                  <script type="text/javascript" src="index-1.js"></script>
                  <script type="text/javascript" src="index-2.js"></script>
                  <link rel="stylesheet" href="style.css">
              </head>
              <body>
                  <div id="app"></div>
              </body>
          </html>1.2.3.4.5.6.7.8.9.10.11.12.13.14.

          其后面的內(nèi)容將會被掛起等待,直到index-1.js 加載、執(zhí)行完畢,才會執(zhí)行第二個腳本文件 index-2.js,這個時候頁面又將被掛起等待腳本的加載和執(zhí)行完成,一次類推,這樣用戶打開該界面的時候,界面內(nèi)容會明顯被延遲,我們就會看到一個空白的頁面閃過,這種體驗是明顯不好的,因此 我們應(yīng)該盡量的讓內(nèi)容和樣式先展示出來,將 js 文件放在 最后,以此來優(yōu)化用戶體驗。如下所示:

          <!DOCTYPE html>
          <html>
          
              <head>
               <meta charset="utf-8">
                  <meta name="viewport" content="width=device-width,initial-scale=1.0">
                  <title>js 引用的位置性能優(yōu)化</title>
                  <link rel="stylesheet" href="style.css">
              </head>
              <body>
               <div id="app"></div>
               <script type="text/javascript" src="index-1.js"></script>
               <script type="text/javascript" src="index-2.js"></script>
              </body>
          </html>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.

          這段代碼展示了在 HTML 文檔中放置<script>標(biāo)簽的推薦位置。盡管腳本下載會阻塞另一個腳本,但是頁面的大部分內(nèi)容都已經(jīng)下載完 成并顯示給了用戶,因此頁面下載不會顯得太慢。這是雅虎特別性能小組提出的優(yōu)化 JavaScript 的首要規(guī)則:將腳本放在底部。

          從請求次數(shù)上優(yōu)化:減少請求次數(shù)

          由于每個<script>標(biāo)簽初始下載時都會阻塞頁面渲染,所以減少頁面包含的<script>標(biāo)簽數(shù)量有助于改善這一情況。這不僅針對外鏈腳本,內(nèi)嵌腳本的數(shù)量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>標(biāo)簽,都會因執(zhí)行腳本而導(dǎo)致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體性能。

          這個問題在處理外鏈 JavaScript 文件時略有不同。考慮到 HTTP 請求會帶來額外的性能開銷,因此下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數(shù)量將會改善性能。

          通常一個大型網(wǎng)站或應(yīng)用需要依賴數(shù)個 JavaScript 文件。您可以把多個文件合并成一個,這樣只需要引用一個<script>標(biāo)簽,就可以減少性能消耗。文件合并的工作可通過離線的打包工具或者一些實(shí)時的在線服務(wù)來實(shí)現(xiàn)。

          需要特別提醒的是,把一段內(nèi)嵌腳本放在引用外鏈樣式表的之后會導(dǎo)致頁面阻塞去等待樣式表的下載。這樣做是為了確保內(nèi)嵌腳本在執(zhí)行時能獲得最精確的樣式信息。因此,建議不要把內(nèi)嵌腳本緊跟在標(biāo)簽后面。

          有一點(diǎn)我們需要知道:頁面加載的過程中,最耗時間的不是 js 本身的加載和執(zhí)行,相比之下,每一次去后端獲取資源,客戶端與后臺建立鏈接才是最耗時的,也就是大名鼎鼎的Http 三次握手,當(dāng)然,http 請求不是我們這一次討論的主題,因此,減少 HTTP 請求,是我們著重優(yōu)化的一項,事實(shí)上,在頁面中 js 腳本文件加載很很多情況下,它的優(yōu)化效果是很顯著的。

          從加載方式上優(yōu)化:無阻塞腳本加載

          在 JavaScript 性能優(yōu)化上,減少腳本文件大小并限制 HTTP 請求的次數(shù)僅僅是讓界面響應(yīng) 迅速的第一步,現(xiàn)在的 web 應(yīng)用功能豐富,js 腳本越來越多,光靠精簡源碼大小和減少 次數(shù)不總是可行的,即使是一次 HTTP 請求,但文件過于龐大,界面也會被鎖死很長一段 時間,這明顯不好的,因此,無阻塞加載技術(shù)應(yīng)運(yùn)而生。簡單來說, 就是 頁面在加載完成后才加載 s js 代碼,也就是在 w window 對象的 d load 事件觸 發(fā)后才去下載腳本。要實(shí)現(xiàn)這種方式,常用以下幾種方式:

          延遲腳本加載( defer )

          HTML4 為<script>標(biāo)簽定義了一個擴(kuò)展屬性:defer。Defer 屬性指明本元素所含的腳本不會修改 DOM,因此代碼能安全地延遲執(zhí)行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會被直接忽略,因此<script>標(biāo)簽會以默認(rèn)的方式處理,也就是說會造成阻塞。然而,如果您的目標(biāo)瀏覽器支持的話,這仍然是個有用的解決方案。

          <script type="text/javascript" src="index-1.js" defer></script>1.

          帶有 defer 屬性的<script>標(biāo)簽可以放置在文檔的任何位置。對應(yīng)的 JavaScript 文件將在頁面解析到<script>標(biāo)簽時開始下載,但不會執(zhí)行,直到 DOM 加載完成,即 onload事件觸發(fā)前才會被執(zhí)行。當(dāng)一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽的其他進(jìn)程,因此這類文件可以與其他資源文件一起并行下載。·任何帶有 defer 屬性的<script>元素在 DOM 完成加載之前都不會被執(zhí)行,無論內(nèi)嵌或者是外鏈腳本都是如此。

          延遲腳本加載( async )

          HTML5 規(guī)范中也引入了 async 屬性,用于異步加載腳本,其大致作用和 defer 是一樣的,都是采用的并行下載,下載過程中不會有阻塞,但 不同點(diǎn)在于他們的執(zhí)行時機(jī),c async 需要加載完成后就會自動執(zhí)行代碼 ,但是 r defer 需要等待頁面加載完成后才會執(zhí)行。

          從加載方式上優(yōu)化:動態(tài)添加腳本元素

          把代碼以動態(tài)的方式添加的好處是:無論這段腳本是在何時啟動下載,它的下載和執(zhí)行過程都不會阻塞頁面的其他進(jìn)程,我們甚至可以直接添加帶頭部 head 標(biāo)簽中,都不會影響其他部分。因此,作為開發(fā)的你肯定見到過諸如此類的代碼塊:

          var script=document.createElement('script');
          script.type='text/javascript';
          script.src='file.js';
          document.getElementsByTagName('head')[0].appendChild(script);1.2.3.4.

          這種方式便是動態(tài)創(chuàng)建腳本的方式,也就是我們現(xiàn)在所說的動態(tài)腳本創(chuàng)建。通過這種方式下載文件后,代碼就會自動執(zhí)行。但是在現(xiàn)代瀏覽器中,這段腳本會等待所有動態(tài)節(jié)點(diǎn)加載完成后再執(zhí)行。這種情況下,為了確保當(dāng)前代碼中包含的別的代碼的接口或者方法能夠被成功調(diào)用,就必須在別的代碼加載前完成這段代碼的準(zhǔn)備。解決的具體操作思路是:現(xiàn)代瀏覽器會在 script 標(biāo)簽內(nèi)容下載完成后接收一個load 事件,我們就可以在 load 事件后再去執(zhí)行我們想要執(zhí)行的代碼加載和運(yùn)行,在 IE 中,它會接收 loaded 和 complete事件,理論上是 loaded 完成后才會有 completed,但實(shí)踐告訴我們他兩似乎并沒有個先后,甚至有時候只會拿到其中的一個事件,我們可以單獨(dú)的封裝一個專門的函數(shù)來體現(xiàn)這個功能的實(shí)踐性,因此一個統(tǒng)一的寫法是:

          function LoadScript(url, callback) {
              var script=document.createElement('script');
              script.type='text/javascript';
              // IE 瀏覽器下
              if (script.readyState) {
                  script.onreadystatechange=function () {
                      if (script.readyState=='loaded' || script.readyState==        'complete') {
                          // 確保執(zhí)行兩次
                          script.onreadystatechange=null;
                          // todo 執(zhí)行要執(zhí)行的代碼
                          callback()
                      }
                  }
              } else {
                  script.onload=function () {
                      callback();
                  }
              }
              script.src='file.js';
              document.getElementsByTagName('head')[0].appendChild(script);
          }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.

          LoadScript 函數(shù)接收兩個參數(shù),分別是要加載的腳本路徑和加載成功后需要執(zhí)行的回調(diào)函數(shù),LoadScript 函數(shù)本身具有特征檢測功能,根據(jù)檢測結(jié)果(IE 和其他瀏覽器),來決定腳本處理過程中監(jiān)聽哪一個事件。實(shí)際上這里的 LoadScript()函數(shù),就是我們所說的 LazyLoad.js(懶加載)的原型。

          從加載方式上優(yōu)化:XMLHttpRequest 腳本注入

          通過 XMLHttpRequest 對象來獲取腳本并注入到頁面也是實(shí)現(xiàn)無阻塞加載的另一種方式,這個我覺得不難理解,這其實(shí)和動態(tài)添加腳本的方式是一樣的思想,來看具體代碼:

          var xhr=new XMLHttpRequest();
          xhr.open('get', 'file-1.js', true);
          xhr.onreadystatechange=function () {
              if (xhr.readyState===4) {
                  if (xhr.status >=200 && xhr.status < 300 || xhr.status===304) {
                      // 如果從后臺或者緩存中拿到數(shù)據(jù),則添加到 script 中并加載執(zhí)行。
                      var script=document.createElement('script');
                      script.type='text/javascript';
                      script.text=xhr.responseText;
                      // 將創(chuàng)建的 script 添加到文檔頁面
                      document.body.appendChild(script);
                  }
              }
          }1.2.3.4.5.6.7.8.9.10.11.12.13.14.

          通過這種方式拿到的數(shù)據(jù)有兩個優(yōu)點(diǎn):其一,我們可以控制腳本是否要立即執(zhí)行,因為我們知道新創(chuàng)建的 script 標(biāo)簽只要添加到文檔界面中它就會立即執(zhí)行,因此,在添加到文檔界面之前,也就是在 appendChild()之前,我們可以根據(jù)自己實(shí)際的業(yè)務(wù)邏輯去實(shí)現(xiàn)需求,到想要讓它執(zhí)行的時候,再 appendChild()即可。其二:它的兼容性很好,所有主流瀏覽器都支持,它不需要想動態(tài)添加腳本的方式那樣,我們自己去寫特性檢測代碼;但由于是使用了 XHR 對象,所以不足之處是獲取這種資源有“域”的限制。資源 必須在同一個域下才可以,不可以跨域操作。

          總結(jié)

          減少 JavaScript 對性能的影響有以下幾種方法:

          • 將所有的<script>標(biāo)簽放到頁面底部,也就是</body>閉合標(biāo)簽之前,這能確保在 腳本執(zhí)行前頁面已經(jīng)完成了渲染。
          • 盡可能地合并腳本。頁面中的<script>標(biāo)簽越少,加載也就越快,響應(yīng)也越迅速。無論是外鏈腳本還是內(nèi)嵌腳本都是如此。
          • 采用無阻塞下載 JavaScript 腳本的方法:

          使用<script>標(biāo)簽的 defer 屬性(僅適用于 IE 和 Firefox 3.5 以上版 本);

          使用動態(tài)創(chuàng)建的<script>元素來下載并執(zhí)行代碼;

          使用 XHR 對象下載 JavaScript 代碼并注入頁面中。

          通過以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 網(wǎng)站和應(yīng)用的實(shí)際性能。

          來源: Python共享之家

          者:lzg9527

          原文鏈接:https://segmentfault.com/a/1190000038180453

          webpack 異步加載原理

          webpack ensure 有人稱它為異步加載,也有人稱為代碼切割,他其實(shí)就是將 js 模塊給獨(dú)立導(dǎo)出一個.js 文件,然后使用這個模塊的時候,再創(chuàng)建一個 script 對象,加入到 document.head 對象中,瀏覽器會自動幫我們發(fā)起請求,去請求這個 js 文件,然后寫個回調(diào)函數(shù),讓請求到的 js 文件做一些業(yè)務(wù)操作。

          舉個例子

          需求: main.js 依賴兩個 js 文件: A.js 是點(diǎn)擊 aBtn 按鈕后,才執(zhí)行的邏輯, B.js 是點(diǎn)擊 bBtn 按鈕后,才執(zhí)行的邏輯。

          webpack.config.js ,我們先來寫一下 webpack 打包的配置的代碼

          const path=require('path') // 路徑處理模塊
          const HtmlWebpackPlugin=require('html-webpack-plugin')
          const { CleanWebpackPlugin }=require('clean-webpack-plugin') // 引入CleanWebpackPlugin插件
          
          module.exports={
            entry: {
              index: path.join(__dirname, '/src/main.js'),
            },
            output: {
              path: path.join(__dirname, '/dist'),
              filename: 'index.js',
            },
            plugins: [
              new HtmlWebpackPlugin({
                template: path.join(__dirname, '/index.html'),
              }),
              new CleanWebpackPlugin(), // 所要清理的文件夾名稱
            ],
          }
          

          index.html 代碼如下

          <!DOCTYPE html>
          <html lang="en">
            <head>
              <meta charset="UTF-8" />
              <meta name="viewport" content="width=device-width, initial-scale=1.0" />
              <title>webpack</title>
            </head>
            <body>
              <div id="app">
                <button id="aBtn">按鈕A</button>
                <button id="bBtn">按鈕B</button>
              </div>
            </body>
          </html>
          

          入口文件 main.js 如下

          import A from './A'
          import B from './B'
          
          document.getElementById('aBtn').onclick=function () {
            alert(A)
          }
          
          document.getElementById('bBtn').onclick=function () {
            alert(B)
          }
          

          A.jsB.js 的代碼分別如下

          // A.js
          const A='hello A'
          module.exports=A
          
          // B.js
          const B='hello B'
          module.exports=B
          

          此時,我們對項目進(jìn)行 npm run build , 打包出來的只有兩個文件

          • index.html
          • index.js

          由此可見,此時 webpackmain.js 依賴的兩個文件都同時打包到同一個 js 文件,并在 index.html 中引入。但是 A.jsB.js 都是點(diǎn)擊相應(yīng)按鈕才會執(zhí)行的邏輯,如果用戶并沒有點(diǎn)擊相應(yīng)按鈕,而且這兩個文件又是比較大的話,這樣是不是就導(dǎo)致首頁默認(rèn)加載的 js 文件太大,從而導(dǎo)致首頁渲染較慢呢?那么有能否實(shí)現(xiàn)當(dāng)用戶點(diǎn)擊按鈕的時候再加載相應(yīng)的依賴文件呢?

          webpack.ensure 就解決了這個問題。

          require.ensure 異步加載

          下面我們將 main.js 改成異步加載的方式

          document.getElementById('aBtn').onclick=function () {
            //異步加載A
            require.ensure([], function () {
              let A=require('./A.js')
              alert(A)
            })
          }
          
          document.getElementById('bBtn').onclick=function () {
            //異步加載b
            require.ensure([], function () {
              let B=require('./B.js')
              alert(B)
            })
          }
          

          此時,我們再進(jìn)行一下打包,發(fā)現(xiàn)多了 1.index.js2.index.js 兩個文件。而我們打開頁面時只引入了 index.js 一個文件,當(dāng)點(diǎn)擊按鈕 A 的時候才引入 1.index.js 文件,點(diǎn)擊按鈕 B 的時候才引入 2.index.js 文件。這樣就滿足了我們按需加載的需求。

          require.ensure 這個函數(shù)是一個代碼分離的分割線,表示回調(diào)里面的 require 是我們想要進(jìn)行分割出去的,即 require('./A.js') ,把 A.js 分割出去,形成一個 webpack 打包的單獨(dú) js 文件。它的語法如下

          require.ensure(dependencies: String[], callback: function(require), chunkName: String)
          

          我們打開 1.index.js 文件,發(fā)現(xiàn)它的代碼如下

          (window.webpackJsonp=window.webpackJsonp || []).push([
            [1],
            [
              ,
              function (o, n) {
                o.exports='hello A'
              },
            ],
          ])
          

          由上面的代碼可以看出:

          1. 異步加載的代碼,會保存在一個全局的 webpackJsonp 中。
          2. webpackJsonp.push 的的值,兩個參數(shù)分別為異步加載的文件中存放的需要安裝的模塊對應(yīng)的 id 和異步加載的文件中存放的需要安裝的模塊列表。
          3. 在滿足某種情況下,會執(zhí)行具體模塊中的代碼。

          import() 按需加載

          webpack4 官方文檔提供了模塊按需切割加載,配合 es6 的按需加載 import() 方法,可以做到減少首頁包體積,加快首頁的請求速度,只有其他模塊,只有當(dāng)需要的時候才會加載對應(yīng) js。

          import() 的語法十分簡單。該函數(shù)只接受一個參數(shù),就是引用包的地址,并且使用了 promise 式的回調(diào),獲取加載的包。在代碼中所有被 import() 的模塊,都將打成一個單獨(dú)的包,放在 chunk 存儲的目錄下。在瀏覽器運(yùn)行到這一行代碼時,就會自動請求這個資源,實(shí)現(xiàn)異步加載。

          下面我們將上述代碼改成 import() 方式。

          document.getElementById('aBtn').onclick=function () {
            //異步加載A
            import('./A').then((data)=> {
              alert(data.A)
            })
          }
          
          document.getElementById('bBtn').onclick=function () {
            //異步加載b
            import('./B').then((data)=> {
              alert(data.B)
            })
          }
          

          此時打包出來的文件和 webpack.ensure 方法是一樣的。

          路由懶加載

          為什么需要懶加載?

          像 vue 這種單頁面應(yīng)用,如果沒有路由懶加載,運(yùn)用 webpack 打包后的文件將會很大,造成進(jìn)入首頁時,需要加載的內(nèi)容過多,出現(xiàn)較長時間的白屏,運(yùn)用路由懶加載則可以將頁面進(jìn)行劃分,需要的時候才加載頁面,可以有效的分擔(dān)首頁所承擔(dān)的加載壓力,減少首頁加載用時。

          vue 路由懶加載有以下三種方式

          • vue 異步組件
          • ES6 的 import()
          • webpack 的 require.ensure()

          vue 異步組件

          這種方法主要是使用了 resolve 的異步機(jī)制,用 require 代替了 import 實(shí)現(xiàn)按需加載

          export default new Router({
            routes: [
              {
                path: '/home',',
                component: (resolve)=> require(['@/components/home'], resolve),
              },
              {
                path: '/about',',
                component: (resolve)=> require(['@/components/about'], resolve),
              },
            ],
          })
          

          require.ensure

          這種模式可以通過參數(shù)中的 webpackChunkName 將 js 分開打包。

          export default new Router({
            routes: [
              {
                path: '/home',
                component: (resolve)=> require.ensure([], ()=> resolve(require('@/components/home')), 'home'),
              },
              {
                path: '/about',
                component: (resolve)=> require.ensure([], ()=> resolve(require('@/components/about')), 'about'),
              },
            ],
          })
          

          ES6 的 import()

          vue-router 在官網(wǎng)提供了一種方法,可以理解也是為通過 Promiseresolve 機(jī)制。因為 Promise 函數(shù)返回的 Promiseresolve 組件本身,而我們又可以使用 import 來導(dǎo)入組件。

          export default new Router({
            routes: [
              {
                path: '/home',
                component: ()=> import('@/components/home'),
              },
              {
                path: '/about',
                component: ()=> import('@/components/home'),
              },
            ],
          })
          

          webpack 分包策略

          在 webpack 打包過程中,經(jīng)常出現(xiàn) vendor.jsapp.js 單個文件較大的情況,這偏偏又是網(wǎng)頁最先加載的文件,這就會使得加載時間過長,從而使得白屏?xí)r間過長,影響用戶體驗。所以我們需要有合理的分包策略。

          CommonsChunkPlugin

          在 Webapck4.x 版本之前,我們都是使用 CommonsChunkPlugin 去做分離

          plugins: [
            new webpack.optimize.CommonsChunkPlugin({
              name: 'vendor',
              minChunks: function (module, count) {
                return (
                  module.resource &&
                  /.js$/.test(module.resource) &&
                  module.resource.indexOf(path.join(__dirname, './node_modules'))===0
                )
              },
            }),
            new webpack.optimize.CommonsChunkPlugin({
              name: 'common',
              chunks: 'initial',
              minChunks: 2,
            }),
          ]
          

          我們把以下文件單獨(dú)抽離出來打包

          • node_modules 文件夾下的,模塊
          • 被 3 個 入口 chunk 共享的模塊

          optimization.splitChunks

          webpack 4 最大的改動就是廢除了 CommonsChunkPlugin 引入了 optimization.splitChunks 。如果你的 modeproduction ,那么 webpack4 就會自動開啟 Code Splitting

          它內(nèi)置的代碼分割策略是這樣的:

          • 新的 chunk 是否被共享或者是來自 node_modules 的模塊
          • 新的 chunk 體積在壓縮之前是否大于 30kb
          • 按需加載 chunk 的并發(fā)請求數(shù)量小于等于 5 個
          • 頁面初始加載時的并發(fā)請求數(shù)量小于等于 3 個

          雖然在 webpack4 會自動開啟 Code Splitting ,但是隨著項目工程的最大,這往往不能滿足我們的需求,我們需要再進(jìn)行個性化的優(yōu)化。

          應(yīng)用實(shí)例

          我們先找到一個優(yōu)化空間較大的項目來進(jìn)行操作。這是一個后臺管理系統(tǒng)項目,大部分內(nèi)容由 3-4 個前端開發(fā),平時開發(fā)周期較短,且大部分人沒有優(yōu)化意識,只是寫好業(yè)務(wù)代碼完成需求,日子一長,造成打包出來的文件較大,大大影響性能。

          我們先用 webpack-bundle-analyzer 分析打包后的模塊依賴及文件大小,確定優(yōu)化的方向在哪。

          然后我們再看下打包出來的 js 文件

          看到這兩張圖的時候,我內(nèi)心是崩潰的,槽點(diǎn)如下

          • 打包后生成多個將近 1M 的 js 文件,其中不乏 vendor.js 首頁必須加載的大文件
          • xlsx.js 這樣的插件沒必要使用,導(dǎo)出 excel 更好的方法應(yīng)該是后端返回文件流格式給前端處理
          • echartiview 文件太大,應(yīng)該使用 cdn 引入的方法

          吐槽完之后我們就要開始做正事了。正是因為有這么多槽點(diǎn),我們才更好用來驗證我們優(yōu)化方法的可行性。

          抽離 echart 和 iview

          由上面分析可知, echartiview 文件太大,此時我們就用到 webpack4 的 optimization.splitChunks 進(jìn)行代碼分割了,把他們單獨(dú)抽離打包成文件。(為了更好地呈現(xiàn)優(yōu)化效果,我們先把 xlsx.js 去掉)

          vue.config.js 修改如下:

          chainWebpack: config=> {
              config.optimization.splitChunks({
                chunks: 'all',
                cacheGroups: {
                  vendors: {
                    name: 'chunk-vendors',
                    test: /[/]node_modules[/]/,
                    priority: 10,
                    chunks: 'initial'
                  },
                  iview: {
                    name: 'chunk-iview',
                    priority: 20,
                    test: /[/]node_modules[/]_?iview(.*)/
                  },
                  echarts: {
                    name: 'chunk-echarts',
                    priority: 20,
                    test: /[/]node_modules[/]_?echarts(.*)/
                  },
                  commons: {
                    name: 'chunk-commons',
                    minChunks: 2,
                    priority: 5,
                    chunks: 'initial',
                    reuseExistingChunk: true
                  }
                }
              })
            },
          

          此時我們再用 webpack-bundle-analyzer 分析一下

          打包出來的 js 文件

          從這里可以看出我們已經(jīng)成功把 echartiview 單獨(dú)抽離出來了,同時 vendor.js 也相應(yīng)地減小了體積。此外,我們還可以繼續(xù)抽離其他更多的第三方模塊。

          CDN 方式

          雖然第三方模塊是單獨(dú)抽離出來了,但是在首頁或者相應(yīng)路由加載時還是要加載這樣一個幾百 kb 的文件,還是不利于性能優(yōu)化的。這時,我們可以用 CDN 的方式引入這樣插件或者 UI 組件庫。

          1. index.html 引入相應(yīng) cdn 鏈接
          <head>
            <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/iview/3.5.4/styles/iview.css" />
          </head>
          <body>
            <div id="app"></div>
            <script src="https://cdn.bootcss.com/vue/2.6.8/vue.min.js"></script>
            <script src="https://cdn.bootcdn.net/ajax/libs/iview/3.5.4/iview.min.js"></script>
            <script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.16.8/xlsx.mini.min.js"></script>
            <script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.16.8/cpexcel.min.js"></script>
          </body>
          
          1. vue.config.js 配置 externals
          configureWebpack: (config)=> {
            config.externals={
              vue: 'Vue',
              xlsx: 'XLSX',
              iview: 'iView',
              iView: 'ViewUI',
            }
          }
          
          1. 刪除之前的引入方式并卸載相應(yīng) npm 依賴包
          npm uninstall vue iview echarts xlsx --save
          

          此時我們來看一下打包后的情況

          打包出來的 js 文件

          well done ! 這時基本沒有打包出大文件了,首頁加載需要的 vendor.js 也只有幾十 kb,而且我們還可以進(jìn)一步優(yōu)化,就是把 vue 全家桶的一些模塊再通過 cdn 的方法引入,比如 vue-routervuexaxios 等。這時頁面特別是首頁加載的性能就得到大大地優(yōu)化了。

          多開發(fā)者可能平時并不關(guān)心自己維護(hù)的頁面是否存在內(nèi)存泄漏,原因可能是剛開始簡單的頁面內(nèi)存泄漏的速度很緩慢,在造成嚴(yán)重卡頓之前可能就被用戶刷新了,問題也就被隱藏了,但是隨著頁面越來越復(fù)雜,尤其當(dāng)你的頁面是 SAP 方式交互時,內(nèi)存泄漏的隱患便越來越嚴(yán)重,直到突然有一天用戶反饋說:“操作一會兒頁面就卡住不動了,也不知道為什么,以前不這樣的呀”。

          這篇文章通過一些簡單的例子介紹內(nèi)存泄漏的調(diào)查方法、總結(jié)內(nèi)存泄漏出現(xiàn)的原因和常見情況,并針對每種情況總結(jié)如何避免內(nèi)存泄漏。希望能對大家有所幫助。

          一 一個簡單的例子

          先看一個簡單的例子,下面是這個例子對應(yīng)的代碼:

          代碼 1

          代碼 1 的邏輯很簡單:點(diǎn)擊“add date”按鈕時會向 dateAry 數(shù)組中 push 3000 個 new Date 對象,點(diǎn)擊“clear”按鈕時將 dateAry 清空。很明顯,“add date”操作會造成內(nèi)存占用不斷增長,如果將這個邏輯用在實(shí)際應(yīng)用中便會造成內(nèi)存泄漏(不考慮故意將代碼邏輯設(shè)計成這樣的情況),下面我們看一下如何調(diào)查這種內(nèi)存增長出現(xiàn)的原因以及如何找出內(nèi)存泄漏點(diǎn)。

          1 heap snapshot

          為了避免瀏覽器插件的干擾,我們在 chrome 中新建一個無痕窗口打開上述代碼。然后在 chrome 的 devtools 中的 Memory 工具中找到 “Heap Snapshot”工具,點(diǎn)擊左上角的錄制按鈕錄制一個 Snapshot,然后點(diǎn)擊“add date”按鈕,在手動觸發(fā) GC(Garbage Collect)之后,再次錄制一個 Snapshot,反復(fù)執(zhí)行上述操作若干次,像圖 1 中操作的那樣,得到一系列的 Snapshot。

          圖 1 錄制 Snapshot

          圖 2 是我們剛剛得到的 Snapshot 組,其中的第一個是頁面初始加載的時候錄制的,不難發(fā)現(xiàn),從第二個開始,每個 Snapshot 相比于上一個其大小都增加了約 200KB,我們點(diǎn)擊選擇 Snapshot 2,在 class filter 輸入框中處輸入 date,可以得到 Snapshot 2 中所有被 Date 構(gòu)造器構(gòu)造出來的 JS 對象,也就是 Date 對象,這里看到的構(gòu)造器跟瀏覽器內(nèi)部的實(shí)現(xiàn)有關(guān),不必跟 JS 的對象對應(yīng)。

          選中一個 Date 對象,在下面的面板中可以看到所選對象的持有鏈以及相關(guān)持有對象的內(nèi)存的保留大小(Retained Size),從圖中可以看出選中的 Date 對象是 Array 的第 1 個元素(index 從 0 開始),而這個 Array 的持有者是 system/Context 上下文中的 dateAry,system/Context 上下文就是代碼中 script 標(biāo)簽的上下文,我們可以看到在這個 dataAry 的保留大小是 197KB,我們再切到 Snapshot 3,用相同的方式查看內(nèi)存持有和大小,可以發(fā)現(xiàn) Snapshot 3 中的 dataAry 的保留大小變成了 386KB,相比于 Snapshot 2 增漲了約 200KB!逐一比較后面的 Snapshot 4、5 后也能得到相同的對比結(jié)果,即下一個 Snapshot 中的 dateAry 比上一個的保留大小大約 200KB。

          圖 2 錄制的 Snapshot 組

          參考【代碼 1】我們可以知道,“add date”按鈕在被點(diǎn)擊時,會向 dateAry 數(shù)組中 push 3000 個新的 Date 對象,而在圖 2 中的 Date 構(gòu)造器的右側(cè)可以看到這 3000 個 Date 對象(Date x 3000),它對應(yīng)的正式我們的循環(huán)創(chuàng)建的那 3000 個 Date 對象。綜合上面的操作我們可以知道,chorome devtools 中的 Memroy 的 Heap Snapshot 工具可以錄制某一個時刻的所有內(nèi)存對象,也就是一個“快照”,快照中按“構(gòu)造器”分組,展示了所有被記錄下來的 JS 對象。

          如果這個頁面是一個實(shí)際服務(wù)于用戶的網(wǎng)站的某個頁面話(用戶可能非常頻繁的點(diǎn)擊“add date”按鈕,作者可能想記錄用戶點(diǎn)擊的次數(shù)?也許吧,雖然我也不知道他什么要這么做)隨著用戶使用時間的增長,“add date”按鈕的反應(yīng)就會越來越慢,整體頁面也隨之越來越卡,原因除了系統(tǒng)的內(nèi)存資源被占用之外,還有 GC 的頻率和時長增長,如圖 3 所示,因為 GC 執(zhí)行的過程中 JS 的執(zhí)行是被暫停的,所以頁面就會呈現(xiàn)出越來越卡的樣子。

          圖 3 Performance 錄制的 GC 占比

          圖 4 chrome 的任務(wù)管理器

          最終:

          圖 5 內(nèi)存占用過高導(dǎo)致瀏覽器崩潰

          那么,在這個“實(shí)際”的場景下,如何找出那“作祟”的 3000 個 Date 對象呢?我們首先想到的應(yīng)該是就是:之前不是錄制了好多個 Snapshot 嗎?可不可以把它們做對比找到“差異”呢,從差異中找到增長的地方不就行了?思路非常正確,在此之前我們再分析一下這幾個 Snapshot:每次點(diǎn)擊“add date”按鈕、手動觸發(fā) GC、得到的 Snapshot 的大小相比上一次都有所增加,如果這種內(nèi)存的增長現(xiàn)象不符合“預(yù)期”的話(顯然在這個“實(shí)際”的例子中是不符合預(yù)期的),那么這里就有很大的嫌疑存在內(nèi)存泄漏。

          這個時候我們選中 Snapshot 2,在圖 2 所示的 " Summary" 處選擇“Comparison”,在右側(cè)的 "All objects" 處選擇 Snapshot 1,這樣一來,Constructor 里展示便是 Snapshot 1 和 Snapshot 2 的對比,通過觀察不難發(fā)現(xiàn),圖中的 +144KB 最值得懷疑,于是我們選中它的構(gòu)造器 Date,展開選中任意子項看詳情,發(fā)現(xiàn)其是被 Array 構(gòu)造器構(gòu)造出來的 dateAry 持有的(即 dateAry 中的一員),并且 dateAry 被三個地方持有,其中系統(tǒng)內(nèi)部的 array 我們不用理會,圖 6 中寫有 "context in ()" 地方給了我們持有 dateAry 的 context 所在的位置,點(diǎn)擊便可以跳到代碼所在的位置了,整個操作如圖 6 所示:

          圖 6 定位代碼位置

          這里有一個值得注意的地方,圖 6 中的 “context in () @449305” 中的 "()",這里之所以展示為了 "()" 是因為代碼中用了“匿名函數(shù)”(代碼 2 中第 2 行的箭頭函數(shù)):

          // 【寫入 date】
          pushDate.addEventListener("click", ()=> {
              dateCount.innerHTML=`${++dateNum}`;
          
              for (let j=0; j < 3000; ++j) {
                  dateAry.push(new Date());
              }
          });

          代碼 2 匿名函數(shù)

          但是如果我們給函數(shù)起一個名字,如下面的代碼所示,也就是如果我們使用具名函數(shù)(代碼3 第 2 行函數(shù) add)或者將函數(shù)賦值給一個變量并使用這個變量(第 10 和 18 行的行為)的時候,devtools 中都可以看到相應(yīng)的函數(shù)的名字,這也就可以幫助我們更好的定位代碼,如圖 7 所示。

          // 【寫入 date】
          pushDate.addEventListener("click", function add() {
              dateCount.innerHTML=`${++dateNum}`;
          
              for (let j=0; j < 3000; ++j) {
                  dateAry.push(new Date());
              }
          });
          
          const clear=document.querySelector(".clear");
          
          const doClear=function () {
              dateAry=[];
              dateCount.innerHTML="0";
          };
          
          // 【回收內(nèi)存】
          clear.addEventListener("click", doClear);

          代碼 3 具名函數(shù)

          圖 7 具名函數(shù)方便定位

          這樣我們便找到了代碼可疑的地方,只需要將代碼的作者抓過來對著他一頓“分析”這個內(nèi)存泄漏的問題基本就水落石出了。

          其實(shí),Snapshot 除了“Comparison”之外還有一個更便捷的用于對比的入口,在這里直接可以看到在錄制 Snapshot 1 和 Snapshot 2 兩個時間點(diǎn)之間被分配出來的內(nèi)存,用這種方式也可以定位到那個可疑的 Date x 3000:

          圖 8 Snapshot 比較器

          上文件介紹的是用 Heap Snapshot 尋找內(nèi)存泄漏點(diǎn)的方法,這個方法的優(yōu)點(diǎn):可以錄制多個 Snapshot,然后方便的兩兩比較,并且能看到 Snapshot 中的全量內(nèi)存,這一點(diǎn)是下文要講的“Allocation instrumentation on timeline”方法不具備的,并且這種方法可以更加方便地查找后面會講的因 Detached Dom 導(dǎo)致的內(nèi)存泄漏。

          2 Allocation instrumentation on timeline

          但是,不知道你有沒有覺得,這種高頻率地錄制 Snapshot、對比、再對比的方式有點(diǎn)兒麻煩?我需要不斷的去點(diǎn)擊“add date”,然后鼠標(biāo)又要跑過去點(diǎn)擊手動 GC、錄制 Snapshot、等待錄制完畢,再去操作,再去錄制。有沒有簡單一些的方式來查找內(nèi)存泄漏?這個時候我們回到 Memory 最初始的界面,你突然發(fā)現(xiàn) “Heap snapshot”下面還有一個 radio:“Allocation instrumentation on timeline”,并且這個 radio 下面的介紹文案的最后寫著:“Use this profile type to isolate memory leaks”,原來這是一個專門用于調(diào)查內(nèi)存泄漏的工具!于是,我們選中這個 radio,點(diǎn)擊開始錄制按鈕,然后將注意力放在頁面上,然后你發(fā)現(xiàn)當(dāng)點(diǎn)擊“add date”按鈕時,右面錄制的 timeline 便會多出一個心跳:

          圖 9 Allocation instrumentation on timeline

          如圖 9 所示,每當(dāng)我們點(diǎn)擊“add date”按鈕時,右面都有一個對應(yīng)的心跳,當(dāng)我們點(diǎn)擊“clear”按鈕時,剛才出現(xiàn)的所有心跳便全都“縮回”去了,于是我們得出結(jié)論:每一個“心跳”都是一次內(nèi)存分配,其高度代表內(nèi)存分配的量,在之后的時間推移過程中,如果剛才心跳對應(yīng)的被分配的內(nèi)存被 GC 回收了,“心跳”便會跟著變化為回收之后的高度。于是,我們便擺脫了在 Snapshot 中來回操作、錄制的窘境,只需要將注意力集中在頁面的操作上,并觀察哪個操作在右邊的時間線變化中是可疑的。

          經(jīng)過一系列操作,我們發(fā)現(xiàn)“add date”這個按鈕的點(diǎn)擊行為很可疑,因為它分配的內(nèi)存不會自動被回收,也就是只要點(diǎn)擊一次,內(nèi)存就會增長一點(diǎn),我們停止錄制,得到了一個 timeline 的 Snapshot,這個時候如果我們點(diǎn)擊某個心跳的話:

          圖 10 點(diǎn)擊某個心跳

          熟悉的 Date x 3000 又出現(xiàn)了(圖 11),點(diǎn)擊一個 Date 對象看持有鏈,接下來便跟上文 Snapshot 的持有鏈分析一樣了:

          圖 11 通過 timeline 找到泄漏點(diǎn)

          這個方法的優(yōu)點(diǎn)上文已經(jīng)說明,可以非常直觀、方便的觀察內(nèi)存隨可疑操作的分配與回收過程,可以方便的觀察每次分配的內(nèi)存。它的缺點(diǎn):錄制時間較長時 devtools 收集錄制結(jié)果的時間會很長,甚至有時候會卡死瀏覽器;下文會講到 detached DOM,這個工具不能比較出 detached DOM,而 heap snapshot 可以。

          3 performance

          devtools 中的 Performance 面版中也有一個 Memory 功能,下面看一下它如何使用。我們把 Memory 勾選上,并錄制一個 performance 結(jié)果:

          圖 12 Performance 的錄制過程

          在圖 12 中可以看到,在錄制的過程中我們連續(xù)點(diǎn)擊“add date”按鈕 10 次,然后點(diǎn)擊一次“clear”按鈕,然后再次點(diǎn)擊“add date” 10 次,得到的最終結(jié)果如圖 13 所示:

          圖 13 Performance 的錄制結(jié)果

          在圖 13 中我們可以得到下面的信息:

          • 整個操作過程中內(nèi)存的走勢:參見圖 13 下方的位置,第一輪點(diǎn)擊 10 次的過程中內(nèi)存不斷增長,點(diǎn) clear 之后內(nèi)存斷崖式下跌,第二輪點(diǎn)擊 10 次內(nèi)存又不斷增長。這也是這個工具的主要作用:得到可疑操作的內(nèi)存走勢圖,如果內(nèi)存持續(xù)走高則有理由懷疑此操作由內(nèi)存泄漏的可能。
          • 內(nèi)存的增長量:參見 JS Heap 位置,鼠標(biāo)放上去可以看見每個階梯上下位置的內(nèi)存增長/下跌的量
          • 通過在 timeline 中定位某個“階梯”,我們也能找到可疑的代碼,如圖 14 所示:

          圖 14 通過 Performance 定位問題代碼

          這種方法的優(yōu)點(diǎn):可以直觀得看到內(nèi)存的總體走勢,并且同時得到所有操作過程中的函數(shù)調(diào)用棧和時間等信息。缺點(diǎn):沒有具體的內(nèi)存分配的細(xì)節(jié),錄制的過程不能實(shí)時看到內(nèi)存分配的過程。

          二 內(nèi)存泄漏出現(xiàn)的場景

          1 全局

          JS 采用標(biāo)記清掃法去回收無法訪問的內(nèi)存對象,被掛載在全局對象(在瀏覽器中即指的是 window 對象,在垃圾回收的角度上稱其為根節(jié)點(diǎn),也叫 GC root)上的屬性所占用內(nèi)存是不會被回收的,因為其是始終可以訪問的,這也符合“全局”的命名含義。

          解決方案就是避免用全局對象存儲大量的數(shù)據(jù)。

          2 閉包(closure)

          我們把【代碼 1】稍加改動便可以得到一個閉包導(dǎo)致內(nèi)存泄漏的版本:

          代碼 3 閉包導(dǎo)致內(nèi)存泄漏

          將上述代碼加載到 chrome 中,并用 timeline 的方式錄制一個 Snapshot,得到的結(jié)果如圖 15 所示:

          圖 15 閉包的錄制結(jié)果

          我們選中 index=2 的心跳,可以看到 Constructor 里面出現(xiàn)了一個 "(closure)",我們展開這個 closure,可以看到里面的 "inner()",inner() 后面的 "()" 表示 inner 是一個函數(shù),這時候你可能會問:“圖中的 Constructor 的 Retained Size 大小都差不多,為什么你要選 (closure)?”,正是因為沒有明顯占比較高的 Retained Size 我們才隨便選一個調(diào)查,后面你會發(fā)現(xiàn)不管你選了哪一個最后的調(diào)查鏈路都是殊途同歸的。

          我們在下面的 Retainers 中看下 inner() 的持有細(xì)節(jié):從下面的 Retainers 中可以看出 inner() 這個 closure 是某個 Array 的第 2 項(index 從 0 開始),而這個數(shù)組的持有者是 system/Context(即全局) 中的 ary,通過觀察可以看到 ary 的持有大小(Retained Size)是 961KB 大約等于 192KB 的 5 倍,5 即是我們點(diǎn)擊“add date”按鈕的次數(shù),而下面的 5 個 "previous in system/Context" 每個大小都是 192KB,而它們最終都是被某個 inner() 閉包持有,至此我們便可以得出結(jié)論:全局中有一個 ary 數(shù)組,它的主要內(nèi)存是被 inner() 填充的,通過藍(lán)色的 index.html:xx 處的代碼入口定位到代碼所在地看一下一切就都了然了,原來是 inner() 閉包內(nèi)部持有了一個大對象,并且所有的 inner() 閉包及其持有的大對象都被 ary 對象持有,而 ary 對象是全局的不會被回收,導(dǎo)致了內(nèi)存泄漏(如果這種行為不符合預(yù)期的話)。返回去,如果這個時候你選擇上面提到的 system/Context 構(gòu)造器,你會看到(見圖 16,熟悉吧):

          圖 16 system/Context

          也就是你選擇的 system/Context 其實(shí)是 inner() 閉包的上下文對象(context),而此上下文持有了 192KB 內(nèi)存,通過藍(lán)色的 index.html:xx 又可以定位到問題代碼了。如果你像圖 17 一樣選擇了 Date 構(gòu)造器進(jìn)行查看的話也可以最終定位到問題,此處將分析過程留給讀者自己進(jìn)行:

          圖 17 選中 Date 構(gòu)造器

          3 Detached DOM

          我們先看一下下面的代碼,并用 chrome 載入它:

          代碼 4 Detached Dom

          然后我們采用 Heap Snapshot 的方式將點(diǎn)擊“del”按鈕前后的兩個 snapshot 錄制下來,得到的結(jié)果如圖 6 所示。我們選用和 snapshot 1 對比的方式并在 snapshot 2 的過濾器中輸入 "detached"。我們觀察得到的篩選結(jié)果的 "Delta" 列,其中不為 0 的列如下:

          要解釋上述表格需要先介紹一個知識點(diǎn):DOM 對象被回收需要同時滿足兩個條件,1、DOM 在 DOM 樹中被刪掉;2、DOM 沒有被 JS 對象引用。其中第二點(diǎn)還是比較容易被忽視的。正如上面的例子所示,Detached HTMLButtonElement +1 代表有一個 button DOM 被從組件樹中刪掉了,但是仍有 JS 引用之(我們不考慮有意為之的情況)。

          相似的,Detached EventListener 也是因為 DOM 被刪掉了,但是事件沒有解綁,于是 Detached 了,解決方案也很簡單:及時解綁事件即可。

          于是解決的方法就很簡單了:參見代碼 5,回掉函數(shù) del 在執(zhí)行完畢時臨時變量會被回收,于是兩個條件就都同時滿足了,DOM 對象就會被回收掉,事件解綁了,Detached EventListener 也就沒有了。值得注意的是 table 元素,如果一個 td 元素發(fā)生了 detached,則由于其自身引用了自己所在的 table,于是整個 table 就也不會被回收了。

          代碼 5 Detached DOM 的解決方法

          圖 18 Detached DOM 的 Snapshot

          Performance monitor 工具

          DOM/event listener 泄漏在編寫輪播圖、彈窗、toast 提示這種工具的時候還是很容易出現(xiàn)的,chrome 的 devtools 中有一個 Performance monitor 工具可以用來幫助我們調(diào)查內(nèi)存中是否有 DOM/event listener 泄漏。首先看一下代碼 6:

          代碼 6 不斷增加 DOM NODE

          按照我們圖 19 的方式打開 Performance monitor 面版:

          圖 19 打開 Performance monitor 工具

          DOM Nodes 右側(cè)的數(shù)量是當(dāng)前內(nèi)存中的所有 DOM 節(jié)點(diǎn)的數(shù)量,包括當(dāng)前 document 中存在的和 detached 的以及計算過程中臨時創(chuàng)建的,每當(dāng)我們點(diǎn)擊一次“add date”按鈕,并手動觸發(fā) GC 之后 DOM Nodes 的數(shù)量就 + 2,這是因為我們向 document 中增加了一個 button 節(jié)點(diǎn)和一個 button 的文字節(jié)點(diǎn),就像圖 20 中所示。如果你寫的 toast 組件在臨時插入到 document 并過一會兒執(zhí)行了 remove 之后處于了 detached 狀態(tài)的話,Performance monitor 面版中的 DOM Nodes 數(shù)量就會不斷增加,結(jié)合 snapshot 工具你便可以定位到問題所在了。值得一提的是,有的第三方的庫的 toast 便存在這個問題,不知道你被坑過沒有。

          圖 20 不斷增加的 DOM Nodes

          4 console

          這一點(diǎn)可能有人不會留意到,控制臺打印的內(nèi)容是需要始終保持引用的存在的,這一點(diǎn)也是值得注意的,因為打印過多過大對象的話也是會造成內(nèi)存泄漏的,如圖 21 所示(配合代碼 7)。解決方法便是不要肆意打印對象到控制臺中,只打印必要的信息出來。

          代碼 7 console 導(dǎo)致內(nèi)存泄漏

          圖 21 console 導(dǎo)致的內(nèi)存泄漏

          三 總結(jié)

          本文用了幾個簡單的小例子介紹了內(nèi)存泄漏出現(xiàn)的時機(jī)、尋找泄漏點(diǎn)的方法并將各種方法的優(yōu)缺點(diǎn)進(jìn)行了對比,總結(jié)了避免出現(xiàn)內(nèi)存泄漏的注意點(diǎn)。希望能對讀者有所幫助。文中如果有本人理解錯誤或書寫錯誤的地方歡迎留言指正。

          參考

          https://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art038
          https://developer.chrome.com/docs/devtools/memory-problems/
          https://www.bitdegree.org/learn/chrome-memory-tab

          作者 | 木及

          原文鏈接:http://click.aliyun.com/m/1000287965/

          本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。


          主站蜘蛛池模板: 无码人妻精品一区二区蜜桃| 无码日韩AV一区二区三区| 亚洲字幕AV一区二区三区四区| 无码一区18禁3D| 日本成人一区二区| 高清无码一区二区在线观看吞精| 亚洲第一区视频在线观看| 一本AV高清一区二区三区| 久久久人妻精品无码一区| 成人免费一区二区无码视频 | 蜜桃无码AV一区二区| 精品国产免费一区二区三区| 精品国产区一区二区三区在线观看 | 国产日韩一区二区三区在线播放| 中文字幕日韩人妻不卡一区| 亚洲AV成人精品日韩一区18p | 国产美女视频一区| 精品视频一区二区| 国产av一区二区精品久久凹凸| 亚洲av日韩综合一区久热| 97久久精品无码一区二区天美 | 果冻传媒董小宛一区二区| 丝袜人妻一区二区三区网站| 曰韩精品无码一区二区三区| 日韩AV无码一区二区三区不卡毛片| 亚洲一区二区精品视频| 99久久精品国产免看国产一区| 中文字幕永久一区二区三区在线观看| 久久久99精品一区二区| 激情综合一区二区三区| 国产一区二区中文字幕| 久久久久99人妻一区二区三区| 台湾无码一区二区| 亚洲欧洲无码一区二区三区| 动漫精品第一区二区三区| 日韩精品无码视频一区二区蜜桃| 久久精品一区二区三区四区 | 一区二区三区四区精品视频| 精品国产一区二区三区麻豆| 韩国精品福利一区二区三区| 视频一区视频二区在线观看|