JavaScript之闭包

本文一共 1300 字,读完只需 5 分钟javascript

概述

闭包, 能够说是每一个前端工程师都据说的一个词,咋一看很难从字面上去理解,从而给人留下了闭包是一个重要又难以理解的概念。前端

可是,闭包在 JS 代码能够说是随处可见,闭包也只是计算机领域的一个概念而已,它的存在是由于 JS 的一些语言特性,好比:函数式语言执行上下文执行上下文栈,做用域链词法做用域java

执行上下文:Execution Context
执行上下文栈:Execution Context Stack
做用域链:Scope Chain
做用域:Scopebash

本篇文章,将先给结论,到底什么是闭包,再来分析产生闭包的过程和缘由。前端工程师

1、什么是闭包

当函数记住并访问所在词法做用域的自由变量时,就产生了闭包,即便函数是在当前词法做用域外执行。 --《你不知道的 JavaScript》闭包

来段经典的闭包代码:app

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
复制代码

内部函数 inner 记住了它被定义时的词法做用域,也就是 outter 的函数做用域,并访问了该做用域里的自由变量 a, 同时,inner 函数做用返回值,在外部做用域中被执行。函数

以上描述,所有符合闭包的描述,那这就是闭包post

2、执行过程

以前的文章讲了函数的执行上下文栈,变量对象,做用域链等内容,接下来经过闭包代码回顾代码是怎么样的执行过程。ui

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
复制代码
  1. 进入全局代码的执行上下文,全局上下文被压入执行上下文栈。
ECStack = [
        globalContext
    ];
复制代码
  1. 全局上下文建立全局变量对象,建立 this 并指向全局上下文。
globalContext = {
    VO: global,
    scope: [global.VO],
    this: global
}
复制代码
  1. 全局上下文初始化时,outter 函数被建立,创建做用域链,复制 Scope 属性到 outter 函数的内部属性[[scope]]
outter.[[scope]] = [     
    globalContext.VO
  ];
复制代码
  1. 执行 outter 函数,建立 outter 函数执行上下文,将 outter 上下文压入执行上下文栈。
ECStack = [
        globalContext,
        outterContext
    ];
复制代码
  1. 初始化 outter 函数执行上下文,用 arguments 建立活动对象,加入形参、函数声明、变量声明。将活动对象压入 outter 做用域链顶端。
outterContext = {
    AO: {
        arguments: {
            a: undefined,
        }
        length: 1
    },
    scope: undefined,
    inner: reference to function inner(){}
    Scope: [AO, globalContext.VO],
    this: undefined
}
复制代码
  1. outter 执行完毕,接着执行 outter 返回的被变量引用的函数 inner;
ECStack = [
        globalContext,
        innerContext
    ];
复制代码
  1. inner 函数初始化,过程和第4步同样。
innerContext = {
        AO: {
            arguments: {
                length: 0
            }
        },
        Scope: [AO, outterContext.AO, globalContext.VO],
        this: undefined
    }
复制代码
  1. inner 执行,沿着做用域链查找变量 a, 打印 a 值。
  2. inner 函数执行结束,弹出执行上下文栈。
ECStack = [
        globalContext
    ];
复制代码

在这个过程当中,第 5 步,outter 已经执行结束,执行上下文按理来讲已经被销毁,内部函数 inner 怎么还能访问 outter 做用域的变量呢。

正是因为闭包,inner 引用了它所在词法做用域的自由变量 a,inner 的做用域链中仍然是完整的, 尽管 inner 在其余地方执行,仍是返回了正确结果。

3、函数式语言

闭包中,一个很重要的特色就是,内部函数做为一个数据被返回。这是因为 JS 是函数式语言,函数能够做为参数传递进函数,也能够做为一个数据返回。函数的嵌套构成了做用域的嵌套,也就有了做用域链。

因为函数具备做用域,且变量的寻找具备 “遮蔽效应”(从内到外,找到第一个就中止),使得局部做用域的变量对于外部做用域是不可见的,因而函数就有了封闭性,因此咱们拿函数来包裹封装私有变量,同时也有了闭包。

4、自由变量

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
复制代码

对于 inner 函数而言,变量 a, 不是它的函数参数,也不是它的局部变量,a 就是自由变量。

5、闭包的用处和缺点

从闭包的特色能够看出,自由变量保存在了内存中,并能间接访问。

那么闭包的做用就是:

隐藏私有变量,解决变量命名空间污染的问题。

缺点
若是闭包过多,变量常驻内存,确定会占用大量内存空间。

总结

因为 JS 是函数式语言,当函数记住并访问所在词法做用域的自由变量时,就产生了闭包,即便函数是在当前词法做用域外执行。

闭包在 JS 代码中很是常见,没必要把它想得太玄乎。

欢迎关注个人我的公众号“谢南波”,专一分享原创文章。

掘金专栏 JavaScript 系列文章

  1. JavaScript之变量及做用域
  2. JavaScript之声明提高
  3. JavaScript之执行上下文
  4. JavaScript之变量对象
  5. JavaScript原型与原型链
  6. JavaScript之做用域链
  7. JavaScript之闭包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值传递
  11. JavaScript之例题中完全理解this
  12. JavaScript专题之模拟实现call和apply
相关文章
相关标签/搜索