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
son
pode retornar e ser executada a qualquer momento. - O escopo léxico determina : uma função
son
pode acessarparent
as variáveis da funçãon
.
Não pense em outros conceitos agora.
Nós intuitivamente pensamos que: execução da função , son
execução doparent
escopo da função de acesso .n
console.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入栈,
复制代码
要让 son
可以在 parent
出栈后依然能访问到 n
。
需要个新技术来解决这个问题。
闭包诞生
为了解决上述问题( 词法作用域 + 函数是一等公民 + 函数调用栈 正常运行)。诞生了闭包这个概念。
闭包其实是一种技术:在函数调用栈的基础上,同时实现函数是一等公民、词法作用域。
闭包的具体实现
核心原理:将闭包所需的数据,都存储到堆(Heap)上。
堆(Heap)的数据,是不会随着函数调用结束而回收的。
上面的举例函数,在 chrome
中的闭包信息:
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)上。
- 闭包的特性:
- 保存:闭包将局部变量的生命周期拉长,不在随着函数调用结束而回收。
- 保护:不会成为全局变量、也不会收到外部影响。
- 可以构建私有变量。
- 闭包的应用
- 模块
- 私有属性
- 高阶函数、有状态的函数
- 闭包的缺点
- 内存泄漏