到底什么是闭包

一开始接触闭包有些问题一直绕不过去,可了看其他的资料,也从网上查了查,下面是我总结的一些东西:

“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

通俗的讲:就是函数a的内部函数b,被函数a外部的一个变量引用的时候,就创建了一个闭包。

(这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b)

function a(){
           var i=0;
           function b(){
              alert(++i);
           }
              return b;
       }

 var c = a();
        c();

闭包的特性:

①.封闭性:外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口;
②.持久性:一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在
系统中,闭包中的数据依然存在,从而实现对数据的持久使用。

优点:

① 减少全局变量。

② 减少传递函数的参数量

③ 封装;


 缺点:
 使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等.

最简洁、直击要害的回答,我能想到的分别有这么三句

1、闭包是一个有状态(不消失的私有数据)的函数。

2、闭包是一个有记忆的函数。

3、闭包相当于一个只有一个方法的紧凑对象(a compact object)。

上面这三句话是等价的,而其中第 3 句最精妙,可以指导何时、如何用好闭包。

我换个角度来谈谈。

首先,很明确——闭包是一个函数,一种比较特殊的函数。什么是函数?函数就是一个基本的程序运行逻辑单位(模块),通常有一组输入,有一个输出结果,内部还有一些进行运算的程序语句。所以,那些仅仅说闭包是作用域(scopes)或者其它什么的,是错误的,至少不准确。

理解了以上这些概念,关于“什么是闭包”您的大脑中是否出现了下面这张图(用 UML 组成结构图

有实例有真相

让我们先回顾下传统函式的机理。 

我们说普通函式自身是没有状态的(stateless),它们所使用的局部变量都是保存在函式调用栈(Stack)上,随着函式调用的结束、退出,这些临时保存在栈上的变量也就被清空了,所以普通函式是没有状态、没有记忆的。

例如下面的普通函式 inc(),不管执行多少次都只返回 1:

var inc = function () {
  var count = 0;
  return ++count;
};

inc(); // return: 1
inc(); // return: 1

为什么这样?这是因为这里的 count 只是一个普通函式的局部变量,每次执行函式时都会被重新初始化(被第一条语句清零),它不是下面例子中可以保持状态的闭包变量。

再来看闭包的例子。

这可以说是一个最简单的 JavaScript 闭包的例子,这里的 inc() 是一个闭包(函式),它有一个私有数据(也叫闭包变量) count(即函式中的第 2 个 count)。

var inc = (function () { // 该函数体中的语句将被立即执行(IIFE)
  var count = 0; // 局部变量 count 初始化
  return function () { // 父函式返回一个闭包(函式引用)
    return ++count; // 当父函式 return(即上一个 return)后,这里的 count 不再是父函式的局部变量,而是返回结果闭包中的一个闭包(环境)变量。
  };
}) ();

inc(); // return: 1
inc(); // return: 2

我还未研究过任何 JavaScript 引擎(解释器)的源码,所以只好根据常识与逻辑作些合理的推测。 

在本例中第 2 个 count 作为闭包的私有数据,很可能是被 JS 引擎存放到了堆(Heap)上,而且是按引用(byref)来访问,所以可以保持状态,实现计数累加;而第 1 个 count 只是存放在函式调用栈(Stack)上的局部变量,于是那个 IIFE 父函式一退出它就被销毁了,它的作用主要是用来初始化(赋值)给担任闭包变量的第 2 个 count。

可见两个 count 虽然同名,却是两个截然不同的变量!

这点恐怕正是许多 JS 初学者(包括当年的我)屡屡见到闭包时,感到最为大惑不解的地方吧。我们以为父子函式里外两个同名的变量是一回事,而其实它们不是,也不知道这背后究竟发生了哪些变化。

闭包 vs. 对象 

实现同样的计数功能,不用闭包怎么写?同样以 JavaScript 为例,用传统的 OOP 来写:

var obj = {
  count: 0,
  inc: function () {
    return ++this.count;
  }
};
obj.inc(); // count: 1
obj.inc(); // count: 2

用闭包与用对象,区别在哪?

其实主要区别就一个:这里用的是普通对象 obj 的方法(函数)inc,让 count 作为 obj 的成员变量来保存数据。而前面第一个例子直接用闭包函数 inc 的话,连 obj 这个对象也可以省掉,让 count 直接成为 inc 闭包内部所保存的状态(环境)变量,这样写起来就比传统的 OOP 更为紧凑,前者用 inc(),而后者用 obj.inc(),尽管两者最终实现的功能和效果基本是一致的。

通过以上这两个小例子的比较,你可以充分体会到在 JavaScript 中,函数(functions)作为首席/头等公民(first-class object)的地位。由于有了闭包,加上在 JavaScript 中函数也是对象——一个函数可以像一个传统的对象那样拥有自己的属性、私有数据和状态(不会随着栈而清空),许多简单功能的实现可能无需再借助 objects 了。

最近有遇到一个这个的面试题,也是有关于闭包方面,很有意思,大家可以一起来看一下:

试题的要求是:遍历ul下的li,点击弹出li的索引

首先我们需要一个html结构

<div >
        <ul>
            <li>a</li>
            <li>a</li>
            <li>a</li>
            <li>a</li>
            <li>a</li>
        </ul>
    </div>

 我们遍历ul 下所有的li 并添加点击事件,一般我们会在for循环里面添加点击事件,但是结果和我们所期盼不一样,那么是为什么呢????

var nodes = document.getElementsByTagName("li");
            for( var i = 0; i<nodes.length; i++ ){
                nodes[i].onclick = function(){
                    console.log(i);  // 4
                }
            }

接下来看看我们的js代码

var nodes = document.getElementsByTagName("li");
            for( var i = 0; i<nodes.length; i++ ){
                (function(index){
                    nodes[index].onclick = function(){
                        console.log(index);
                    }
                })(i)
            }

我们实现了!!!

这样就是得来我们想要的效果点击相应的li得来相应的索引。

简单说一下实现的过程吧

(function () { /* code */ } ()); // 推荐使用这个
(function () { /* code */ })(); // 但是这个也是可以用的

这是我整理立调函数或自执行函数;

本质上我们是利用闭包的原理实现弹出的索引,我们立调函数传一个参数Index,也就是我们的索引i,在函数里面实现了闭包,

Index会一直保留在作用域块内,这样我们再点击的时候,会调用作用域名内保存的索引,从而实现我们需要的功能;

我们几个简单的例子:

function num(){
            var i = 0;
            return function(){
                console.log(i++);
            }
}
var counter = num();
console.log(counter()); // 0
console.log(counter()); // ??
var counter1 = (function(){
            var i = 0;
            
            return {
                get:function(){
                    return i;
                },
                set:function(val){
                    i = val;
                },
                increment:function(){
                    return ++i;
                }
            }
        }());
    
    console.log(counter1);
    console.log(counter1.get()); //
    console.log(counter1.set(3)); //
    console.log(counter1.increment()); //
    console.log(counter1.increment()); //

大家可以去思考一下答案哦!

猜你喜欢

转载自www.cnblogs.com/goodluck-tang/p/10414460.html