What is a closure? Let's start with why there are closures!

What is a closure? Let's start with why there are closures.

The origin of closures

Wikipedia: The concept of closures was developed in the 1960s for machine evaluation of expressions using lambda calculus, and was first fully implemented in the PAL programming language in 1970 to support lexically scoped first-class functions.

Key words:

Lexical scope : The scope of a function is determined when the function is defined (that is, where the code is written).

First- class functions ( functions are first-class citizens ): A function can be used as a parameter to a function, the return value of a function, assigned to a variable, or stored in a data structure.

Example

Write a JS function to illustrate

function parent() {
  let n = 1
  function son() {
    // 词法作用域: 可以访问到n
    console.log(n);
  }
  return son // 函数是一等公民: 可以被返回
}

let son = parent()
son()// 函数是一等公民: 返回的函数可以执行。
复制代码
  • The function is a first-class citizen . It is determined that the function soncan return and execute at any time.
  • Lexical scope determines : a function soncan access parentthe variables of the function n.

Don't think about other concepts now.

We intuitively think that: function sonexecution , access function parentscope nexecution console.log.

Is it right? correct.

What about the concept of closures?

Found it? We don't know the concept of closure, but it doesn't affect it at all, we know the execution of the code!

Why is there a concept of closures?

Let’s talk about a concept first: Function Call Stack

Briefly talk about the execution flow of a function on the function call stack.

  • Functions run on the stack and use stack memory .
  • Functions store local variables , basic type data, and function execution information on the stack memory .
  • After the function is executed, it is popped from the stack.

After the stack is popped, the local variables and basic type data saved on the stack cannot be accessed, and the data does not exist.

for example

What happens to execution if there is no closure.

// 在没有闭包的调用栈中执行
// 下面有图,看图理解
function parent() {
  let n = 1 // 局部变量n,基本数据类型1
  function son() { // 局部变量son

    // son执行时,parent已经出栈,变量n访问不到了
    // 报错!
    console.log(n);
  }
  return son  
  // parent出栈
}

let son = parent() // parent入栈
son()// son入栈,
复制代码

Untitled Diagram.drawio (5).png 要让 son 可以在 parent 出栈后依然能访问到 n

需要个新技术来解决这个问题。

闭包诞生

为了解决上述问题( 词法作用域 + 函数是一等公民 + 函数调用栈 正常运行)。诞生了闭包这个概念。

闭包其实是一种技术:在函数调用栈的基础上,同时实现函数是一等公民、词法作用域。

闭包的具体实现

核心原理:将闭包所需的数据,都存储到堆(Heap)上。

堆(Heap)的数据,是不会随着函数调用结束而回收的。

Untitled Diagram.drawio (4).png

上面的举例函数,在 chrome 中的闭包信息:

Untitled.png

v8将函数闭包所需的数据,构成一个Closure对象放在堆上,然后函数引用这个对象。

再次调用时,函数会去访问Closure对象里的数据。

(深入原理请看一文颠覆大众对闭包的认知

闭包的特性:保存、保护变量

根据闭包的实现,我们可以看出,Closure 是一个私有的、不影响全局的对象。

从而有了以下特性:

  • 保存:闭包将局部变量的生命周期拉长,不在随着函数调用结束而回收。
  • 保护:不会成为全局变量、也不会收到外部影响。
    • (我认为是因为词法作用域的作用,也算闭包的一部分)

私有变量,很重要。

闭包的应用

模块

我们要提供一个模块供别人使用,要解决命名空间污染的问题。

命名空间污染:模块要用多个变量,我们希望变量不影响全局,全局也不影响我们的变量。

IIFE + 闭包 即可实现。


// 闭包实现模块化。
var moduleA = (function (global, doc) {
  var methodA = function() {};
  var dataA = {};
  return {
    methodA: methodA,
    dataA: dataA
  };
})(this, document);

复制代码

现代JS提供ESM,原生支持模块。

模拟私有属性

// 模拟私有属性
function Test(name) {
  let _name = name;
  return {
    getName: function () {return _name;},
  };
}
let obj = new Test('z');
obj.getName(); // z
obj._name // undefined
复制代码

现代JS,class支持私有属性(操作符#)。

高阶函数、有状态的函数

例如:高阶函数、科里化、节流防抖(等要判断状态的函数)

// 节流
function throttle(fn, timeout) {
    let timer = null
    return function (...arg) {
        if(timer) return
        timer = setTimeout(() => {
            fn.apply(this, arg)
            timer = null
        }, timeout)
    }
}
复制代码

其实都应该算函数式编程的高阶函数。

闭包的缺点

内存泄漏

数据被闭包对象 Closure 引用,无法被释放回收。

闭包函数又在多个地方被引用,导致数据引用复杂,容易发生内存泄漏问题。

什么是闭包?

闭包(多种说法):

  • MDN:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被用包围),这样的组合就是闭包closure)。
    • 闭包让你可以在一个内层函数中访问到其外层函数的作用域。
    • 在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
  • Rust语言:闭包允许捕获调用者作用域中的值。
  • 维基百科:闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。
  • 闭包其实是一种技术:在函数调用栈的基础上,同时实现函数是一等公民、词法作用域。

闭包的实现有多种方式,所以解释就有差异。

面试就回答MDN的说法就好。

总结

  • 闭包解决了什么问题?
    • 函数调用栈的基础上,同时实现函数是一等公民、词法作用域。
  • JS中的闭包是什么?(MDN的解释)
    • 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被用包围),这样的组合就是闭包closure)。
    • 闭包让你可以在一个内层函数中访问到其外层函数的作用域。
    • 在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
  • 闭包的实现:
    • 原理:将闭包所需的数据,都存储到堆(Heap)上。
    • V8会将闭包所需的数据,存在函数的[[Scope]]Closure 对象上,这个对象在堆(Heap)上。
  • 闭包的特性
    • 保存:闭包将局部变量的生命周期拉长,不在随着函数调用结束而回收。
    • 保护:不会成为全局变量、也不会收到外部影响。
    • 可以构建私有变量
  • 闭包的应用
    • 模块
    • 私有属性
    • 高阶函数、有状态的函数
  • 闭包的缺点
    • 内存泄漏

参考文献

  1. MDN
  2. 维基百科
  3. JavaScript深入之词法作用域和动态作用域
  4. 深入理解JavaScript闭包(closure)
  5. 一文颠覆大众对闭包的认知(深讲)

Guess you like

Origin juejin.im/post/7084549768067678245