[学习笔记] JavaScript 闭包

学习Javascript闭包(Closure)
javascript的闭包
JavaScript 闭包深刻理解(closure)
理解 Javascript 的闭包
JavaScript 中的闭包javascript

看了《JS高级程序设计》和上面几篇文章,小总结下JS闭包~html

做为一个初学者,学习一个新的概念无非就是知道 “它是什么”、“什么原理”、“怎样建立”、“有什么用”、“怎么用”,这篇文章就从这几个角度介绍闭包,欢迎小伙伴们拍砖~java


什么是闭包

上面几篇文章,每一篇对闭包的定义都不相同,其实这真是一个很难用语言完美描述出来的东西。我的更喜欢阮一峰写的:编程

闭包就是可以读取其余函数内部变量的函数。segmentfault

在JavaScript中,函数A内部的局部变量不能被A之外的函数访问到,只能被A内部的子函数B访问到,当在函数A外调用函数B时,即可经过B访问A中局部变量,那么子函数B就是闭包。(注意,是子函数哦~)
这样说比较抽象,举个栗子~ruby

function outer() {

    var a = 100; // outer的局部变量

    function inner() { // 闭包
        console.log(a);
    }

    return inner; // 没有这条语句,闭包起不到在outer外部访问变量a的做用~
}
console.log(a); // 在外部直接访问a出错,Uncaught ReferenceError: a is not defined

var test = outer(); // outer运行完返回inner,赋值给test
test(); // 100,执行test(),至关于执行inner(),这样就能够访问到outer内部的a了

司徒正美在blog中从另外一个角度解释了闭包:闭包

简单来讲,闭包就是在另外一个做用域中保存了一份它从上一级函数或做用域取得的变量(键值对),而这些键值对是不会随上一级函数的执行完成而销毁。周爱民说得更清楚,闭包就是“属性表”,闭包就是一个数据块,闭包就是一个存放着“Name=Value”的对照表。就这么简单。可是,必须强调,闭包是一个运行期概念。函数

闭包的原理

说原理可能有些大,但这部分确实是要从内部机制的角度,解释下“为何上面的栗子中经过inner就能够在outer外部访问outer内部的a”~性能

我的认为在JS中,有两个链很重要:原型链做用域链。经过 原型链 能够实现继承,而与 闭包 相关的就是 做用域链学习

仍是上面的栗子,假设outer定义在全局做用域中

1  function outer() {

2     var a = 100; 

3     function inner() { 
4         console.log(a);
5     }

6     return inner; 
7  }

8  var test = outer(); 
9  test();

当执行到1处,outer函数定义,其做用域链中只有一个全局对象。
而后执行8,运行outer()。此时,outer的做用域链中有两个对象:outer的活动对象-->全局对象
运行outer(),会执行outer里面的3,定义inner(),此时inner的做用域链是:outer的活动对象-->全局对象
执行9,其实就是执行inner函数,此时其做用域链是:inner的活动对象-->outer的活动对象-->全局对象

由于inner的做用域链中有outer的活动对象,因此它能够访问到outer的局部变量a。

常理来讲,一个函数执行完毕,其执行环境的做用域链会被销毁。可是outer执行完毕后,其活动对象仍然保留在内存中,由于inner的做用域链仍在引用着这个活动对象。因此此时,outer的做用域链虽然销毁了,可是其活动对象仍在内存中。直到test执行完毕,outer的活动对象才被销毁。

也正由于如此,闭包只能取得包含函数中任何变量的最后一个值,即包含函数执行完毕时变量的值。改改以前的栗子~

function outer() {

    var a = 100; 

    function inner() { 
        console.log(a);
    } 

    a = a + 50; // 改变a的值

    return inner; 
}

var test = outer(); 
test(); // 150,取得的a是150,而不是100

正是由于做用域链,只能里面的访问外面的,外面的不能访问里面的。也是基于做用域链,聪明的大师们想出了闭包,使得外面的能够访问里面的。掌握做用域链很关键啊~

建立闭包的方式

建立闭包最多见的方式就是在一个函数里面建立一个函数,以前举得栗子就是。

司徒正美大神给出了3种闭包实现:

with(obj){
    //这里是对象闭包
}
(function(){
    //函数闭包
 })();
try{
   //...
} catch(e) {
   //catch闭包 但IE里不行
}

闭包的做用

1. 在函数外面读取函数的局部变量
前面一直在说这个事儿~

2. 在内存中维持一个变量

function outer() {

    var a = 100; 

    function inner() { 
        console.log(a++);
    } 

    return inner; 
}

var test = outer(); 
test(); // 100
test(); // 101
test(); // 102

栗子中a一直在内存中,每执行一次test(),输出值加1。由于test在全局执行环境中,因此a一直在内存中,若是test在其余执行环境中,当这个执行执行环境销毁的时候,a就不会再在内存中了。

3. 实现面向对象中的对象
javascript并无提供类这样的机制,可是能够经过闭包来模拟类的机制。把父函数看成对象(object)使用,把闭包看成它的公用方法(Public Method),把内部变量看成它的私有属性(private value)。

function Person(){
    var name = 'XiaoMing';

    return {
        setName : function(theName){
            name = theName;
        },

        getName : function(){
            return name;
        }
    };
}
var person = new Person();
console.log(person.name); // undefined
console.log(person.getName()); // XiaoMing

几个闭包示例

参考的这几篇文章基本都给出了闭包示例,悄悄摘选司徒正美大神的几个放到这里~

//*************闭包uniqueID*************

uniqueID = (function(){

    var id = 0; 
    return function() { 
        return id++; // 返回,自加
    };  
})(); 

document.writeln(uniqueID()); //0
document.writeln(uniqueID()); //1
document.writeln(uniqueID()); //2
document.writeln(uniqueID()); //3
document.writeln(uniqueID()); //4
//*************闭包阶乘*************

var a = (function(n) {
    if (n<1) { 
        alert("invalid arguments"); 
        return 0; 
    }
    if(n==1) { 
        return 1; 
    }
    else { 
        return n * arguments.callee(n-1); 
    }
})(4);

document.writeln(a);
//***********实现面向对象中的对象***********

function Person(){
    var name = 'XiaoMing';

    return {
        setName : function(theName){
            name = theName;
        },

        getName : function(){
            return name;
        }
    };
}
var person = new Person();
console.log(person.name); // undefined
console.log(person.getName()); // XiaoMing
//***********避免 Lift 效应***********

var tasks = [];
for (var i = 0; i < 5; i++) {
    // 注意有一层额外的闭包
    tasks[tasks.length] = (function (i) {
        return function () {
            console.log('Current cursor is at ' + i);
        };
    })(i);
}

var len = tasks.length;
while (len--) {
    tasks[len]();
}

运行结果:

Current cursor is at 4
Current cursor is at 3
Current cursor is at 2
Current cursor is at 1
Current cursor is at 0

补充:Lift效应

var tasks = [];
for (var i = 0; i < 5; i++) {
    tasks[tasks.length] = function () {
        console.log('Current cursor is at ' + i);
    };
}

var len = tasks.length;
while (len--) {
    tasks[len]();
}

上面这段代码输出5

Current cursor is at 5

这一现象称为 Lift效应。缘由在于

在引用函数外部变量时,函数执行时外部变量的值由运行时决定而非定义时

实际编程的时候,很容易碰见lift效应,加一层闭包就能够解决哦~

需注意的问题

因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露。解决方法是,在退出函数以前,将不使用的局部变量所有删除。
若是有很是庞大的对象,且预计会在老旧的引擎中执行,则使用闭包时,注意将闭包不须要的对象置为空引用。


这是我对于闭包学习的总结,错误和不足欢迎你们指正~

相关文章
相关标签/搜索