20.閉包、定時(shí)器

問題

一汪榔、什么是閉包? 有什么作用蒲拉?

1.什么是閉包
①JavaScript高級程序設(shè)計(jì)第三版定義閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。在javascript中痴腌,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量雌团,所以可以把閉包理解為定義在一個(gè)函數(shù)內(nèi)部的函數(shù)
②內(nèi)部函數(shù)在包含它們的外部函數(shù)之外被調(diào)用時(shí)士聪,就會(huì)形成閉包锦援,而且只要存在調(diào)用內(nèi)部函數(shù)的可能,JavaScript就需要保留被引用的函數(shù)剥悟。
例如:

function f1() { 
 var n = 999; 
 function f2() {  
   console.log(n); // 999 
 }
}

上面代碼中灵寺,函數(shù)f2就在函數(shù)f1內(nèi)部,這時(shí)f1內(nèi)部的局部變量区岗,對f2都是可見的略板。但是f2內(nèi)部的局部變量,對f1就是不可見的慈缔。這就是JavaScript語言的”鏈?zhǔn)阶饔糜?chain scope)”結(jié)構(gòu)叮称,也就是子對象會(huì)一級一級地向上尋找父對象的變量。所以藐鹤,父對象的所有變量瓤檐,對子對象都是可見的,反之則不成立娱节。
因?yàn)閒2可以讀取f1的局部變量挠蛉,所以只要把f2作為返回值,我們就可以在f1外部讀取它的內(nèi)部變量肄满。

function f1() { 
    var n = 999; 
    function f2() { 
      console.log(n); 
    } 
    return f2;
}
var result = f1();
result(); // 999

上面代碼中碌秸,函數(shù)f1的返回值就是函數(shù)f2绍移,由于f2可以讀取f1的內(nèi)部變量,所以就可以在f1外部獲得f1的內(nèi)部變量讥电。閉包就是函數(shù)f2蹂窖,即能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。由于在JavaScript語言中恩敌,只有函數(shù)內(nèi)部的子函數(shù)才能讀取它的內(nèi)部變量瞬测,因此可以把閉包簡單理解成定義在一個(gè)函數(shù)內(nèi)部的函數(shù)。閉包最大的特點(diǎn)纠炮,就是它可以記住誕生的環(huán)境月趟,比如f2記住了它誕生的環(huán)境f1,所以從f2可以得到f1的內(nèi)部變量恢口。在本質(zhì)上孝宗,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁

2.閉包的作用
①讀取函數(shù)內(nèi)部的變量,是溝通函數(shù)內(nèi)部和外部的橋梁耕肩;
②閉包可以使得它誕生環(huán)境一直存在因妇。也就是父函數(shù)的變量被子函數(shù)引用,因此內(nèi)存不被釋放猿诸,讓這些變量的值始終保持在內(nèi)存中婚被,可能會(huì)導(dǎo)致內(nèi)存泄露。

參考
閉包
閉包

二梳虽、setTimeout 0有什么作用址芯?

1.setTimeout是延時(shí)函數(shù),用來指定某個(gè)函數(shù)或某段代碼窜觉,在多少毫秒之后執(zhí)行谷炸。它返回一個(gè)整數(shù),表示定時(shí)器的編號禀挫,以后可以用來取消這個(gè)定時(shí)器旬陡。

var timerId = setTimeout(func|code, delay)

上面代碼中,setTimeout函數(shù)接受兩個(gè)參數(shù)特咆,第一個(gè)參數(shù)func|code是將要推遲執(zhí)行的函數(shù)名或者一段代碼,第二個(gè)參數(shù)delay是推遲執(zhí)行的毫秒數(shù)录粱。
注意:推遲執(zhí)行的代碼必須以字符串的形式腻格,放入setTimeout,因?yàn)橐鎯?nèi)部使用eval函數(shù)啥繁,將字符串轉(zhuǎn)為代碼菜职。如果推遲執(zhí)行的是函數(shù),則可以直接將函數(shù)名旗闽,放入setTimeout酬核。一方面eval函數(shù)有安全顧慮蜜另,另一方面為了便于JavaScript引擎優(yōu)化代碼,setTimeout方法一般總是采用函數(shù)名的形式

setTimeout('console.log(2)',1000);

function f(){
  console.log(2);
}
setTimeout(f,1000);
或者
setTimeout(function (){
  console.log(2)
},1000);

2.setTimeout 0的作用:
①使setTimeout內(nèi)的函數(shù)在所有要執(zhí)js語句執(zhí)行完成之后再執(zhí)行嫡意,實(shí)現(xiàn)javascript的異步
例如:

console.log(1)
setTimeout('console.log(2)',0)
console.log(3)

打印結(jié)果


Paste_Image.png

②setTimeout(f, 0)可以調(diào)整事件的發(fā)生順序举瑰。
例如:等事件完成后執(zhí)行


Paste_Image.png

參考
定時(shí)器timer

代碼

一、下面的代碼輸出多少蔬螟?修改代碼讓fnArri輸出 i此迅。使用兩種以上的方法。

var fnArr = [];
for (var i = 0; i < 10; i ++) {
    fnArr[i] =  function(){
    return i;
    };
}
console.log( fnArr[3]() );  //

輸出的是10
原因:js的運(yùn)行順序是首先聲明一個(gè)變量定義成數(shù)組旧巾,然后執(zhí)行for循環(huán)耸序,同時(shí)將函數(shù)賦給fnArr[i],而fnArr[i]指的是數(shù)組里面的每一項(xiàng),就相當(dāng)于將函數(shù)賦給數(shù)組里面的每一項(xiàng)鲁猩。然后再執(zhí)行fnArr[3](),也就是調(diào)用這個(gè)函數(shù)坎怪,return出i。但在for循環(huán)時(shí)i就已經(jīng)變成了10廓握,因?yàn)楹瘮?shù)return出來i是要等到函數(shù)執(zhí)行的時(shí)候搅窿,for循環(huán)在函數(shù)的前面就執(zhí)行了。還有只有for循環(huán)完成之后輸出i的一個(gè)結(jié)果疾棵,后面執(zhí)行函數(shù)的時(shí)候才能return出i的值戈钢。所以這里無論i是多少函數(shù)return出來的都是10。


Paste_Image.png

1.1是尔、立即執(zhí)行函數(shù)內(nèi)賦值殉了,且用臨時(shí)變量保存i的值

var fnArr = [];
for(var i=0;i<10;i++){
    (function (){ //這個(gè)立即執(zhí)行函數(shù)相當(dāng)于生成了10個(gè)閉包,每個(gè)閉包保存一個(gè)變量i
        var n = i;  //這里臨時(shí)變量保存i的值
        fnArr[n]=function(){
            return n;
        }
    })();
}
console.log( fnArr[3]() );

1.2拟枚、立即執(zhí)行函數(shù)內(nèi)賦值薪铜,父函數(shù)傳參

var fnArr = [];
for(var i=0;i<10;i++){
    (function (n){
        //var n = i 就相當(dāng)于聲明了一個(gè)臨時(shí)變量,保存i
        fnArr[n]=function(){
            return n;
        }
    })(i);
}
console.log( fnArr[3]() );

2.1恩溅、整個(gè)立即執(zhí)行函數(shù)被賦值隔箍,且用臨時(shí)變量保存i的值

var fnArr = [];
for(var i=0;i<10;i++){
        fnArr[i]=(function(){
            var n=i;
            return function(){
                return n;
            }
        })();
}
console.log( fnArr[3]() );

2.2、整個(gè)立即執(zhí)行函數(shù)被賦值脚乡,父函數(shù)需要傳參

var fnArr = [];
for(var i=0;i<10;i++){
        fnArr[i]=(function(n){
            return function(){
                return n;
            }
        })(i);
}
console.log( fnArr[3]() );

3.1蜒滩、可以把i綁定在函數(shù)的屬性上作為函數(shù)屬性的一個(gè)值,得到函數(shù)就能得到值(這個(gè)很少用)

var fnArr = [];
for(var i=0;i<10;i++){
    var fn = function(){};
    fn.idx = i; //函數(shù)身上可以綁定序號的奶稠,把這i直接綁在函數(shù)本身身上作為函數(shù)屬性的值
    fnArr[i] = fn;
//i作為函數(shù)屬性的一個(gè)值俯艰,得到這個(gè)函數(shù)就能得到這個(gè)值
//再將函數(shù)賦給數(shù)組,就可以通過數(shù)組獲得這個(gè)值
}
console.log( fnArr[4].idx );//數(shù)組可以直接通過索引idx獲取i值
Paste_Image.png

二锌订、使用閉包封裝一個(gè)汽車對象竹握,可以通過如下方式獲取汽車狀態(tài)。

var Car = //todo;
Car.setSpeed(30);
Car.getSpeed(); //30
Car.accelerate();
Car.getSpeed(); //40;
Car.decelerate();
Car.decelerate();
Car.getSpeed(); //20
Car.getStatus(); // 'running';
Car.decelerate(); 
Car.decelerate();
Car.getStatus();  //'stop';
//Car.speed;  //error
var Car = (function(){
    var speed = 0;
    function setSpeed(num){
        speed = num;
    }
    function getSpeed(){
        console.log(speed);
    }
    function accelerate(){
        speed +=10;
    }
    function decelerate(){
        speed -=10;
    }
    function getStatus(){
        if(speed>0){
            console.log('running');
        }else{
            console.log('stop')
        }
    }
    return {
        setSpeed:setSpeed,
        getSpeed:getSpeed,
        accelerate:accelerate,
        decelerate:decelerate,
        getStatus:getStatus
    }
}());

Car.setSpeed(30);
Car.getSpeed(); //30
Car.accelerate();
Car.getSpeed(); //40;
Car.decelerate();
Car.decelerate();
Car.getSpeed(); //20
Car.getStatus(); // 'running';
Car.decelerate(); 
Car.decelerate();
Car.getStatus();  //'stop';
//Car.speed;  //error
Paste_Image.png

三辆飘、寫一個(gè)函數(shù)使用setTimeout模擬setInterval的功能啦辐。

1.setInterval計(jì)時(shí)函數(shù)的用法與setTimeout完全一致谓传,區(qū)別僅僅在于setInterval指定某個(gè)任務(wù)每隔一段時(shí)間就執(zhí)行一次,也就是無限次的定時(shí)執(zhí)行芹关。

2.setTimeout模擬setInterval

var i=0;
function intv(){
  setTimeout(function(){
      console.log(i++);
      intv();  //注意在setTimeout函數(shù)內(nèi)续挟,對外層函數(shù)進(jìn)行遞歸
  },1000)
};

還可以這樣

var i = 0;
var b = setTimeout(function(){
    console.log(i++);
    setTimeout(arguments.callee,5000)
},2000);

四、寫一個(gè)函數(shù)充边,計(jì)算setTimeout平均[備注:新加]最小時(shí)間粒度庸推。

function getMin() {
    var i = 0;
    var start = Date.now()  //獲取當(dāng)前時(shí)間
    var clock = setTimeout(function() { 
           i++;
           if(i === 1000) {   //當(dāng)i執(zhí)行到1000時(shí),延時(shí)器停止浇冰,然后獲取結(jié)束時(shí)間贬媒。最小時(shí)間粒度就是最后的時(shí)間減去開始的時(shí)間除以執(zhí)行的次數(shù)
               clearTimeout(clock);
               var end = Date.now();
               console.log((end - start)/i)  //end-start是整個(gè)的執(zhí)行時(shí)間,i就是調(diào)用了多少次
          }

          clock = setTimeout(arguments.callee,0); //arguments.callee就是這個(gè)匿名函數(shù)肘习,如果i沒到1000的話际乘,就再立即執(zhí)行一遍這函數(shù),這個(gè)0也屬于最小時(shí)間粒度

    },0)
}
getMin()

打印結(jié)果:


Paste_Image.png

五漂佩、下面這段代碼輸出結(jié)果是? 為什么?

var a = 1;  //變量提前脖含,然后把a(bǔ)賦值為1

setTimeout(function(){ //因?yàn)槭茄舆t函數(shù)且延遲0,所以在所有代碼最后執(zhí)行
    a = 2; 
    console.log(a); //最后才開始執(zhí)行到這里投蝉,所以a為2
}, 0);

var a ;  //變量提升养葵,上面a賦值為1,所以這里a也是1
console.log(a);  //所以a為1

a = 3;  //a賦值為3
console.log(a); //所以這里a為3

打印結(jié)果


Paste_Image.png

六瘩缆、下面這段代碼輸出結(jié)果是? 為什么?

var flag = true;
setTimeout(function(){  //關(guān)鍵點(diǎn)延遲函數(shù)最后執(zhí)行
    flag = false;
},0)
while(flag){}  //執(zhí)行while循環(huán)关拒,因?yàn)閒lag為ture,所以循環(huán)一直存在庸娱,執(zhí)行空語句着绊,后面的代碼就沒法執(zhí)行
console.log(flag); //沒有輸出

七、下面這段代碼輸出熟尉?如何輸出delayer: 0, delayer:1...(使用閉包來實(shí)現(xiàn))

for(var i=0;i<5;i++){
    setTimeout(function(){
         console.log('delayer:' + i ); 
//設(shè)置setTimeout归露,但是里面的函數(shù)是在最后執(zhí)行,此時(shí)i已經(jīng)為5斤儿。
//for每循環(huán)一次剧包,setTimeout都要執(zhí)行一次,只不過它是等到所有的代碼執(zhí)行完了再執(zhí)行往果,在這里就是for循環(huán)的遍歷疆液。
//所以最后一共輸出五個(gè)delayer: 5(分別是i等于0,1棚放,2枚粘,3馅闽,4的輸出),但是每次輸入值為5飘蚯。
    }, 0);
    console.log(i); //執(zhí)行console.log馍迄,輸出結(jié)果為0,1局骤,2攀圈,3,4
}

打印結(jié)果


Paste_Image.png

這和上面第一題思路差不多:

for(var i=0;i<5;i++){
    (function(){
        var n = i; //因?yàn)閕賦值給n峦甩,所以可以console.log(n)
        setTimeout(function(){
            console.log('delayer:' + n)
        },0)
        console.log(n);
    })();
}
for(var i=0;i<5;i++){
    (function(n){
        setTimeout(function(){
            console.log('delayer:' + n)
        },0)
        console.log(n);
    })(i);
}

這上面兩種方法非常相同赘来,就是在立即執(zhí)行函數(shù)里面去聲明變量,然后保存i

for(var i=0;i<5;i++){
        setTimeout((function(){
            var n = i;
            return function(){
               console.log('delayer:' + n) 
            };
        })(),0)
        console.log(i);
}
for(var i=0;i<5;i++){
        setTimeout((function(n){
            return function(){
               console.log('delayer:' + n) 
            };
        })(i),0)
        console.log(i);
}

上面兩種方法也差不多相同凯傲,此時(shí)立即執(zhí)行函數(shù)放在延遲函數(shù)里面了犬辰,也是聲明一個(gè)變量,然后去保存i冰单。

總結(jié):閉包就是函數(shù)里面嵌套函數(shù)幌缝,關(guān)鍵點(diǎn)是理解函數(shù)有作用域鏈和內(nèi)存不被釋放的兩個(gè)概念,所以可以通過暴露里面嵌套的函數(shù)诫欠,在函數(shù)外部去利用暴露出來的嵌套的函數(shù)涵卵,然后調(diào)用它。就可以訪問父函數(shù)里面的變量荒叼。這是我學(xué)完這個(gè)知識(shí)點(diǎn)和做作業(yè)中的個(gè)人體會(huì)轿偎。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市被廓,隨后出現(xiàn)的幾起案子坏晦,更是在濱河造成了極大的恐慌,老刑警劉巖伊者,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件英遭,死亡現(xiàn)場離奇詭異,居然都是意外死亡亦渗,警方通過查閱死者的電腦和手機(jī)挖诸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來法精,“玉大人多律,你說我怎么就攤上這事÷眩” “怎么了狼荞?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長帮碰。 經(jīng)常有香客問我相味,道長,這世上最難降的妖魔是什么殉挽? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任丰涉,我火速辦了婚禮拓巧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘一死。我一直安慰自己肛度,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布投慈。 她就那樣靜靜地躺著承耿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伪煤。 梳的紋絲不亂的頭發(fā)上加袋,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機(jī)與錄音抱既,去河邊找鬼锁荔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蝙砌,可吹牛的內(nèi)容都是我干的阳堕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼择克,長吁一口氣:“原來是場噩夢啊……” “哼恬总!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肚邢,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤壹堰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后骡湖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贱纠,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年响蕴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谆焊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浦夷,死狀恐怖辖试,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劈狐,我是刑警寧澤罐孝,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站肥缔,受9級特大地震影響莲兢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一改艇、第九天 我趴在偏房一處隱蔽的房頂上張望俗慈。 院中可真熱鬧,春花似錦遣耍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘦穆,卻和暖如春纪隙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扛或。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工绵咱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人熙兔。 一個(gè)月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓悲伶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親住涉。 傳聞我的和親對象是個(gè)殘疾皇子麸锉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容