1. 引入闭包
我们先从两个经典例子中提出我们的问题
- function a(b){
- var c = "123";
- return function(){
- console.log(b);
- console.log(c);
- }
- }
- let b = a("b");
- b(); //"b" "123"
- let func = function(){
- var arr = new Array();
- for(var i = 0 ; i < 5;i++){
- arr[i] = function(){
- return i;
- }
- }
- return arr;
- }
当然, 如果你有了解过相关闭包的知识, 那么上面的每个数组函数都会返回
5
. 在这里可以很明确的告诉你这就是闭包. 闭包究竟是什么, 按照红宝书上面的定义是这么解释的
闭包是指有权访问另一个函数作用域中的变量的函数
当然, 我觉得闭包有一点也特别重要, 创建闭包时就创建了一个不销毁的作用域.
接下来我就简单的剖析一下闭包, 说到闭包总不能忘记几个概念, 作用域链, 执行上下文, 变量对象
2. 执行上下文
当我们调用一个函数的时候就会创建一个函数的环境, 我们可以称之为执行环境, 简单点也可称之为环境. 当然, 咱们在这里叫作执行上下文. 当然, 每个函数都会有各自的执行上下文. 简单来说, 就是函数里面的执行环境.++ 执行上下文定义了变量或函数有权访问其他数据 ++,++ 决定了他们各自的行为 ++.
3. 变量对象
每个执行上下文中有会有与之关联的变量对象, 在上下文中定义的所有变量和函数都会放在这里面. 当然我们是取不到它的, 我们只能想象它的存在. 解析器在处理数据的时候就会在后台使用变量对象. 但是如果在函数中的话, 我们称之为活动对象.
- var a = 2;
- var b = 0;
- function abc(c){
- var b = 1;
- return a+b+c;
- }
- abc(3) //5
那么上面这个例子是不是闭包? 当然不是, 第一它没有创建一个不销毁的作用域, 第二他没有返回引用类型. 但是上面这个函数可以帮我们理解变量对象. 那么上例中函数 abc 的活动对象是 b,c, 全局的变量对象是 a,b
4. 作用域链
作用域链是用来指向变量对象的, 作用域的用途是保证对执行环境有权访问的所有变量和函数的有序访问. 作用域的前面, 始终都是当前执行的代码所在环境的变量对象
我们回到上面那个 abc 的例子, 在创建 abc 函数的时候就会创建一个包含全局变量对象的作用域链, 当调用 abc 函数时, 就会将 abc 的活动对象推到作用域链的前端, 当函数要寻找变量时就会按照作用域链依次往下找
未命名文件 (6).png
所以上例子中, abc 函数会先在活动对象变量 b,c, 再到外部去寻找 a, 因此结果得到为
6
.
5. 闭包
既然作用域链, 变量对象, 执行上下文我们都简述了一遍, 那么接下来我们说回闭包.
闭包是指有权访问另一个函数作用域中的变量的函数
我们再一次把闭包的定义扔出来, 这下子我们就大概清晰许多了. 那么我们接下来创建闭包函数
- function abc(c){
- let a = 1;
- return function(b){
- return a+b+c;
- }
- }
- let test = abc(2);
- let result = test(3); //6
当我们调用 abc 函数时, 其 abc 活动对象就会推到作用域链的前端, 当我们返回其匿名函数时, 匿名函数的活动对象就会被推到作用域链的前端, 因此在里面就可以访问到外面的值.
作用域链
由于我们将 test 变量赋予了 abc 的返回值, 函数 abc 中就产生一个无法销毁的活动对象, 匿名函数的作用域链一直引用其 abc 函数活动对象. 因此在内存就产生了一个不销毁的活动对象, 这也是导致内存泄漏的原因. 这么来说, 只要 test 一直存在, 那么 abc 的活动对象都不会被销毁.
- function abc(c){
- let a = 1;
- return function(b){
- return a+b+c;
- }
- }
- let test = abc(2);
- let result = test(3); //6
- test=null; // 解除对匿名函数引用, 释放 abc 活动对象, 随后执行完便可被回收
那么问题又来了, 以下的函数算不算闭包呢?
- function abc(c){
- let a = 1;
- return function(b){
- return a+b+c;
- }
- }
- abc(2)(3); //6
看上去和上面没什么区别, 实际上这已经不是闭包了. 原因在于它没有创建一个不销毁的作用域链, 它并没有定义一个变量去引用这个匿名函数, 所以该函数在执行完以后就会被销毁. 这只是一个普通的匿名函数而已.
说完我们回到第一个问题上面
- let func = function(){
- var arr = new Array();
- for(var i = 0 ; i < 5;i++){
- arr[i] = function(){
- return i;
- }
- }
- return arr;
- }
这个每个数组函数中返回的都是
5
, 原因在于闭包保存的是整个变量对象, 因此每个函数中都保存一样的变量对象. 它们引用的都是一个变量 i. 所以在执行闭包时, i 已经执行到
5
, 返回的 i 自然便是
5
.
闭包的作用
闭包的作用就是可以在内存中保留一些变量, 使其不被销毁. 当然, 还有重要的一点就是可以访问外界的变量与函数, 取到另外一个作用域上面的值.
最后
对闭包的简述就到这里了, 想要深入了解闭包还需要花更多功夫在上面. 成功不是在一朝一夕之间, 我们都需要努力!
来源: http://www.jianshu.com/p/553a6bc3e05c