閉包:指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)
閉包實現(xiàn)條件:內(nèi)部函數(shù)使用了外部函數(shù)的變量拯腮、外部函數(shù)已退出辆脸、內(nèi)部函數(shù)可訪問
1. 當函數(shù)被調(diào)用時
1. 會創(chuàng)建一個執(zhí)行環(huán)境(execution context)及相應的作用域鏈
2. 使用arguments和其他命名參數(shù)的值來初始化函數(shù)的活動對象(activation object)
function foo(){
var a = 2;
function bar(){ //bar()擁有包含foo()內(nèi)部作用域的閉包挎扰,并使其作用域不被銷毀
console.log(a);
}
return bar;
}
var a = foo();
a(); //相當于執(zhí)行bar();因此bar()是在自己所處的作用域以外被執(zhí)行
通常認為在foo
執(zhí)行完之后其內(nèi)部作用域就銷毀了霉旗,然而閉包會阻止這件事的發(fā)生澄步;因為bar
會將包含函數(shù)也就是foo
的活動對象添加到他自己的作用域鏈中;這就是閉包的核心—被內(nèi)部函數(shù)訪問的外部函數(shù)的變量可以保存在外部函數(shù)作用域內(nèi)而不被回收
2.作用域鏈的機制決定了閉包只能取得包含函數(shù)中任何變量的最后一個值目锭,閉包所保存的是整個變量對象评汰,而不是某個特殊變量
function loop(){
var arr = [];
for(var i=0;i<10;i++){
arr[i] = function(){ //這里每個匿名函數(shù)都保存著loop的活動對象,所以他們引用的是同一個變量i
return i;
}
}
return arr[3](); //看起來每個函數(shù)都應該返回自己的索引值痢虹,然而實際上每個函數(shù)都返回10
}
console.log( loop() ); //10
//將以上函數(shù)做如下修改:
function loop(){
var arr = [];
for(var i=0;i<10;i++){
arr[i] = function(num){
return function(){
return num; //參數(shù)是按值傳遞的被去,這樣每次調(diào)用都會將i的值復制給num
};
}(i);
}
return arr[3]();
}
console.log( loop() ); //3
3. 閉包的內(nèi)存泄漏
- 內(nèi)存泄漏:對于持續(xù)運行的服務進程(daemon),需要及時釋放不再用到的內(nèi)存奖唯;否則惨缆,內(nèi)存占用越來越高,輕則影響系統(tǒng)性能,重則導致進程崩潰坯墨;不再用到的內(nèi)存寂汇,沒有及時釋放,就叫做內(nèi)存泄漏(memory leak)
- 閉包在
IE
中導致的問題:如果閉包的作用域鏈中保存著一個HTML
元素捣染,意味著該元素將無法被銷毀
function assignHandler(){
var elm = document.getElementById("id");
elm.onclick = function(){ //在這個匿名函數(shù)中保存了assignHandler的活動對象骄瓣,只要匿名函數(shù)存在elm至少被引用一次,因此所占用的內(nèi)存就一直不會被回收
console.log(elm.id);
}
}
//修改方式如下:
function assignHandler(){
var elm = document.getElementById("id");
var id = elm.id;
elm.onclick = function(){
console.log(id);
}
elm = null;
}
4.利用閉包私有化變量隱藏數(shù)據(jù)
- 私有變量:任何在函數(shù)中定義的變量耍攘,都可以被認為是私有變量榕栏,因為不能在函數(shù)外部訪問這些變量;因此少漆,函數(shù)中的私有變量包括函數(shù)的參數(shù)臼膏、局部變量和內(nèi)部定義的其他函數(shù)
- 特權(quán)方法:有權(quán)訪問私有變量和私有函數(shù)的公有方法
function Person(name){
this.getName = function(){ //特權(quán)方法,有權(quán)訪問私有變量name
return name;
}
this.setName = function(val){ //特權(quán)方法示损,有權(quán)訪問私有變量name
name = val;
}
}
var person = new Person("Asher"); //每次使用構(gòu)造函數(shù)創(chuàng)建實例都是不同的
console.log( person.getName() ); //Asher
person.setName("Taylor");
console.log( person.getName() ); //Taylor
5.下面代碼輸出什么渗磅,想辦法讓fnArr[i]()
輸出i
var fnArr = [];
for (var i = 0; i < 10; i ++) {
fnArr[i] = function(){
return i;
};
}
console.log( fnArr[3]() ); //10;因為引用的是同一個變量i
//寫法1:
var fnArr = [];
for (var i = 0; i < 10; i ++) {
fnArr[i] = (function(){
return i;
})(); //遍歷時立馬把值存進數(shù)組中
}
console.log( fnArr[3] ); //3
//寫法2:
var fnArr = [];
for (var i = 0; i < 10; i ++) {
fnArr[i] = (function(num){ //將i作為參數(shù)進行傳遞
return function(){
return num;
};
})(i);
}
console.log( fnArr[3]() ); //3
//寫法3:
var fnArr = [];
for (var i = 0; i < 10; i ++) {
(function(n){
fnArr[i] = function(){
return n; //每次傳進去的方法體返回值都不一樣
}
})(i);
}
console.log( fnArr[3]() ); //3
//寫法4:
for(var i=0;i<10;i++){
fnArr[i] = (function(){
var n = i;
return function(){
return n; //每次傳進去的方法體返回值都不一樣
}
})()
}
console.log( fnArr[3]() ); //3
6.使用閉包封裝一個汽車對象检访,可以通過如下方式獲取汽車狀態(tài)
(function(){
var speed = 0;
var todo = {
setSpeed:function (n){
return speed = n;
},
getSpeed:function(){
console.log(speed);
},
accelerate:function(){
return speed += 10;
},
decelerate:function(){
return speed -= 10;
},
getStatus:function(){
if( speed == 0 ){
console.log("stop");
}else if( speed > 0){
console.log("running");
}
}
}
return Car = {
setSpeed:todo.setSpeed,
getSpeed:todo.getSpeed,
accelerate:todo.accelerate,
decelerate:todo.decelerate,
getStatus:todo.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';