当面试官问到闭包时该如何回答?
当面试官问到闭包的时候其实是想问:为什么其他非闭包的函数没有权限访问另一个函数的内部作用域?为什么闭包有这个权限?什么是函数作用域?
应该这样回答:由于在js中,变量的作用域属于函数作用域,在函数执行后作用域会被清理掉,内存也会被回收,但是由于闭包是建立在一个函数内部的子函数,因为其可以访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时候子函数(也就是闭包),变拥有了访问上级作用域中的变量的权限,即使上级函数执行完之后作用域内的值也不会被销毁。
什么是闭包
闭包是指有权访问另外一个函数作用域中的变量的函数;
父函数被销毁的情况下,返回的子函数中仍保留着父级单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。
创建闭包的方式
在一个函数内部创建另外一个函数
闭包只能取得包含函数中的任何变量的最后一个值
function arrFunc () {
var arr = [];
for (var i = 0;i < 10;i++) {
arr[i] = function() {
return i;
}
}
return arr;
}
arrFunc()
当arrFunc执行完毕之后,其作用域会被销毁,但它的变量对象仍保存在内存中,得以被匿名访问,这是i的值为10。
若想保存在循环过程中的每一个i的值,需要在匿名函数外部再套用一个匿名函数,在这个匿名函数中定义另一个变量并且立即执行来保存i的值。如下代码:
function arrFunc(){
var arr=[];
for(var i=0;i<10;i++){
arr[i]=function(num){
return function(){
return num;
};
}(i);
}
return arr;
}
闭包中的this指向
var name='window';
var obj={
name:'object',
getName:function(){
return function(){
return this.name;
}
}
}
console.log(obj.getName()());//window
在上面这段代码中,obj.getName()()实际上是在全局作用域中调用了匿名函数,this指向了window。
如果想使this指向外部函数的执行环境,可以这样改写:
var name='window';
var obj={
name:'object',
getName:function(){
var that=this;
return function(){
return that.name;
}
}
}
console.log(obj.getName()());//object
内存泄漏
闭包会引用包含函数的整个变量对象,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。所以我们有必要在对这个元素操作完之后主动销毁。
function assignHandler(){
var element=document.getElementById('text');
var id=element.id;
element.onclick=function(){
alert(id);
};
element=null;
}
函数内部的定时器
当函数内部的定时器引用外部函数的变量对象时,该对象不会被销毁;
(function(){
var a=0;
setInterval(funtion(){
console.log(a++);
},1000);
})();
闭包的两种应用环境:
①函数作为返回值
function fn(){
var max=10;
return function bar(x){
if(x>max){
console.log(x)
}
}
}
var f1=fn();
f1(15)
②函数作为参数被传递
var max=10
fn=function(x){
if(x>max){
console.log(x)
}
}
(function (f){
var max = 100;
f(15);
})(fn)
示例解析:
function fn(){
var max=10;
return function bar(x){
if(x>max){
console.log(x)
}
}
}
var f1=fn();
f1(15)
分析:重点来了:因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的作用域。而正巧合的是,返回的这个函数体中,还有一个自由变量max要引用fn作用域下的fn()上下文环境中的max。因此,这个max不能被销毁,销毁了之后bar函数中的max就找不到值了。
因此,这里的fn()上下文环境不能被销毁,还依然存在与执行上下文栈中。
执行bar(15)时,max是自由变量,需要向创建bar函数的作用域中查找,找到了max的值为10。这个过程在作用域链一节已经讲过。
这里的重点就在于,创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了。
使用闭包会增加内容开销,现在很明显了吧!
闭包解决了什么?
因为闭包可以缓存上级作用域,那么使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。以常用的ajax回调为例:Ajax回调就是一个闭包,由于他的特性,回调就拥有了整个上级作用域的访问和操作能力,提高了极大的遍历。开发者不用去写钩子函数来操作上级函数作用域内部的变量了。
闭包应用场景:Ajax请求的成功回调、一个事件绑定的回调方法、一个setTimeout的延时回调、或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都有闭包的身影。
闭包的应用:
①设计私有的方法和变量
任何在函数中定义的变量,都认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数的参数、局部变量和函数内定义的其他函数。
把有权访问私有变量的公有方法称为特权方法。
function Animal(){
//私有变量
var series="哺乳动物";
function run(){
console.log('run')
}
//特权方法
this.getSeries=function(){
return series;
}
}
②匿名函数最大的用途是创建闭包,并且还可以构建命名空间,以减少全局变量的使用。
从而使用闭包模块化代码,减少全局变量的污染。
var objEvent=objEvent || {};
(function(){
var addEvent=function(){};
function removeEvent(){};
objEvent.addEvent=addEvent;
objEvent.removeEvent=removeEvent;
})();
这段代码中函数addEvent和removeEvent都是局部变量,但是我们可以通过全局变量objEvent使用它,这就大大减少全局变量的使用,增强网页完全性。
运用闭包的关键:
闭包引用外部函数变量对象中的值;
在外部函数的外部调用闭包。
闭包原理比较深奥:要想掌握闭包,一定要清楚函数作用域、内存回收机制、作用域继承等,闭包是随处可见的,很可能开发者在不经意间就写出了一个闭包,理解不够深入的话很可能造成运行结果与预期不符。
闭包代码难以维护:闭包内部是可以缓存上级作用域,而如果闭包又是异步执行的话,一定要清楚上级作用域都发生了什么,而这样就需要对代码的运行逻辑和JS运行机制相当了解才能弄明白究竟发生了什么。