Please enable Javascript to view the contents

从头学习js-8-闭包

 ·  ☕ 3 分钟

这个系列是我读冴羽老师博客的感悟,
加入了个人的解读和练习题的解答

在MDN广泛定义中闭包指的是能访问自由变量的函数(自由变量指的是即不是这个函数的参数也不是这个函数的内部变量)

由于函数都有作用域链,所有函数都可以访问到全局上下文中的变量那所有函数其实都是闭包

在实际场景中,对闭包的定义更加具体,在第7篇的第二个例子中其实遇到了闭包的经典例子
闭包需要满足:
1.即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
2.在代码中引用了自由变量


分析一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo(); //这里会打印local scope

例子中的函数f在父函数checkScope中,
由于父函数会return f且在之后赋值给foo调用了,所以即使父函数checkScope执行结束,函数f也存在
函数f中引用了父函数中的变量scope所以函数f符合闭包的定义

通过之前的文章,我们其实知道更深的原因了,就是函数f在创建的时候就根据上下文保存了一个作用域链
在函数f初始化执行上下文的时候复制了这个作用域链

1
2
3
4
f.[[scope]] = [checkscopeContext.AO, globalContext.VO]
fContext = {
  scope: [AO, checkscopeContext.AO, globalContext.VO]
}

在沿着作用域链查找scope变量时,AO中没有,而继续查checkscopeContext.AO时找到了scope变量(值为local scope)


例子2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

这道题是非常经典的闭包问题,当时作为初学者的我无法理解真正的原因最后只能硬记下结论
现在我们用作用域链和执行上下文的知识点就可以拨开云雾了

首先for不是函数for 中var声明的i其实是全局变量,
for循环执行完后的情况是这样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
globalContext = {
  VO: {
    data: [
      0: reference to function(){console.log(i)}, //这个匿名函数的内部属性[[scope]]=[globalContext.VO]
      1: reference to function(){console.log(i)}, //这个匿名函数的内部属性[[scope]]=[globalContext.VO]
      2: reference to function(){console.log(i)}, //这个匿名函数的内部属性[[scope]]=[globalContext.VO]
    ],
    i: 3
  }
}

函数的内部代码如果只是创建是不会去执行的,匿名函数内部还是变量i的指向状态,
在之后真正开始执行的时候data0,开始创建AO,完善执行上下文的作用域链scope=[AO, globalContext.VO]
AO中没有i,就找到了globalContext中的i,而此时globalContext中的i已经是3了,
所以三个匿名函数最后都会打印3

通常要解决这个问题会用一个新的匿名函数形成闭包或者用let
我们想在data[0]的执行上下文作用域链接里塞上真正期望的i,scope=[AO, xContext.AO, globalContext.VO]
xContext.AO中存有不同的i

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var data = []

for (var i = 0; i < 3; i++) {
  data[i] = (function(i){
    return function(){
      console.log(i)
    }
  })(i);
}

data[0]();
data[1]();
data[2]();

(functionx(i){})(i)相当于

function x(i){}
x(i)

这个匿名函数会在data[0]的执行上下文的作用域中插入,
此时data[0]在执行时,作用域变为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
data[0]Context = {
  scope: [AO, 匿名函数Context.AO, globalContext.VO]
}

匿名函数Context = {
  AO: {
    arguments: {
      0:0,
      length: 1
    },
    i: 0
  }
}

data[0]Context.AO中没有i值,沿着作用域找到了匿名函数Context中的i
在for循环的时候匿名函数立即执行了才在AO中保留了不同的i,如果没有立即执行也只是保留了一个指向i的值

分享

Llane00
作者
Llane00
Web Developer