JS中的闭包

闭包

闭包(closure)是javascript的一大难点,也是它的特点。不少高级应用都要依靠闭包来实现。javascript

变量做用域

要理解闭包,首先要理解javascript的特殊的变量做用域。java

变量的做用域无非就两种:全局变量和局部变量。数组

javascript语言的特别之处就在于: 函数内部能够直接读取全局变量,可是在函数外部没法读取函数内部的局部变量。安全

注意点:在函数内部声明变量的时候,必定要使用var命令。 若是不用的话,你实际上声明的是一个全局变量!闭包

如何从外部读取函数内部的局部变量

出于种种缘由,咱们有时候须要获取到函数内部的局部变量。可是,上面已经说过了,正常状况下,这是办不到的!只有经过变通的方法才能实现。dom

那就是在函数内部,再定义一个函数。模块化

function f1(){
    var n=999;
    function f2(){
        alert(n); // 999
    }
}
复制代码

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的全部局部变量,对f2都是可见的。可是反过来就不行,f2内部的局部变量,对f1就是不可见的。函数

这就是Javascript语言特有的 "链式做用域"结构(chain scope), 子对象会一级一级地向上寻找全部父对象的变量。因此,父对象的全部变量,对子对象都是可见的,反之则不成立。性能

既然f2能够读取f1中的局部变量,那么只要把f2做为返回值,咱们不就能够在f1外部读取它的内部变量了吗!ui

闭包的概念

上面代码中的f2函数,就是闭包。

各类专业文献的闭包定义都很是抽象,个人理解是: 闭包就是可以读取其余函数内部变量的函数。

因为在javascript中,只有函数内部的子函数才能读取局部变量,因此说,闭包能够简单理解成“定义在一个函数内部的函数“。

因此,在本质上,闭包是将函数内部和函数外部链接起来的桥梁。

闭包的用途

一、函数做为返回值,二、函数做为参数传递。

  • 函数做为返回值
function fn() {
  var max = 10;
  return function bar(x) {
    if(x > max){
      console.log(x)
    }
  }
}
复制代码

如上代码,bar函数做为返回值,赋值给f1变量。执行f1(15)时,用到了fn做用域下的max变量的值。

  • 函数做为参数被传递
var max = 10;
var fn = function(x) {
  if(x > max){
    console.log(x)
  }
}

(function(f) {
  var max = 10;
  f(15)
})(fn)

复制代码

如上代码中,fn函数做为一个参数被传递进入另外一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100。

闭包能够用在许多地方。它的最大用处有两个,一个是前面提到的能够读取函数内部的变量,另外一个就是让这些变量的值始终保持在内存中,不会在f1调用后被自动清除。

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

补充:JS中的函数定义

JS中定义一个函数,最经常使用的就是函数声明和函数表达式

Js中的函数声明是指下面的形式:

function functionName(){  
  
}
复制代码

函数表达式则是相似表达式那样来声明一个函数:

var functionName = function(){  

}
复制代码

咱们可使用函数表达式建立一个函数并立刻执行它,如:

(function() {
  var a, b    // local variables
  // ... // and the code
})()
复制代码

()();第一个括号里放一个无名的函数。

两者区别:js的解析器对函数声明与函数表达式并非一视同仁地对待的。对于函数声明,js解析器会优先读取,确保在全部代码执行以前声明已经被解析,而函数表达式,如同定义其它基本类型的变量同样,只在执行到某一句时也会对其进行解析,因此在实际中,它们仍是会有差别的,具体表如今,当使用函数声明的形式来定义函数时,可将调用语句写在函数声明以前,然后者,这样作的话会报错。

闭包的好处

一、模块化代码

使用自执行的匿名函数来模拟块级做用域

(function(){
        // 这里为块级做用域
 })();
复制代码

该方法常常在全局做用域中被用在函数外部,从而限制向全局做用域中添加过多的变量和函数影响全局做用域。也能够减小如闭包这样的对内存的占用,因为匿名函数没有变量指向,执行完毕就能够当即销毁其做用域链。

var test = (function(){
    var a= 1;
    return function(){
        a++;
        alert(a);
    }
})();

test();//2
test();//3
复制代码

实现a的自加,不污染全局。

二、循环闭包

循环给每一个li注册一个click事件,点击alert序号。代码以下:

var aLi = document.getElementByClassName("test");
function showAllNum( aLi ){
    for( var i =0,len = aLi.length ;i<len;i++ ){
        aLi[i].onclick = function(){
        alert( i );//all are aLi.length!
      }
   }
}
复制代码

点击后会一直弹出同一个值 aLi.length 而不是123。当点击以前,循环已经结束,i值为aLi.length。

利用闭包,建一个匿名函数,将每一个i存在内存中, onclick函数用的时候提取出外部匿名函数的i值。代码以下:

var aLi = document.getElementByClassName("test");
function showAllNum( aLi ){
    for( var i =0,len = aLi.length ;i<len;i++ ){
       (function(i){
          aLi[i].onclick = function(){
            alert( i );
          }
       })(i);
    }
}
复制代码

或者:

function showAllNum( aLi ){
    for( var i =0,len = aLi.length ;i<len;i++ ){
        aLi[i].onclick = (function(i){
           return function(){
              alert( i );
           }
       })(i);
    }
}
复制代码

解释:实际上只是经过函数的赋值表式方式付给了标签点击事件,并无运行;当遍历完后,i变成标签组的长度,根据做用域的原理,向上找到for函数里的i,因此点击执行的时候都会弹出标签组的长度。闭包可使变量长期驻扎在内存当中,咱们在绑定事件的时候让它自执行一次,把每一次的变量存到内存中;点击执行的时候就会弹出对应本做用域i的序号。

闭包的做用

  • 能够减小全局变量的对象,防止全局变量过去庞大,致使难以维护

  • 防止可修改变量,由于内部的变量外部是没法访问的,而且也不可修改的。安全

  • 能够读取函数内部的变量,能够把变量始终保存在内存中,不会在外层函数调用后被自动清除

闭包的优势

  • 变量长期驻扎在内存中

  • 避免全局变量的污染

  • 私有成员的存在

闭包的特性

  • 函数套函数

  • 内部函数能够直接使用外部函数的局部变量或参数

  • 变量或参数不会被垃圾回收机制回收GC

闭包的缺点

常驻内存,会增大内存的使用量,使用不当会形成内存泄露,详解:

一、因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露。解决方法是,在退出函数以前,将不使用的局部变量所有删除。

二、闭包会在父函数外部,改变父函数内部变量的值。因此,若是你把父函数看成对象object使用,把闭包看成它的公用方法Public Method,把内部变量看成它的私有属性private value,这时必定要当心,不要随便改变父函数内部变量的值。

闭包在IE下内存泄露问题

IE9以前,JScript对象和COM对象使用不一样的垃圾收集例程,那么闭包会引发一些问题。

建立一个闭包,然后闭包有建立一个循环引用,那么该元素将没法销毁。常见的就是dom获取的元素或数组的属性(或方法)再去调用本身属性等。例如:

function handler(){
    var ele = document.getElementById("ele");
    ele.onclick = function(){
        alert(ele.id);
    }
}
复制代码

闭包会引用包含函数的整个活动对象,便是闭包不直接引用ele,活动对象依然会对其保存一个引用,那么设置null就能够断开保存的引用,释放内存。代码以下:

function handler(){
    var ele = document.getElementById("ele");
    var id = ele.id;
    ele.onclick = function(){
        alert(id);
    }
    ele = null;
}
复制代码

闭包的工做原理

由于闭包只有在被调用时才执行操做,因此它能够被用来定义控制结构。多个函数可使用同一个环境,这使得他们能够经过改变那个环境相互交流。

相关文章
相关标签/搜索