闭包

当面试官问到闭包时该如何回答?

当面试官问到闭包的时候其实是想问:为什么其他非闭包的函数没有权限访问另一个函数的内部作用域?为什么闭包有这个权限?什么是函数作用域?

应该这样回答:由于在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运行机制相当了解才能弄明白究竟发生了什么。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值