闭包的概念
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
可以简单的理解为:闭包 = 内层函数引用了外层函数的变量
其实只要支持函数式编程特性的语言都有闭包的概念,而闭包(Closure,译为:永久的关闭)之所以被称为闭包,是因为它“封闭”(close over)了在其内部创建时所处环境的状态(即变量)。这也引出了闭包的特性:私有化变量
使用闭包私有化变量
let a = 1
function badFn() {
a++
}
function goodFn() {
console.log(a) // 2
}
badFn()
goodFn()
先看没有使用闭包的情况,可以看到badFn在goodFn调用前将a变量修改了,需求是goodFn不希望其他函数来修改a变量,我们可以使用闭包完成这一个需求。
let a = 1
function badFn() {
a++
}
function outer() {
let a = 1
function goodFn() {
console.log(a) // 1
}
goodFn()
}
badFn()
outer()
我们给goodFn套一层outer函数并在outer函数中再定义一个a,现在goodFn有了属于他自己的a,外部环境访问不到outer中的a变量,这就实现了变量的私有化,不用担心badFn插手goodFn的a变量。
使用return让外部访问私有化的变量
现在goodFn想在不被外部干扰a的情况下继续做一些事情,他想统计自己被调用的次数。
function outer() {
let a = 1
function goodFn() {
a++
console.log(a)
}
return goodFn
}
const printA = outer()
printA() // 2
printA() // 3
printA() // 4
printA() // 5
通过将goodFn函数return出去,现在goodFn可以在不让外部影响的情况下,只向外部提供加工后的a变量了。也就是私有化变量,外面的人可以用,但是没办法修改。
闭包的场景
其实在开发中闭包经常被使用
- 防抖
const debounce = function (func, wait) {
let timeout
return function () {
let args = arguments
if (timeout) clearTimeout(timeout)
timeout = setTimeout(()=> {
func.apply(this. args)
}, wait)
}
}
- 在react State Hook(useState)中
function Counter() {
const [count, setCount] = useState(0);
// ...
return (
<button onClick={() => setCount(count + 1)}>
Increment
</button>
);
}
这个更新状态的函数setCount就是一个闭包,因为它能访问并修改其定义时所在作用域的状态变量,即使在组件重新渲染后,它仍然保持对原状态变量的引用。
- 在vue中
-
- 每个Vue组件都拥有独立的作用域,这意味着组件内部定义的方法和变量构成了一个闭包环境。组件的方法如 methods 和 computed 属性所包含的函数,它们都可以访问到组件自身的 data、props、computed 等属性,这种访问机制正是闭包的体现。
-
- Vue组件的生命周期钩子函数同样是闭包,它们能够在不同的生命周期阶段访问并操作组件的内部状态。
闭包的内存泄露
借助于垃圾回收机制的标记清除法可以看出:全局变量printA不会被销毁那么变量a也不会被销毁,此时变量a存在内存泄露
闭包的this指向问题
- 普通函数调用
- 如果闭包是在普通函数调用方式下形成的,那么闭包内部的this将指向全局对象(浏览器环境下是windo, 在严格模式下是undefined)
function outer() {
var self = this;
function inner() {
console.log(this); // 在非严格模式下指向 window,在严格模式下为 undefined
}
inner(); // 此处直接调用,内部的 this 不指向 outer 函数
}
outer();
- 对象方法调用
- 如果闭包是作为对象的方法被调用,则 this 指向该对象。
var obj = {
outer: function() {
var inner = function() {
console.log(this); // 这里的 this 指向 obj
};
inner(); // 此处调用,内部的 this 指向 obj
}
};
obj.outer();
- 构造函数调用
- 若闭包在构造函数中定义并在 new 关键字创建实例时调用,this 指向新创建的实例。
function Outer() {
this.inner = function() {
console.log(this); // 这里的 this 指向 Outer 的实例
};
}
new Outer().inner();
- 箭头函数
- 箭头函数不创建自己的 this,它会捕获其所在(即定义的位置)上下文的 this 值。
function outer() {
this.someValue = 'I am from outer';
const inner = () => {
console.log(this); // 这里的 this 指向 outer 函数调用时的 this { someValue: 'I am from outer', method: λ:outer }
};
inner(); // 若 outer 作为对象方法调用,此处的 this 也将指向该对象
}
const obj = {
method: outer
};
obj.method(); // 这里的 this 指向 obj
- Function.prototype.bind()、call()、apply() 方法调用
- 使用这些方法调用闭包时,可以显式地设置 this 的指向。
function outer() {
var inner = function() {
console.log(this);
};
var boundInner = inner.bind(someObject);
boundInner(); // 这里的 this 指向 someObject
}
在闭包中维持稳定的 this 指向,常常采用的方法是保存外部作用域的 this (如使用 var self = this; 或者箭头函数),或者使用 .bind() 方法等技术手段。
引用:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
https://www.bilibili.com/video/BV1ot4y1j7W2/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=311cbee331f47013f566b865fc68f492
http://8.134.168.35/article/detail/48