云天视角-浅谈闭包

1、现状

闭包是jser绕不过的坎,一直在都在说,套用 simpson 的话来讲:JavaScript中闭包无处不在,你只须要可以识别并拥抱它。前端

闭包是基于词法做用域书写代码时的天然结果,你甚至不须要为了利用它们而有意识的去建立闭包。闭包的建立和使用在你的代码中随处可见。你缺乏的只是根据你的意愿来识别、拥抱和影响闭包的思惟环境数组

2、什么是闭包(closure)

当函数能够记住并访问所在的词法做用域时,就产生了闭包。即便函数是在当前词法做用域以外执行 --《你不知道的js》(上卷)
闭包是指有权访问另外一个函数做用域中的变量的函数 --《JavaScript高级程序设计》

先来看一个例子:
例子1:闭包

function foo(){
    var a = 2;
    
    function bar(){
        console.log(a); // 2
    } 
    bar();
}

foo()

这是闭包吗?
这个代码从技术上来讲是,但也能够说不是。准确的来讲bar()对a的引用的方法是词法做用域的查找规则。咱们再来看:
例子2:函数

function foo(){
    var a = 2;
    
    function bar(){
        console.log(a)
    }
    
    return bar;
}
var baz = foo();
baz(); // 2, 这就是闭包了

在例2中,咱们将bar()函数自己当作一个值类型进行传递,函数bar()可以访问foo()的内部做用域。在这个例子中,它在本身定义的词法做用域之外的地方执行。spa

3、怎么造成的

要了解清楚,得先了解几个概念设计

  • 做用域链(scope chain)
  • 词法做用域

词法做用域

每一个函数都有本身的执行环境。这个环境能够访问外部环境,以此类推。每一个环境能访问到的标识符集合,称之为 做用域,也就是词法做用域code

做用域链(scope chain)

将做用域一层一层的嵌套,就造成了做用域链对象

以下,一般咱们都但愿foo()在执行完成之后,整个的内部做用域都被销毁。由于咱们知道引擎有垃圾回收机制用来释放再也不使用的内存空间。因为看上去foo()的内容不会再被使用,因此很天然的想到会对其回收。可是,事实上内部做用域依然存在blog

var globalVar = 10;
function foo() {
    var fooVar = 20;
    function bar() {
        var barVar = 30;
        return globalVar + fooVar + barVar;
    }
    return bar;
}
var baz = foo();
baz();

如上,用一张图表示
做用域链索引

这个做用域链在函数建立的时候就保存起来了。

baz()函数在执行的时候(执行bar()函数),将当前的变量对象(因为当前的环境是函数,因此将其活动对象做为变量对象)添加到做用域链的前端。此时,因为bar()在执行,而做用域链也存在,因此能够在做用域链上进行查找,去访问foo()的变量。

4、闭包的应用场景有哪些

  • 建立私有变量或函数

5、闭包的缺点

  • 闭包中的值是存在于内存中,滥用的话会致使内存消耗过大

闭包经典问题

// 函数做用:但愿它返回一个数组。该数组的元素为遍历的索引值
function hello(){
    var res = [];
    for (var i = 0,len = 5;i < len;i++){
        res[i] = function () {
            return i;
        }
    }
    return res;
}

返回的结果跟咱们期待的不同,由于:闭包保存的是整个变量对象,而不是每一个变量。
解决方案:

function hello(){
    var res = [];
    for (var i = 0,len = 5;i < len;i++){
        res[i] = (function(i){
            return i;
        })(i)
    }
    return res;
}

这里,没有没有把闭包直接赋值给数组。而是定义了一个匿名函数,而且将当即执行该匿名函数的结果赋值给数组,因为参数是按值传递的,因此会将当前值传给参数num。

参考资料:《你不知道的js》(中卷)、《JavaScript高级程序设计》

相关文章
相关标签/搜索