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

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

Feb 22, 2017 pm 02:00 PM
作用域鏈 前端 基礎 呼叫堆疊 進階 閉包

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

在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

熱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

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

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

C++ lambda 表達式中閉包的意思是什麼? C++ lambda 表達式中閉包的意思是什麼? Apr 17, 2024 pm 06:15 PM

在C++中,閉包是能夠存取外部變數的lambda表達式。若要建立閉包,請擷取lambda表達式中的外部變數。閉包提供可重複使用性、資訊隱藏和延遲求值等優點。它們在事件處理程序等實際情況中很有用,其中即使外部變數被銷毀,閉包仍然可以存取它們。

C++ Lambda 表達式如何實作閉包? C++ Lambda 表達式如何實作閉包? Jun 01, 2024 pm 05:50 PM

C++Lambda表達式支援閉包,即保存函數作用域變數並供函數存取。語法為[capture-list](parameters)->return-type{function-body}。 capture-list定義要捕獲的變量,可以使用[=]按值捕獲所有局部變量,[&]按引用捕獲所有局部變量,或[variable1,variable2,...]捕獲特定變量。 Lambda表達式只能存取捕獲的變量,但無法修改原始值。

C++ 函式中閉包的優點和缺點是什麼? C++ 函式中閉包的優點和缺點是什麼? Apr 25, 2024 pm 01:33 PM

閉包是一種巢狀函數,它能存取外層函數作用域的變量,優點包括資料封裝、狀態保持和靈活性。缺點包括記憶體消耗、效能影響和調試複雜性。此外,閉包還可以建立匿名函數,並將其作為回調或參數傳遞給其他函數。

Go語言前端技術探秘:前端開發新視野 Go語言前端技術探秘:前端開發新視野 Mar 28, 2024 pm 01:06 PM

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

golang函數閉包在測試中的作用 golang函數閉包在測試中的作用 Apr 24, 2024 am 08:54 AM

Go語言函數閉包在單元測試中發揮著至關重要的作用:捕獲值:閉包可以存取外部作用域的變量,允許在巢狀函數中捕獲和重複使用測試參數。簡化測試程式碼:透過擷取值,閉包消除了對每個循環重複設定參數的需求,從而簡化了測試程式碼。提高可讀性:使用閉包可以組織測試邏輯,使測試程式碼更清晰、更易於閱讀。

PHP 函數的鍊式呼叫與閉包 PHP 函數的鍊式呼叫與閉包 Apr 13, 2024 am 11:18 AM

是的,可以透過鍊式呼叫和閉包優化程式碼簡潔性和可讀性:鍊式呼叫可將函數呼叫連結為一個流暢介面。閉包可建立可重複使用程式碼區塊,並在函數外部存取變數。

函數指標和閉包對Golang效能的影響 函數指標和閉包對Golang效能的影響 Apr 15, 2024 am 10:36 AM

函數指針和閉包對Go性能的影響如下:函數指針:稍慢於直接調用,但可提高可讀性和可復用性。閉包:通常更慢,但可封裝資料和行為。實戰案例:函數指標可最佳化排序演算法,閉包可建立事件處理程序,但會帶來效能損失。

閉包在 Java 中是如何實現的? 閉包在 Java 中是如何實現的? May 03, 2024 pm 12:48 PM

Java中的閉包允許內部函數存取外部的作用域變量,即使外部函數已經退出。透過匿名內部類別實現,內部類別持有一個外部類別的引用,使外部變數保持活動。閉包增強了程式碼靈活性,但需要注意記憶體洩漏風險,因為匿名內部類別對外部變數的參考會保持這些變數的活動狀態。

See all articles