JS中的闭包
by Awayer 2023 / 6 / 9
一、什么是闭包
官方的解释:
- 闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
- 闭包是由一个函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时能访问的所有局部变量。
我的解释:
-
闭包就像是一个背包,它在每个函数创建时都会拥有自己的背包,这个背包类似于上下文,会储存函数创建时作用域中的变量,并且保护这些私有变量。
-
闭包是一个函数,并且存在于另一个函数当中
闭包可以访问到父级函数的变量,且该变量不会销毁
二、闭包原理
一般在一个内层函数中调用外层函数变量会产生闭包,下面我们来看几个例子:
const obj = {
name:"Awayer",
sayName:function() {
const name = this.name;
return function () {
console.log(name);
}
}
}
obj.sayName()();
上述例子中,sayName方法就是一个外层函数,它返回的内层函数中用到了sayName函数的变量(const name = this.name),所以内层函数就相当于一个闭包
再来看一个例子:
var fn;
function foo() {
var a = 2;
function baz() {
console.log(a);
}
fn = baz;
}
function bar() {
if (typeof fn == 'function') {
fn()
} else {
return false;
}
}
foo();
bar(); // 2
现在,我们来分析代码的执行过程:
- 执行
foo()函数时,声明并初始化了变量a,并定义了内部函数baz。 - 然后,将
baz函数赋值给全局变量fn,此时fn指向baz函数。 - 执行到
bar()函数时,判断fn的类型为函数,因此进入if语句块。 - 在
if语句块中,调用fn(),即调用了baz()函数。 baz()函数在其作用域链中找到了变量a,并打印出其值2。
因为baz函数形成了闭包,它可以访问外部函数foo的作用域,包括其中的变量a。即使foo函数已经执行完毕,baz函数仍然可以访问和操作foo函数中的变量。这是因为baz函数的作用域链仍然保持对foo函数作用域的引用。
需要注意的是,在调用foo()之前调用bar(),fn还没有被赋值为baz函数,因此bar()函数中的else语句块会被执行,返回false。
这个例子展示了闭包的一个常见用途:延长变量的生命周期,使其在函数执行完毕后仍然可访问。
三、闭包的应用
- 封装私有变量和数据:通过闭包,可以创建私有变量和数据,只能通过内部函数进行访问。这种方式可以避免全局命名冲突,并提供了一种封装数据和实现信息隐藏的方式。
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
},
decrement: function() {
count--;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
- 保存函数状态:闭包可以在函数调用之间保持函数的状态。这对于实现计数器、缓存和记忆化函数等功能非常有用。
function createCache() {
const cache = {};
return {
get: function(key) {
return cache[key];
},
set: function(key, value) {
cache[key] = value;
},
clear: function() {
for (let key in cache) {
delete cache[key];
}
}
};
}
const cache = createCache();
cache.set('foo', 'bar');
console.log(cache.get('foo')); // 输出:'bar'
- 实现模块化和命名空间:闭包可以创建独立的模块和命名空间,避免全局命名冲突,并将相关的函数和数据组织在一起。
var myModule = (function() {
var privateData = 'I am private';
function privateFunction() {
console.log(privateData);
}
return {
publicFunction: function() {
privateFunction();
}
};
})();
myModule.publicFunction(); // 输出:'I am private'
上述例子中的闭包都有一个共同点:它们能够捕获并保持对其创建时的作用域的引用。这使得函数可以在其定义的词法环境之外访问和操作变量。