JavaScript之變量、執行環境及做用域

前言

最近一直在看紅寶書,說實話看的有點費勁,一些深點的知識點就能讓我思考許久,例如:參數傳遞都是按值傳遞的、環境執行棧、做用域鏈等。我想了想,仍是決定經過寫博客作筆記的方式記錄下本身的學習過程和經驗,一方面有利於加深印象和回顧;另外一方面也是對本身的一種督促吧,期待半年後再看的時候,能有新的感悟和理解。固然,也但願經過這種形式,你們能給我提一些建議和指導。前端

變量

ECMAScript 變量可能包含兩種不一樣數據類型的值:基本類型值和引用類型值。基本類型值指的是簡單的數據段,而引用類型值指那些可能由多個值構成的對象。web

這裏先簡單說一下兩個概念:堆內存和棧內存。數組

堆內存:先進先出(相似單向的管道,排隊往前走,先進的先出去);是動態分配的內存,大小不一且不會被系統自動釋放。 瀏覽器

棧內存:後進先出(相似只有一個出口的管道,後面進入的先出去);是自動分配相對固定大小的內存,且會被系統自動釋放。bash

基本類型

Undefined、Null、Boolean、Number和String。他們都是按值存儲在棧內存中,每種類型的數據佔用內存的大小都是固定的,由系統自動分配和釋放。函數

var a = 10;
var b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20複製代碼


引用類型

除了上述五種基本類型外都是引用類型,例如Object,Array,Function等。js不容許直接訪問堆內存中的位置,也就是說不能直接操做對象的內存空間。在操做對象時,實際是在操做對象的引用而不是實際的對象,是按地址訪問的。( 即:引用類型數據在棧內存中保存的其實是對象在堆內存中的引用地址,經過這個引用地址能夠快速查找到保存中堆內存中的對象。)學習

var obj1 = new Object();
var obj2 = obj1;
obj2.name = 'hexh';
console.log(obj1.name); //  hexh複製代碼

obj1和obj2指向了堆內存中的同一個對象。var obj2 = obj1;,其實是將這個存儲在堆內存中的對象,在棧內存的引用地址複製了一份給obj2,可是實際上他們共同指向了同一個堆內存對象,因此修改obj2其實就是修改那個對象,因此經過obj1訪問也能訪問的到。ui


參數傳遞

ECMAScript 中全部函數的參數都是按值傳遞。也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變量複製到另外一個變量同樣。基本類型值的傳遞如何基本類型變量的複製同樣,而引用類型值的傳遞,則如同引用類型變量的複製同樣。spa

  • 基本類型值傳遞

function sum (num) {
    num += 10;
    return num;
}

ver count = 20;
var res = sum(count);
console.log(count); // 20
console.log(res); // 30複製代碼

函數 sum() 有一個參數 num,而參數 num 其實是該函數的局部變量。在調用 sum() 函數時,變量 count 做爲參數傳遞給該函數時,值是20。因而 20 被複制給參數 num 以便在 sum() 中使用。在函數內部,參數 num 的值被加上了 10,但這個操做不會影響函數外部的 count 變量。參數 num 和 count 互不認識,它們僅僅是具備相同的值。3d

  • 引用類型值傳遞

例子1

function setName (obj) {
    obj.name = 'hexh'
}
var person = new Object();
setName(person);
console.log(person.name); // hexh複製代碼

建立一個對象,並將其保存在變量person中。而後該變量傳遞到 setName() 函數中以後,被複制給了 obj。在這個函數內部,obj 和 person 引用的是同一個對象。(即時這個變量是按值傳遞的,obj 也會按引用來訪問同一個對象。)

有不少開發人員錯誤地認爲:在局部做用域中修改的對象在全局做用域中反映出來,就說明參數是按引用傳遞的。

例子2

function setName (obj) {
    obj.name = 'hexh'
    obj = new Object();
    obj.name = 'test'
}
var person = new Object();
setName(person);
console.log(person.name); // hexh複製代碼

經過例子2能夠看出,若是 person 是按引用傳遞的,那麼 person 會被指向 obj 新對象。但打印 person.name,顯示的值仍然是 "hexh"。這說明即便在函數內部修改了參數的值,但原始的引用仍然保持不變。

總結:

當引用類型的數據 Object 傳遞給函數時,也是使用的值傳遞。我是這樣理解的:

此時傳遞給函數的是 Object 在棧內存中的 「引用地址」(A)。

「A」 被傳遞給函數的過程,實際上是複製了 Object 在棧內存中的 「引用地址」(A) ,並將複製的結果賦值(值傳遞)給了函數的參數 「B」 。因爲 「A」 和 「B」的引用地址都是指向堆內存的同一個對象,因此在函數中經過 「B」 修改對象屬性的值時,打印 「A」 的結果顯示和 「B」 一致。符合了例子1中的結果。而例子2的函數中,將 「B」 從新 new 了一個Object對象,至關於改變了 「B」 的引用地址,此時 「A」 和 「B」 是互不關聯的。

執行環境

執行環境(execution context, EC)是JavaScript中最爲重要的一個概念。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個與之關聯的變量對象(variable object,VO),環境中定義的全部變量和函數都保存在這個對象中。

每當程序的執行流進入到一個能夠執行的代碼中時,就進入到了一個執行環境中。

執行環境分爲三種:全局執行環境、函數執行環境、Eval執行環境。

  • 全局執行環境是最外圍的一個執行環境。在web瀏覽器中,全局執行環境被認爲是 window 對象,所以全部全局變量和函數都是做爲 window 對象的屬性和方法建立的。
  • 函數執行環境。每一個函數都有本身的執行環境,當執行流進入一個函數時,「函數的環境」 就會被推入一個環境棧中。在函數執行完成以後,環境棧會將 「函數的環境」 彈出,把控制權返還給以前的執行環境。
  • Eval執行環境,執行eval()函數時建立。

執行環境棧

執行流依次進入的執行環境在邏輯上造成了一個棧,棧的底部永遠是全局執行環境的變量對象,棧的頂部則是當前執行的代碼所在環境的變量對象(瀏覽器老是執行處於棧頂的上下文)。

var a = "global";

function example () {
    console.log(a);   
}

function outer () {
    var b = "outer";
    console.log(b);   

    function inner () {
        var c = "inner";
        console.log(c);
        example();
    }
    inner();
}
outer();複製代碼

咱們能夠經過數組的形式表示上面代碼的執行環境棧。程序首先進入全局執行環境(GlobalContext),此時環境棧中已經存放了全局執行環境。而後程序依次調用了outer、inner和example函數,每次調函數時都會建立一個函數執行環境放入環境棧中。當前的環境棧存儲狀況以下:

ECStack = [
    // 棧頂
    example(),
    inner(),
    outer(),
    GlobalContext
    // 棧底
]複製代碼

個人我的理解是:環境棧就是用來存儲當前程序的執行內容和順序。

做用域鏈

當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈。做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有序訪問

做用域鏈的前端始終是當前執行代碼的環境的變量對象。若是這個環境是函數,則將函數的活動對象(activition object,AO)做爲變量對象。活動對象在最開始時,只包含一個變量,即 arguments 對象(這個對象在全局環境中是不存在的)。

做用域鏈的末端始終是全局執行環境的變量對象。

因此做用域鏈中的內容都是變量對象。

那做用域鏈究竟是什麼呢?咱們先看一個例子:

var color = "blue";

function getColor () {
    return color;
}

console.log(getColor()); // blue複製代碼

在這個例子中,做用域鏈從前到後的順序是:getColor函數的活動對象、全局執行環境的變量對象。

當咱們調用 getColor() 時,首先搜索 getColor() 的變量對象,查找名爲 color 的標識符,在沒有找到的狀況下,搜索下一個變量對象(這裏就是全局執行環境的變量對象),而後找到了名爲 color 的標識符。這個查找鏈路就是例子中的做用域鏈,而它的做用也正是 保證對執行環境有權訪問的全部變量和函數的有序訪問

即:內部環境能夠經過做用域鏈訪問全部的外部環境,可是外部環境不能訪問內部環境中的任何變量和函數。

延長做用域鏈

雖然執行環節的類型只有:全局和局部(函數)兩種,可是能夠經過:

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

對做用域鏈進行延長。對 with 語句來講,會將指定的對象添加到做用域鏈中。對 catch 語句來講,會建立一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。

總結

紅寶書中這章的內容我看了好久,到如今仍是處於一種似懂非懂的狀態,總感受還差點什麼。上面的一些我的的理解,可能還存在很多問題,你們要是對這塊瞭解比較深刻,有什麼好的理解和想法,還請和我分享一下,謝謝!

另外,各位小哥哥小姐姐,點個贊吧~

相關文章
相關標籤/搜索