前言
闭包在项目和工作中几乎无处不在,同时也作为面试官的高频考点,下面是我在学习闭包中的一些看法与总结,希望能帮助一些小伙伴学习;
什么是闭包
闭包就是能够读取其他函数内部变量的函数,一个函数和它周围作用域引用所形成的组合,就称为闭包;
那么什么是作用域呢?我们先来了解一下
作用域
当我们运行JavaScript代码时就会生成执行上下文,执行上下文决定了代码的作用域,作用域相当于一个封闭的盒子,给代码划清了界限,JavaScript作用域主要分为三种:
- 全局作用域(在任何位置都能找到,比如window内置对象属相)
- 函数作用域(只在函数体内才能被访问到,私有作用域)
- 块作用域 (使用let或const定义,任何一个{}中的语句集合都属于一个块,在里面定义的所有变量外部都是不可见的)
// 全局作用域
console.log(window.Array); //全局作用域,任何位置都可以访问
// 函数作用域
function fn() {
var a = 1;// 具有函数作用域,无法访问
}
fn()
console.log(a); //Uncaught ReferenceError: a is not defined
//块作用域
{
var b = 2 ;//不具备块级作用域,可以访问
const c = 3; // 具备块级作用域,不可以返回
}
console.log(b); // 2
console.log(c); //Uncaught ReferenceError: c is not defined
作用域继承
作用域继承,就如同盒子嵌套,比如一个大盒子作为一个父级作用域,大盒子里面可以放一个小盒子作为子作用域,小盒子可以从大盒子获取东西,但是大盒子不能获取小盒子中的东西,就称为作用域继承
function show() {
var a = 1;
return function() {
console.log(a) // 1
}
}
show()()
函数执行,形成一个独立作用域,保护里边的私有变量不受外界的干扰,除了保护私有变量外,还可以存储一些内容,这样的模式叫做闭包。
闭包实现
产生一个闭包创建闭包最常见方式,就是在一个函数内部创建另一个函数。
function show() {
var a = 1;
return function() {
console.log(a) // 1
}
}
show()()
闭包中的this
在闭包中使用this会让代码变得复杂,如果内部函数没有使用箭头函数,则this对象会在运行时绑定到执行函数的上下文。如果在全局函数中被调用,this非严格模式下指向window
window.a = 1
var obj = {
a: 2,
show() {
return function() {
return this.a;
}
}
}
console.log(obj.show()()) //1
可以通过把this保存在闭包中进行访问this
window.a = 1
var obj = {
a: 2,
show() {
var that = this;
return function() {
return that.a
}
}
}
console.log(obj.show()()) //2
注意 this和arguments都是不能直接在内部函数中访问的。如果想访问包含作用域中的arguments对象,同样需要将其保存在闭包的能访问对象中;
但在一些特殊情况下,this的和我们想象的不一样:例如
window.a = 1;
const obj = {
a: 2,
show() {
return this.a
},
};
const s = obj.show
console.log(s()) //1
该例子执行了一次赋值,调用赋值后的结果,由于赋值表达式的值是函数本身,this值不再与任何对象绑定,返回的就是1
更改this
当然我们可以通过更改this指向来达到我们想要的结果
- bind() 里面传递的是对应指向的对象
- call() 里面传递的是指向的对象和参数(会自动调用)
- apply() 里面传递时指向的对象及参数数组(会自动调用)
闭包特点
- 闭包拥有全局变量的不被释放的特点
- 局部变量不被外部访问到的特点
闭包优点
- 可以让变量长期不被垃圾回收机制回收,不让局部变量使用后立即释放
- 避免全局变量的污染,和全局变量不同,闭包中的变量无法被外部使用
- 私有成员变量,无法被外部调用,只能直接内部调用
闭包可能造成的问题
- 闭包会保留他们包含的作用域,所以比其它函数更加占内存,过度使用闭包会导致内存过度占用,建议仅在十分必要时使用闭包;或者在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。