JavaScript 的作用域鏈原來是這樣子的

作用域:負責收集並維護由所有聲明的標識符(變量)組成的一系列查詢,並實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限。

當前代碼運行的環境,可訪問的變量以及作用域鏈上的變量環境對象。

標識符解釋

所謂標識符,就是變量、函數、屬性或函數參數的名稱。標識符可以由一或多個下列字符組成:

標識符中的字母可以是擴展 ASCII(Extended ASCII)中的字母,也可以是 Unicode 的字母字符,如 À 和 Æ(但不推薦使用)。

執行上下文

每一個執行上下文對應三個對象,分成兩個不同的階段:

創建

執行

作用域鏈

var color = "blue";

function changeColor() {
  let anotherColor = "red";

  function swapColors() {
    let tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
  }
  
  swapColors();
}

changeColor();

複製代碼

全局上下文執行

GlobalContext: {
  ...window,
  color: 'blue',
  changeColor: fn
}
複製代碼

於此同時:changeColor 函數的執行上下文就是 GlobalContext。

changeColor 調用

當前的執行上下文爲:

changeColorContext: {
  'activation object'{
      anotherColor: 'blue',
      swapColors: fn
  },
  'scope chain'[changeColorContext => GlobalContext]
}
複製代碼

swapColors 調用

當前的執行上下文爲:

swapColorsContext: {
  'activation object'{
      tempColor: 'blue'
  },
  'scope chain'[swapColorsContext =changeColorContext => GlobalContext]
}
複製代碼

這裏的 color 不屬於當前當前執行上下文的活動對象,而是屬於作用域鏈上 GlobalContext 的值。anotherColor 則屬於 changeColorContext 的。

代碼執行時的標識符解析是通過沿作用域鏈逐級搜索標識符名稱完成的。搜索過程始終從作用域鏈的最前端開始,然後逐級往後,直到找到標識符

一道面試題

比較下面兩段代碼,試述兩段代碼的不同之處
// A--------------------------
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
      debugger
        return scope;
    }
    return f();
}
checkscope();

// B---------------------------
var scope = "global scope";

function checkscope(){
    var scope = "local scope";
  
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
複製代碼

A 的 ECS

  1. 主流程:【GlobalContext】

  2. checkscope 執行:【checkscopeContext => GlobalContext】

  3. 第 9 行代碼執行:【fContext => checkscopeContext => GlobalContext】

  4. 第 9 行 return 語句:【checkscopeContext => GlobalContext】

  5. checkscope 執行完:【GlobalContext】

B 的 ECS

  1. 主流程:【GlobalContext】

  2. checkscope 執行:【checkscopeContext => GlobalContext】

  3. 第 9 行 return 語句:【checkscopeContext => GlobalContext】

  4. checkscope 執行方法調用:【fContext => GlobalContext】

  5. checkscope 執行完:【GlobalContext】

但是這裏爲什麼是 “local scope”
所以這裏上面兩種寫法返回的代碼結果都是:"local scope",因爲 js 是靜態作用域,因爲 f 函數創建的時候變量對象內部的 scope 就是 "local scope"。

dmitrysoshnikov.com/ecmascript/…

in the further explanation we’ll mainly use the concept of an environment, rather than scope,

通過環境的概念來代替 scope。那 scope 如何和環境變量對應。個人理解如下:

scope 這裏指的是 outer 指向父環境的變量,一直到 global。執行棧 ECS 和當前執行上下文沒有必然的聯繫,唯一有關係的是 this 指向

image.png

環境概念代替 scope 下的執行上下文

執行上下文【具體會在下個文章學習

  1. this 值的決定,即我們所熟知的 This 綁定。

  2. 創建詞法環境組件。

  3. 創建變量環境組件。

作用域鏈增強

這兩種情況下,都會在作用域鏈前端添加一個變量對象。對 with 語句來說,會向作用域鏈前端添加指定的對象;對 catch 語句而言,則會創建一個新的變量對象,這個變量對象會包含要拋出的錯誤對象的聲明。

let name = 'rod';
let name1 = 'rod';

function testWith() {
  let withObject = {
    name: 'rodchen',
    age: '15'
  }

  with(withObject) {
    console.log(name)
    console.log(name1)
  }
}

testWith()
複製代碼

image.png

with 語句使用 var 創建變量,則歸屬於所屬函數。使用 let / const,則屬於 block 作用域。

let name = 'rod';
var name1 = 'rod';

function testWith() {
 let withObject = {
    name: 'rodchen',
    age: '15'
  }

  with(withObject) {
    let test = 'test'
    console.log(name)
    console.log(name1)
  }

  console.log(test)
}

testWith()
複製代碼

image.png

塊級作用域

image.png

這裏所說的分別是:

擋在函數當中定義 const / let 的時候,這兩種類型的變量是存儲在當前函數的執行上下文的。如果是函數內部的 {} 內部創建的,則會在爲當前的 {} 創建新的塊級的變量對象。

let name = 'rod';
var name1 = 'rod';

function testWith() {
  let withObject = {
    name: 'rodchen',
    age: '15'
  }

  if(1) {
    let a = 1
    console.log(a)
  }

  console.log(withObject)
}

testWith()
複製代碼

chorme

firefox

圖示

這是一個函數內部沒有 {} 裏創建 let / const

這是一個函數內部存在 {} 裏創建 let / const

總結:

什麼是作用域鏈?

當查找變量的時候,會先從當前上下文的變量對象中查找,如果沒有找到,就會從父級 (詞法層面上的父級) 執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫做作用域鏈

關於本文

作者:耳東蝸牛

https://juejin.cn/post/6961711638978232356

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GA50BUJxRTF4pCJdWiQiKg