纯手工模拟Vue中的数据劫持和代理

为什么要实现数据劫持和代理

举一个场景:比如在小程序开发中,我们需要逻辑层修改的数据能同步响应更新到视图层的页面上,那么底层框架在实现这种效果的时候,机制是什么样的呢?
其实这里的底层原理类似于Vue中的数据劫持和代理。所以,弄懂前端中的数据劫持和代理是非常重要的,对我们理解如何实现响应式具有非常重要的意义,本文主要通过一个小小的样例,来展示如何通过最简单直接的代码来实现(如有不对,欢迎大家批评指正,希望和大家一起学习,一起进步)。

如何实现数据劫持和代理

我们所要实现的核心功能就是:一个模拟的_this对象,能够动态的将data数据进行劫持和代理,并且能够通过_this对象进行获取和修改,通过修改之后的数据在data和_this对象中都能够同步更新。

1)模拟数据

// 模拟数据
let data = {
	userName: "harry",
	age: 20
}

2)模拟对象

// 模拟组件的实例
let _this = {

}

3)通过Object.defineProperty() 函数进行数据的劫持和代理
3.1 方法介绍
Object.defineProperty()

/**
	 * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
	 * 
	 * 语法:Object.defineProperty(obj, prop, descriptor)
	 * 
	 * 参数:
	 * obj 要定义属性的对象
	 * prop 要定义或者修改的属性的名称
	 * descriptor 要定义或者修改的属性描述符
	 * 
	 * 返回值:
	 * 被传递非函数的对象(obj)
	 * 
	 **/

3.2 get方法介绍
get() 方法用来获取拓展属性值,当获取该属性值的时候调用get方法;
比如:

for(let item in data){
	Object.defineProperty(_this, item, {
		get(){
			console.log("get()");
			return data[item];
		}
	});
}

console.log(_this)

纯手工模拟Vue中的数据劫持和代理_第1张图片
当点击age和userName的时候,就会自动调用get()方法:
纯手工模拟Vue中的数据劫持和代理_第2张图片
在这里插入图片描述

3.3 set方法介绍
set 监视拓展属性的,只要一修改对象的值就会自动调用,具体将在3.4中介绍。

3.4 错误的直接修改方法
比如在for循环之外,使用直接修改的方法,来看看是否生效。
原本的数据:
纯手工模拟Vue中的数据劫持和代理_第3张图片
修改方法:
纯手工模拟Vue中的数据劫持和代理_第4张图片
结果:
在这里插入图片描述
没有变化。表明没有修改成功,所以通过这种方法是不能直接作用于get方法添加的拓展属性。但是,这里要注意,虽然没有修改成功,但还是会调用set方法。
验证一下是否调用set方法;

set(newValue){
			console.log('set()', newValue)
			// 同时注意: 如果在这里使用_this.userName = newValue 会出现死循环,因为会再次调用set方法
		}

每次直接修改的时候,都会出现newValue(新的值)。
纯手工模拟Vue中的数据劫持和代理_第5张图片
那么会有同学想到在_this的set方法中直接通过_this.userName = newValue 来进行过值得修改,是否可行呢?
听起来是可行的,但是我们分析一下:因为我们如果通过这种方式进行值得修改得时候,会调用set方法,而set方法中又有修改值得操作,所有又会调用set方法。。。这样就会导致深度无限递归操作,所以是不行的。

3.5 正确的修改方法
那么正确的修改方法是什么呢?
我们可以通过迂回战术,先去修改data,然后通过_this获取数据的时候,就会通过get方法进行响应式获取新值,从而达到数据同步更新的结果。
具体代码实现则为:


// 模拟数据
let data = {
	userName: "harry",
	age: 20
}


// 模拟组件的实例
let _this = {

}

// 利用Object.defineProperty() 进行数据的劫持代理
for(let item in data){
	// console.log(item, data[item]);

	/**
	 * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
	 * 
	 * 语法:Object.defineProperty(obj, prop, descriptor)
	 * 
	 * 参数:
	 * obj 要定义属性的对象
	 * prop 要定义或者修改的属性的名称
	 * descriptor 要定义或者修改的属性描述符
	 * 
	 * 返回值:
	 * 被传递非函数的对象(obj)
	 * 
	 **/
	Object.defineProperty(_this, item, {
		// get 用来获取拓展属性值的,当获取该属性值的时候调用get方法
		get(){
			console.log("get()");
			return data[item];
		},

		// set 监视拓展属性的,只要一修改就调用
		set(newValue){
			console.log('set()', newValue)
			// *** 换个思路,先修改data数据,这样的话,下一次_this对象获取的值就会是修改之后的值
			data[item] = newValue;

			// 同时注意: 如果在这里使用_this.userName = newValue 会出现死循环,因为会再次调用set方法
		}
	});
}

console.log(_this)
// 通过Object.defineProperty() 的get方法添加的拓展属性不能直接作用于对象的属性修改
_this.userName = 'wade'	//因为这里进行了数据的修改,所以会调用set函数

console.log(_this.userName)	//但通过_this.userName = 'wade' 来进行直接修改是不生效的

最重要的就是set方法中的data[item] = newValue 这一句。

总结

这里通过Object.defineProperty() 函数实现了数据的劫持和代理,通过这种方式,实现了框架的响应式,不过只是最简单版本的,可以帮助理解。加油。

你可能感兴趣的:(研究生日常技术,vue.js,前端,javascript)