js手写系列(1)| Vue3.x响应式原理

这是我参与新手入门的第三篇文章

知识点

本篇文章主教你一步一步的手写Vue3.x的响应式原理,文中涉及的知识点如下:

  • defineProperty 和 Proxy 数据代理
  • ES6 之 WeakMap
  • 设计模式 | 发布-订阅

一 、defineProperty 和 Proxy 数据代理

相同点:
(1)两者都能对数据进行挟持

不同点
(1)defineProperty根据key值来进行监听,Proxy则根据整个对象进行监听
(2)defineProperty在原对象上修改操作污染原对象
(3)Proxy 则是会返回一个新的代理对象,操作都再新对象当中,原对象并不会被污染

1. defineProperty

一个简易的defineProperty数据挟持

    const watch = (target, property, callback) => {
  	// 存储当前值
  	let _value = target[property];
  	
  	// 使用defineProperty对数据进行监听
  	Object.defineProperty(target, property, {
  		get() {
  			console.log('正在获取对象的:' + property + '属性值')
  			return _value;
  		},
  		set(newVal) {
  			// 记录旧值
  			const lastVal = _value;
  			// 设置新值
  			_value = newVal;
  			// 回调返回新、旧值
  			callback && callback(newVal, lastVal);
  		}
  	});
  }
  
  // 创建一个测试对象
  let test = {
  	count: 1
  }
  
  // 执行监听
  watch(test, 'count', (newVal, lastVal) => {
  	console.log("test对象中count发生变化:", newVal, lastVal);
  })
  
  test.count += 1;
复制代码

控制台输出

image.png

2. Proxy

一个简易的Proxy数据挟持

// Proxy 直接传入一个对象,返回一个新对象(代理对象)
	const watch = (target, callback) => {
		return new Proxy(target, {
			get(target, key) {
				return target[key];
			},
			set(target, key, newVal) {
				// 获取旧值
				const lastVal = target[key];
				// 设置新值
				target[key] = newVal;
				
				// 回调 - 返回新、旧值
				callback(newVal, lastVal);
			}
		})
	}
	
	// 创建一个测试对象
	let test = {
		count: 1
	}
	
	let newTest = watch(test, (newVal, lastVal) => {
		console.log("test对象中count发生变化:", newVal, lastVal);
	})
	// 修改新对象中的count
	newTest.count += 1;
复制代码

控制台输出:

image.png

二、ES6 WeakMap

1.Map

Map 是 ES6 新增的有序键值对集合。键值对的 key 和 value 都可以是任何类型的元素。

示例:

        let map = new Map();
	
	// 创建测试对象
	let test_1 = {
		func: () => {
			console.log('对象的方法')
		}
	};
	
	// 创建测试字符串变量
	let test_2 = 'test_2';
	
	// 设置 key - value
	map.set(test_1, 1);
	map.set(test_2, 2);
	
	// 以对象为key来访问数据
	let value_1 = map.get(test_1); // -> value_1 = 1
	let value_2 = map.get(test_2); // -> value_2 = 2
复制代码

2. WeakMap

WeakMap 相对于普通的 Map,也是键值对集合,只不过 WeakMap 的 key 只能是非空对象(non-null object)。WeakMap 对它的 key 仅保持弱引用,也就是说它不阻止垃圾回收器回收它所引用的 key。WeakMap 最大的好处是可以避免内存泄漏。一个仅被 WeakMap 作为 key 而引用的对象,会被垃圾回收器回收掉。

示例:

        let map = new WeakMap();
	
	// 创建测试对象
	let test_1 = {
		func: () => {
			console.log('对象的方法')
		}
	};
	
	// 创建测试字符串变量, 报异常
	// let test_2 = 'tt'; // error -> Invalid value used as weak map keyat WeakMap.set
	// let test_2 = null;// error -> Invalid value used as weak map keyat WeakMap.set
	
	
	let test_2 = {};

	// 设置 key - value
	map.set(test_1, 1);
	map.set(test_2, 2);
	
	// 以对象为key来访问数据
	let value_1 = map.get(test_1); // -> value_1 = 1
	let value_2 = map.get(test_2); // -> value_2 = 2
复制代码

三、设计模式 | 发布-订阅

参考本人写的小菜文: 设计模式 | “观察者”与“订阅发布”

四、手写实现Vue3.x响应式

 let activeEffect;
	// 订阅
	class Dep {
		constructor(arg) {
			this.subscribers = new Set();
		}

		depend() {
			// 添加订阅者
			if (activeEffect) {
				this.subscribers.add(activeEffect);
			}
		}

		notify(...args) {
			// 通知
			this.subscribers.forEach(effect => effect(...args))
		}
	}

	// 设置当前订阅者
	function watchEffect(effect) {
		activeEffect = effect;
		effect();
		activeEffect = null;
	}

	// 存储所有订阅实例
	let depMap = new WeakMap();
	// 获取当前对象的dep订阅,不存在则新建
	function getDep(target) {
		if (!depMap.get(target)) {
			depMap.set(target, new Dep());
		}
		return depMap.get(target);
	}

	// 响应
	function reactive(obj) {
		return new Proxy(obj, reactiveHandler);
	}

	const reactiveHandler = {
		get(target, key, reactive) {
			let dep = getDep(target);
			dep.depend();
			// 使用Reflect 的原因
			// Reflect 跟 Proxy 一样,只不过Reflect 不会报异常,一旦产生错误,则会返回false
			return Reflect.get(target, key, reactive);
		},
		set(target, key, value, reactive) {
			Reflect.set(target, key, value, reactive);

			let dep = getDep(target);
			dep.notify();
		}
	}

	const op = reactive({
		op: 1,
		test: 2
	})

	watchEffect(() => {
		console.log(op.test, 'op.test');
	})

	op.test = 5;
	// -> 2 'op.test'
	// -> 5 'op.test'

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享