Blog

闭包

闭包,可以说是对 执行上下文词法作用域 的一个实际应用,它不是语言的基本性质,而是由基本性质引申出来的功能特性,一种奇技淫巧。

  1. 概念
  2. 闭包类型
  3. 闭包原理
  4. 闭包应用场景

1 概念

自己总结:闭包指的就是函数,它能够访问其作用域链上的属性。

mdn: 闭包是函数和声明该函数的词法环境的组合

解释:因为js采用词法作用域,在函数定义的时候,就决定了它能够访问到外部变量的范围;在函数运行的时候,不管它在什么地方被调用,其作用域不变

2 闭包类型

理论上的闭包:所有函数都是闭包。因为函数在创建的时候就将上层上下文的数据保存起来了

实践上的闭包:1. 内部函数从父函数中返回 2. 在代码中引用了自由变量

自由变量:在函数中使用,但是不是函数的参数,也不是函数的自由变量,即从上层上下文获取到的变量

3 闭包原理

3.1 执行过程

引用 mqyqingfeng 大神的例子

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();

执行过程

  1. 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
  2. 全局执行上下文初始化
  3. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
  4. checkscope 执行上下文初始化,创建变量对象、作用域链、this等
  5. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
  6. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
  7. f 执行上下文初始化,创建变量对象、作用域链、this等
  8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

3.2 例子解释

var data = [];

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

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

为什么每次都输出3呢?

以往的理解:在每次函数执行的时候,for循环已经执行完了,此刻 i=3

为什么是这个样子呢?

答:当执行data[0]函数的时候,创建其执行环境,它的作用域链为

data[0]Context = {
    Scope: [AO, globalContext.Vo]
}

因为他的AO并没有 i 值,它能够访问到的 i 是全局上下文中的 i ,这也说明了为什么for循环执行完了函数再执行,函数的值为什么是3。


改成闭包

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]();

当执行data[0]函数的时候,创建其执行环境,它的作用域链为

data[0]Context = {
    Scope: [AO, 匿名函数.VO, globalContext.VO]
}

在data[0]函数的作用域链中存在i,也就是匿名函数的i,首先访问到的是匿名函数的i。

4 闭包的实际应用场景

  1. 工厂函数:用以设置初始值
  2. 柯里化:保存参数个数,当参数个数够了才真正执行函数
  3. 函数式继承:传入父对象,不直接复制父对象属性值,而是在子对象上添加能访问父对象属性的方法,用以保护父类属性方式实现继承

很多地方都用到闭包,以后有应用场景就总结在这里

相关文章:深入js-词法作用域 深入js-执行上下文