借vue 领悟object的变化侦测和依赖操作

一、文章简介

写这篇文章起源于 vue2.js 数据侦测,参考书籍和多个网上资料,加上自己的领悟,遂写下这篇关于 对象的变化侦测和触发依赖 的文章。写的不足之处,希望道友对我指点。
对象变化侦测思考步骤分为

  1. 数据劫持 ---- defineReactive;
  2. 每一个对象劫持属性所绑定的依赖(函数或模板)---- Watcher;
  3. 对 单个对象属性 绑定的所有依赖(函数或模板) 收集 ---- Dep;
  4. 深度(逐层遍历)对 对象内的属性 进行数据劫持 ---- Observer;
  5. 其他函数(抛在外面方便调用):
    5.1 ---- 向外抛删除依赖的函数,可改删除成功回调: remove;
    5.2 ---- 删除依赖函数(由于多次调用,我选择放在外面), delKeyDep;
    5.3 ---- 获取对象属性值 getPatchData 等;
  6. 对象变化侦测申明;
  7. 对象属性绑定依赖。

二、实现代码

将上述步骤转化为代码如下所示(方便理解就 稍微 进行简化):

第一步 进行1~4操作:

// 数据劫持
function defineReactive(data, key, val) { 
	if('object' == typeof val) new Observer(val); // 判断劫持属性值是否为对象,如果是 进行深度数据劫持

	let dep = new Dep(); // 对每个 劫持对象属性,附带当对象发生变化时 对应 保存的依赖(函数或模板)数组
	Object.defineProperty(data, key,  {
		enumerable: true, // 该属性是否可被枚举
		configurable: true, // 该属性的配置后续是否可进行更改
		get: function () { // 用于获取属性值
			if(window.target) {
				
				// 如果依赖不存在,添加依赖
				if(-1 == dep.subs.indexOf(window.target)) {
					dep.addSub(window.target); // 当对劫持属性进行获取值时,如果存在需要添加的依赖,进行添加依赖操作
				}else {
					if(window.boolBelKeyDep) { // 如果window.boolBelKeyDep 为真,删除依赖
						dep.removeSub(window.target);
						return;
					}
				}
			}
			return val
		},
		set: function (newVal) { // 设置属性值
			if (newVal === val) return;
			val = newVal; // 用新值替换旧值
			dep.notify(); // 当劫持属性值发生改变时,通知绑定的依赖
		}
	})
}

// 每一个对象劫持属性所绑定的依赖(函数或模板),及cb
class Watcher {
	/**
	 * 
	 * @param {*} vm 
	 * @param {String} expr 
	 * @param {Function} cb 
	 */
	constructor (vm, expr, cb) {
		this.vm = vm;
		this.cb = cb;
		this.getter = getPatchData(expr); // 第一次通过get添加依赖
		this.value = this.getValue(); // 存储值
	}
	getValue() {
		window.target = this;
		let value = this.getter(this.vm);
		window.target = undefined;
		return value
	}
	update() {
		const oldValue = this.value;
		this.value = this.getValue();
		this.cb(this.value, oldValue)
	}
}

/**
 * 对 单个对象属性 绑定的所有依赖(函数或模板) 收集
 */
class Dep {
	constructor() {
		this.subs = []
	}
	addSub(sub) {
		this.subs.push(sub)
	}
	removeSub(sub) {
		remove(this.subs, sub)
	}

	/**
	 * 当检测到对象属性发生变化,进行通知所有依赖
	 */
	notify() {
		const arr = this.subs.slice()
		for(let i = 0; i < arr.length; i++) {
			arr[i].update()
		}
	}
}

/**
 * 深度(逐层遍历)对 对象内的属性 进行数据劫持
 */
class Observer {
	constructor(data) {
		this.value = data;
		if(Array.isArray(data)) {
			// 劫持数组
		}else {
			this.walk(this.value)
		}
	}

	/**
	 * (逐层遍历)对象内的属性, 逐一进行数据劫持
	 * @param {object} obj 
	 */
	walk(obj) {
		const keys = Object.keys(obj);
		for(let i = 0; i < keys.length; i++) {
			defineReactive(obj, keys[i], obj[ keys[i] ]);
		}
	}
}

第二步 进行5操作,主要用于抛出需要外部调用的方法:


/**删除依赖 */
function remove(subs, sub) {
	if(subs.length && subs.length > 0) {
		const index = subs.indexOf(sub);
		if(index > -1) {
			subs.splice(index, 1);
			return; // '删除依赖回调'
		}
	}
}
// 在get中进行判断,是否删除依赖
function delKeyDep(vm, expr, delWatch) {
	let objK = getPatchData(expr);
	objK(vm, delWatch);

	window.target = undefined;
	window.boolBelKeyDep = false;
}


// 获取劫持对象属性值,且绑定依赖
function getPatchData(expr) {
	// const reg = /\[^\w.$/;
	// if(!reg.test(expr)) return;
	if(0 == expr.indexOf('.')) return;

	const arr = expr.split('.');

	return function(vm, delWatch) { // 第一个参数表示监听对象(如 obj),第二个参数表示删除的依赖
		if(!vm) return;
		let obj = vm;
		for(let i = 0; i < arr.length; i++){
			if(delWatch && i == arr.length - 1) { // 当 delWatch 存在时,则为删除依赖,window.boolBelKeyDep 表示进行删除依赖操作
				window.target = delWatch;
				window.boolBelKeyDep = true;
			}
			obj = obj[arr[i]] // 当循环对劫持数据进行获取值时,会触发 defineReactive 函数内的get函数,即 如果依赖存在,添加依赖到依赖数组内
		}
		return obj
	}
};

第三步 6~7操作,申明侦测对象,进行依赖操作

{ // 使用对象属性变化侦测
	var obj = {
		name: '',
		age:'?',
		hobby: {
			allLength: '?'
		}
	}
	new Observer(obj); //  目标对象进行数据侦测
	var watch = new Watcher(obj, 'name', function(newVal, oldVal){ // 侦测目标对象属性 添加依赖
		console.log(newVal, oldVal)
	})
	var allLengthWatch = new Watcher(obj, 'hobby.allLength',function(newVal, oldVal){
		console.log('newVal, oldVal: ', newVal, oldVal);
	});
}

三、效果图

附上效果图吧
对象属性新增依赖效果图:
对象属性新增依赖
对象属性删除依赖效果图:
对象属性删除依赖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值