Wenzi

对js闭包的一些理解

蚊子前端博客
发布于 2014/05/27 06:00
闭包是什么,闭包都会用在什么场景下呢?本篇文章将进行一下比较的讲解

对 js 的闭包看了很多遍,只是每次都没有进行深入的了解,过去了就忘了。

这次也算比较深入的了解了一下吧,不过应该也有很多不足的地方,嘿嘿!

1. 了解闭包前必要知道的概念 #

在我们开始探索闭包前,我们首先应该理解清楚一些必要的概念,这样对我们之后理解闭包有很大的帮助。

每个变量都有其作用的范围,全局变量全局有效,会一直驻扎在内存中;局部变量一般情况下会在函数执行完毕之后,就会销毁。这里要提到一个作用域链的重要概念,作用域链是当代码在一个环境中执行时创建的,作用域链的用途就是要保证执行环境中能有效有序的访问所有变量和函数。作用域链的最前端始终都是当前执行的代码所在环境的变量对象,下一个变量对象是来自其父亲环境,再下一个变量对象是其父亲的父亲环境,直到全局执行环境。其实,通俗的理解就是:在本作用域内找不到变量或者函数,则在其父亲的作用域内寻找,再找不到则到父亲的父亲作用域内寻找,直到在全局的作用域内寻找!

一般情况下呢,当函数执行完毕后,局部变量就会被销毁,内存中仅保存这全局作用域。这里也涉及到了 js 的垃圾回收机制,在 js 中有两种垃圾收集的方式:标记清除和引用计数。标记清除:垃圾收集器在运行时会给存储在内存中的所有变量都加上标记(具体的标记方式暂时就不清楚了),待变量已不被使用或者引用,去掉该标记或添加另一种标记。最后,垃圾收集器完成内存清除工作,销毁那些已无法访问到的这些变量并回收他们所占用的空间。

不过,闭包可不是说函数执行完了,变量就回收了。

2. 闭包的简要解释 #

这里简单解释一下闭包的概念:闭包使之有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数的内部创建另一个函数。

首先我们看一个简单的例子。

function createA() {
    var c = 0;
    return function () {
        c++;
        return c;
    };
}

这就是一个简单的闭包了: var func = createA(); 从代码中可以看出,func 是一个 Function 类型的变量,让我们执行以下:func();结果会发现输出了 1。多次执行以下 func()函数,发现输出的结果一直在增长。这就说明,在 func()执行之后,变量 c 依然保存在内存中,没有被释放掉。在 return 的 function 中能够使用变量 c 我们能够理解,因为变量 c 是其父亲环境中的变量,在本环境中找不到变量 c 时就会去父亲环境中寻找。

对js闭包的一些理解

再来看一个例子:

function createB() {
    var result = [];
    for (var i = 0; i < 10; i++) {
        result[i] = function () {
            return i;
        };
    }
    return result;
}
var result = createB();

我们期望的是执行result[0]()能够返回 0,执行result[1]()能够返回 1,以此类推。可是实际上呢,每个函数返回的都是 10.这是因为每个函数的作用域链中都保存着 createB()函数的活动对象,所以它们引用的都是同一个变量 i。当 createB()函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量对象,所以每个函数内部 i 的值都是 10.不过我们可以通过这样的设定来让闭包的行为符合我们的预期。

function createB() {
    var result = [];
    for (var i = 0; i < 10; i++) {
        result[i] = (function (num) {
            return function () {
                return num;
            };
        })(i);
    }
    return result;
}
var result = createB();

3. 闭包的应用场景 #

  1. 在内存中维持一个变量。比如前面讲的小例子,由于闭包,函数 createA 中的 c 会一直存在于内存中,因此每次执行 func(),都会给变量 c 加 1.
  2. 保护函数内的变量安全。还是以最开始的例子,函数 createA()中的变量 c 只有内部的函数才能访问,而无法通过其他途径访问到,因此保护了变量 c 的安全。
  3. 实现面向对象中的对象。javascript 并没有提供类这样的机制,但是我们可以通过闭包来模拟出类的机制,不同的对象实例拥有独立的成员和状态。

这里我们看一个例子:

var student = (function () {
    var name = 'bing';
    var score = 80;

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

分别执行下面的语句:

student.name;
student.getName();
student.setName('zhongguo');
student.getName();

对js闭包的一些理解

可以看到,变量 student 是不能直接访问变量 name 的。只能通过 getName 和 setName 来对变量 name 进行读写操作。

针对第三点,我们看这样的一个例子。

function Student() {
    var name = 'bing';

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

分别创建两个对象 stu1,stu2:

var stu1 = Student();
stu1.setName('beijing');
stu1.getName();
var stu2 = Student();
stu2.setName('shanghai');
stu2.getName();

stu1.getName();

输出的结果分别是:"beijing", "shanghai", "beijing"。可以发现 stu1,stu2 这两个对象之间相互独立,互不影响。

4. 闭包的另一种写法 #

在 jQuery 中我们经常见这样的写法:

(function (x, y) {})(3, 5);

这是立即执行的匿名函数,匿名函数也是一种闭包。我们来看这个:

for (var i = 0; i < 10; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i);
        }, i * 1000);
    })(i);
}

这段代码是每隔 1000ms 依次输出:0, 1, 2, 3, 4, 5, 6, 7, 8, 9

如果我们不使用匿名函数,直接在 for 循环里写 setTimeout 会是什么结果呢,这就回到了第二部分讨论的问题,setTimeout 也是一个函数呀,他使用了外部环境的变量 i,因此每隔 1000ms 输出一个 10,最后输出十个 10。

阅读(830)
Simple Empty
No data