闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

闭包在js中是一个很常见的一个结构,它允许你访问父级作用域的变量,并在持续保持引用时保证父级作用域的这个变量不会被内存回收机制回收。这样的特性,在实际项目开发中技能给我们带来很多方便与好处,但如果用的不好,也可能能导致内存泄漏,让你的项目卡顿。 

我们今天要讨论的主要是在Vue源码中的一些闭包的使用,并且通过这些使用实例深入的了解闭包的运行原理。

下面,我们就暂举Vue源码中使用闭包的两个例子来看看Vue是如何使用闭包的吧(事实上,Vue源码中使用闭包来实现功能的地方不胜枚举,此处只是挑出两个比较有代表性的实例讲解,如果想了解更多,可以详细研读Vue源码,相信你会收获满满):

  • 数据响应化Observer中使用闭包
// 注:由于Vue源码中有大量与闭包不相关的代码可能会影响阅读和理解,
// 故此处不照抄Vue源码,而是本人根据Vue源码的实现原理实现的简易版响应式代码,方便阅读与理解
function defineRealive(target, key, value){
	return Object.defineProperty(target, key, {
		get(){
			console.log(`通过getter获取数据:${value}`);
			return value;
		},
		set(val){
			console.log(`通过setter设置数据:新值-${val};旧值-${value}`);
			// 很多人会疑问,value明明是形参,为什么给他赋值就能够达到数值改变的效果呢?形参不是出了这个函数就没用了么?
			// 其实,这就用到了闭包的原理,value是外层函数defineRealive的参数,而我们实际上使用value确是在内层的get或set方法里面
			// 这样就形成了一个闭包的结构了。根据闭包的特性,内层函数可以引用外层函数的变量,并且当内层保持引用关系时外层函数的这个变量
			// 不会被垃圾回收机制回收。那么,我们在设置值的时候,把val保存在value变量当中,然后get的时候再通过value去获取,这样,我们再访问
			// obj.name时,无论是设置值还是获取值,实际上都是对value这个形参进行操作的。
			value = val;
		}
	});
}

let obj = {
	name: 'kiner',
	age: 20
};

Object.keys(obj).forEach(key=>defineRealive(obj, key, obj[key]));

obj.name = 'kanger';// 控制台输出:通过setter设置数据:新值-kanger;旧值-kiner
obj.age = 18;// 控制台输出:通过setter设置数据:新值-18;旧值-20


// 控制台输出:通过getter获取数据:kanger
// 控制台输出:通过getter获取数据:18
// 控制台输出:kanger 18
console.log(obj.name,obj.age);

如果觉得上面的代码还是不容易理解,那么,我们换种方式再看看:

// 这样是不是就比较好理解了呢,形参也是一个普通的局部变量,只是可能我们平时使用的时候,
// 一般不会对形参进行赋值操作,因为大部分情况,形参都是外部传入的数据,我们无需修改。
// 所以,我们直接理解为:我们在一个函数外定义了一个变量,然后在函数内使用这个变量,而这个变量
// 在内部函数,无论是get、还是set,其实都是同一个。我们只是用这个变量拿来暂存数据而已
function defineRealive(target, key, value){
	
	// 相比上面的代码,实际只加了这一行,以及后面参数的参数名,把形参保存在一个局部变量里面
	let realValue = value;

	return Object.defineProperty(target, key, {
		get(){
			console.log(`通过getter获取数据:${realValue}`);
			return realValue;
		},
		set(val){
			console.log(`通过setter设置数据:新值-${val};旧值-${realValue}`);
			realValue = val;
		}
	});
}

 

  • 全局Api中的$watch方法中使用闭包
// 根据Vue的$watch原理实现的简易版$watch
Vue.prototype.$watch = function(exp, cb, options = {immediate: true, deep: false}) {
    let watcher = new Watcher(this, exp, cb, options);
    return () => {
        watcher.unWatch();
    };
}

从$watch方法的实现中,我们可以看出,这其实也是一个闭包的结构,用户调用方法时,会实例化一个Watcher对象并保存在变量watcher,然后返回一个函数,在这个函数里面,调用了watcher下的unWatch方法。也就是说,用户可以通过以下方式进行监听/移除监听属性变化


let obj = {
	name: 'kiner',
	age: 20
};

//  监听obj.name的改变
let unWatchName = this.$watch("obj.name",function(newVal, oldVal){
	console.log(`新值:${newVal};旧值:${oldVal};`);
});

// 取消监听,取消后,obj.name的改变不再会通知
unWatchName();

这样实现的巧妙之处在于,我们无须关心要调用谁去取消监听,你怎么监听的,他就给你返回一个取消监听的方法,直接调用这个方法取消就可以了。

通过上面的讲解,你是否对闭包的使用有了更加深入的认识呢,如果对闭包的使用有什么疑问或者有什么新奇的想法的话,欢迎一起讨论研究。

Logo

前往低代码交流专区

更多推荐