闭包:指有权访问另一个函数作用域中的变量的函数
闭包实现条件:内部函数使用了外部函数的变量、外部函数已退出、内部函数可访问
1. 当函数被调用时
1. 会创建一个执行环境(execution context)及相应的作用域链
2. 使用arguments和其他命名参数的值来初始化函数的活动对象(activation object)
function foo(){
var a = 2;
function bar(){ //bar()拥有包含foo()内部作用域的闭包,并使其作用域不被销毁
console.log(a);
}
return bar;
}
var a = foo();
a(); //相当于执行bar();因此bar()是在自己所处的作用域以外被执行
通常认为在foo
执行完之后其内部作用域就销毁了,然而闭包会阻止这件事的发生;因为bar
会将包含函数也就是foo
的活动对象添加到他自己的作用域链中;这就是闭包的核心—被内部函数访问的外部函数的变量可以保存在外部函数作用域内而不被回收
2.作用域链的机制决定了闭包只能取得包含函数中任何变量的最后一个值,闭包所保存的是整个变量对象,而不是某个特殊变量
function loop(){
var arr = [];
for(var i=0;i<10;i++){
arr[i] = function(){ //这里每个匿名函数都保存着loop的活动对象,所以他们引用的是同一个变量i
return i;
}
}
return arr[3](); //看起来每个函数都应该返回自己的索引值,然而实际上每个函数都返回10
}
console.log( loop() ); //10
//将以上函数做如下修改:
function loop(){
var arr = [];
for(var i=0;i<10;i++){
arr[i] = function(num){
return function(){
return num; //参数是按值传递的,这样每次调用都会将i的值复制给num
};
}(i);
}
return arr[3]();
}
console.log( loop() ); //3
3. 闭包的内存泄漏
- 内存泄漏:对于持续运行的服务进程(daemon),需要及时释放不再用到的内存;否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃;不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)
- 闭包在
IE
中导致的问题:如果闭包的作用域链中保存着一个HTML
元素,意味着该元素将无法被销毁
function assignHandler(){
var elm = document.getElementById("id");
elm.onclick = function(){ //在这个匿名函数中保存了assignHandler的活动对象,只要匿名函数存在elm至少被引用一次,因此所占用的内存就一直不会被回收
console.log(elm.id);
}
}
//修改方式如下:
function assignHandler(){
var elm = document.getElementById("id");
var id = elm.id;
elm.onclick = function(){
console.log(id);
}
elm = null;
}
4.利用闭包私有化变量隐藏数据
- 私有变量:任何在函数中定义的变量,都可以被认为是私有变量,因为不能在函数外部访问这些变量;因此,函数中的私有变量包括函数的参数、局部变量和内部定义的其他函数
- 特权方法:有权访问私有变量和私有函数的公有方法
function Person(name){
this.getName = function(){ //特权方法,有权访问私有变量name
return name;
}
this.setName = function(val){ //特权方法,有权访问私有变量name
name = val;
}
}
var person = new Person("Asher"); //每次使用构造函数创建实例都是不同的
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;
})(); //遍历时立马把值存进数组中
}
console.log( fnArr[3] ); //3
//写法2:
var fnArr = [];
for (var i = 0; i < 10; i ++) {
fnArr[i] = (function(num){ //将i作为参数进行传递
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.使用闭包封装一个汽车对象,可以通过如下方式获取汽车状态
(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';