爬蟲逆向 | 經過斷點調試觀察JS執行過程


點擊「Python編程與實戰」,選擇「置頂公衆號」
javascript

第一時間獲取 Python 技術乾貨!html


前端開發中,有一個重要的技能,叫作斷點調試前端

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

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

這篇文章的另外一個目的在於藉助對於斷點調試的學習,進一步加深對閉包的理解。web

1、基礎概念回顧

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

2、認識斷點調試工具

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

瀏覽器右上角豎着的三點 -> 更多工具 -> 開發者工具 -> Sources瀏覽器

界面如圖。微信

在個人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表示當前做用域鏈中的閉包。藉助此處的做用域鏈展現,咱們能夠很直觀的判斷出一個例子中,到底誰是閉包,對於閉包的深刻了解具備很是重要的幫助做用。

3、斷點設置

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

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

4、實例

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

// demo01
var fn;function foo() { var a = 2; function baz() { console.log(a); } fn = baz;}function bar() { fn();}
foo();bar(); // 2

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

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

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

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

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

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

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

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

這裏chrome中理解的閉包,與我所閱讀的這幾本書中的理解的閉包不同。其實在以前對於閉包分析的文章中,我已經有對這種狀況作了一個解讀。閉包詳解[1]

閉包是一個特殊對象,它由執行上下文(代號A)與在該執行上下文中建立的函數(代號B)共同組成。

當B執行時,若是訪問了A中變量對象中的值,那麼閉包就會產生。

在大多數理解中,包括許多著名的書籍,文章裏都以函數B的名字代指這裏生成的閉包。而在chrome中,則以執行上下文A的函數名代指閉包。

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

// demo02var fn;var m = 20;function foo() { var a = 2; function baz(a) { console.log(a); } fn = baz;}function bar() { fn(m);}
foo();bar(); // 20

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

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

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

還有更有意思的。

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

// demo03
function foo() { var a = 2;
return function bar() { var b = 9;
return function fn() { console.log(a); } }}
var bar = foo();var fn = bar();fn();

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

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

// demo04
function foo() { var a = 2;
return function bar() { var b = 9;
return function fn() { console.log(a, b); } }}
var bar = foo();var fn = bar();fn();

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

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

// demo05(function () {
var a = 10; var b = 20;
var test = { m: 20, add: function (x) { return a + x; }, sum: function () { return a + b + this.m; }, mark: function (k, j) { return k + j; } }
window.test = test;
})();
test.add(100);test.sum();test.mark();
var _mark = test.mark;_mark();

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

test.mark能造成閉包,跟下面的補充例子(demo07)狀況是同樣的。

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

// demo06
var a = 10;var obj = { a: 20}
function fn() { console.log(this.a);}
fn.call(obj); // 20

最後繼續補充一個例子。

// demo07function foo() { var a = 10;
function fn1() { return a; }
function fn2() { return 10; }
fn2();}
foo();

這個例子,和其餘例子不太同樣。雖然fn2並無訪問到foo的變量,可是foo執行時仍然變成了閉包。而當我將fn1的聲明去掉時,閉包便不會出現了。

那麼結合這個特殊的例子,咱們能夠這樣這樣定義閉包。

閉包是指這樣的做用域(foo),它包含有一個函數(fn1),這個函數(fn1)能夠調用被這個做用域所封閉的變量(a)、函數、或者閉包等內容。一般咱們經過閉包所對應的函數來得到對閉包的訪問。

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

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

閉包是在函數被調用執行的時候才被確認建立的。閉包的造成,與做用域鏈的訪問順序有直接關係。只有內部函數訪問了上層做用域鏈中的變量對象時,纔會造成閉包,所以,咱們能夠利用閉包來訪問函數內部的變量。

你們也能夠根據我提供的這個方法,對其餘的例子進行更多的測試。以增強對閉包的理解

   
     
 
    
    
     
     
     
 
    

推薦閱讀


19 個接私活平臺彙總升級版,你有技術就有錢

一個普通程序員,靠 GitHub 打賞就年入 70 萬,要不你也試試



THANKS

- End -



點個「在看」必升職加薪喔!

本文分享自微信公衆號 - Python編程與實戰(GoPy1024)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索