深入理解javascript作用域系列第五篇——一張圖理解執行環境和作用域

来源:http://www.cnblogs.com/xiaohuochai/archive/2016/07/31/5722905.html
-Advertisement-
Play Games

× 目錄 [1]圖示 [2]概念 [3]說明[4]總結 前面的話 對於執行環境(execution context)和作用域(scope)並不容易區分,甚至很多人認為它們就是一回事,只是高程和犀牛書關於作用域的兩種不同翻譯而已。但實際上,它們並不相同,卻相互糾纏在一起。本文先用一張圖開宗明義,然後進 ...


×
目錄
[1]圖示 [2]概念 [3]說明[4]總結

前面的話

  對於執行環境(execution context)和作用域(scope)並不容易區分,甚至很多人認為它們就是一回事,只是高程和犀牛書關於作用域的兩種不同翻譯而已。但實際上,它們並不相同,卻相互糾纏在一起。本文先用一張圖開宗明義,然後進行術語的簡單解釋,最後根據圖示內容進行詳細說明

 

圖示

查看大圖

 

概念

【作用域】

  作用域是一套規則,用於確定在何處以及如何查找標識符。關於LHS查詢和RHS查詢詳見作用域系列第一篇內部原理

  作用域分為詞法作用域動態作用域。javascript使用詞法作用域,簡單地說,詞法作用域就是定義在詞法階段的作用域,是由寫代碼時將變數和函數寫在哪裡來決定的。於是詞法作用域也可以描述為程式源代碼中定義變數和函數的區域

  作用域分為全局作用域和函數作用域,函數作用域可以互相嵌套

  在下麵的例子中,存在著全局作用域,fn作用域和bar作用域,它們相互嵌套

【作用域鏈和自由變數】

  各個作用域的嵌套關係組成了一條作用域鏈。例子中bar函數保存的作用域鏈是bar -> fn -> 全局,fn函數保存的作用域鏈是fn -> 全局

  使用作用域鏈主要是進行標識符的查詢,標識符解析就是沿著作用域鏈一級一級地搜索標識符的過程,而作用域鏈就是要保證對變數和函數的有序訪問

  【1】如果自身作用域中聲明瞭該變數,則無需使用作用域鏈

  在下麵的例子中,如果要在bar函數中查詢變數a,則直接使用LHS查詢,賦值為100即可

var a = 1;
var b = 2;
function fn(x){
    var a = 10;
    function bar(x){
        var a = 100;
        b = x + a;
        return b;
    }
    bar(20);
    bar(200);
}
fn(0);

  【2】如果自身作用域中未聲明該變數,則需要使用作用域鏈進行查找

  這時,就引出了另一個概念——自由變數。在當前作用域中存在但未在當前作用域中聲明的變數叫自由變數

  在下麵的例子中,如果要在bar函數中查詢變數b,由於b並沒有在當前作用域中聲明,所以b是自由變數。bar函數的作用域鏈是bar -> fn -> 全局。到上一級fn作用域中查找b沒有找到,繼續到再上一級全局作用域中查找b,找到了b

var a = 1;
var b = 2;
function fn(x){
    var a = 10;
    function bar(x){
        var a = 100;
        b = x + a;
        return b;
    }
    bar(20);
    bar(200);
}
fn(0);

  [註意]如果標識符沒有找到,則需要分為RHS和LHS查詢進行分析,若進行的是LHS查詢,則在全局環境中聲明該變數,若是嚴格模式下的LHS查詢,則拋出ReferenceError(引用錯誤)異常;若進行的是RHS查詢,則拋出ReferenceError(引用錯誤)異常。詳細情況移步至此

【執行環境】

  執行環境(execution context),有時也稱為執行上下文、執行上下文環境或環境,定義了變數或函數有權訪問的其他數據。每個執行環境都有一個與之關聯的變數對象(variable object),環境中定義的所有變數和函數都保存在這個對象中

  這是例子中的代碼執行到第15行時fn(0)函數的執行環境,執行環境裡面保存了fn()函數作用域內所有的變數和函數的值

【執行流】   代碼的執行順序叫做執行流,程式源代碼並不是按照代碼的書寫順序一行一行往下執行,而是和函數的調用順序有關   例子中的執行流是第1行 -> 第2行 -> 第4行 -> 第15行 -> 第5行 -> 第7行 -> 第12行 -> 第8行 -> 第9行 -> 第10行 -> 第11行 -> 第13行 -> 第8行 -> 第9行 -> 第10行 -> 第11行 -> 第14行   [註意]在程式代碼執行之前存在著編譯聲明提升(hoisting)的過程,本例中假設代碼是已經經過聲明提升過程之後的代碼  

【執行環境棧】

  執行環境棧類似於作用域鏈,有序地保存著當前程式中存在的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。javascript程式中的執行流正是由這個方便的機制控制著

  在例子中,當執行流進入bar(20)函數時,當前程式的執行環境棧如下圖所示,其中黃色的bar(20)執行環境表示當前程式正處此執行環境中

  當bar(20)函數執行完成後,當前程式的執行環境棧如下圖所示,bar(20)函數的執行環境被銷毀,等待垃圾回收,控制權交還給黃色背景的fn(0)執行環境

 

說明

  下麵按照代碼執行流的順序對該圖示進行詳細說明

  【1】代碼執行流進入全局執行環境,並對全局執行環境中的代碼進入聲明提升(hoisting)

  【2】執行流執行第1行代碼var a = 1;,對a進行LHS查詢,給a賦值1;執行流執行第2行代碼var b = 2;,對b進行LHS查詢,給b賦值2   【3】執行流執行第15行代碼fn(0);,調用fn(0)函數,此時執行流進入fn(0)函數執行環境中,對該執行環境中的代碼進行聲明提升過程,並將實參0賦值給形參x中。此時執行環境棧中存在兩個執行環境,fn(0)函數為當前執行流所在執行環境   【4】執行流執行第5行代碼var a = 10;,對a進行LHS查詢,給a賦值10   【5】執行流執行第12行代碼bar(20);,調用bar(20)函數,此時執行流進入bar(20)函數執行環境中,對該執行環境中的代碼進行聲明提升過程,並將實參20賦值給形參x中。此時執行環境棧中存在三個執行環境,bar(20)函數為當前執行流所在執行環境   在聲明提升的過程中,由於b是個自由變數,需要通過bar()函數的作用域鏈bar() -> fn() -> 全局作用域進行查找,最終在全局作用域中也就是代碼第2行找到var b = 2;,然後在全局執行環境中找到b的值是2,所以給b賦值2   【6】執行流執行第8行代碼var a = 100;,給a賦值100;執行流執行第9行b = x + a;,對x進行RHS查詢,找到x的值是20,對a進行RHS查詢,找到a的值是100,所以通過計算b的值是120,給b賦值120;執行第10行代碼return b;,對b進行RHS查詢,找到b的值是120,所以函數返回值為120   【7】執行流執行完第10行代碼後,bar(20)的執行環境被彈出執行環境棧,並被銷毀,等待垃圾回收,控制權交還給fn(0)函數的執行環境   【8】執行流執行第13行代碼bar(200);,調用bar(200)函數,此時執行流進入bar(200)函數執行環境中,對該執行環境中的代碼進行聲明提升過程,並將實參200賦值給形參x中。此時執行環境棧中存在三個執行環境,bar(200)函數為當前執行流所在執行環境   與第5步相同,在聲明提升的過程中,由於b是個自由變數,需要通過bar()函數的作用域鏈bar() -> fn() -> 全局作用域進行查找,最終在全局作用域中也就是代碼第2行找到var b = 2;,然後在全局執行環境中找到b的值是2,所以給b賦值2   【9】與第6步相同,執行流執行第8行代碼var a = 100;,給a賦值100;執行流執行第9行b = x + a;,對x進行RHS查詢,找到x的值是20,對a進行RHS查詢,找到a的值是100,所以通過計算b的值是120,給b賦值120;執行第10行代碼return b;,對b進行RHS查詢,找到b的值是120,所以函數返回值為120   【10】執行流執行完第10行代碼後,bar(200)的執行環境被彈出執行環境棧,並被銷毀,等待垃圾回收,控制權交還給fn(0)函數的執行環境   【11】執行流執行第14行代碼},fn(0)的執行環境被彈出執行環境棧,並被銷毀,等待垃圾回收,控制權交還給全局執行環境   【12】當頁面關閉時,全局執行環境被銷毀,頁面再無執行環境  

總結

  【1】javascript使用的是詞法作用域。對於函數來說,詞法作用域是在函數定義時就已經確定了,與函數是否被調用無關。通過作用域,可以知道作用域範圍內的變數和函數有哪些,卻不知道變數的值是什麼。所以作用域是靜態的

  [註意]通過eval()函數with語句可以對作用域進行動態修改

  【2】對於函數來說,執行環境是在函數調用時確定的,執行環境包含作用域內所有變數和函數的值。在同一作用域下,不同的調用(如傳遞不同的參數)會產生不同的執行環境,從而產生不同的變數的值。所以執行環境是動態的


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

-Advertisement-
Play Games
更多相關文章
  • JS對象的比較 由於JS是解釋執行的語言,那麼代碼中出現函數與對象如果重覆執行,會創建多個副本 在開發中會引入各種框架和庫,自定義的成員越多,出現命名衝突的幾率越大 在開發中會有多個構造函數,,每一個構造函數有很多方法,就會變得不容易維護 原型相關的概念 關於面向對象的概念 類 class 在js中 ...
  • HTML簡介 HyperText Markup Language:超文本標記語言 HyperText:超文本(文本 + 圖片 + 視頻 + 音頻 + 鏈接) Markup Language:標記語言 網站的三大元素 圖片,超鏈接,文字 HTML基本結構 標準網頁的HTML標簽 1.HTML文檔聲明: ...
  • 1、表單驗證<form></form> (1).非空驗證(去空格) (2).對比驗證(跟一個值對比) (3).範圍驗證(根據一個範圍進行判斷) (4).固定格式驗證:電話號碼,身份證號,郵箱,信用卡號等的驗證;需要用到正則表達式來進行驗證。 (5).其它驗證 2、正則表達式 用符號來描述書寫規則:/ ...
  • 1.無序列表 HTML <ul> 元素代表多項的無序列表,即無數值排序項的集合,且它們在列表中的順序是沒有意義的。通常情況下,無序列表項的頭部可以是幾種形式,如一個點,一個圓形或方形。通過設置<ul>元素的type屬性來改變無序列表頭部的形式。 無序列表和有序列表都使用<li>標簽來定義單列。 2. ...
  • DOM 操作 訪問與樹關係(節點) 訪問父節點: parentNode 訪問上一個兄弟節點: previousSibling 訪問下一個兄弟節點: nextSibling 訪問第一個屬性節點: attributes[ 1 ] 訪問第一個子節點:fristChild 或 childNodes[ 0 ] ...
  • /*! jQuery v1.8.3 jquery.com | jquery.org/license */ (function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}fun ...
  • 代碼: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www. ...
  • [TOC] 在CSS中對元素進行 水平居中 是非常簡單的:如果它是一個行內元素,就對它的父元素應用 ;如果它是一個塊級元素,就對它自身應用 。然而要對一個元素進行垂直居中,就有點束手無策了,本文介紹了幾種常用的垂直居中方法以供參考! 一、表格佈局法 利用表格的vertical align屬性,當然首 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...