深入理解JS闭包

650 阅读6分钟

深入理解JS闭包

变量作用域

要理解闭包,先理解变量作用域。

变量作用域无非两种: 全局变量局部变量

函数内部可以直接读取全局变量

var n = 999;

function f1(){

  console.log(n);

}

f1();  //999

函数外部无法读取函数内部的局部变量

function f1(){

  var n = 999;

}

console.log(n);  //error

函数内部声明变量,需要用到var,否则声明的是全局变量

function f1(){

  n = 999;

}

f1();

console.log(n);  //999

如何从外部读取局部变量

函数内部,再定义一个函数

function f1(){

  n = 999;

  function f2(){

    console.log(n);  //999

  }

}

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量对f1就是不可见的。这就是javascript语言特有的‘链式作用域’结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

js的作用域链

作用域链是JS在函数创建的时候定义的,用于寻找一个索引。作用域链索引的内部规则是将函数自身的本地变量放在最前面,把自身的父级函数变量放在其次,再把高一级的函数的变量放在更后面,以此类推知道全局对象为止。当需要查找一个变量时,js解释器会从作用域链去查找该变量,先从该函数的本地变量开始查找,如果没有,则在下一级作用域链进行查找,如果查找到相应变量则返回该变量,如果直到最后也没找到相应变量则返回undefined。

function f1(){

  n = 999;

  function f2(){

    console.log(n);

  }

  return f2;

}

var result = f1();  //返回的是f2函数

result();  //999

所以我们只要把f2作为返回值,就可以在f1的外部读取内部变量了。f2函数就是闭包

闭包的概念

闭包就是能够读取其他函数内部变量的函数,番薯没有被释放,整条作用域链上的局部变量都将得到保留

只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成'定义在一个函数内部的函数'

本质上,闭包就是将函数内部和函数外部链接的一座桥梁

闭包的特性

  1. 闭包的外层是个函数,闭包内部有函数
  2. 闭包会return内部函数,闭包返回的函数内部不能有return
  3. 执行闭包后,闭包的内部变量会存在,闭包内部函数的内部变量会回收

闭包的好处

  1. 希望一个变量长期驻扎在内存中
  2. 避免全局变量污染

闭包的用途

匿名的自执行函数

(function(){    
    alert("已进入就执行");
})();  

创建了一个匿名函数,并且立即执行,由于外部无法引用她的内部变量,因此执行完很快就会被释放,并且不会污染全局对象

闭包对数据进行缓存

在我们做项目的时候,经常遇到一些数据非常大且没有必要进行及时查询的数据。如下拉框数据等。那么在此时我们可以在启动应用的时候在页面将这些数据进行缓存起来,如果缓存中有我们需要的数据则直接读缓存,如果缓存中没有我们需要的数据,则进行查询数据库。闭包可以为我们做到这点。

var CachedSearchData = (function () {
        var cacheData = [], count = cacheData.length;
        return {
            getSearchData: function (id) {
                if (id in cacheData) {//如果结果在缓存中
                    return cacheData[id];//直接返回缓存中的对象
                } else {
                    //到数据库中查找
                    alert("search in database");
                }
            },

            clearSearchData: function (id) {
                if (dsid in cache) {
                    cache[dsid].clearSelection();
                }
            }
        };
    })();

    CachedSearchData.getSearchData(77);

闭包注意事项

闭包会将变量都存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成页面性能问题。在函数退出之前,要将不适用的局部变量全部删除

js内存回收机制

代码回收规则如下:

    1.全局变量不会被回收。

    2.局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁。

    3.只要被另外一个作用域所引用就不会被回收

一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了,对应的空间也就被回收了。下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用。但是如果这个函数内部又嵌套了另一个函数(这就是闭包了),而这个函数是有可能在外部被调用到的。并且这个内部函数又使用了外部函数的某些变量的话.这种内存回收机制就会出现问题。如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了。所以js解释器在遇到函数定义的时候会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来。也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收。

JavaScript的垃圾回收机制

  1. 在JavaScript中,如果一个对象不再被引用,那么这个对象会被GC回收
  2. 如果两个对象互相引用,而不被第三者嗦引用,那么这两个互相引用的对象也会被回收