JS闭包理解及使用

JS闭包理解及使用

JS中的闭包,其本质就是在函数内部再创建一个函数。

当外部函数执行环境被销毁后,内部函数的作用域链依然保持着对外部函数活动对象的引用,简而言之,闭包就是能够读取其他函数内部变量的函数。
我们可以将闭包理解为:

  • 对于外部函数f1,内部函数f2,
  • 当f1终止后,f2任然对f1的AO保持访问。

因此,可以总结出闭包的三个特性:

  • 函数嵌套函数
  • 函数内部可以引用函数外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收

js的闭包与java略有不同,它有两种主要表现形式:

  • 函数作为返回值
function fun1(){
    var name = 'Tom';
    return function(){
        return name;
    }
}
var b = fun1();
console.log(b());//Tom

当执行var b = fun1()时,返回的是function(){return name;}这个函数,再执行b()时,返回内部函数返回值name。
它的作用域链如下:

闭包-1

  • 函数作为参数传递
var num = 100;
var fn1 = function(x){
    if(x > num)console.log(x);
}
function(fn2){
    var num = 200;
    fn2(250)
}(fn1)

function(fn2)是一个自执行函数,将fn1函数传入它当作参数,在自执行函数中执行了传入的函数,所以fn1函数中的num = 200,而函数fn1的参数x = 250,所以执行if后面的输出语句得到结果250。

闭包的作用:

  • 读取自身函数外部的变量——沿着作用域链去查找
  • 让外部变量始终保存在内存中

我们来看一个例子:

function outer() {
     var result = new Array();
     for (var i = 0; i < 2; i++) {//注:i是outer()的局部变量
          result[i] = function () {
             return i;
         }
     }
     return result;//返回一个函数对象数组
      //这个时候会初始化result.length个关于内部函数的作用域链的值
 }
var fn = outer();
console.log(fn[0]());//2
console.log(fn[1]());//2
  • fn[0]执行过程:

可以看到result[0]函数的活动对象里并没有定义i这个变量,于是沿着作用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果;从这个步骤可以知道:js函数内的变量值不是在编译的时候就确定的,而是等在运行时期再去寻找的

那么,我们如何让result数组返回期望值呢?
我们知道,result的AO里有一个arguments,arguments对象是一个参数的集合,是用来保存对象的类数组。那么我们就可以把i当成参数传进去,这样一调用函数生成的活动对象内的arguments就有当前i的副本。

  • 方法一
function outer() {
    var result = new Array();
    for (var i = 0; i < 2; i++) {
          //定义一个带参函数
        function argu(num) {
           return num;
        }
        //把i当成参数传进去
        result[i] = argu(i);
    }
    return result;
}
var fn = outer();
console.log(fn[0]);//0
console.log(fn[1]);//1

我们可以画出作用域链来理解方法一fn[0]的执行过程:
闭包-2

  • 方法二:
function outer() {
    var result = new Array();
    for (var i = 0; i < 2; i++) {
         //定义一个带参函数
         function arg(num) {
           function innerarg() {
               return num;
           }
           return innerarg;
        }
       //把i当成参数传进去
       result[i] = arg(i);
    }
   return result;
}
var fn = outer();
console.log(fn[0]());
console.log(fn[1]());

方法一虽然可以得到期望值但是会破坏闭包,方法二可以得到期望值并且不会破坏闭包,但是会让函数变得复杂,但是更推荐方法二。

向上面方法二这样,保证了闭包完整性,但函数较为复杂,写起来会比较困难,因此徐娅将其进行一些简化,简化后的方法二如下:

function outer() {
   var result = new Array();
   for (var i = 0; i < 2; i++) {
      //定义一个带参函数
     result[i] = function (num) {
          function innerarg() {
              return num;
          }
      return innerarg;
     }(i);//预先执行函数写法
     //把i当成参数传进去
  }
   return result;
}

闭包的好处:

  • 保护函数内部变量安全,实现封装,防止变量流入其他作用域
  • 维持变量,可用于做缓存
  • 使用闭包时用匿名函数可以减少内存消耗

闭包的弊端:

  • 变量不销毁,增加了内存消耗,严重可能造成内存泄漏——解决方案:使用完该变量后删除或者赋值null
  • 闭包涉及跨作用域访问,如果使用过多,会造成性能下降——解决方案:将需要跨作用域访问的变量保存在局部变量中,每次访问时直接使用局部变量

使用闭包时请注意:

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除或者赋值为null;
  • 闭包会在外部函数的外面,改变外部函数内部变量的值。所以,如果你把外部函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,一定不要随便改变父函数内部变量的值!

猜你喜欢

转载自blog.csdn.net/qq_42602282/article/details/106841817