問題
一汪榔、什么是閉包? 有什么作用蒲拉?
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é)果
②setTimeout(f, 0)可以調(diào)整事件的發(fā)生順序举瑰。
例如:等事件完成后執(zhí)行
代碼
一、下面的代碼輸出多少蔬螟?修改代碼讓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。
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值
二锌订、使用閉包封裝一個(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
三辆飘、寫一個(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é)果:
五漂佩、下面這段代碼輸出結(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é)果
六瘩缆、下面這段代碼輸出結(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é)果
這和上面第一題思路差不多:
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ì)轿偎。