1.什么是閉包? 有什么作用
- 定義:閉包就是嵌套在函數(shù)里面的內(nèi)部函數(shù),并且該內(nèi)部函數(shù)可以訪問外部函數(shù)中聲明的所有局部變量,參數(shù)和其他內(nèi)部的函數(shù)。當(dāng)該內(nèi)部函數(shù)在外部函數(shù)外被調(diào)用了,就生成了閉包。
(1)閉包包括了函數(shù)和創(chuàng)建該函數(shù)的環(huán)境。這個環(huán)境由閉包創(chuàng)建時在作用域中的任何局部變量組成。
(2)[函數(shù)]和[函數(shù)內(nèi)部能訪問到的變量(也叫環(huán)境)]的總和,就是一個閉包。
(3)閉包不是非得return回一個函數(shù),return出一個對象也可以是閉包。
(4)特點一:作為一個函數(shù)變量的一個引用,當(dāng)函數(shù)返回時,其處于激活的狀態(tài)。
(5)特點二:一個閉包就是當(dāng)一個函數(shù)返回的時候,一個沒有釋放資源的棧區(qū)。
- 作用:
(1)保存變量現(xiàn)場,封裝私有變量
(2)隔離作用域;
(3)做計時器
(4)獨立了一個命名空間
(5)閉包會使變量駐留內(nèi)存,不會被自動清除;使用時應(yīng)謹(jǐn)慎,以免引起內(nèi)存泄露,影響性能
閉包的缺點:
1.在IE9之前因為使用不同的垃圾收集機(jī)制會導(dǎo)致循環(huán)引用會造成內(nèi)存泄漏
2. 閉包會攜帶包含函數(shù)的作用域,所以會比其他函數(shù)占用更多內(nèi)存,過度使用閉包會造成內(nèi)存占用過多
例子:
在函數(shù)內(nèi)部讀取它的內(nèi)部變量:
function fn(){
var a = 1;
function add(){
a+=1
console.log(a)
}
return add //add就是一個橋梁
}
var r = fn() //閉包,包括函數(shù)add和包含變量a的環(huán)境
r() //輸出2
//add被賦給了一個全局變量r,導(dǎo)致add一直存在內(nèi)存中,而add的存在依賴于a,所以a也始終在內(nèi)存中。
因此:閉包就是一個橋梁,連接著外部環(huán)境和父函數(shù)的局部變量
經(jīng)典例子:
(function(){
var arr = []
for(var i=0;i<5;i++){
arr[i] = function(){
console.log(i)
}
}
arr[1]()
})()
為什么!?。o論arr[1]-arr[5]()輸出結(jié)果都是5,因為所訪問的i是外部函數(shù)的,
當(dāng)循環(huán)完以后,i已經(jīng)累加到5了,所以無論多少輸出當(dāng)然是5(作用域鏈)
利用閉包特點可以保存變量現(xiàn)場(讓變量不被釋放):
(function() {
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function (n) {
return function () {
alert(n)
}
}(i) //把i傳遞進(jìn)來,作用域中就可以取到當(dāng)前的i
}
arr[1]()//輸出當(dāng)前值1
})()
2.setTimeout 0 有什么作用
- 定義:setTimeout函數(shù)用來指定某個函數(shù)或某段代碼,在多少毫秒之后執(zhí)行。它返回一個整數(shù),表示定時器的編號,以后可以用來取消這個定時器。
(1)setTimeout(func|code, delay),一般接受兩個參數(shù),第一個參數(shù)可以是字符串,也可以是func函數(shù)名,第二個參數(shù)是設(shè)置多少毫秒ms后執(zhí)行代碼.如果省略第二個參數(shù),該參數(shù)默認(rèn)為0.
(2)setTimeout(“code”,millisec):執(zhí)行代碼必須用字符串形式,millise設(shè)置在多少毫秒之后執(zhí)行這段代碼。
eg:setTimeout('console.log(2)',1000); //1000ms后控制臺輸出2
(3)setTimeout(fn,millisec)中fn為函數(shù)名,也可以是回調(diào)函數(shù),但記得是寫函數(shù)名而不是寫調(diào)用函數(shù),millise設(shè)置在多少毫秒之后執(zhí)行這個函數(shù)。
(4)可以把setTimeout中的函數(shù)排在任務(wù)隊列末尾,等待其他語句全部執(zhí)行完畢再開始立即執(zhí)行,可以理解為setTimeout優(yōu)先級在任務(wù)隊列中最低
setTimeout (‘console.log(最尾)',0)//代表當(dāng)前任務(wù)隊列執(zhí)行后才立刻執(zhí)行輸出“最尾”
setTimeout('console.log(最尾了但還要1000ms后再執(zhí)行)',1000)//代表當(dāng)前任務(wù)隊列執(zhí)行后再過1000ms才執(zhí)行
例子:
setTimeout('console.log(1)',1000); //右邊為執(zhí)行順序: 2
console.log(2); // 3
console.log(3); // 4
setTimeout('console.log(4)',0);// 1
總結(jié):setTimeout 0 有什么作用?(任務(wù)隊列中優(yōu)先級最低)
可以讓setTimeout中的函數(shù)排在任務(wù)隊列末尾,等待其他語句全部執(zhí)行完畢再開始立即執(zhí)行。
3.下面的代碼輸出多少?修改代碼讓fnArr1輸出 i。使用兩種以上的方法
題目:
var fnArr = [];
for (var i = 0; i < 10; i ++) {
fnArr[i] = function(){
return i;
};
}
console.log( fnArr[3]() );~~~無奈都是輸出10,因為執(zhí)行函數(shù)之前 i=10;在for循環(huán)中,
i的值并沒有隨著循環(huán)保存在函數(shù)中,所以關(guān)鍵在于如何保存i的值,所以有了以下閉包的方法:
——————————————————————————————————————————————————————
//使用閉包函數(shù)實現(xiàn)返回當(dāng)前傳入的值
方法一:
var fnArr = [];
for(var i = 0;i < 10;i++) {
fnArr[i] = function(n){//用數(shù)組記錄下循環(huán)中每次的值
return function(){
return n;
}
}(i) //每次循環(huán)傳入實參i
}
console.log( fnArr[3]() );//輸出3
————————————————————————————————————————————————————
方法二:
function func(val) {
return function() {
return val;
};
}
var fnArr = [];
for (var i = 0; i < 10; i++) {
//將返回值(i)存入fnArr數(shù)組
fnArr[i] = func(i);
}
console.log(fnArr[3]());//輸出3
——————————————————————————————————————————————————————
方法三:
var fnArr = [];
for(var i=0;i<10;i++){
(function(n){
fnArr[n] = function(){
return n
}
})(i)//將i作為實參傳入立即執(zhí)行函數(shù)
}
console.log( fnArr[3]());//輸出3
4.使用閉包封裝一個汽車對象,可以通過如下方式獲取汽車狀態(tài)
解答:
var Car = (function(){
var val;
function setSpeed(val){
speed = val;
}
function getSpeed(){
return speed;
}
function accelerate(){
speed +=10;
}
function decelerate(){
speed -=10;
}
function getStatus(){
if(speed===0){
return "stop";
}else{
return "running";
}
}
return {
"setSpeed":setSpeed,
"getSpeed":getSpeed,
"accelerate":accelerate,
"decelerate":decelerate,
"getStatus":getStatus
};
}());
// var Car = //todo;
console.log(Car.setSpeed(30));
console.log(Car.getSpeed()); //30
console.log(Car.accelerate());
console.log(Car.getSpeed()); //40;
console.log(Car.decelerate());
console.log(Car.decelerate());
console.log(Car.getSpeed()); //20
console.log(Car.getStatus()); // 'running';
console.log(Car.decelerate());
console.log(Car.decelerate());
console.log(Car.getStatus()); //'stop';
//Car.speed; //error
5.寫一個函數(shù)使用setTimeout模擬setInterval的功能
方法一:
var i = 0;
function fn(){
setTimeout(function(){
console.log(i);
i++
fn() //遞歸不斷調(diào)用自己
},2000);
}
fn()
方法二:
var i = 0;
function fn(){
var clock = setTimeout(function(){
console.log(i);
i++
clock = setTimeout(arguments.callee,2000); //遞歸不斷調(diào)用自己
},2000);
}
fn()
每隔兩秒輸出:0,1,2,3,4,5,6~~~~~~~~~~
需要注意的是,使用setTimeout并不能完全模擬出setInterval的功能。
在問答1中介紹過,setTimeout是等前面的任務(wù)執(zhí)行之后再開始計算時間間隔。
而setInterval函數(shù)指定的是“開始執(zhí)行”之間的間隔,并不考慮每次任務(wù)執(zhí)行本身所消耗的時間,比如,
setInterval指定每100ms執(zhí)行一次,每次執(zhí)行需要5ms,那么第一次執(zhí)行結(jié)束后95毫秒,第二次執(zhí)行就會
開始。如果某次執(zhí)行耗時特別長,比如需要105毫秒,那么它結(jié)束后,下一次執(zhí)行就會立即開始。
6.寫一個函數(shù),計算setTimeout平均[備注:新加]最小時間粒度(參考)
function getMini() {
var t1 = Date.now(); //開始執(zhí)行函數(shù)的時間
var i = 0;
var clock = setTimeout(function(){
i++;
if(i === 1000) { //計算1000次
clearTimeout(clock); //停止
var t2 = Date.now(); //結(jié)束時間
console.log((t2-t1)/i); //平均值得到最小粒度
}else{
//arguments 當(dāng)前函數(shù)的參數(shù)"數(shù)組"
//arguments.callee 調(diào)用這個"數(shù)組"的函數(shù),也就是當(dāng)前函數(shù)
//相當(dāng)于循環(huán)執(zhí)行當(dāng)前函數(shù), 直到 i 滿足判斷語句中的值
clock = setTimeout(arguments.callee,0);
}
},0) //此處一定是0,無間隔地循環(huán)執(zhí)行直到到1000為止
}
getMini()
輸出大約4.249秒~4.5秒
7.下面這段代碼輸出結(jié)果是? 為什么?
題目:
var a = 1;
setTimeout(function(){
a = 2;
console.log(a);
}, 0);
var a ;
console.log(a);
a = 3;
console.log(a);
結(jié)果:
setTimeout的delay值被設(shè)為0,也就意味著里面的函數(shù)要等待其他語句全部執(zhí)行完畢才會運行,setTimeout( ,0),代碼會在最后執(zhí)行。所以最先輸出的是1,然后賦值3給a,最后執(zhí)行setTimeout()里面的代碼
8.下面這段代碼輸出結(jié)果是? 為什么?
題目:
var flag = true;
setTimeout(function(){
flag = false;
},0)
while(flag){}
console.log(flag);
------------------
等同于
var flag = true;
while(flag){} //一直卡在這位置不往下執(zhí)行了
console.log(flag)
setTimeout(function(){
flag = false
})
分析:只要while(expression){statement},ex條件一直為true,那么就不斷循環(huán)執(zhí)行st,setTimeout的delay值被設(shè)為0,也就意味著里面的函數(shù)要等待其他語句全部執(zhí)行完畢才開始再運行。而while(flag){}因為flag為true的關(guān)系永遠(yuǎn)不會停止,所以console.log(flag)也就永遠(yuǎn)不會執(zhí)行。
9.下面這段代碼輸出?如何輸出delayer: 0, delayer:1...(使用閉包來實現(xiàn))
題目:
for(var i=0;i<5;i++){
setTimeout(function(){
console.log('delayer:' + i );
}, 0);
console.log(i);
}

解答:用閉包保存循環(huán)中每次的i值,閉包只是起到保存變量的作用,并未因此改變setTimeout( ,0)中的代碼依然放置事件隊列最尾處才執(zhí)行的規(guī)則
方法一:
for(var i=0;i<5;i++){
(function(n){
setTimeout(function(){
console.log('delayer:' + n );
}, 0);
})(i)
console.log(i);
}
結(jié)果3.png

方法二:
for(var i=0;i<5;i++){
setTimeout((function(n){
return function(){
console.log('delayer:' + n );
}
})(i), 0);
console.log(i);
}
結(jié)果3.png

方法三:
for(var i=0;i<5;i++){
fn = function(n){
return function(){
console.log("delayer:"+n)
}
}(i)
setTimeout(fn,0)
console.log(i)
}

本文版權(quán)歸本人和饑人谷所有,轉(zhuǎn)載請注明來源。