搜尋
首頁web前端js教程前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

在前端開發中,有一個非常重要的技能,叫做斷點調試。

在chrome的開發者工具中,透過斷點調試,我們能夠非常方便的一步一步的觀察JavaScript的執行過程,直觀感知函數調用棧,作用域鏈,變量對象,閉包,this等關鍵訊息的變化。因此,斷點偵錯對於快速定位程式碼錯誤,快速了解程式碼的執行過程有著非常重要的作用,這也是我們前端開發者必不可少的一個高階技能。

當然如果你對JavaScript的這些基礎概念[執行上下文,變數對象,閉包,this等]了解還不夠的話,想要透徹掌握斷點調試可能會有一些困難。但好在前面幾篇文章,我都對這些概念進行了詳細的概述,因此要掌握這個技能,對大家來說,應該是比較輕鬆的。

為了幫助大家對於this與閉包有更好的了解,也因為上一篇文章裡對閉包的定義有一點偏差,因此這篇文章裡我就以閉包有關的例子來進行斷點調試的學習,以便大家及時修正。在這裡認個錯,誤導大家了。


一、基礎概念回顧

##函數在被呼叫執行時,會建立一個目前函數的執行上下文。在該執行上下文的建立階段,變數物件、作用域鏈、閉包、this指向會分別被確定。而一個JavaScript程式中一般來說會有多個函數,JavaScript引擎使用函數呼叫堆疊來管理這些函數的呼叫順序。函數呼叫棧的呼叫順序與堆疊資料結構一致。


二、認識斷點偵錯工具

在盡量新版的chrome瀏覽器中(不確定你用的舊版與我的一致),調出chrome瀏覽器的開發者工具。

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


介面如圖。

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

在我的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表示目前作用域鏈中的閉包。借助此處的作用域鏈展示,我們可以很直觀的判斷出一個例子中,到底誰是閉包,對於閉包的深入了解具有非常重要的幫助作用。


三、斷點設定

在顯示程式碼行數的地方點擊,即可設定一個斷點。斷點設定有以下幾個特點:

在單獨的變數宣告(如果沒有賦值),函數宣告的那一行,無法設定斷點。

設定斷點後重新整理頁面,JavaScript程式碼會執行到斷點位置處暫停執行,然後我們就可以使用上邊介紹過的幾個操作開始偵錯了。

當你設定多個斷點時,chrome工具會自動判斷從最早執行的那個斷點開始執行,因此我通常都是設定一個斷點就行了。


四、實例
##接下來,我們借助一些實例,來使用斷點偵錯工具,看一看,我們的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中的理解。

● 第一步:设置断点,然后刷新页面。

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包


● 第二步:点击上图红色箭头指向的按钮(step into),该按钮的作用会根据代码执行顺序,一步一步向下执行。在点击的过程中,我们要注意观察下方call stack 与 scope的变化,以及函数执行位置的变化。

一步一步执行,当函数执行到上例子中

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

我们可以看到,在chrome工具的理解中,由于在foo内部声明的baz函数在调用时访问了它的变量a,因此foo成为了闭包。这好像和我们学习到的知识不太一样。我们来看看在《你不知道的js》这本书中的例子中的理解。

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

书中的注释可以明显的看出,作者认为fn为闭包。即baz,这和chrome工具中明显是不一样的。

而在备受大家推崇的《JavaScript高级编程》一书中,是这样定义闭包。

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

这里chrome中理解的闭包,与我所阅读的这几本书中的理解的闭包不一样。具体这里我先不下结论,但是我心中更加偏向于相信chrome浏览器。

我们修改一下demo01中的例子,来看看一个非常有意思的变化。

// 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。在使用断点调试看看作用域链。

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

是不是结果有点意外,闭包没了,作用域链中没有包含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。

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

修改一下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();

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

这个时候,闭包变成了两个。分别是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();

前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

1前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

1前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包


1前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

注意:这里的this指向显示为Object或者Window,大写开头,他们表示的是实例的构造函数,实际上this是指向的具体实例

上面的所有调用,最少都访问了自执行函数中的test变量,因此都能形成闭包。即使mark方法没有访问私有变量a,b。

我们还可以结合点断调试的方式,来理解那些困扰我们很久的this指向。随时观察this的指向,在实际开发调试中非常有用。

// demo06

var a = 10;
var obj = {
    a: 20
}

function fn () {
    console.log(this.a);
}

fn.call(obj); // 20

1前端基礎進階(六):在chrome開發者工具中觀察函數呼叫堆疊、作用域鏈與閉包

更多的例子,大家可以自行尝试,总之,学会了使用断点调试之后,我们就能够很轻松的了解一段代码的执行过程了。这对快速定位错误,快速了解他人的代码都有非常巨大的帮助。大家一定要动手实践,把它给学会。

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

閉包是在函數被呼叫執行的時候才被確認創建的。

閉包的形成,與作用域鏈的存取順序有直接關係。

只有內部函數存取了上層作用域鏈中的變數物件時,才會形成閉包,因此,我們可以利用閉包來存取函數內部的變數。

chrome中理解的閉包,與《你不知道的js》與《JavaScript高級編程》中的閉包理解有很大不同,我個人更傾向於相信chrome。這裡就不妄下結論了,大家可以根據我的思路,探索後自行確認。在之前一篇文中我根據從書中學到的下了定義,應該是錯了,目前已經修改,對不起大家了。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具