最近面试的过程中,但凡问到JavaScript闭包的时候,一脸懵逼的情况的占多数。
闭包这个东西,你说它重要吧,确实很重要,毕竟这是很多前端框架广泛使用的开发技巧。你说他不重要吧,也确实没那么重要,因为我们开发项目基本都是套用现有的框架,在框架的范围里面写代码,很少会用到闭包。
可是,耐不住面试经常要问啊。
这一节,我们就来聊聊闭包的话题!
先看一个例子:
function f(){
let a = 1;
return function(){
console.log(a);
}
}
f()()
a是函数f内部的一个变量,按理说函数结束后,外面就没法访问到a了。
可是假如我们用一个函数访问到a,然后再把这个函数返回出去,就延续了a的生命周期。
上面代码的结果是1。
不仅如此,我们还可以在外面反复地操作a。
上面的例子中,我们把a变量悄悄地送出了函数f,接下来我们可以对它进行反复蹂躏!
function f(){
let a = 1;
return function(increment){
a += increment;
console.log(a);
}
}
f()(1)
答案是2,可见,我们可以在外面对a变量进行操作。下面思考一个问题,假如我多次操作会怎样?
var inner = f();
inner(1);
inner(1);
inner(1);
答案
可见,多次操作的话,操作的是同一个a变量,这个a变量就如同鬼魅一般,你在外面看不到他,他却真实存在着。
直接看下面的例子,有一定的实战意义
var obj = {}
function f(){
let a = 1;
obj.num = a;
obj.get = function(){
return a;
}
obj.set = function(v){
a = v;
}
}
f()
这个代码和之前的有不同,它没有在函数f中直接返回另一个函数,而是给外面的obj对象赋予了一些属性和方法,很神奇吧,可这是语法允许的。
函数f被运行后,那么obj.num的值想必大家一看就知道是1
那假如我这么做呢?
obj.num += 1;
这样一来,obj.num就等于2
但是obj.get()得到的值是多少?
还是1,这是因为a是基本类型的数字1,而 obj.num = a 其实是值拷贝,我们没法通过改变num的大小去同步改变a,他们是两个变量了。而 obj.get,obj.set 操作的才是真正的a
上面我们说 a是基本类型的数字1,而 obj.num = a 其实是值拷贝 ,那如果a是对象类型,就可以操作了。
let a = {};
obj.a = a;
obj.get = function(){
return a;
}
obj.set = function(v){
a = v;
}
好了,从这个例子中,我们不难发现,闭包的最大作用就是延续函数内部某些变量的生命周期。但是也正因为此,闭包可能会引发一些内存泄漏的问题。不过,大部分情况,这点内存泄漏真的无伤大雅。
可能有的同学会说,我不用闭包也可以啊,大不了在obj里面设置一个属性a,一样的。
在这个例子中,确实是的。
可是,在某些特殊的时候,用闭包是真的很有必要。
在Vue中,我们知道,会有一个初始化过程,这个过程会对data中的所有属性进行挟持,以便触发watcher更新。以下是简化的代码
var data = {
username:'',
password:''
}
for(let key in data){
Object.defineProperty(data,key,{
set(v){
data[key] = v
},
get(){
return data[key]
}
})
}
这个代码乍一看没啥问题,可是当我们给username赋值,就会触发set方法
报错是因为发生了循环引用,set被重复触发。
同样的,get也会报错
这可怎么办,我们是不是必须得有一个临时变量,来保存当前属性的value呢?
像这样
for(let key in data){
let value = data[key]
Object.defineProperty(data,key,{
set(v){
value = v
},
get(){
return value
}
})
}
瞬间完美了。
按理说,value是for循环中的一个临时变量而已,因为用的是let,所以只在当前作用域有效。而这个临时的value因为被set和get方法访问到了,于是其生命周期也得以延续!
所以,我对闭包的理解就是:函数访问了外部的变量,并且这个函数会在未来的某个时刻用到这个变量,就是闭包。