在chrome的開發者工具中,經過斷點調試,咱們可以很是方便的一步一步的觀察JavaScript的執行過程,直觀感知函數調用棧,做用域鏈,變量對象,閉包,this等關鍵信息的變化。所以,斷點調試對於快速定位代碼錯誤,快速瞭解代碼的執行過程有着很是重要的做用,這也是咱們前端開發者必不可少的一個高級技能。html
固然若是你對JavaScript的這些基礎概念[執行上下文,變量對象,閉包,this等]瞭解還不夠的話,想要透徹掌握斷點調試可能會有一些困難。可是好在在前面幾篇文章,我都對這些概念進行了詳細的概述,所以要掌握這個技能,對你們來講,應該是比較輕鬆的。前端
爲了幫助你們對於this與閉包有更好的瞭解,也由於上一篇文章裏對閉包的定義有一點誤差,所以這篇文章裏我就以閉包有關的例子來進行斷點調試的學習,以便你們及時糾正。在這裏認個錯,誤導你們了,求輕噴 ~ ~git
函數在被調用執行時,會建立一個當前函數的執行上下文。在該執行上下文的建立階段,變量對象、做用域鏈、閉包、this指向會分別被肯定。而一個JavaScript程序中通常來講會有多個函數,JavaScript引擎使用函數調用棧來管理這些函數的調用順序。函數調用棧的調用順序與棧數據結構一致。github
在儘可能新版本的chrome瀏覽器中(不肯定你用的老版本與個人一致),調出chrome瀏覽器的開發者工具。chrome
1
|
瀏覽器右上角豎着的三點
-> 更多工具 -> 開發者工具 -> Sources
|
界面如圖。編程
在個人demo中,我把代碼放在app.js中,在index.html中引入。咱們暫時只須要關注截圖中紅色箭頭的地方。在最左側上方,有一排圖標。咱們能夠經過使用他們來控制函數的執行順序。從左到右他們依次是:瀏覽器
其中跨過,跨入,跳出是我使用最多的三個操做。數據結構
上圖左側第二個紅色箭頭指向的是函數調用棧(call Stack),這裏會顯示代碼執行過程當中,調用棧的變化。閉包
左側第三個紅色箭頭指向的是做用域鏈(Scope),這裏會顯示當前函數的做用域鏈。其中Local表示當前的局部變量對象,Closure表示當前做用域鏈中的閉包。藉助此處的做用域鏈展現,咱們能夠很直觀的判斷出一個例子中,到底誰是閉包,對於閉包的深刻了解具備很是重要的幫助做用。app
在顯示代碼行數的地方點擊,便可設置一個斷點。斷點設置有如下幾個特色:
接下來,咱們藉助一些實例,來使用斷點調試工具,看一看,咱們的demo函數,在執行過程當中的具體表現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// demo01
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz;
}
function bar() {
fn();
}
foo();
bar(); // 2
|
在向下閱讀以前,咱們能夠停下來思考一下,這個例子中,誰是閉包?
這是來自《你不知道的js》中的一個例子。因爲在使用斷點調試過程當中,發現chrome瀏覽器理解的閉包與該例子中所理解的閉包不太一致,所以專門挑出來,供你們參考。我我的更加傾向於chrome中的理解。
一步一步執行,當函數執行到上例子中
咱們能夠看到,在chrome工具的理解中,因爲在foo內部聲明的baz函數在調用時訪問了它的變量a,所以foo成爲了閉包。這好像和咱們學習到的知識不太同樣。咱們來看看在《你不知道的js》這本書中的例子中的理解。
書中的註釋能夠明顯的看出,做者認爲fn爲閉包。即baz,這和chrome工具中明顯是不同的。
而在備受你們推崇的《JavaScript高級編程》一書中,是這樣定義閉包。
這裏chrome中理解的閉包,與我所閱讀的這幾本書中的理解的閉包不同。具體這裏我先不下結論,可是我心中更加偏向於相信chrome瀏覽器。
咱們修改一下demo01中的例子,來看看一個很是有意思的變化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// demo02
var 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了。我靠,跟咱們理解的好像又有點不同。因此經過這個對比,咱們能夠肯定閉包的造成須要兩個條件。
還有更有意思的。
咱們繼續來看看一個例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 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變量試試看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 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。
咱們知道,閉包在模塊中的應用很是重要。所以,咱們來一個模塊的例子,也用斷點工具來觀察一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// 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方法沒有訪問私有變量a,b。
咱們還能夠結合點斷調試的方式,來理解那些困擾咱們好久的this指向。隨時觀察this的指向,在實際開發調試中很是有用。
1
2
3
4
5
6
7
8
9
10
11
12
|
// demo06
var a = 10;
var obj = {
a: 20
}
function fn () {
console.log(this.a);
}
fn.call(obj); // 20
|
更多的例子,你們能夠自行嘗試,總之,學會了使用斷點調試以後,咱們就可以很輕鬆的瞭解一段代碼的執行過程了。這對快速定位錯誤,快速瞭解他人的代碼都有很是巨大的幫助。你們必定要動手實踐,把它給學會。
最後,根據以上的摸索狀況,再次總結一下閉包: