O que é um fechamento? Vamos começar com por que existem fechamentos!

O que é um fechamento? Vamos começar com por que existem fechamentos.

A origem dos fechamentos

Wikipedia: O conceito de closures foi desenvolvido na década de 1960 para avaliação de máquinas de expressões usando cálculo lambda e foi implementado pela primeira vez na linguagem de programação PAL em 1970 para suportar funções de primeira classe com escopo lexical.

Palavras-chave:

Escopo léxico : O escopo de uma função é determinado quando a função é definida (ou seja, onde o código é escrito).

Funções de primeira classe ( funções são cidadãos de primeira classe ): As funções podem ser usadas como argumentos para funções, retornar valores de funções, atribuídas a variáveis ​​ou armazenadas em estruturas de dados.

Exemplo

Escreva uma função JS para ilustrar

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

let son = parent()
son()// 函数是一等公民: 返回的函数可以执行。
复制代码
  • A função é um cidadão de primeira classe e é determinado que a função sonpode retornar e ser executada a qualquer momento.
  • O escopo léxico determina : uma função sonpode acessar parentas variáveis ​​da função n.

Não pense em outros conceitos agora.

Nós intuitivamente pensamos que: execução da função , sonexecução doparent escopo da função de acesso .nconsole.log

Está certo? correto.

E quanto ao conceito de fechamentos?

Encontrei? Não conhecemos o conceito de encerramento, mas isso não afeta em nada, conhecemos a execução do código!

Por que existe um conceito de fechamentos?

Vamos falar primeiro sobre um conceito: Pilha de Chamadas de Função

Fale brevemente sobre o fluxo de execução de uma função na pilha de chamadas de função.

  • As funções são executadas na pilha e usam a memória da pilha .
  • As funções armazenam variáveis ​​locais , dados de tipo básico e informações de execução de funções na memória da pilha .
  • Depois que a função é executada, ela é retirada da pilha.

Depois que a pilha é aberta, as variáveis ​​locais e os dados de tipo básico salvos na pilha não podem ser acessados ​​e os dados não existem.

por exemplo

O que acontece com a execução se não houver encerramento.

// 在没有闭包的调用栈中执行
// 下面有图,看图理解
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. 一文颠覆大众对闭包的认知(深讲)

Acho que você gosta

Origin juejin.im/post/7084549768067678245
Recomendado
Clasificación