什么是闭包(closure)? 为什么要用它? 如何使用?

闭包的形成和作用域以及生命周期有关

闭包的定义

● 当某个函数调用时会创建一个执行环境以及作用域链,然后根据arguments和其它命名参数初始化形成活动对象。

● 在外部函数调用结束后,其执行环境与作用域链被销毁,但是其活动对象保存在了闭包之中,最后在闭包函数调用结束后才销毁。

● 简单的说,闭包就是能够读取其他函数内部变量的函数。在js中,闭包是指有权访问另一个函数作用域中的变量的函数。

如何使用

A函数内部的B函数调用了A中的变量,将A函数内部的B函数作为A函数的返回值返回;

在一个函数内部又定义了一个闭包;

为什么要用它?有什么作用

匿名自执行函数;

更优雅,更简洁的表达出代码;

在某些方面提升代码的执行效率;

实现封装;

实现面向对象中的对象;

缓存

最大的用处有两个:

1)可以读取其他函数内部的变量

2)让这些变量的 值始终保存在内存中(闭包使得Javascript的垃圾回收机制GC不会收回外部函数所占用的资源,因为外部函数的内部函数的执行需要依赖外部函数中的变量。 )

看下面这个例子:

  function f1(){
 
    var n=999;
 
    nAdd=function(){n+=1}
 
    function f2(){
      alert(n);
    }
 
    return f2;
 
  }
 
  var result=f1();
 
  result(); // 999
 
  nAdd();
 
  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的注意事项

(1) 问题:由于闭包会使得函数中的变量都被保存在内存中,内存消耗大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能会造成内存泄露。

解决方法:在退出函数之前,将不使用的局部变量全部删掉

(2) 问题:闭包会在父函数外部,改变父函数内部变量的值。

如果把父函数当做对象使用,把闭包当做公用方法,把内部变量当做私有属性,此时不要随便改变父函数内部变量的值。

   //----代码一 
   var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){      //js中,函数就是作用域
        return this.name;   // 闭包  + this,造成的问题,函数中的this不能访问到作用域外的变量
      };
    }
  };
 
  alert(object.getNameFunc()());   //this的作用域是在函数执行的时候确定,因为匿名函数是一个全局变量,因此this指向的是window对象
                                     // 因此输出结果是:The Window
 
   //-----代码二
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;         //--this指向object,保留了对象object在that里面,
      return function(){ 
        return that.name;    //这里输出对象object的name属性的值
      };
 
    }
 
  };
 
alert(object.getNameFunc()());  // My  Object
function fun(n,o){
    console.log(o);
    return {
        fun:function(m){
            return fun(m,n);
        }
    };
 }
 
 var a = fun(0);   a.fun(1);   a.fun(2);  a.fun(3);
 var b = fun(0).fun(1).fun(2).fun(3);
 var c = fun(0).fun(1);  c.fun(2);  c.fun(3);
 
 //问:三行a,b,c的输出分别是什么?
var a = fun(0);   a.fun(1);   a.fun(2);  a.fun(3);
//undefined  0  0  0
 
var b = fun(0).fun(1).fun(2).fun(3);
//undefined  0  1  2
 
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);
//undefined  0  1  1

对于js中一些变量操作的取值问题:
对js中一些变量操作的理解

● 匿名自执行函数

a) 在js中声明变量的时候,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。

b) 有的函数只需要执行一次,其内部变量无需维护,比如UI的初始化,那么我们可以使用闭包:我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象

● 缓存—减少全局变量,减少出错的几率

再来看一个例子,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

● 封装

即将函数A内部的函数B作为A函数的返回值,这样在A函数外即可以访问到A函数内部的私有变量。

● 实现面向对象中的对象

虽然JavaScript中没有类这样的机制,但是通过使用闭包,我们可以模拟出这样的机制

带例子解释的链接:https://blog.csdn.net/sunlylorn/article/details/6534610

用闭包实现一个函数,每调用一次这个函数,变量的值就会自增

// 方法一
// var add = (function () {
//     var counter = 0;
//     return function () {return counter += 1;}
// })();
// console.log(add()); //1
// console.log(add()); //2
// console.log(add()); //3
 
 
// 方法二
function autoAddOne(){  
    var sum = 0;  
  
  
    return function(){  
        sum = sum +1;  
        return sum; 
    };  
}  
  
var tempFunc = autoAddOne();  
console.log(tempFunc())
console.log(tempFunc())

方法二中之所以每次调用tempFunc()都会自增1,是因为在autoAddOne()中返回一个函数,该函数调用了autoAddOne()里面的变量,同时由于var声明的变量有提升的作用,tempFunc是一个全局变量,所以对返回函数的内存不释放,由于返回函数又调用了autoAddOne中的变量,所以这个函数所占用的内存也不释放

用什么方式可以实现外部函数访问到闭包里面的变量

将函数的返回值设定为一个对象,对象里面有一个属性是函数,该函数返回其里面定义的变量的值

function f1(){
    var s1 = 1;
    return {
        f2:function(){
            var temp = 1;
            return temp;
        }
    };
}
 
var a1 = f1();
console.log(a1.f2());

使用闭包实现一个once函数

function once() {
  var isFirst = true;
  return function () {
    if (isFirst) {
      isFirst = !isFirst;  //闭包对外部函数的变量的引用不释放,从而使引用的变量存放在内存中
      console.log('test');
    }
  };
};
 
var b = once();
b(); // 'test'
b(); // nothing
 
 
//或者写成
var once = (function(){
  var isFirst = true;
  return function () {
    if (isFirst) {
      isFirst = !isFirst;  //闭包对外部函数的变量的引用不释放,从而使引用的变量存放在内存中
      console.log('test');
    }
  }; 
})()

在代理单例模式中,就使用闭包实现单例模式

let getSingle = function(fn){
	let obj = null;
	return function(){
		obj ? obj : (obj = fn.apply(this,arguments));
	}
};

如何强制释放闭包?

//强制释放闭包的方法,就是给保存闭包的变量,重新赋值为null
function f1(){
 var n=999;
 return function f2(){
  alert(n);
  }
}
 
var a = f1();
//当代码运行到这里,f1虽然已经执行完毕,但是变量n并未被回收,而是被变量a(也就是返回的函数f2)
//所引用着
a=null;
//代码到这里的时候,变量a又指向了null,a和函数f2的联系被切断。f2不再被引用,因此f2被回收
//f2被回收后,它对n的引用也就不存在了,因此同理,n不再被引用,n也被回收不再占用内存。
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值