前言
Profiles面板功能的作用主要是監控網頁中各種方法執行時間和記憶體的變化,簡單來說它就是Timeline的數字化版本。它的功能選項卡不是很多(只有三個),操作起來比較前面的幾塊功能版本來說簡單,但是裡面的資料確很多,很雜,要弄懂它們需要花費一些時間。尤其是在記憶體快照中的各種龐雜的資料。在這篇部落格中滷煮將繼續給大家分享Chrome開發者工具的使用經驗。如果你遇到不懂的地方或者有不對的地方,可以在評論中回覆滷煮,文章最後滷煮會最後把秘籍交出來。下麵要介紹的是Profiles。首先開啟Profiles面板。
Profiles介面分為左右兩個區域,左邊區域是放檔案的區域,右邊是展示資料的區域。在開始檢測之前可以看到右邊區域有三個選項,它們分別代表者不同的功能:
1.(Collect JavaScript CPU Profile)監控函式執行期花費的時間
2.(Take Heap Snapshot)為當前介面拍一個記憶體快照
3.(Record Heap Allocations)實時監控記錄記憶體變化(物件分配跟蹤)
一、Collect JavaScript CPU Profile(函式收集器)
首先來關註第一個功能,(Collect JavaScript CPU Profile)監控函式執行期花費的時間。講道理不如舉例子,為了更清楚地瞭解它的功能概況,我們可以編寫一個測試列子來觀察它們的作用。這個列子簡單一些,使得我們分析的資料更清晰一些。
</p><br />
<p/><<br />
<p/><br />
<p><button id=”btn”> click me</button></p><br />
<p><script type=”text/javascript”/></p><br />
<p>function a() {</p><br />
<p>console.log(‘hello world’);</p><br />
<p>}</p><br />
</p><br />
<p>function b() {</p><br />
<p>a();</p><br />
<p>}</p><br />
</p><br />
<p>function c() {</p><br />
<p>b();</p><br />
<p>}</p><br />
</p><br />
<p>document.getElementById(‘btn’).addEventListener(‘click’, c, true);</p><br />
<p/><br />
<p/><br />
<p/><br />
<p></p>
</p></blockquote>
<p><p>在右邊區域中選擇Collect JavaScript CPU Profile 選項,點選下方的Start按鈕(也可以點選左邊的黑色圓圈),這時候Chrome會開始記錄網頁的方法執行,然後我們點選介面的按鈕來執行函式。最後再點選右邊區域的Stop按鈕(或者左邊的紅色圓圈),這時監控就結束了。左邊Profiles會列出一個檔案,單擊可以看到如下介面:</p><br />
<p></p><br />
<p style=”text-align: center;”><img class=”” data-ratio=”0.16205533596837945″ data-type=”jpeg” data-w=”” src=”http://pic.ipshop.xyz/wx/6pBFbKtrAX0.jpg”/></p><br />
<p></p><br />
<p>生存了一個資料表格,它們的意義在上圖中已經標記出來了。它記錄的是函式執行的時間以及函式執行的順序。透過右邊區域的型別選項可以切換資料顯示的方式。有正包含關係,逆包含關係,圖表型別三種選項。我們可以選擇其中的圖表型別:</p><br />
<p></p><br />
<p style=”text-align: center;”><img class=”” data-ratio=”0.6126482213438735″ data-type=”jpeg” data-w=”” src=”http://pic.ipshop.xyz/wx/UlV6Gmz8CT.jpg”/></p><br />
<p></p><br />
<p>可以看到這個面板似曾相識,沒錯,它跟之前的TimeLine面板很像,的確,雖然很像,但功能不一樣,不然也就沒必要重覆做了。從上圖可以看到點選按鈕執行的各個函式執行的時間,順序,包含關係和CUP變化等。你可以在生成檔案之後在左邊區域中儲存該檔案記錄,下次只需要在區域2這中點選load按鈕便可以載入出來。也就是說你可以本地永久地記錄該段時間內的方法執行時間。第一個功能大概就這麼多,比較其他兩個來說簡單。</p><br />
<p></p><br />
<p><span style=”color: rgb(192, 0, 0);”><strong>二、Take Heap Snapshot(記憶體快照)</strong></span></p><br />
<p></p><br />
<p>下麵我們來介紹一下第二個功能的用法。第二個功能是給當前網頁拍一個記憶體快照.選擇第二個拍照功能,按下 Take Snapshot 按鈕,給當前的網頁拍下一個記憶體快照,得到如下圖。</p><br />
<p></p><br />
<p style=”text-align: center;”><img class=”” data-ratio=”0.5474308300395256″ data-type=”jpeg” data-w=”” src=”http://pic.ipshop.xyz/wx/H8bVZkXR5tu.jpg”/></p><br />
<p></p><br />
<p>可以看到左邊區域生成個檔案,檔案名下方有數字,表示這個張快照記錄到的記憶體大小(此時為3.2M)。右邊區域是個串列,它分為五列,表頭可以按照數值大小手動排序。在這張表格中列出的一些列數字和標識,以及表頭的意義比較複雜,涉及到一些js和記憶體的知識,我們就先從這些表頭開始瞭解他們。從左到右的順序它們分別表示:</p><br />
<p></p><br />
<ul class=” list-paddingleft-2″ style=”list-style-type: disc;”><br />
<li><br />
<p>Constructor(建構式)表示所有透過該建構式生成的物件</p><br />
</li><br />
<li><br />
<p>Distance 物件到達GC根的最短距離</p><br />
</li><br />
<li><br />
<p>Objects Count 物件的實體數</p><br />
</li><br />
<li><br />
<p>Shallow size 對應建構式生成的物件的shallow sizes(直接佔用記憶體)總數</p><br />
</li><br />
<li><br />
<p>Retained size 展示了對應物件所佔用的最大記憶體</p><br />
</li><br />
</ul><br />
<p></p><br />
<p>CG根!是神馬東西?在google的官方檔案中的建議是CG根不必用到開發者去關心。但是我們在這裡可以簡單說明一下。大家都知道js物件可以互相取用,在某個物件申請了一塊記憶體後,它很可能會被其他物件應用,而其他物件又被另外的物件應用,一層一層,但它們的指標都是指向同一塊記憶體的,我們把這最初取用的那塊記憶體就可以成為GC根。用程式碼表示是這樣的:</p><br />
<p></p><br />
<blockquote><br />
<p>var obj = {a:1};</p><br />
<p>obj.pro = { a : 100 };</p><br />
<p>obj.pro.pro = { b : 200 };</p><br />
<p>var two = obj.pro.pro;</p><br />
<p>//這種情況下 {b:200} 就是被two取用到了,{b:200}物件取用的記憶體就是CG根</p><br />
</blockquote><br />
<p></p><br />
<p>用一張官方的圖可以如下表示:</p><br />
<p></p><br />
<p style=”text-align: center;”><img class=”” data-ratio=”0.6171548117154811″ data-type=”jpeg” data-w=”478″ src=”http://pic.ipshop.xyz/wx/GV9fYmLNeUs.jpg”/></p><br />
<p></p><br />
<p>構成這張關係網的元素有兩種:</p><br />
<p></p><br />
<p>Nodes:節點,對應一個物件,用建立該物件的構造方法來命名</p><br />
<p>Edges:連線線,對應著物件間的取用關係,用物件屬性名來命名</p><br />
<p></p><br />
<p>從上圖你也可以看到了第二列的表頭Dishtance的意義是什麼,沒錯,它指的就是CG根和取用物件之間的距離。根據這條解釋,圖中的物件5到CG根的距離就是2!那麼什麼是直接佔用記憶體(Shallow size)和最大佔用記憶體(Retained size)呢?直接佔用記憶體指的是物件本身佔用的記憶體,因為物件在記憶體中會透過兩種方式存在著,一種是被一個別的物件保留(我們可以說這個物件依賴別的物件)或者被Dom物件這樣的原生物件隱含保留。在這裡直接佔有記憶體指的就是前一種。(通常來講,陣列和字串會保留更多的直接佔有記憶體)。而最大記憶體(Retained size)就是該物件依賴的其他物件所佔用的記憶體。你要明白這些都是官方的解釋,所以即使你覺得雲裡霧裡也是正常的,官方解釋肯定是官腔嘛。按照滷煮自己的理解是這樣的:</p><br />
<p></p><br />
<blockquote><br />
<p>function a() {</p><br />
<p> var obj = [1,2,…….n];</p><br />
<p> return function() {</p><br />
</p><br />
<p>//js作用域的原因,在此閉包執行的背景關係中可以訪問到obj這個物件</p><br />
<p> console.log(obj);</p><br />
<p> }</p><br />
<p>}</p><br />
<p>//正常情況下,a函式執行完畢 obj佔用的記憶體會被回收,但是此處a函式傳回了一個函式運算式(見Tom大叔的部落格函式運算式和函式宣告),其中obj因為js的作用域的特殊性一直存在,所以我們可以說b取用了obj。</p><br />
<p>var b = a();</p><br />
<p>//每次執行b函式的時候都可以訪問到obj,說明記憶體未被回收 所以對於obj來說直接佔用記憶體[1,2,….n], 而b依賴obj,所obj是b的最大記憶體。</p><br />
<p>b()</p><br />
</blockquote><br />
<p></p><br />
<p>在dom中也存在著取用關係:我們透過程式碼來看下這種取用關係:</p><br />
<p></p><br />
<blockquote><br />
<p/><br />
</p><br />
</p><br />
<div id=”refA”/><br />
</p><br />
<ul/><br />
</p><br />
<li><a/></li><br />
</p><br />
<li><a/></li><br />
</p><br />
<li><a id=”#refB”/></li><br />
</p><br />
</blockquote><br />
</p><br />
</p><br />
<div/><br />
</p><br />
<div/><br />
</p><br />
<p/><br />
</p><br />
<p><script/></p><br />
<p> var refA = document.getElementById(‘refA’);</p><br />
<p> var refB = document.getElementById(‘refB’);</p><br />
<p>//refB取用了refA。它們之間是dom樹父節點和子節點的關係。</p><br />
<p/><br />
<p></p><br />
<p>現在,問題來了,如果我現在在dom中移除div#refA會怎麼樣呢?答案是dom記憶體依然存在,因為它被js取用。那麼我把refA變數置為null呢?答案是記憶體依然存在了。因為refB對refA存在取用,所以除非在把refB釋放,否則dom節點記憶體會一直存在瀏覽器中無法被回收掉。上圖:</p><br />
<p></p><br />
<p style=”text-align: center;”><img class=”” data-ratio=”0.3715415019762846″ data-type=”jpeg” data-w=”” src=”http://pic.ipshop.xyz/wx/I1bBoE7Za8I.jpg”/></p><br />
<p></p><br />
<p>所以你看到Constructor這一列中物件如果有紅色背景就表示有可能被JavaScript取用到但是沒有被回收。以上只是滷煮個人理解,如果不對頭,請你一定要提醒滷煮好即時更新,免得誤人子弟!接著上文,Objects Count這一列是什麼意思呢?Objects Count這一列的意義比較好理解,從字面上我們就知道了其意義。就是物件實體化的數量。用程式碼表示就是這樣的:</p><br />
<p></p><br />
<blockquote><br />
<p>var ConstructorFunction = function() {};</p><br />
<p>//建構式</p><br />
<p>var a = new ConstructorFunction();</p><br />
<p>//第一個實體</p><br />
<p>var b = new ConstructorFunction();</p><br />
<p>//第二個實體</p><br />
<p>…….</p><br />
<p>var n = new ConstructorFunction();</p><br />
<p>//第n個實體</p><br />
</blockquote><br />
<p></p><br />
<p>可以看到建構式在上面有n個實體,那麼對應在Objects Count這列裡面就會有數字n。在這裡,ConstructorFunction是我們自己定義的建構式。那麼這些建構式在哪裡呢,聰明的你一定可以猜到就在第一列Constructor中。實際上你可以看到串列中的Constructor這一列,其中多數都是系統級別的建構式,有一部分也是我們自己編寫的:</p><br />
<p></p><br />
<p><strong>global property</strong> – 全域性物件(像 ‘window’)和取用它的物件之間的中間物件。如果一個物件由建構式Person生成並被全域性物件取用,那麼取用路徑就是這樣的:[global] > (global property > Person。這跟一般的直接取用彼此的物件不一樣。我們用中間物件是有效能方面的原因,全域性物件改變會很頻繁,非全域性變數的屬性訪問最佳化對全域性變數來說並不適用。</p><br />
<p><strong>roots</strong> – constructor中roots的內容取用它所選中的物件。它們也可以是由引擎自主建立的一些取用。這個引擎有用於取用物件的快取,但是這些取用不會阻止取用物件被回收,所以它們不是真正的強取用(FIXME)。</p><br />
<p><strong>closure</strong> – 一些函式閉包中的一組物件的取用</p><br />
<p><strong>array, string, number, regexp</strong> – 一組屬性取用了Array,String,Number或正則運算式的物件型別</p><br />
<p><strong>compiled code</strong> – 簡單來說,所有東西都與compoled code有關。Script像一個函式,但其實對應了<script/></p><br />
</div>