这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情
闭包不管是日常的研发过程中还是面试中都是一个常见的场景,很多时候我们在使用着闭包而不自知。为了避免在面试过程中被问到闭包相关的问题回答不上来,通过写文章的方式,再来加深一下闭包的概念。同时也希望能帮助到不熟悉闭包的同学。
什么是闭包?
可以这样理解闭包,闭包是一种现象,在一个函数中返回一个子函数,子函数中引用了外部函数的变量,那么这个函数形成了闭包。
这里提到了变量以及函数,函数可以理解为一个局部的作用域,变量和作用域是形成闭包的两个前提条件,必要条件是,在函数的作用域中引用了为外部作用域的变量。
举个例子:
let x = 5;
const fn = function fn(x) {
return function innerFn(){
x++
console.log("inner",x)
}
}
fn(6)();
console.log("outer",x)
// inner 7
// outer 5
- 官方说法:闭包就是指有权访问另一个函数作用域中的变量的函数。
- MDN说法:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
闭包的实现原理
主要考察 JavaScript 引擎在代码预编译阶段所做的工作有哪些。
-
- 变量提升。
-
- 执行上下文栈的顺序。
闭包的应用
闭包的应用,大多数是在需要维护内部变量的场景下。
1. 创建只执行一次的函数
很多时候,我们希望一个函数只执行一次。如果多次调用该函数,则只会执行第一次。
function once(fn) {
let called = false;
return function () {
if (!called) {
called = true;
fn.apply(this, arguments);
}
};
}
function launchRocket() {
console.log("我已经执行了");
}
const launchRocketOnce = once(launchRocket);
launchRocketOnce();
launchRocketOnce();
launchRocketOnce();
2. for 循环
for(var i = 0;i<10;i++){
setTimeout(()=>{console.log(i)},0)
}
// 打印10 遍 10
// 闭包
var printNum = (num) => {return setTimeout(()=>{console.log(num)},0)}
for(var i = 0;i<10;i++){
printNum(i)
}
// 打印出 1-10
3. 创建私有变量
var module = {
a: 1,
getA: function() {
return this.a;
}
}
var a = 2;
console.log(module.getA()); // 1
console.log(module.a); // 1
console.log(a); // 2
模块中的属性都可看作维护的“私有”变量,如 module.a 的值并不与全局的 a 冲突,但是这也仅仅是防止了模块内外的变量命名冲突,其实还是能进行全局访问或修改,准确讲上面的模块的属性都为“公共”属性;
但很多时候又确实需要维护一些仅模块内部可访问的私有属性或变量,下面便是一种使用闭包特性实现的常用手法:
var module = (function() {
var a = 1;
this.b = 2;
return {
getA: function() {
return a;
},
setA: function(n) {
a = n;
}
}
})();
console.log(module.getA()); // 1
console.log(module.a); // undefined
console.log(module.b); // undefined
module.setA(3);
console.log(module.getA()); // 3