JavaScript詞法作用域與呼叫物件深入理解_javascript技巧
關於 Javascript 的函數作用域、呼叫物件和閉包之間的關係很微妙,關於它們的文章已經有很多,但不知道為什麼很多新手都難以理解。我就嘗試用比較通俗的語言來表達我自己的理解吧。
作用域 Scope
Javascript 中的函數屬於詞法作用域,也就是說函數在它被定義時的作用域中運作而不是在被執行時的作用域內運作。這是犀牛書上的說法。但"定義時"和"執行(被調用)時"這兩個東西有些人搞不清楚。簡單來說,一個函數A在"定義時"就是 function A(){} 這個語句執行的時候就是定義這個函數的時候,而A被呼叫的時候是 A() 這個語句執行的時候。這兩個概念一定要分清楚。
那詞法作用域(以下稱為"作用域",除非特別指明)到底是什麼呢?它是個抽象的概念,說白了它就是一個"範圍",scope 在英文裡就是範圍的意思。一個函數的作用域是它被定義時它所處的"範圍",也就是它外層的"範圍",這個"範圍"包含了外層的變數屬性,這個"範圍"被設定成這個函數的一個內部狀態。一個全域函數被定義的時候,全域(這個函數的外層)的"範圍"就被設定成這個全域函數的一個內部狀態。一個巢狀函數被定義的時候,被巢狀函數(外層函數)的"範圍"就被設定成這個巢狀函數的一個內部狀態。這個"內部狀態"其實可以理解成作用域鏈,見下文。
照以上說法,一個函數的作用域是它被定義的時候所處的"範圍",那麼Javascript 裡的函數作用域是在函數被定義的時候就確定了,所以它是靜態的作用域,詞法作用域又稱為靜態作用域。
呼叫物件 Call Object
一個函數的呼叫物件是動態的,它是在這個函數被呼叫時才被實例化的。我們已經知道,當一個函數被定義的時候,已經確定了它的作用域鏈。當 Javascript 解譯器呼叫一個函數的時候,它會加入一個新的物件(呼叫物件)到這個作用域鏈的前面。這個呼叫對象的一個屬性被初始化成一個名為 arguments 的屬性,它引用了這個函數的 Arguments 對象,Arguments 物件是函數的實際參數。所有用 var 語句宣告的本地變數也被定義在這個呼叫物件裡。這時候,呼叫物件處在作用域鏈的頭部,本地變數、函數形式參數和 Arguments 物件全部都在這個函數的範圍裡了。當然,這個時候本地變數、函數形式參數和 Arguments 物件就覆蓋了作用域鏈裡同名的屬性。
作用域、作用域鍊與呼叫物件之間的關係
我的理解是,作用域是是抽象的,而呼叫物件是實例化的。
在函數被定義的時候,實際上也是它外層函數執行的時候,它確定的作用域鏈其實是它外層函數的呼叫物件鏈;當函數被呼叫時,它的作用域鏈是根據定義的時候確定的作用域鏈(它外層函數的呼叫物件鏈)加上一個實例化的呼叫物件。所以函數的作用域鏈其實是呼叫物件鏈。在一個函數被呼叫的時候,它的作用域鏈(或稱為呼叫物件鏈)實際上是它在被定義的時候所確定的作用域鏈的一個超集。
它們之間的關係可以表示成:作用域?作用域鏈?呼叫物件。
太繞口了,舉例說明吧:
function f(x) {
var g = function () { return x; }
return g;
}
var g1 = f(1);
alert(g1()) ; //輸出1
假設我們把全域看成類似以下這樣的大匿名函數:
(function() {
//這裡是全域範圍
})();
那麼例子就可以看成是:
(function() {
function f(x) {
var g = function () { return x; }
return g;
}
var g1 = f(1);
alert(g1()); //輸出1
})();
全域的大匿名函數被定義的時候,它沒有外層,所以它的作用域鍊是空的。
全域的大匿名函數直接被執行,全域的作用域鏈裡只有一個 '全域呼叫物件'。
函數 f 被定義,此時函數 f 的作用域鍊是它外層的作用域鏈,即 '全域呼叫物件'。
函數f(1) 被執行,它的作用域鍊是新的f(1) 呼叫物件加上函數f 被定義的時候的作用域鏈,即'f(1) 呼叫物件->全域調用對象'。
函數g (它要被回傳給g1,就命名為g1吧)在f(1) 中被定義,它的作用域鍊是它外層的函數f(1) 的作用域鏈,即' f(1) 呼叫物件->全域呼叫物件'。
函數 f(1) 傳回函數 g 的定義給 g1。
函數g1 被執行,它的作用域鍊是新的g(1) 呼叫物件加上外層f(1) 的作用域鏈,即'g1 呼叫物件->f(1)呼叫物件->全域調用對象'。
這樣看就很清楚了吧。
閉包 Closuer
閉包的一個簡單的說法是,當巢狀函數在被巢狀函數之外呼叫的時候,就形成了閉包。
之前的這個例子其實就是一個閉包。 g1 是在 f(1) 內部定義的,卻在 f(1) 回傳後才執行。可以看出,閉包的一個效果就是被巢狀函數 f 回傳後,它內部的資源不會被釋放。在外部呼叫 g 函數時,g 可以存取 f 的內部變數。根據這個特性,可以寫出很多優雅的程式碼。
例如要在一個頁面上作一個統一的計數器,如果用閉包的寫法,可以這麼寫:
複製程式碼
程式碼如下:
var counter = (function() {
var i = 0;
var fns = {"get": function() {return i;},
"inc": function() {return i;}};
return fns;
})();
//do something
counter.inc();
counter.inc();
var c_value = counter.get(); //now c_value is 2
程式碼如下:
for(var i=0,delay=1000; setTimeout(function() {
}, delay);
}
這樣,印出來的值都是
i:5 delay:6000
i:5 delay:6000
i:5 delay:6000
程式碼如下:
for(var i=0, delay=1000; i (function(a, _delay) {
setTimeout(function() {
console.log('i:' a " delay:" _delay);
})(i, delay);
}
輸出:
i:0 delay:1000
i:1 delay:2000
i:2 delay:3000 i:3 delay: 4000 i:4 delay:5000
閉包還有一個很常用的地方,就是在綁定事件的回呼函數的時候。也是同樣的道理,綁定的函數句柄不能做參數,但可以透過閉包的形式把參數綁定進去。
總結
函數的詞法作用域和作用域鍊是不同的東西,詞法作用域是抽象概念,作用域鍊是實例化的呼叫物件鏈。
函數在被定義的時候,同時也是它外層的函數在被執行的時候。 函數在被定義的時候它的詞法作用域就已經確定了,但它仍然是抽象的概念,沒有也不能被實例化。 函數在被定義的時候還確定了一個東西,就是它外層函數的作用域鏈,這個是實例化的東西。 函數在被多次呼叫的時候,它的作用域鏈都是不同的。 閉包很強。犀牛書說得對,理解了這些東西,你就可以自稱是高階 Javascript 程式設計師了。因為利用好這些概念,可以玩 Javascript 的許多設計模式。

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

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

如何使用Python呼叫百度地圖API實作地理位置查詢功能?隨著網路的發展,地理位置資訊的取得和利用越來越重要。百度地圖是一款非常常見且實用的地圖應用,它提供了豐富的地理位置查詢服務。本文將介紹如何使用Python呼叫百度地圖API實作地理位置查詢功能,並附上程式碼範例。申請百度地圖開發者帳號和應用程式首先,你需要擁有一個百度地圖開發者帳號,並建立一個應用程式。登入

PHP攝影機呼叫技巧:如何實現多攝影機切換攝影機應用已成為許多Web應用的重要組成部分,例如視訊會議、即時監控等等。在PHP中,我們可以使用各種技術來實現對攝影機的呼叫和操作。本文將重點放在如何實現多鏡頭的切換,並提供一些範例程式碼來幫助讀者更好地理解。攝影機呼叫基礎在PHP中,我們可以透過呼叫JavaScript的API來實現攝影機的呼叫。具體來說,我們

楔子我們知道物件被創建,主要有兩種方式,一種是透過Python/CAPI,另一種是透過呼叫類型物件。對於內建類型的實例物件而言,這兩種方式都是支援的,例如列表,我們即可以透過[]創建,也可以透過list(),前者是Python/CAPI,後者是呼叫類型物件。但對於自訂類別的實例物件而言,我們只能透過呼叫類型物件的方式來創建。而一個物件如果可以被調用,那麼這個物件就是callable,否則就不是callable。而決定一個物件是不是callable,就取決於其對應的型別物件中是否定義了某個方法。如

如何解決PHP開發中的外部資源存取和調用,需要具體程式碼範例在PHP開發中,我們經常會遇到需要存取和調用外部資源的情況,例如API介面、第三方庫或其他伺服器資源。在處理這些外部資源時,我們需要考慮如何進行安全的存取和調用,同時確保效能和可靠性。本文將介紹幾種常見的解決方案,並提供相應的程式碼範例。一、使用curl函式庫進行外部資源呼叫curl是一個非常強大的開源函式庫

如何透過Python程式呼叫百度地圖API實現地圖展示功能?隨著網路的快速發展,地圖應用成為了我們生活中不可或缺的一部分。而百度地圖作為國內最大的地圖應用之一,為我們提供了豐富的服務和API接口,可以很方便地實現地圖展示功能。本文將介紹如何透過Python程式呼叫百度地圖API來實現地圖展示功能,並給出對應的程式碼範例。首先,我們需要在百度開放平台上註冊一個

有很多朋友還不知道matlab如何呼叫m文件,所以下面小編就講解了matlab調用m文件的方法,有需要的小伙伴趕緊來看一下吧,相信對大家一定會有所幫助哦。 1.先開啟matlab軟體,在主介面中點選“開啟”,如下圖所示。 2、然後選擇一個需要打開的m文件,選擇打開,如下圖。 3.接著在編輯器中看m檔案的檔名和變數數目,如下圖。 4.可以在命令列中輸入m檔名後括號加變數值,就可以調用,如下圖所示。 5.最後就可以成功呼叫m文件,如下圖所示。上面就是小編為大家帶來的matlab如何呼叫m檔的全

使用Java呼叫WebService的方法步驟,需要具體程式碼範例Web服務是一種基於Web的應用程式接口,透過網路提供各種功能。在Java開發中,我們經常需要使用Web服務來實現與其他系統的互動。本篇文章將介紹如何使用Java呼叫WebService,並提供具體的程式碼範例。一、了解WebServiceWebService是一種標準化的通訊協議,使用XML格式

如何透過PHP調用攝影機進行車牌辨識引言:隨著科技的不斷發展,車牌辨識技術在交通管理、安防監控等領域發揮重要作用。本文將介紹如何使用PHP編寫程序,透過呼叫攝影機實現車牌辨識功能。一、概述車牌辨識技術主要涉及影像處理及機器學習演算法。傳統的車牌辨識方案使用C++或Python等程式語言進行開發。然而,由於PHP作為一種被廣泛應用於Web開發的腳本語言,開發人
