5、watch侦听器实现原理

80 阅读1分钟

一、watch侦听器的特点:

1、关注点在数据改变时,内部逻辑的处理,就是要做一点什么。

2、当依赖的数据发生改变时,执行watch中对应的方法

class Vue {

	constructor(options) {
		const { data, computed, watch } = options;
		this.$data = data();
		this.init(this, computed, watch);
	}
	init(vm, computed, watch) {
		this.initData(vm);
		this.initComputed(vm, computed);
		this.initWatch(vm, watch);
	}

	initData(vm) {
		this.reactiveData(vm, (key, value) => {
			// console.log(key, value);
		}, (key, newValue, oldValue) => {
			if(newValue===oldValue) return
			this.$computed.update(key,this.$watch.invoke.bind(this.$watch))
			this.$watch.invoke(key, newValue, oldValue)
		})
	}
	
	reactiveData(vm, getCallback, setCallback) {
		var _data = vm.$data;
		for (let key in _data) {
			Object.defineProperty(vm, key, {
				get() {
					getCallback(key, _data[key]);
					return _data[key];
				},
				set(newValue) {
					const oldValue = _data[key];
					_data[key] = newValue;
					setCallback(key, newValue, oldValue);
				}
			})
		}
	}

	initComputed(vm, computed) {
		this.$computed = new Compute()
		for (let key in computed) {
			this.$computed.reactiveCompute(vm, key, computed)
		}
	}
	
	initWatch(vm, watch){
		this.$watch = new Watch()
		for (let key in watch) {
			this.$watch.reactiveWatch(vm, key, watch)
		}
	}
}

class Compute {
	constructor() {
		this.computeData = []
	}

	reactiveCompute(vm, key, compute) {
		var descriptor = Object.getOwnPropertyDescriptor(compute, key),
			fn = descriptor.value.get ? descriptor.value.get : descriptor.value,
			value = fn.call(vm),
			get = fn.bind(vm),
			dep = this.collectionDep(fn);
		this.addCompute({ key, value, get, dep });
		this.reactive(vm, key);
	}

	reactive(vm, key) {
		var item = this.computeData.find(item => item.key === key)
		Reflect.defineProperty(vm, key, {
			get() {
				return item.value
			},
			set(newValue) {
				item.value = item.get()
			}
		})
	}

	addCompute(prop) {
		this.computeData.push(prop)
	}

	collectionDep(fn) {
		var collection = fn.toString().match(/this.(.+?)/g);
		collection = collection.map((item) => item.split('.')[1]);
		return collection;
	}

	update(key,watch) {
		for (let item of this.computeData) {
			if (item.dep.includes(key)) {
				const oldValue=item.value;
				item.value = item.get();
				watch(item.key,item.value,oldValue)
				return
			}
		}
	}
}

class Watch{
	constructor() {
	    this.Watchers=[]
	}
	
	reactiveWatch(vm, key, watch){
		var fn=watch[key].bind(vm);
		this.addWatch({key,fn})
	}
	
	addWatch(prop){
		this.Watchers.push(prop)
	}
	
	invoke(key, newValue, oldValue){
		for (let item of this.Watchers) {
			if (item.key===key) {
				item.fn(newValue, oldValue) 
				return
			}
		}
	}
	
}

var options = {
	data() {
		return {
			a: 1,
			b: 2,
			c:3
		}
	},
	computed: {
		total() {
			console.log('Computed');
			return this.a + this.b+this.c;
		}
	},
	watch: {
		total(newValue, oldValue) {
			console.log('total', newValue, oldValue);
		},
		a(newValue, oldValue) {
			console.log('a', newValue, oldValue);
		},
		b(newValue, oldValue) {
			console.log('b', newValue, oldValue);
		}
	}
}

const vm = new Vue(options);

console.log(vm);
console.log(vm.total);
vm.c = 15;
console.log(vm.total);