好好理解一下作用域和閉包和預編譯

引言

在寫業務代碼的同時,我覺得還是很需要把基礎知識全部理清楚的,閉包和作用域呢我一直覺得我懂了,那麼試試看能不能說清楚?當然我也會參考一下別人說法,所以會在參考資料裏面寫上啦嘻嘻~~

作用域和作用域鏈

作用域是一個語言無關的概念,當然作用域分爲詞法作用域動態作用域
作用域:通常來說,一段程序代碼中所用的名字並不總是有效/可用的,而限定這個名字的可用性的代碼範圍就是這個名字的作用域。
詞法作用域:也叫做靜態作用域,它的作用域是指在詞法分析階段就確定了,是不會改變的。
動態作用域:是在運行的時候根據程序的流程信息來動態確定的,而不是寫代碼是進行靜態確定的。

var a = 2;

function foo() {
  console.log(a); // 會輸出2還是3?
}

function bar() {
  var a = 3;
  foo();
}

bar();
如果是詞法作用域

foo()函數引用到全局作用域中的a,因此會輸出2。因爲詞法作用域是寫代碼的時候就靜態確定下來的,JavaScript的作用域就是詞法作用域(大部分語言也都是基於詞法作用域的),所以這段大媽輸出應該是2.
在這裏插入圖片描述

如果是動態作用域

動態作用域不會關心函數和作用域是如何聲明以及在何處聲明的,只關心它們從何處調用。換句話說,作用域鏈就是基於調用棧的,而不是代碼中的作用域嵌套,因此,如果是JavaScript具有動態作用域,理論上輸出結果應該是3.

總結

JavaScript並不具有動態作用域,它只有詞法作用域。eval()withthis機制在某種程度上很像動態作用域,使用上要特別注意。
詞法作用域關注函數在何處聲明,而動態作用域是在運行時確定的(javascript中的this也是)。

閉包

閉包的創造條件

  • 存在內、外兩層函數
  • 內層函數對外層函數的局部變量進行了引用

閉包是對作用域的眼神,也是在實際開發中經常使用的一個特性。
一個例子:

    var scope = {};
    if (scope instanceof Object) {
        var j = 1;
        for (var i = 0; i < 10; i++) {
            //console.log(i);
        }
        console.log(i); //輸出10
    }
    console.log(j);//輸出1

}

無論用過何種手段將內部函數傳遞到所在的所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包
一個難一點的例子

function wait(message) {
  setTimeout( function timer() {
    console.log( message );
  }, 1000 );
}
wait( "Hello, closure!" );

這也是閉包。傳入的timer函數一定會被執行,知識內部引擎調用執行,深入到引擎的內部,內置的工具函數setTimeout(..)持有對一個參數的引用,引擎會調用這個函數!!!
IIFE(立即執行函數)是閉包嗎
它創建了閉包,在內存中創建了一塊區域,這塊區域保存着作用域鏈上的作用域引用。等同創建了閉包

// 它需要有自己的變量,用來在每個迭代中儲存i 的值:
for (var i=1; i<=5; i++) {
    (function() {
        var j = i;
        setTimeout( function timer() {
            console.log( j );
        }, j*1000 );
    })();
}
// 行了!它能正常工作了!。
// 可以對這段代碼進行一些改進:
for (var i=1; i<=5; i++) {
    (function(j) {
        setTimeout( function timer() {
            console.log( j );
        }, j*1000 );
    })( i );
}
//當然你也可以這樣寫
for (var i=1; i<=5; i++) {
    (function(i) {
        setTimeout( function timer() {
            console.log( i );
        }, i*1000 );
    })( i );
}
//你也可以這樣
function timeManage() {
    for (var i = 0; i < 10; i++) {
        setTimeout((function(e) {
            return function() {
                console.log(e);
            }
        })(i), 1000)
    }
}
//timeManager();輸出1,2,3,4,5
function createClosure() {
    var result = [];
    for (var i = 0; i < 5; i++) {
        result[i] = function(num) {
            return function() {
                console.log(num);
            }
        }(i);
    }
    return result;
}
//createClosure()[1]()輸出1;createClosure()[2]()輸出2

由於閉包會額外的附帶函數的作用域(內部匿名函數攜帶外部函數的作用域),因此,閉包會比其它函數多佔用些內存空間,過度的使用可能會導致內存佔用的增加。

總的來說注意三個問題:

  • 變量
  • this
  • 內存
閉包中的this問題:

閉包中的this之前講過,是特例,跟動態作用域差不多:

var object = {
    scope:"local",
    getScope:function(){
        return function(){
            return this.scope;
        }
    }
}
//object.getScope()()返回值爲global

匿名函數的this永遠指向windows

預編譯

JavaScript腳本的運行其實是由兩個階段組成的:

  • 預編譯階段:在內存中開闢出一塊空間,用來存放變量和函數,爲使用varfunction關鍵字開闢出一片空間,用來存放這二者聲明的變量和函數。
    -預編譯的時候function的優先級高於var(也說明了函數是一等公民~~~)
    預編譯時不會對變量進行賦值(不會進行初始化),賦值是在執行階段進行的。
  • 執行階段

結語

嚶嚶嚶終於寫完了,今天一天特別不在狀態,暈暈乎乎的,所以寫出來的東西可能有點亂,我原來的目的是爲了讓自己方便學習使用,如果大家看到了之後覺得有問題,或者想噴我嚶嚶嚶?評論區是你們的舞臺嗚嗚嗚嗚嗚~~我這麼可愛,別噴我,可以留言我很可愛呀~對了,如果覺得我講的不清楚,都可以看參考資料的,我覺得講得很到位欸

參考資料

詞法作用域 VS 動態作用域
深入javascript——作用域和閉包
你不知道的JS(2)深入瞭解閉包
JavaScript - 預編譯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章