4.2 執行環境及作用域【JavaScript高級程式設計第三版】

来源:http://www.cnblogs.com/itzhoubao/archive/2017/06/02/6931780.html
-Advertisement-
Play Games

執行環境(execution context,為簡單起見,有時也稱為“環境”)是JavaScript 中最為重要的一個概念。執行環境定義了變數或函數有權訪問的其他數據,決定了它們各自的行為。每個執行環境都有一個與之關聯的變數對象(variable object),環境中定義的所有變數和函數都保存在這 ...


執行環境(execution context,為簡單起見,有時也稱為“環境”)是JavaScript 中最為重要的一個概念。執行環境定義了變數或函數有權訪問的其他數據,決定了它們各自的行為。每個執行環境都有一個與之關聯的變數對象(variable object),環境中定義的所有變數和函數都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數據時會在後臺使用它。

全局執行環境是最外圍的一個執行環境。根據ECMAScript 實現所在的宿主環境不同,表示執行環境的對象也不一樣。在Web 瀏覽器中,全局執行環境被認為是window 對象(第7 章將詳細討論),因此所有全局變數和函數都是作為window 對象的屬性和方法創建的。某個執行環境中的所有代碼執行完畢後,該環境被銷毀,保存在其中的所有變數和函數定義也隨之銷毀(全局執行環境直到應用程式退出——例如關閉網頁或瀏覽器——時才會被銷毀)。

每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。ECMAScript 程式中的執行流正是由這個方便的機制控制著。

當代碼在一個環境中執行時,會創建變數對象的一個作用域鏈(scope chain)。作用域鏈的用途,是保證對執行環境有權訪問的所有變數和函數的有序訪問。作用域鏈的前端,始終都是當前執行的代碼所在環境的變數對象。如果這個環境是函數,則將其活動對象(activation object)作為變數對象。活動對象在最開始時只包含一個變數,即arguments 對象(這個對象在全局環境中是不存在的)。作用域鏈中的下一個變數對象來自包含(外部)環境,而再下一個變數對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變數對象始終都是作用域鏈中的最後一個對象。

標識符解析是沿著作用域鏈一級一級地搜索標識符的過程。搜索過程始終從作用域鏈的前端開始,然後逐級地向後回溯,直至找到標識符為止(如果找不到標識符,通常會導致錯誤發生)。

請看下麵的示例代碼:

var color = "blue";
function changeColor() {
	if (color === "blue") {
		color = "red";
	} else {
		color = "blue";
	}
}
changeColor();
alert("Color is now " + color);

運行一下

在這個簡單的例子中,函數changeColor()的作用域鏈包含兩個對象:它自己的變數對象(其中定義著arguments 對象)和全局環境的變數對象。可以在函數內部訪問變數color,就是因為可以在這個作用域鏈中找到它。

此外,在局部作用域中定義的變數可以在局部環境中與全局變數互換使用,如下麵這個例子所示:

var color = "blue";
function changeColor() {
	var anotherColor = "red";
	function swapColors() {
		var tempColor = anotherColor;
		anotherColor = color;
		color = tempColor;
		// 這裡可以訪問color、anotherColor 和tempColor
	}
	// 這裡可以訪問color 和anotherColor,但不能訪問tempColor
	swapColors();
}
// 這裡只能訪問color
changeColor();

以上代碼共涉及3 個執行環境:全局環境、changeColor()的局部環境和swapColors()的局部環境。全局環境中有一個變數color 和一個函數changeColor()。changeColor()的局部環境中有一個名為anotherColor 的變數和一個名為swapColors()的函數,但它也可以訪問全局環境中的變數color。swapColors()的局部環境中有一個變數tempColor,該變數只能在這個環境中訪問到。

無論全局環境還是changeColor()的局部環境都無權訪問tempColor。然而,在swapColors()內部則可以訪問其他兩個環境中的所有變數,因為那兩個環境是它的父執行環境。圖4-3 形象地展示了前面這個例子的作用域鏈。

圖4-3 中的矩形表示特定的執行環境。其中,內部環境可以通過作用域鏈訪問所有的外部環境,但外部環境不能訪問內部環境中的任何變數和函數。這些環境之間的聯繫是線性、有次序的。每個環境都可以向上搜索作用域鏈,以查詢變數和函數名;但任何環境都不能通過向下搜索作用域鏈而進入另一個執行環境。對於這個例子中的swapColors()而言,其作用域鏈中包含3 個對象:swapColors()的變數對象、changeColor()的變數對象和全局變數對象。swapColors()的局部環境開始時會先在自己的變數對象中搜索變數和函數名,如果搜索不到則再搜索上一級作用域鏈。changeColor()的作用域鏈中只包含兩個對象:它自己的變數對象和全局變數對象。這也就是說,它不能訪問swapColors()的環境。

 

函數參數也被當作變數來對待,因此其訪問規則與執行環境中的其他變數相同。

 

4.2.1 延長作用域鏈

雖然執行環境的類型總共只有兩種——全局和局部(函數),但還是有其他辦法來延長作用域鏈。

這麼說是因為有些語句可以在作用域鏈的前端臨時增加一個變數對象,該變數對象會在代碼執行後被移除。在兩種情況下會發生這種現象。具體來說,就是當執行流進入下列任何一個語句時,作用域鏈就會得到加長:

  • try-catch 語句的catch 塊;
  • with 語句。

這兩個語句都會在作用域鏈的前端添加一個變數對象。對with 語句來說,會將指定的對象添加到作用域鏈中。對catch 語句來說,會創建一個新的變數對象,其中包含的是被拋出的錯誤對象的聲明。

下麵看一個例子。

function buildUrl() {
	var qs = "?debug=true";
	with(location) {
		var url = href + qs;
	}
	return url;
}

運行一下

在此,with 語句接收的是location 對象,因此其變數對象中就包含了location 對象的所有屬性和方法,而這個變數對象被添加到了作用域鏈的前端。buildUrl()函數中定義了一個變數qs。當在with 語句中引用變數href 時(實際引用的是location.href),可以在當前執行環境的變數對象中找到。當引用變數qs 時,引用的則是在buildUrl()中定義的那個變數,而該變數位於函數環境的變數對象中。至於with 語句內部,則定義了一個名為url 的變數,因而url 就成了函數執行環境的一部分,所以可以作為函數的值被返回。

 

在IE8 及之前版本的JavaScript 實現中,存在一個與標準不一致的地方,即在catch 語句中捕獲的錯誤對象會被添加到執行環境的變數對象,而不是catch 語句的變數對象中。換句話說,即使是在catch 塊的外部也可以訪問到錯誤對象。IE9 修複了這個問題。

 

4.2.2 沒有塊級作用域

JavaScript 沒有塊級作用域經常會導致理解上的困惑。在其他類C 的語言中,由花括弧封閉的代碼塊都有自己的作用域(如果用ECMAScript 的話來講,就是它們自己的執行環境),因而支持根據條件來定義變數。例如,下麵的代碼在JavaScript 中並不會得到想象中的結果:

if (true) {
    var color = "blue";
}
alert(color); //"blue"

這裡是在一個if 語句中定義了變數color。如果是在C、C++或Java 中,color 會在if 語句執行完畢後被銷毀。但在JavaScript 中,if 語句中的變數聲明會將變數添加到當前的執行環境(在這裡是全局環境)中。在使用for 語句時尤其要牢記這一差異,例如:

for (var i=0; i < 10; i++){
    doSomething(i);
}
alert(i); //10

對於有塊級作用域的語言來說,for 語句初始化變數的表達式所定義的變數,只會存在於迴圈的環境之中。而對於JavaScript 來說,由for 語句創建的變數i 即使在for 迴圈執行結束後,也依舊會存在於迴圈外部的執行環境中。

1. 聲明變數

使用var 聲明的變數會自動被添加到最接近的環境中。在函數內部,最接近的環境就是函數的局部環境;在with 語句中,最接近的環境是函數環境。如果初始化變數時沒有使用var 聲明,該變數會自動被添加到全局環境。如下所示:

function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}
var result = add(10, 20); //30
alert(sum); //由於sum 不是有效的變數,因此會導致錯誤

運行一下

以上代碼中的函數add()定義了一個名為sum 的局部變數,該變數包含加法操作的結果。雖然結果值從函數中返回了,但變數sum 在函數外部是訪問不到的。如果省略這個例子中的var 關鍵字,那麼當add()執行完畢後,sum 也將可以訪問到:

function add(num1, num2) {
    sum = num1 + num2;
    return sum;
}
var result = add(10, 20); //30
alert(sum); //30

運行一下

這個例子中的變數sum 在被初始化賦值時沒有使用var 關鍵字。於是,當調用完add()之後,添加到全局環境中的變數sum 將繼續存在;即使函數已經執行完畢,後面的代碼依舊可以訪問它。

在編寫JavaScript 代碼的過程中,不聲明而直接初始化變數是一個常見的錯誤做法,因為這樣可能會導致意外。我們建議在初始化變數之前,一定要先聲明,這樣就可以避免類似問題。在嚴格模式下,初始化未經聲明的變數會導致錯誤。

2. 查詢標識符

當在某個環境中為了讀取或寫入而引用一個標識符時,必須通過搜索來確定該標識符實際代表什麼。搜索過程從作用域鏈的前端開始,向上逐級查詢與給定名字匹配的標識符。如果在局部環境中找到了該標識符,搜索過程停止,變數就緒。如果在局部環境中沒有找到該變數名,則繼續沿作用域鏈向上搜索。搜索過程將一直追溯到全局環境的變數對象。如果在全局環境中也沒有找到這個標識符,則意味著該變數尚未聲明。

通過下麵這個示例,可以理解查詢標識符的過程:

var color = "blue";
    function getColor(){
    return color;
}
alert(getColor()); //"blue"

運行一下

調用本例中的函數getColor()時會引用變數color。為了確定變數color 的值,將開始一個兩步的搜索過程。首先,搜索getColor()的變數對象,查找其中是否包含一個名為color 的標識符。

在沒有找到的情況下,搜索繼續到下一個變數對象(全局環境的變數對象),然後在那裡找到了名為color 的標識符。因為搜索到了定義這個變數的變數對象,搜索過程宣告結束。圖4-4 形象地展示了上述搜索過程。

在這個搜索過程中,如果存在一個局部的變數的定義,則搜索會自動停止,不再進入另一個變數對象。換句話說,如果局部環境中存在著同名標識符,就不會使用位於父環境中的標識符,如下麵的例子所示:

var color = "blue";
function getColor(){
    var color = "red";
    return color;
}
alert(getColor()); //"red"

運行一下

修改後的代碼在getColor()函數中聲明瞭一個名為color 的局部變數。調用函數時,該變數就會被聲明。而當函數中的第二行代碼執行時,意味著必須找到並返回變數color 的值。搜索過程首先從局部環境中開始,而且在這裡發現了一個名為color 的變數,其值為"red"。因為變數已經找到了,所以搜索即行停止,return 語句就使用這個局部變數,併為函數會返回"red"。也就是說,任何位於局部變數color 的聲明之後的代碼,如果不使用window.color 都無法訪問全局color變數。

變數查詢也不是沒有代價的。很明顯,訪問局部變數要比訪問全局變數更快,因為不用向上搜索作用域鏈。JavaScript 引擎在優化標識符查詢方面做得不錯,因此這個差別在將來恐怕就可以忽略不計了。

 

下載離線版:http://www.shouce.ren/api/view/a/15255


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 相信碼友們對於$.fn.extexd();$.extend()以及$.fn.custom和$.custom都有一定的瞭解;我闡述一下我自己對於$.fn.custom和$.custom的理解、有理解錯誤或是有更好的建議直接噴我就好! 下麵咱們進行簡單插件的封裝; Jquery為開發插件提供了兩個方法, ...
  • 今天碰到個問題,有個報警提示的聲音,在其他瀏覽器都正常,IE11聲音不出來。後來發現,判斷當前瀏覽器的方法用的是 -1 != navigator.userAgent.indexOf("MSIE") 但是此方法對於新版的IE11已經不支持了(IE11的userAgent里是沒有MSIE標誌的) 把判斷 ...
  • <!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><title>焦點輪播圖效果</title><style type="text/css"> *{ margin: 0; padding: 0; list-style-type: ...
  • 原文參考 玉伯 大神些的,我整理了一下。 咱們今天主題說下前端模塊化發展的歷史,主要就是針對AMD CMD 的發展,這兩個東西是一種規範,他們實際產物是 AMD是RequireJS,CMD的產物是seajs,他們的出現都是在COMMONjs基礎上發展而來的,那咱們得先說說COMMONjs。 COMM ...
  • 1、bootstrap 排版 全局樣式style.css: 1、移除body的margin聲明 2、設置body的背景色為白色 3、為排版設置了基本的字體、字型大小和行高 4、設置全局鏈接顏色,且當鏈接處於懸浮“:hover”狀態時才會顯示下劃線樣式 標題 h1-h6 HTML 中的所有標題標簽, 到 ...
  • 最近在做pc端網頁開發時用到了datatables,不得不說這個工具使用還是很方便的。(ps:大數據量時建議使用伺服器端分頁而非前端分頁) 現將相關配置使用記錄如下 配置 常用api 附加功能添加 向上滾動頁面,當datatables表頭接觸到window頂部將其固定,實現代碼如下: 1、給docu ...
  • "I'm Captain Jack Sparrow" 加勒比海盜5上映,為了表示對傑克船長的喜愛,昨天閃現了幾次模仿船長的走路姿勢(哈哈哈,簡直妖嬈)。 為了周天能去看電影,要趕緊做完手上的活兒,比如總結Promise的方法。 2 Promise基本方法簡介 Promise提供了哪些方法了?大招就是 ...
  • 一HTML 1.DOCTYPE的聲明,現在基本都是H5的天下,直接快捷鍵生成即可。 2.字元集的聲明,一般是<meta charset="UTF-8" />,記得轉義下符號類字元如空格-&nbsp; &-amp; 3.正確的縮進,現在各個全家桶里的代碼已經配置好了縮進,無太大必要直接使用即可 4.c ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...