JS閉包、定時器

1.什么是閉包? 有什么作用

函數的作用域scope取決于聲明時,而非調用時。普通函數執行后函數體及內部變量會被垃圾清理機制回收,構建閉包closure能讓函數內部的變量駐留在內存中并被外部引用。閉包可以理解為保留函數聲明時內部變量作用域&函數體構成的一個環境,通過這個環境能讀取函數內部的變量。嵌套函數中子函數返回構成閉包時,返回的內容包括該函數生成時的變量作用域關系,即環境變量+函數。
閉包有兩個基本特征:1.父函數嵌套子函數,返回子函數執行結果。2.父函數作用域的變量被子函數引用著。
閉包的實際作用:1.讀取函數內部的變量;2.內存不被釋放,讓變量的值始終保持在內存中(但使用不當會引起內存泄漏消耗過多內存資源)。
閉包的應用場景:1.聲明私有變量。2.隔離作用域。3.構建計數器。
經典錯誤案例

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

以上代碼count[n]()每次執行都返回結果10。實際預期是count[n]() 返回結果n。每次返回10的原因是for循環運行后count[i]數組中的每個成員實際是一個匿名函數function(){return i},for循環執行完后i變為10,所以執行count[n]()實際上是執行數組成員中的匿名函數即(function(){return i}()),i的值已變為10所以每次執行結果為10。

改進代碼后如下:

var count=[];
for(var i=0;i<10;i++){
count[i]=(function t1(){
var n=i
return function t2(){
return n;
}
}())
}
console.log(count[0]()) //0
console.log(count[1]()) //1
console.log(count[2]()) //2
console.log(count[3]()) //3

count[i]數組的成員變成了10個,由立即執行函數t1即(function t1(){}())的執行結果function t2(){return n},這里n是由i賦值得來,當i循環遞增時n隨之改變,由于子函數t2中變量n始終從父函數t1的作用域上尋找,所以n始終被保留在內存中,每次i循環遞增n就賦值并保留下來。當執行count[n]()即執行(function t2(){return n}())


2.setTimeout 0 有什么作用

var timerId = setTimeout(func|code, delay)
delay參數用來指定在當前JS隊列中的任務執行完后,延遲多長時間將函數/代碼段添加到任務隊列尾部并執行。
以下代碼運行后返回一個整數,表示定時器的編號,可以用于取消這個定時器。但實際任務中,很少這么用。運行clock01返回整數表示定時器編號。
var clock01=setTimeout(function(){var ccc=10},5000)

setTimeout(function(){},0)表示指定的任務在現有的任務隊列執行完畢之后添加并立即執行。


3.下面的代碼輸出多少?修改代碼讓fnArri 輸出 i。使用兩種以上的方法
var fnArr = [];
    for (var i = 0; i < 10; i ++) {
        fnArr[i] =  function(){
            return i;
        };
    }
    console.log( fnArr[3]() );  //

代碼如下:

方法一:
var fnArr=[];
for (var i=0;i<10;i++){
fnArr[i]=(function() t1{
var n=i;  //t1函數立即執行,不指定傳入參數,在父函數t1中聲明變量n,n由i賦值,t1執行返回函數t2
return function() t2{
return n //t2函數執行返回n,每次i改變后n會保留下來
}
}())
}

方法二:

var fnArr=[];
for (var i=0;i<10;i++){
fnArr[i]=(function t1(n){ //這里參數n=i,相當于var n=i
return function t2(){
return n //返回變量n,在父函數t1的作用域中找到n
}
}(i)) //函數立即執行時傳入參數i
}

4.使用閉包封裝一個汽車對象,可以通過如下方式獲取汽車狀態
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 t1(){
 var Speed=0;
 function setSpeed(n){
 Speed=n;
 return Speed;
 }

 function getSpeed(){
 console.log(Speed)
 return Speed;
 }

 function accelerate(){
 Speed+=10;
 return Speed;
 }

 function decelerate(){
 Speed-=10;
 return Speed;
 }

 function getStatus(){
 if(Speed>0){
 console.log("running");
 } else if(Speed===0){
 console.log("stop");
 }
 }

 function error(){
 console.log("error:");
 }

 return {
 "setSpeed":setSpeed,
 "getSpeed":getSpeed,
 "accelerate":accelerate,
 "decelerate":decelerate,
 "getStatus":getStatus,
 "speed":error()
 }
}())

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

5.寫一個函數使用setTimeout模擬setInterval的功能
var i=0; //聲明初始值由0開始
(function intv(){
 setTimeout(function(){//setTimeout(函數,時間間隔)
 console.log(i++)
 intv();//由于setTimeout只執行一次,所以用遞歸調用自身做循環
 },1000)
})()

6.寫一個函數,計算setTimeout平均最小時間粒度
function mintime(){
var i=0;
var starttime=Date.now();
var timer=setTimeout(function t1(){
i++;
if(i===1000){
clearTimeout(timer);
var endtime=Date.now();
console.log((endtime-starttime)/i)
} else{
timer=setTimeout(t1.arguments.callee,0);
}
},0)
}
mintime();

7.下面這段代碼輸出結果是? 為什么?
var a = 1;
setTimeout(function(){
 a = 2;
 console.log(a);
}, 0);
var a ;
console.log(a);
a = 3;
console.log(a);

setTimeout會將其參數1中的函數或代碼段在JS任務隊列中的當前任務執行完后,延遲指定的時間放入隊列尾部執行。
以上分析執行過程為

var a=1;
var a;
console.log(a); //返回1
a=3;
console.log(a); //返回3
setTimeout(function(){
a=2;
console.log(a); //放回2
},0)

8.下面這段代碼輸出結果是? 為什么?
var flag = true;
setTimeout(function(){
 flag = false;
},0)
while(flag){}
console.log(flag);

以上代碼分析如下:

var flag=true;
while(flag){}//while循環條件成立,由于沒有break/continue會一直執行
console.log(flag);
setTimeout(function(){//待當前隊列中所有任務執行完后,延遲0毫秒將函數放入隊列尾部執行
flag=false;
},0)

實際執行結果是不顯示任何內容,while循環條件成立沒有break/continue終止,會一直執行循環,后面的console.log無法執行


9.下面這段代碼輸出?如何輸出delayer: 0, delayer:1...(使用閉包來實現)
for(var i=0;i<5;i++){
 setTimeout(function(){
 console.log('delayer:' + i );
 }, 0);
 console.log(i);
}

實現如下:

//方法一
for(var i=0;i<5;i++){
 setTimeout((function t1() {
 var m=i; //父函數t1下聲明變量m,m的值由for循環中的作用域變量i賦值得來,m保留每次i遞增后的值
 return function t2() {
 console.log('delayer:' + m )
 }
 }()),0);
 console.log(i);
}

//方法二:寫法與方法一類似,只不過t1()立即執行函數執行時傳入實際參數i賦值給t1定義的形式參數m
for(var i=0;i<5;i++){
 setTimeout((function t1(m){//定義函數t1形式參數m,m由t1函數立即執行時傳入的實際參數i賦值得來,相當于在t1函數中var m=i;
 return function t2(){
 console.log('delayer:' + m )
 }
 }(i)),0);//t1函數立即執行時傳入實際參數i
 console.log(i);
}

//方法三:父函數t1是立即執行函數,t1下聲明變量m,m的值由for循環的作用域變量i賦值得來。m保留每次i遞增后的值
for(var i=0;i<5;i++){
 (function t1(){
 var m=i
 return (setTimeout(function t2(){
 console.log('delayer:'+m)
 },0))
 }())
 console.log(i)
}

//方法四:
for(var i=0;i<5;i++){
 (function t1(m){//父函數t1定義形式參數m,m的值有t1函數立即執行時傳入的實際參數i賦值得來,相當于在t1函數中var m=i
 return setTimeout(function t2(){
 console.log('delayer:'+m)
 },0)
 }(i)) //t1立即執行函數傳入實際參數i
 console.log(i)
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 什么是閉包? 有什么作用閉包:函數對象可以通過作用域鏈相互關聯,函數體內部的變量可以保存在函數的作用域內。 上述代...
    coolheadedY閱讀 754評論 0 0
  • 一、問題 (一)、什么是閉包? 有什么作用 閉包是指能夠訪問自由變量的函數 (變量在本地使用,但在閉包中定義)。換...
    該帳號已被查封_才怪閱讀 401評論 0 1
  • 什么是閉包? 有什么作用? 閉包是指有權限訪問另一個函數作用域的變量的函數(就是能夠讀取其他函數內部變量的函數)。...
    _fin閱讀 693評論 0 3
  • 問題 什么是閉包? 有什么作用 閉包可以用來讀取函數內部的變量。 由于作用域鏈表,外部是無法讀取到函數內部的變量的...
    不是魷魚閱讀 412評論 0 0
  • 今天的由于身體的原因,我的感覺陷入到混沌狀態,頭腦一直處于空白的狀態,思維能力急速下降,理解力也脫節,剛才孩子和我...
    尚巾林閱讀 217評論 0 0