在chrome開發者工具中觀察函數調用棧、做用域鏈與閉包

在chrome開發者工具中觀察函數調用棧、做用域鏈與閉包

 

在chrome的開發者工具中,經過斷點調試,咱們可以很是方便的一步一步的觀察JavaScript的執行過程,直觀感知函數調用棧,做用域鏈,變量對象,閉包,this等關鍵信息的變化。所以,斷點調試對於快速定位代碼錯誤,快速瞭解代碼的執行過程有着很是重要的做用,這也是咱們前端開發者必不可少的一個高級技能。html

固然若是你對JavaScript的這些基礎概念[執行上下文,變量對象,閉包,this等]瞭解還不夠的話,想要透徹掌握斷點調試可能會有一些困難。可是好在在前面幾篇文章,我都對這些概念進行了詳細的概述,所以要掌握這個技能,對你們來講,應該是比較輕鬆的。前端

爲了幫助你們對於this與閉包有更好的瞭解,也由於上一篇文章裏對閉包的定義有一點誤差,所以這篇文章裏我就以閉包有關的例子來進行斷點調試的學習,以便你們及時糾正。在這裏認個錯,誤導你們了,求輕噴 ~ ~git

1、基礎概念回顧

函數在被調用執行時,會建立一個當前函數的執行上下文。在該執行上下文的建立階段,變量對象、做用域鏈、閉包、this指向會分別被肯定。而一個JavaScript程序中通常來講會有多個函數,JavaScript引擎使用函數調用棧來管理這些函數的調用順序。函數調用棧的調用順序與棧數據結構一致。github

2、認識斷點調試工具

在儘可能新版本的chrome瀏覽器中(不肯定你用的老版本與個人一致),調出chrome瀏覽器的開發者工具。chrome

界面如圖。編程

599584-56f0737789bb3c36

斷點調試界面

在個人demo中,我把代碼放在app.js中,在index.html中引入。咱們暫時只須要關注截圖中紅色箭頭的地方。在最左側上方,有一排圖標。咱們能夠經過使用他們來控制函數的執行順序。從左到右他們依次是:瀏覽器

  • resume/pause script execution
    恢復/暫停腳本執行
  • step over next function call
    跨過,實際表現是不遇到函數時,執行下一步。遇到函數時,不進入函數直接執行下一步。
  • step into next function call
    跨入,實際表現是不遇到函數時,執行下一步。遇到到函數時,進入函數執行上下文。
  • step out of current function
    跳出當前函數
  • deactivate breakpoints
    停用斷點
  • don‘t pause on exceptions
    不暫停異常捕獲

其中跨過,跨入,跳出是我使用最多的三個操做。數據結構

上圖左側第二個紅色箭頭指向的是函數調用棧(call Stack),這裏會顯示代碼執行過程當中,調用棧的變化。閉包

左側第三個紅色箭頭指向的是做用域鏈(Scope),這裏會顯示當前函數的做用域鏈。其中Local表示當前的局部變量對象,Closure表示當前做用域鏈中的閉包。藉助此處的做用域鏈展現,咱們能夠很直觀的判斷出一個例子中,到底誰是閉包,對於閉包的深刻了解具備很是重要的幫助做用。app

3、斷點設置

在顯示代碼行數的地方點擊,便可設置一個斷點。斷點設置有如下幾個特色:

  • 在單獨的變量聲明(若是沒有賦值),函數聲明的那一行,沒法設置斷點。
  • 設置斷點後刷新頁面,JavaScript代碼會執行到斷點位置處暫停執行,而後咱們就可使用上邊介紹過的幾個操做開始調試了。
  • 當你設置多個斷點時,chrome工具會自動判斷從最先執行的那個斷點開始執行,所以我通常都是設置一個斷點就好了。
4、實例

接下來,咱們藉助一些實例,來使用斷點調試工具,看一看,咱們的demo函數,在執行過程當中的具體表現。

在向下閱讀以前,咱們能夠停下來思考一下,這個例子中,誰是閉包?

這是來自《你不知道的js》中的一個例子。因爲在使用斷點調試過程當中,發現chrome瀏覽器理解的閉包與該例子中所理解的閉包不太一致,所以專門挑出來,供你們參考。我我的更加傾向於chrome中的理解。

  • 第一步:設置斷點,而後刷新頁面。

599584-ed677cf1c64e39e7

設置斷點
  • 第二步:點擊上圖紅色箭頭指向的按鈕(step into),該按鈕的做用會根據代碼執行順序,一步一步向下執行。在點擊的過程當中,咱們要注意觀察下方call stack 與 scope的變化,以及函數執行位置的變化。

一步一步執行,當函數執行到上例子中

599584-1b8e8f6a6cee5b88

baz函數被調用執行,foo造成了閉包

咱們能夠看到,在chrome工具的理解中,因爲在foo內部聲明的baz函數在調用時訪問了它的變量a,所以foo成爲了閉包。這好像和咱們學習到的知識不太同樣。咱們來看看在《你不知道的js》這本書中的例子中的理解。

599584-7a72e8a1b8fdd764

你不知道的js中的例子

書中的註釋能夠明顯的看出,做者認爲fn爲閉包。即baz,這和chrome工具中明顯是不同的。

而在備受你們推崇的《JavaScript高級編程》一書中,是這樣定義閉包。

599584-b30c0aee7668c183

JavaScript高級編程中閉包的定義

599584-ee0c3051f02ec5d8

書中做者將本身理解的閉包與包含函數所區分

這裏chrome中理解的閉包,與我所閱讀的這幾本書中的理解的閉包不同。具體這裏我先不下結論,可是我心中更加偏向於相信chrome瀏覽器。

咱們修改一下demo01中的例子,來看看一個很是有意思的變化。

這個例子在demo01的基礎上,我在baz函數中傳入一個參數,並打印出來。在調用時,我將全局的變量m傳入。輸出結果變爲20。在使用斷點調試看看做用域鏈。

599584-f74b68b5f041ca9e

閉包沒了,做用域鏈中沒有包含foo了。

是否是結果有點意外,閉包沒了,做用域鏈中沒有包含foo了。我靠,跟咱們理解的好像又有點不同。因此經過這個對比,咱們能夠肯定閉包的造成須要兩個條件。

  • 在函數內部建立新的函數;
  • 新的函數在執行時,訪問了函數的變量對象;

還有更有意思的。

咱們繼續來看看一個例子。

在這個例子中,fn只訪問了foo中的a變量,所以它的閉包只有foo。

599584-6e98041bfd2f719f

閉包只有foo

修改一下demo03,咱們在fn中也訪問bar中b變量試試看。

 

599584-431d16611cac1243

這個時候閉包變成了兩個

這個時候,閉包變成了兩個。分別是bar,foo。

咱們知道,閉包在模塊中的應用很是重要。所以,咱們來一個模塊的例子,也用斷點工具來觀察一下。

 

599584-662ec3ce1cf33206

add執行時,閉包爲外層的自執行函數,this指向test

599584-24572d8b5dd381b6

sum執行時,同上

599584-77888095edb980a7

mark執行時,閉包爲外層的自執行函數,this指向test

599584-fedeee99354936a9

_mark執行時,閉包爲外層的自執行函數,this指向window

注意:這裏的this指向顯示爲Object或者Window,大寫開頭,他們表示的是實例的構造函數,實際上this是指向的具體實例

上面的全部調用,最少都訪問了自執行函數中的test變量,所以都能造成閉包。即便mark方法沒有訪問私有變量a,b。

咱們還能夠結合點斷調試的方式,來理解那些困擾咱們好久的this指向。隨時觀察this的指向,在實際開發調試中很是有用。

 

599584-ab511b394be82692

this指向obj

更多的例子,你們能夠自行嘗試,總之,學會了使用斷點調試以後,咱們就可以很輕鬆的瞭解一段代碼的執行過程了。這對快速定位錯誤,快速瞭解他人的代碼都有很是巨大的幫助。你們必定要動手實踐,把它給學會。

最後,根據以上的摸索狀況,再次總結一下閉包:

    • 閉包是在函數被調用執行的時候才被確認建立的。
    • 閉包的造成,與做用域鏈的訪問順序有直接關係。
    • 只有內部函數訪問了上層做用域鏈中的變量對象時,纔會造成閉包,所以,咱們能夠利用閉包來訪問函數內部的變量。
    • chrome中理解的閉包,與《你不知道的js》與《JavaScript高級編程》中的閉包理解有很大不一樣,我我的更加傾向於相信chrome。這裏就不妄下結論了,你們能夠根據個人思路,探索後自行確認。在以前一篇文中我根據從書中學到的下了定義,應該是錯了,目前已經修改,對不起你們了。
相關文章
相關標籤/搜索