闭包
闭包
我们会在开发某些需求时用到 闭包,它可以通过函数来访问或保存一些局部变量,这种特性在某些情况下会非常有用
什么是闭包
闭包(closure) 就是在函数内部嵌套了函数,并且这个嵌套的函数引用了它外部的变量(词法环境),嵌套的函数与它引用的变量(词法环境)共同组合就形成了闭包
function foo(){
let name = "foo"; //name是在foo函数内部定义的一个局部变量
function getName(){ //getName函数是在foo函数内部定义的一个函数
console.log(name) //getName函数内部引用了外部函数foo中定义的变量,形成了闭包
}
getName();
}
foo(); //foo
上面的例子就是一个典型的闭包
function baz(){
let name = "baz";
function getName(){
console.log("baz");
}
getName();
}
baz(); //baz
在这个例子中,虽然在函数 baz
内部定义了函数,但是内部的 getName
函数并没有引用外部的变量,就无法形成闭包
闭包的作用
闭包的作用主要有以下两点:
- 隔离或隐藏变量,防止变量被全局污染
- 读取函数内部定义的变量
避免变量污染
let num = 1; //定义了一个全局变量num
function add(){
num++;
console.log(num);
}
add(); //2
add(); //3
在上面的代码中,我们定义了一个全局变量 num
与一个能够使 num
增加的函数 add
,虽然每次调用 add
方法时都会使 num
的值增加,但是如果也有其他的函数中同样引用了全局变量 num
,那么这样使用全局变量就会变得十分不安全,所以我们可以使用闭包来修改上面的方法,避免变量污染
let num = 1; //定义了一个全局变量num
function add(){
let val = num; //将num的值赋值给add函数内部定义的变量val
function plus(){ //定义一个新的函数plus来使val增加(此时形成了闭包)
val++;
return val;
}
//将plus函数作为add函数的结果返回,这样就可以将val的值保持在内存中,也可以从外部访问到val的值了
return plus;
}
const newAdd = add();
console.log(newAdd()); //2 //实际上运行的是plus函数
console.log(newAdd()); //3
console.log(num); //1 //全局变量num并没有受到干扰
模拟私有方法
const car = function (){ //定义一辆汽车
let speed = 100; //定义汽车速度相关的属性和方法
function speedChange(_speed){
speed += _speed;
}
return {
speedUp:function(up){ //汽车加速方法
speedChange(up);
},
slowDown:function(slow){ //汽车减速方法
speedChange(-slow);
},
speedNow:function(){ //汽车当前速度
return speed;
}
}
}
const car1 = car(); // speed:100
const car2 = car(); // speed:100
car1.speedUp(60); // 160
car2.speedUp(100); // 200
car1.slowDown(80); // 80
car2.slowDown(50); // 150
console.log(car1.speedNow()); // 80
console.log(car2.speedNow()); // 150
在上面的例子中我们通过 car
函数创建了两个“汽车”对象,虽然都是通过同一个函数创建的对象,但是它们各自都有自己的“速度”,并且各自操作“速度”属性不会相互干扰,是因为我们在 car
函数内部创建了闭包 speedChange
函数(通过 speedChange
函数来模拟 car
的私有方法),因为闭包都是引用它自身词法环境中的变量 speed
,所以它们相互独立,互不干扰
闭包的缺陷
闭包并非完全 “封闭” 的,在某些情况下,我们还是可以通过原型链来访问到闭包中的变量,比如:
// 一个闭包,屏蔽了对象 obj
function closure() {
const obj = {
a: 1,
b: 2
}
return function (key) {
return obj[key]
}
}
const closureFn = closure()
console.log(closureFn('a')) //1
console.log(closureFn('b')) //2
Object.defineProperty(Object.prototype, 'private', {
get() {
return this
}
})
const private = closureFn('private')
private.c = 3
console.log(closureFn('c'))
console.log(private)
上面示例中的函数是一个典型的闭包,我们似乎只能通过 closureFn
函数来访问到 obj
的属性,但是我们还是可以通过原型链来访问到 obj
对象本身,所以,稍不注意,闭包也不是完全封闭的,访问 obj
对象本身的代码如下:
function closure() {
const obj = {
a: 1,
b: 2
}
return function (key) {
return obj[key]
}
}
const closureFn = closure()
console.log(closureFn('a')) //1
console.log(closureFn('b')) //2
Object.defineProperty(Object.prototype, 'inner', {
get() {
return this
}
})
const inner = closureFn('inner') //{a: 1, b: 2,inner: {a: 1, b: 2}}
inner.c = 3
console.log(closureFn('c')) // 3
console.log(inner) //{a: 1, b: 2, c:3,inner: {a: 1, b: 2, c:3}}
想要避免上面代码的问题,就要防止原型链上的操作,在操作闭包中的变量时将原型链先屏蔽掉,具体代码如下:
function closure() {
const obj = {
a: 1,
b: 2
}
// 将obj的原型对象设置为null,屏蔽原型链
// Object.setPrototypeOf(obj, null)
return function (key) {
//判断key是否是obj自身的属性,如果是则返回obj的属性值
if(obj.hasOwnProperty(key)){
return obj[key]
}
return undefined
}
}
闭包虽然可以使我们能够很方便的将变量保存在局部作用域中,避免了全局污染,但是它也会消耗内存,严重时甚至可能造成内存泄露的问题
通过前面的 执行上下文 章节我们知道函数在运行时会在 栈 中创建自己的函数执行上下文,在函数运行完成后出栈,但是如果在函数内部创建了闭包,外部函数的变量被闭包引用,那么就会导致外部函数的执行上下文保持在栈中,并且外部函数中的变量也无法被垃圾回收机制销毁。试想一下,栈的内存容量使有限的,如果过多的使用闭包,可能就会造成栈内存被过多的占用从而导致内存泄露的问题