js - 執行上下文和做用域以及閉包

首先,我們一般被"執行上下文","執行上下文環境","上下文環境","執行上下文棧"這些名詞搞混。那咱們一一來揭祕這些名字的含義。javascript

這一塊一直比較晦澀難懂,仍是須要仔細去斟酌斟酌。java

什麼是執行上下文(也叫作「執行上下文環境」,「上下文環境」)?瀏覽器

我們仍是先看代碼。閉包

console.log(a)  // undefined
var a = 100

fn('bella')  // 'bella' 20
function fn(name) {
    age = 20
    console.log(name, age)
    var age
}

console.log(b); // 這裏報錯
// Uncaught ReferenceError: b is not defined

第一個console輸出 undefined,說明瀏覽器在執行console.log(a)的時候,已經知道a的存在的,可是不知道a的值。app

第二個fn("bella")輸出 "bella" 20,說明瀏覽器在執行的時候已經知道fn函數了,而且執行了函數

第三個console報錯,b is noe defined。說明沒有找到bthis

那麼能夠看出來,瀏覽器在執行以前作了一些準備工做。spa

那作了些什麼準備工做:code

  • 全局上下文環境: 變量定義,函數聲明
  • 函數上下文環境(函數內部):變量定義,函數聲明,this,arguments

下面這個例子不少地方都用來說解執行上下文和上下文棧。對象

 1 // 這是一個壓棧出棧的過程--執行上下文棧
 2 let a = 10; // 一、進入全局上下文環境
 3 let fn; 
 4 let bar = function(x) {
 5     let b = 5
 6     fn(x + b) // 三、進入fn函數上下文環境
 7 };
 8 fn = function(y) {
 9     let c = 5
10     console.log(y + c)
11 }
12 
13 bar(10) // 二、 進入bar函數上下文

 這裏引出了執行上下文棧的概念,上下文棧就是壓棧和出棧的過程

1.在代碼執行以前,首先建立全局上下文環境

    // 全局上下文環境
    a: undefined
    fn: undefined
    bar: undefined,
this: window

2.而後執行代碼,在代碼執行到12以前,全局上下文中的變量在執行中被賦值

    // 全局上下文環境
    a: 10
    fn: function
    bar: function,
this: window

而後執行13行代碼,調用bar函數,會建立一個新的執行上下文環境。並將這個bar上下文環境壓棧,並設置爲活動狀態

   // bar函數上下文環境
    b: undefined
    x: 10
    arguments: [10]
this: window

3.而後執行到第6行代碼,調用fn的時候,會建立一個新的執行上下文。並將這個fn上下文環境壓棧,並設置爲活動狀態。

// fn函數上下文環境
c: undefined
y: 15
arguments: [15]
this: window

4.fn執行完畢後,調用fn函數生成的fn上下文環境出棧,銷燬。而後bar出棧銷燬。而後全局上下文出棧銷燬

 

 

理解完了執行上下文,再看看this

相信都知道這句話,誰調用函數,this就指向誰。那麼咱們理解下this:

    var a = {
        name: 'A',
        fn: function() {
            console.log(this.name)
        }
    }
    a.fn() // this === a
    a.fn.call({
        name: 'B'
    }) // this === {name: 'B'}
    var fn1 = a.fn
    fn1() // this === window

this: this的值只有在執行的時候才能確認,定義的時候不能確認。由於this是執行上下文的一部分,而執行上下文須要再代碼執行以前肯定。

this執行會有不一樣,主要集中在這幾個場景中:
  1. 做爲構造函數執行,構造函數中
  2. 做爲對象屬性執行,上述代碼中a.fn()
  3. 做爲普通函數執行,上述代碼中fn1()
  4. 用於call apply bind,上述代碼中a.fn.call()

做用域


ES6以前沒有塊級做用域,除了全局做用域,函數會建立本身的做用域。
做用域在函數定義的時候已經肯定了,不是在函數調用肯定(區別於執行上下文環境,this是執行上下文環境中的)
做用域只是一個「 地盤」,其中沒有變量。變量是經過做用域對應的執行上下文環境中的變量對象來實現的。 因此做用域是靜態的,而執行上下文是動態的
有閉包存在的時候,一個做用域存在兩個上下文環境也是有的。
也就是說,做用域只是用於劃分你在這個做用域裏面定義的變量的有效範圍,出了這個範圍就無效
 

做用域鏈

函數在定義的時候就肯定了函數體內部自由變量的做用域
自由變量:好比a,在fn做用域使用,可是並無在fn做用域定義。這就是自由變量
let a = 100
function fn() {
    let b = 20
    function bar() {
        console.log(a + b) // a是自由變量
    }
    return bar
}
 
let x = fn(), b = 200
x()
 
那麼自由變量是如何獲得的?這就引出了做用域鏈
bar要取得a的值,就要在bar函數的做用域中取值,若是沒有,就往上找,找到fn做用域內,也沒有定義a,繼續往上找,就找到全局做用域,找到就結束了。這就是做用域鏈
 
 
講完這些,咱們再經過一個例子來理解閉包
1 function F1() {
2     var a = 100
3     return function () {
4         console.log(a)
5     }
6 }
7 var f1 = F1()
8 var a = 200
9 f1()

 

自由變量將從做用域鏈中去尋找,可是 依據的是函數定義時的做用域鏈,而不是函數執行時,以上這個例子就是閉包。
怎麼理解依據的是 函數定義時的做用域鏈,而不是函數執行時這句話?
 
調用第9行以後
若是按照執行時,就輸出的時200
可是做用域都是在定義時就生成了,因此f1回去再定義function的做用域去找,所以輸出100.
 
理解閉包以後,咱們就看看閉包的主要場景:
  • 函數做爲返回值,上面的例子就是
  • 函數做爲參數傳遞
相關文章
相關標籤/搜索