作用域、作用域鏈精解,立即執(zhí)行函數(shù),閉包

作用域精解

  • [[scope]]:每個javascript函數(shù)都是一個對象,對象中有些屬性我們可以訪問,但有些不可以,這些屬性僅供javascript引擎存取,[[scope]]就是其中一個。
    [[scope]]指的就是我們所說的作用域,其中存儲了運行期上下文的集合。
    [[scope]]是函數(shù)的一個隱式屬性,不能直接拿出來用。

  • 作用域鏈:[[scope]]中所存儲的執(zhí)行期上下文對象的集合,這個集合呈鏈?zhǔn)竭B接,我們把這種鏈?zhǔn)竭B接叫做作用域鏈。

作用域?qū)儆谝粋€函數(shù),一個函數(shù)產(chǎn)生了一樣的作用域。函數(shù)也是一種特殊的對象(有.name屬性),叫函數(shù)類對象。每個對象都有屬性和方法,一切對象都有屬性,是對象就一定有屬性。

  • 運行期上下文:當(dāng)函數(shù)執(zhí)行時,會創(chuàng)建一個稱為執(zhí)行期上下文的內(nèi)部對象(AO)。一個執(zhí)行期上下文定義了一個函數(shù)執(zhí)行時的環(huán)境,函數(shù)每次執(zhí)行時對應(yīng)的執(zhí)行上下文都是獨一無二的,所以多次調(diào)用一個函數(shù)會導(dǎo)致創(chuàng)建多個執(zhí)行上下文,當(dāng)函數(shù)執(zhí)行完畢,它所產(chǎn)生的上下文被銷毀。
  • 查找變量:在那個函數(shù)查找變量,就在那個函數(shù)從作用域的頂端依次向下查找。

例子:

function a(){
    function b(){
        function c(){
        };
        c();
    }
    b();.
}
a();

//作用域鏈
a defined a.[[scope]] -- > 0:GO
a doing    a.[[scope]] -- > 0:aAO
                                        1:GO

b defined b.[[scope]] -- > 0:aAO
                                        1:GO

b doing     b.[[scope]] -- >  0:bAO
                                         1:aAO
                                         2:GO
                                          
c defined  c.[[scope]] -- >  0:bAO
                                         1:aAO
                                         2:GO

c doing     c.[[scope]] -- >  0:cAO
                                         1:bAO
                                         2:aAO
                                         3:GO

立即執(zhí)行函數(shù)

示例:

function a(){       //a里面b沒有執(zhí)行
    function b(){
         var bbb = 234;
        console.log(aaa);
    }
     var aaa = 123;
    return b;     //這里最后一條語句把b的引用扔出去,給glob;
}
var glob = 100;
var demo = a();
demo();

對于執(zhí)行期上下文,哪個函數(shù)先執(zhí)行完就先銷毀哪個函數(shù)的執(zhí)行期上下文,執(zhí)行期上下文是在執(zhí)行的時候創(chuàng)建的,但要注意,對于函數(shù)的引用,它的執(zhí)行期上下文是不被銷毀的,因為沒有被執(zhí)行(在被定義了時候就有一個初始的執(zhí)行期上下文)。

3.png

a銷毀的時候b還沒有執(zhí)行,而是被返回出去了,但是b已經(jīng)拿到了a的執(zhí)行期上下文,以后就可以隨便用。即使a被銷毀,它也可以用,因為它已經(jīng)被拿出來引用了。

閉包

當(dāng)內(nèi)部函數(shù)被保存到外部時,將會生成閉包。閉包會導(dǎo)致原有作用域不釋放,造成內(nèi)存泄漏(也就是內(nèi)存占用)。

閉包的作用

  • 實現(xiàn)公有變量
    • eg:函數(shù)累加器
function add(){
    var count = 0;
    function demo(){
        count ++;
        console.log(count);
    }
    return demo;
}
var counter = add();
counter();
counter();
counter();
counter();
  • 可以做緩存在(存儲結(jié)構(gòu))
    • eg:eater
function test(){
    var num = 100;
    funcrion a(){
        num ++;
        console.log(num);
    }
//a defined a.[[scope]] 0:testAO
//                                  1:GO
    function b(){
        num --;
        console.log(num);
    }
//b defined b.[[scope]] 0:testAO
//                                  1:GO
    return [a, b];
}

var myArr = test();
myArr[0]();
//a doing  a.[[scope]]  0:aAO
//                                1:testAO
//                                 2:GO 
myArr[1]();
//b doing  b.[[scope]]  0:bAO
//                                1:testAO
//                                 2:GO

eater示例:

funcrion eater(){
    var food = "";
    var obj = {
        eat : function(){
            console.log("i am eating" + food);
            food = "";
        },
        push : function (myFood){
            food = myFood;
        }
    }
    return obj;
}

var eater1 = eater();

eater1.push('banana');
eater1.eat();

這里是一對二的閉包。a和b被定義的時候都有一個狀態(tài):test的AO連著GO。他們共用一個testAO和GO,所以a和test形成一個閉包,b和test也形成一個閉包。a在test基礎(chǔ)上使用num,改完之后,b又在改后的基礎(chǔ)上修改值。

  • 可以實現(xiàn)封裝,屬性私有化
    eg:Person();
  • 模塊化開發(fā),防止污染全局變量

立即執(zhí)行函數(shù)

定義:此函數(shù)沒有聲明,在一次執(zhí)行過后即釋放。適合做初始化工作。

凡是只被執(zhí)行一次、只想要它執(zhí)行一次或者說只想執(zhí)行一次返回結(jié)果,無論如何這個函數(shù)只執(zhí)行一次的話,這個函數(shù)被稱為初始化函數(shù),或者初始化功能。(針對初始化功能的函數(shù))。

比如只需要求一個特別復(fù)雜的數(shù)的函數(shù),只用一次,又很占用內(nèi)存空間。初始化函數(shù)就可以解決這個問題。立即執(zhí)行函數(shù)的好處就是它執(zhí)行完會立即釋放空間。舉個例子:

function a(){
    ....此處省略10萬行代碼,但只被執(zhí)行一次。
}

立即執(zhí)行函數(shù)是外面一對小括號包著,結(jié)尾再有一對小括號。這里是匿名的,也可以有名稱。這是javascript提供的唯一一個可以立即銷毀函數(shù)的東西。

立即執(zhí)行函數(shù)的例子:

(function (){
  var a = 123;
  var b = 234;
  console.log(a + b);
}());

立即函數(shù)和函數(shù)的區(qū)別:除了執(zhí)行完就被釋放意外,沒有任何其他的區(qū)別。函數(shù)有的東西立即函數(shù)都能有,比如參數(shù)(最后的小括號里就可以寫形參)、返回值。立即函數(shù)雖然執(zhí)行完就被銷毀,但即便這樣也還是可以有返回值。

var num = (function(a, b, c){    //返回值的接收形式
     var d = a + b + c*2;      // 9
}(1, 2 ,3));   //形參

立即執(zhí)行函數(shù)有執(zhí)行期上下文,也要經(jīng)過預(yù)編譯。立即函數(shù)是函數(shù),函數(shù)就必須有預(yù)編譯過程。

立即執(zhí)行函數(shù)的寫法有兩種:

  • 第一種:官網(wǎng)寫法(function (){}())
  • 第二種:(function (){})()
    W3C建議使用第一種。下面來看看兩種寫法。
function test(){
    var a = 123;
}

test();    //這也是表達式
//一般我們執(zhí)行函數(shù)的時候是用函數(shù)名加括號,但
//function test(){      //這是函數(shù)聲明,不是表達式
//    var a = 123;
//}()
//這里加個括號是不能執(zhí)行的,而且會報很低端的錯誤。

只有函數(shù)表達式才能被執(zhí)行符號執(zhí)行。)執(zhí)行符號就是一對括號()。像這樣:

var test = function(){     //函數(shù)表達式
    console.log('a');
}();     //這里可以執(zhí)行

注意,能被執(zhí)行符號執(zhí)行的表達式,這個函數(shù)名字就會被自動忽略。也就是說,唄執(zhí)行符號執(zhí)行的表達式,基本上就成了立即執(zhí)行函數(shù)。就像上面這個例子,被立即執(zhí)行之后再訪問test,test是訪問不到的,會是undefined。上面這種寫法就和立即執(zhí)行函數(shù)沒有什么區(qū)別。

下面這種情況也能被執(zhí)行。正、負(fù)和嘆號、&都可以把函數(shù)變成表達式。

+ function test(){
    console.log("a");
}();

括號()是執(zhí)行符,把函數(shù)放到括號里面,函數(shù)就變成函數(shù)表達式,所以可以執(zhí)行,也就是上面提到的官方寫法。

看一個阿里巴巴的筆試題:

function test(a, b ,c, d){
    console.log(a + b + c +d);
}(1, 2, 3, 4);

理論上是不能執(zhí)行、會報錯的。但是執(zhí)行之后發(fā)現(xiàn),不報錯,只是函數(shù)也沒運行。這是為什么?因為函數(shù)一般來講,能不報錯盡量不給你報錯,盡量會找一種不報錯的方式來理解你的代碼。這里他會理解成:

function test(a, b ,c, d){
    console.log(a + b + c +d);
}



(1, 2, 3, 4);   //逗號運算符的運算
//因為()只有是里面什么的都沒有的時候,系統(tǒng)才會把它當(dāng)做立即執(zhí)行函數(shù)。

閉包

先看一個小demo:

function test(){
    var arr = [];
    for(var i = 0; i < 10; i++){
        arr[i] = function(){
            document.write(i + "  ");    //10 10 10 10 10 10  10 10 10 10 10 10
        }
    }
    return arr;
}
var myArr = test();
for(var j = 0;j < 10; j++){
    myArr[j]();
}

這里我們先要的結(jié)果是1到9,但實際上打印出了10個10。為什么呢?程序在識別函數(shù)體的時候,只知道把函數(shù)體給了數(shù)組,但并不知道里面寫了什么,而里面的語句也要在真正運行的時候才去執(zhí)行。另外,這里了for循環(huán)時每個函數(shù)生成的時候用的test執(zhí)行上下文AO是共用一個。雖然定義了,但是沒有立即執(zhí)行。在第二個循環(huán)執(zhí)行的時候,arr數(shù)組被保存到了外部,test也執(zhí)行完了,這時候i也變成了10。

函數(shù)在定義的時候不用看里面的變量或者語句,在執(zhí)行的時候才去查找。

那怎么在現(xiàn)在的原型上打印出1到9呢?用立即執(zhí)行函數(shù):

function test(){
    var arr = [];
    for(var i = 0; i < 10; i++){
        (function (j){
            arr[j] = function(){
                document.write(j + "  ");    
            }
        }(i))
    }
    return arr;
}
var myArr = test();
for(var j = 0;j < 10; j++){
    myArr[j]();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容