Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 2.x 依赖收集 #36

Open
lovelmh13 opened this issue Feb 14, 2020 · 0 comments
Open

Vue 2.x 依赖收集 #36

lovelmh13 opened this issue Feb 14, 2020 · 0 comments

Comments

@lovelmh13
Copy link
Owner

lovelmh13 commented Feb 14, 2020

代码继续承接上一章

终于正式开始依赖收集了。用到了发布订阅的思想

依赖收集,简单的说就是有个Dep订阅者,收集watcher对象。Dep里面有一个添加和一个通知方法。在Object.defineProperty 触发get的时候,进行添加watcher对象。在set的时候,触发通知方法,通知watcher对象里的update进行视图的更新。

Dep

所以,可以写出来一个Dep雏形:

let id = 0;
// 订阅者
class Dep {
	constructor() {
		this.subs = []; // 存放观察者
		this.id = id++;
	}

	addSub(watcher) {
		/* 目前,是一个Watcher对多个Dep,不明白的话,在最后写完以后,看一下打印结果就会明白 */
		this.subs.push(watcher);
		console.log('订阅watcher:', this.subs);
		console.log('dep的id:', this.id)
	}

	notify() {
		this.subs.forEach(sub => {
			sub.update();
			console.log('发布时Dep的id:', this.id)
		});
	}
}

不过,Watcher不仅会在渲染视图的时候用,在计算属性和watch$watch等都会创建watcher
所以我们给Dep一个target属性,在记住当前的watcher

添加两个方法

let stack = []; // 存放watcher的栈

export function pushTarget(watcher) {
	// 给Dep加一个target属性,存放watcher,好知道现在是哪个watcher。
	// 每一次创建watcher的时候,就会存一个到Dep.target上。
	// 所以会方便找到watcher,直接addSub(Dep.target),就是订阅当前的watcher了。从自身就能存进来watcher
	Dep.target = watcher;
	stack.push(watcher);
	console.log('存Wctcher')
}

export function popTarget() {
	// 用完当前的watcher,就给它清掉
	stack.pop();
	Dep.target = stack[stack.length - 1]; // 只有一个watcher的时候,pop以后,length就为0了,-1的话 stack[-1],会返回undefined
	console.log('删Wacther')
}

Watcher

然后我们看回Watcher

在上次我们写的Watcher的基础上,添加一个update方法:

let id = 0;	// 用来区分是哪一个Watcher
class Watcher {
	/**
	 * 
	 * @param {*} vm 当前组件的实例
	 * @param {*} exprOrFn 更新视图或者渲染视图。用户可能传一个表达式或者一个函数
	 * @param {*} cb 用户传的回调函数 vm.$watch
	 * @param {*} opts 其他参数
	 */
	constructor(vm, exprOrFn, cb=() => {}, opts={}) {
		this.vm = vm;
		this.exprOrFn = exprOrFn;
		this.cb = cb;
		this.opts = opts;
		this.id = id++; // 每次新创建一个watcher实例,就给定一个id号

		if (typeof exprOrFn === 'function') {	// 如果是函数,那么就把exprOrFn赋值给this.getter
			this.getter = exprOrFn;
		}
		this.get();	// 默认创建一个watcher  会调用自身的get方法
	}

	get() {
		this.getter();
	}
	update() {
		console.log('更新数据');
		this.get();	// 更新的时候,再次触发get(),触发Object.defineProperty的set,更新视图
	}
}

export default Watcher

现在有了Dep,需要把DepWatcher关联上。
在触发get()的时候,我们会执行this.getter()也就是我们传递进来的渲染视图的方法。
在渲染之前,我们把当前的watcher存到Dep.target中来,然后在执行完视图更新(this.getter)以后,再把Dep.target里当前的watcher删除掉,方便下次使用。

改写一下代码:

import { pushTarget, popTarget } from "./observe/dep";

let id = 0;	// 用来区分是哪一个watcher
class Watcher {
	/**
	 * 
	 * @param {*} vm 当前组件的实例
	 * @param {*} exprOrFn 更新视图或者渲染视图。用户可能传一个表达式或者一个函数
	 * @param {*} cb 用户传的回调函数 vm.$watch
	 * @param {*} opts 其他参数
	 */
	constructor(vm, exprOrFn, cb=() => {}, opts={}) {
		this.vm = vm;
		this.exprOrFn = exprOrFn;
		this.cb = cb;
		this.opts = opts;
		this.id = id++; // 每次新创建一个watcher实例,就给定一个id号

		if (typeof exprOrFn === 'function') {	// 如果是函数,那么就把exprOrFn赋值给this.getter
			this.getter = exprOrFn;
		}
		this.get();	// 默认创建一个watcher  会调用自身的get方法
	}

	get() {
		pushTarget(this);	// 把当前的watcher传到Dep.target上
		this.getter();	// 在更新视图的时候,会获取数据,会调用Object.defineProperty的get取数据,在get的时候,执行dep.addSub来订阅watcher。set的时候就会让订阅的watcher依次执行
		popTarget();	// 为了方便下次使用,把当前的watcher从Dep.target上删掉
	}
	update() {
		console.log('更新数据');
		this.get();	// 更新的时候,再次触发get(),触发Object.defineProperty的set,更新视图
	}
}

export default Watcher

依赖收集

在获取vm.data或者设置vm.data的时候,会分别触发Object.definePropertygetset。我们只需要在get获取属性值的时候,把当前的watcher也就是Dep.target添加到dep中。在set设置的时候再执行dep.notify(),就可以触发watcherupdate方法来重新渲染视图了。

并且要注意,**现在的DepWatcher是多对一的关系。**因为我们目前只实现了渲染Watcher

可以打印一下,就明白了。
HTML:

<div id="app">
	<input type="text" v-model="msg">
	{{msg}}
	<div>
		学校 {{school.name}}
	</div>
	<div>
		{{arr}}
	</div>
</div>

JS:

let vm = new Vue({
	el: '#app',
	data() {
		return {
			msg: 'hello',
			school: { name: 'lmh', age: 24 },
			arr: [{ name: 'lmh', age: 24 }, 1, 2, 3]
		}
	}
})
console.log(vm.msg = 1)

打印结果:
目前是dep与watcher是多对一的关系

画了一个不太成熟的图,可能不太对:
vue运行原理

但是,目前还是有问题!,如果我多次调用vm.data,比如

<span>{{msg}}</span>
<span>{{msg}}</span>

那么我们在Dep addSub的时候,就会重复存入watcher。我们要存入的watcher不能重名

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant