javascript闭包

前言

  闭包对于初学者而言一直是一个不太好理解的概念。最近在学习javascript的时候碰巧看到了关于这方面的讲解,本身才明白了许多,因此把它写出来分享给你们。固然,本文也是参考了不少blog和书籍,加上本身的理解写出来的,文章末尾会附上对应的参考文档。javascript

基础知识

//javascript的变量做用域能够分为两种:全局变量和局部变量。
//在函数内声明的变量就是局部变量,这个变量在函数体内可访问,在函数外部没法直接读取局部变量。
//例如:
var globalVariable = 1; //全局变量
function f() {
    var localVariable = 100; //局部变量 注意函数内的变量必定要加上关键字var才能成为局部变量,否则就会成为全局变量
}
alert(globalVariable); //显示1
alert(localVariable);  //抛出错误 提示localVariable未声明
变量做用域
//javascript内部函数又称为嵌套函数,就是在函数中声明的函数
//函数能够实现多级嵌套
//例如:
function f() {
    function innerFunction() {
        //innerFunction是f的内部函数
        function anotherInnerFunction() {
            //anotherInnerFunction是innerFunction的内部函数
        }
    }
}
内部函数
//局部变量对于内部函数而言是可访问的,整个做用域链只能向下传递。
//例如:
function f() {
    var localVariable = 100; //局部变量
    function innerFunction() {
        var anotherLocalVariable = 200; //局部变量
        alert(localVariable);//内部函数能够访问外部函数的局部变量
    }
    alert(anotherLocalVariable); //抛出错误 提示anotherLocalVariable未声明
}
变量做用域
//javascript借鉴了函数式编程的概念,把函数提到了一等公民的地位,意思是函数能够跟变量同样被赋值,做为传入参数和返回参数。
//例如:
function f1() {
}
var temp1 = f1; //函数能够赋值给变量

function f2(f) {
    f();
}
f2(f1); //函数f1能够做为函数f2的参数

function f3() {
    function f4() {
    }
    return f4; //函数f4能够做为函数f3执行后返回的参数
}
var temp2 = f3(); //temp2的值是f4
函数是第一公民

闭包的概念

  若是单纯从字面上来理解"闭包"这两个字,每每让人摸不着头脑,因此咱们能够更多地把"闭包"看成一个代号名称,不要从字面上理解这个概念。参考了多处资料,发现闭包的官方定义很晦涩难懂,通过个人理解,以为闭包的概念能够这样描述:当一个内部函数被调用,就会造成闭包。看代码应该会更清晰一些:html

//在声明它的函数中调用
function f() {
    var localVariable = 100; //局部变量
    function innerFunction() {
        //访问局部变量
        localVariable += 1;
        alert(localVariable);
    }
    innerFunction();
}
f(); //显示101
//这样子确实也造成了闭包,但暂时没发现这样作有什么意义


//在声明它的函数外部调用
function f() {
    var localVariable = 100; //局部变量
    function innerFunction() {
        //访问局部变量
        localVariable += 1;
        alert(localVariable);
    }
    return innerFunction; //innerFunction做为函数f的返回参数
}
var temp = f(); 
temp(); //显示101
temp(); //显示102
//可见,闭包使得函数外部操做局部变量变为可能
//假如咱们只是单纯执行f(),不把结果赋值给全局变量temp,那么执行完后,因为不存在其余引用,这时候函数f就会成为垃圾回收的对象
//而因为赋值给全局变量temp了,innerFunction引用到f函数,函数f的局部变量localVariable就不会被回收,因此就出现了上文中执行两次temp(),实现累加效果
内部函数可使用两种调用方式

闭包的应用场景

  单纯地知道闭包的概念是远远不够的,要充分理解闭包这个概念,还得从它的使用场景入手。java

  其实闭包的应用能够简单总结为一句话:将变量维持在内存,带来更好的安全性和封装性。就像在上一节中使用的demo同样,将localVariable维持在内存中,使得改变它变为可能。其实,要实现这个功能,你可能以为使用全局变量就能够了,除非从新加载或关闭页面,不然全局变量一直在内存中。可是使用全局变量其实不是一种很差的作法,最主要是由于全局变量是能够手动更改的,例如定义一个globalVariable,你在接下来的代码能够为所欲为地修改,而使用闭包,想要修改变量,就只能经过调用已经实现的特定函数,学过面向对象的人都知道,这就是良好封装性的体现,不能随意修改,就保证了变量的安全性。例以下面的demo:node

function f() {
    var localVariable = 100; //局部变量
    function innerPlus() {
        localVariable += 1;
        alert(localVariable);
    }
    function innerMinus() {
        localVariable -= 1;
        alert(localVariable);
    }
    return {
        plus: innerPlus,
        minus:innerMinus
    }; //此次返回的再也不是一个单独的函数,而是一个对象,对象的属性是两个内部函数
}
var temp = f();
temp.plus();  //显示101
temp.minus(); //显示100
//只能经过temp的特定API操做变量,实现了封装性和更好的安全性
Demo

  这里再补充一个Demo,关于内部函数在声明它的函数中调用的实际应用场景。编程

//对node节点的背景颜色作一次渐变
function fadeBgColor(node) {
    var level = 1;
    function fade() {
        if (level < 15) {
            var hex = level.toString(16);//将整型转化为16位的字符 如10对应a
            node.style.backgroundColor = '#FFFF' + hex + hex;
            level += 1;//访问局部变量
            setTimeout(fade, 200);//递归调用自身
        }
    }
    setTimeout(fade, 200);//200ms后执行fade函数
}
fadeBgColor(document.body);
内部函数在声明函数中调用Demo

深刻理解闭包

  接下来的这一小节将从闭包的微观世界提及,展现一段javascipt代码,经过对javascript解析器执行原理的剖析,逐步地探讨闭包的内部机理。安全

function f() {
    var localVariable = 100;
    function innerFunction() {
        localVariable += 1;
        alert(localVariable);
    }
    return innerFunction;
}
var temp = f();
temp();
Demo

  1. javascript解析器会在页面加载后建立一个全局执行上下文(Execution Context),全局执行上下文的做用域链中只有一个全局对象,它定义了javascript中全部可用的全局变量和函数,全局对象在初始化的时候会包含this,window,document等对象,全局对象会随着javascript的执行动态地新增对象。网络

  2. 当javascript解析器执行到函数声明function f(){...}的时候,由于函数f是一个全局函数,因此会把全局执行上下文的做用域链赋给函数f的内部属性scope(内部属性没法经过javascript直接访问)。闭包

  3. 接下来var temp = f(),这时javascript解析器会执行函数f,同时建立一个对应的执行上下文,并建立一个活动对象,并初始化给this和函数传入参数arguments,同时加入函数f中全部的变量(初始化为undifined),随着函数的执行变量将逐步被赋值。活动对象会出如今执行上下文做用域链的顶端,紧接着是f函数的scope属性中的对象。ide

  

  4. 执行f函数的时候,javascript解析器会完成函数innerFunction的声明,这时innerFunction的执行上下文是函数f的执行上下文,因此会把函数f的执行上下文做用域链赋值给innerFunction的scope属性。函数式编程

  5. 最后执行temp(),其实就是执行innerFunction(),这时候会建立一个对应的新的执行上下文,同时建立temp函数的活动对象。一样活动对象会出如今做用域链的顶端,接下来是scope属性的做用域链。

  

  此时,函数f从声明到执行的步骤就完成了。最终函数temp的做用域链引用着函数f的活动对象,因此在函数f返回后就不会被GC回收。从这里也能够清晰地看到,函数temp在查找localVariable变量的时候,首先先从自身的活动对象中查找,找不到再到原型对象(有关原型对象这里就暂时不讨论了)中找,以后就到函数f的活动对象中找,再找不到就到全局对象中查找。

  若是你有什么新的想法,欢迎留言讨论。

参考文献: 阮一峰的网络日志-学习javascript闭包,javascript权威指南第8章闭包,高性能网站建设进阶指南第7章编写高效的javascript,闭包维基百科,javascirpt语言精髓4.8节

修改记录: [2014.8.25 第一次修改 主要修改了闭包的概念] [2014.9.23 第二次修改 主要修改了活动对象的描述] [2014.11.28 第三次修改 新增内部函数在声明它的函数中调用的案例]

相关文章
相关标签/搜索