紅寶書總結-執行環境、作用域鏈

_會笑的鸚鵡_發表於2019-05-07

執行環境、作用域鏈

基本概念

  1. 執行環境定義了變數或函式有權訪問的其他資料,決定了他們各自的行為。每個執行環境都有一個與之關聯的變數物件,環境中定義的所有變數和函式都儲存在這個物件中(開發者無法訪問,解析器在後臺使用)

  2. 全域性執行環境是最外圍的一個執行環境。web瀏覽器中,全域性執行環境被認為是window物件——因此,所有全域性變數和函式都是作為window物件的屬性和方法建立的。

  3. 某個執行環境中的所有程式碼執行完畢後,該環境被銷燬,儲存在其中的所有變數和函式定義也隨之銷燬(全域性執行環境直到應用程式退出,如網頁關閉or瀏覽器關閉時才會被銷燬)

  4. 每個函式都有自己的執行環境。當執行流進入一個函式時,函式的環境就會被推入一個環境棧中。而在函式執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。

  5. 當程式碼在一個環境中執行時,會建立變數物件的一個作用域鏈,作用域鏈保證對執行環境有權訪問的所有變數和函式的有序訪問。作用域鏈的前端,始終都是當前執行的程式碼所在環境的變數物件。如果這個環境是函式,則將其活動物件作為變數物件。活動物件在最開始時只包含一個變數,即arguments物件(arguments物件在全域性環境中不存在)

  6. 識別符號解析是沿著作用域鏈一級一級的搜尋識別符號的過程。

延長作用域鏈

  1. 執行環境的型別總共只有兩種——全域性和區域性(函式)
  2. 有些語句可以在作用域鏈的前端臨時增加一個變數物件,該變數物件會在程式碼執行後被移除。 a. try-cath語句的catch塊--建立一個新的變數物件,其中包含的是被丟擲的錯誤物件的宣告 b. with語句--會將指定的物件新增到作用域鏈中
        (with語句例項)
        function buildUrl(){
            var qs = "?debug=true";
            with(location){
                var url = href + qs
            }
            return url;
        }
    複製程式碼
    with接收了location物件,因此其變數物件中就包含了location物件的所有屬性和方法。因此,其內部的href其實就是location.href。而url成了函式執行環境的一部分,所以可以作為函式的值被返回。

沒有塊級作用域

  1. 宣告變數

    使用var宣告的變數會自動新增在最近的環境中,在函式內部,最接近的環境就是函式的區域性環境;在with語句中,最接近的環境是函式環境。如果初始化變數時沒有使用var宣告,該變數會自動被新增到全域性環境。

    function(){
        var a=b=1;//a:function內;b:全域性內
    }
    複製程式碼

    嚴格模式下,初始化未經宣告的變數會導致錯誤

  2. 查詢識別符號

    使用識別符號時,通過搜尋來確定該識別符號實際代表什麼。搜尋過程從作用域鏈的前端開始,向上逐級查詢與給定名字匹配的識別符號。如果在區域性環境中找到,搜尋過程停止,變數就緒。如果在區域性環境中沒找到,則繼續沿作用域鏈向上搜尋,直至全域性環境的變數物件。如果全域性環境中也沒有,則意味著該變數尚未宣告。

let與塊級作用域

  • 以上內容都是es5。es6中,引入let,從而產生了塊級作用域的概念

  • var有一種“變數提升”的現象,即變數可以在宣告前使用,值為undefined;但是let定義的變數,如果在宣告前使用,會直接報錯

console.log(foo);//undefined
var foo = 2;

console.log(bar);//報錯
let bar = 2;
複製程式碼
  • 暫時性死區:變數在let宣告前使用會直接報錯,這就產生了一個“暫時性死區”:
var tmp = 123;
if(true){
    tmp = 'abc';//報錯
    let tmp;
}
複製程式碼
  • es6明確規定,如果區塊中存在let和const命令,這個區塊對這些命令宣告的變數,從一開始就形成了封閉的作用域。凡是在宣告之前就使用這些變數,就會報錯
暫時性死區意味著typeof不再是一個百分之百安全的操作
typeof x;//報錯
let x;

typeof undeclared_variable//undefined
複製程式碼

塊級作用域與函式宣告

  • es5中函式宣告不能在塊級作用域內定義;但是瀏覽器不會報錯;

  • es6中允許在塊級作用域中宣告函式,函式宣告語句的行為類似於let,在塊級作用域之外不可引用——但是,es6瀏覽器為了相容老程式碼,定義函式宣告類似於var,即會提升到全域性作用域或函式作用域的頭部;同時,函式宣告還會提升到所在塊級作用域的頭部

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重複宣告一次函式f
    function f() { console.log('I am inside!'); }
  }

  f();
}());
es5瀏覽器中執行:I am inside!因為將f提升到了函式頭部

es6瀏覽器:報錯:
相當於
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
複製程式碼
  • 所以,不要在塊級作用域內定義函式

  • 另外,這種情形注意下:

// 不報錯
'use strict';
if (true) {
  function f() {}
}

// 報錯
'use strict';
if (true)
  function f() {}
複製程式碼

相關文章