We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
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
代码继续承接上一章
终于正式开始依赖收集了。用到了发布订阅的思想
依赖收集,简单的说就是有个Dep订阅者,收集watcher对象。Dep里面有一个添加和一个通知方法。在Object.defineProperty 触发get的时候,进行添加watcher对象。在set的时候,触发通知方法,通知watcher对象里的update进行视图的更新。
Dep
watcher
Object.defineProperty
get
set
update
所以,可以写出来一个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。
Watcher
watch
$watch
target
添加两个方法
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的基础上,添加一个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,需要把Dep跟Watcher关联上。 在触发get()的时候,我们会执行this.getter()也就是我们传递进来的渲染视图的方法。 在渲染之前,我们把当前的watcher存到Dep.target中来,然后在执行完视图更新(this.getter)以后,再把Dep.target里当前的watcher删除掉,方便下次使用。
get()
this.getter()
Dep.target
this.getter
改写一下代码:
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.defineProperty的get和set。我们只需要在get获取属性值的时候,把当前的watcher也就是Dep.target添加到dep中。在set设置的时候再执行dep.notify(),就可以触发watcher的update方法来重新渲染视图了。
vm.data
dep
dep.notify()
并且要注意,**现在的Dep和Watcher是多对一的关系。**因为我们目前只实现了渲染Watcher。
渲染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)
打印结果:
画了一个不太成熟的图,可能不太对:
但是,目前还是有问题!,如果我多次调用vm.data,比如
<span>{{msg}}</span> <span>{{msg}}</span>
那么我们在Dep addSub的时候,就会重复存入watcher。我们要存入的watcher不能重名
Dep addSub
The text was updated successfully, but these errors were encountered:
No branches or pull requests
代码继续承接上一章
终于正式开始依赖收集了。用到了发布订阅的思想
依赖收集,简单的说就是有个
Dep
订阅者,收集watcher
对象。Dep
里面有一个添加和一个通知方法。在Object.defineProperty
触发get
的时候,进行添加watcher
对象。在set
的时候,触发通知方法,通知watcher
对象里的update
进行视图的更新。Dep
所以,可以写出来一个
Dep
雏形:不过,
Watcher
不仅会在渲染视图的时候用,在计算属性和watch
、$watch
等都会创建watcher
。所以我们给
Dep
一个target
属性,在记住当前的watcher
。添加两个方法
Watcher
然后我们看回
Watcher
。在上次我们写的
Watcher
的基础上,添加一个update
方法:现在有了
Dep
,需要把Dep
跟Watcher
关联上。在触发
get()
的时候,我们会执行this.getter()
也就是我们传递进来的渲染视图的方法。在渲染之前,我们把当前的
watcher
存到Dep.target
中来,然后在执行完视图更新(this.getter
)以后,再把Dep.target
里当前的watcher
删除掉,方便下次使用。改写一下代码:
依赖收集
在获取
vm.data
或者设置vm.data
的时候,会分别触发Object.defineProperty
的get
和set
。我们只需要在get
获取属性值的时候,把当前的watcher
也就是Dep.target
添加到dep
中。在set
设置的时候再执行dep.notify()
,就可以触发watcher
的update
方法来重新渲染视图了。并且要注意,**现在的
Dep
和Watcher
是多对一的关系。**因为我们目前只实现了渲染Watcher
。可以打印一下,就明白了。
HTML:
JS:
打印结果:
画了一个不太成熟的图,可能不太对:
但是,目前还是有问题!,如果我多次调用
vm.data
,比如那么我们在
Dep addSub
的时候,就会重复存入watcher
。我们要存入的watcher
不能重名The text was updated successfully, but these errors were encountered: