欢迎来到我的《从源码中学Vue》专题系列文章,更多精彩内容持续更新中,欢迎关注 :)
![00e2a3f0a2ab3fa932dd726b38ec23f8.png](https://i-blog.csdnimg.cn/blog_migrate/be951f6219b54faf2bef6ce75baeaa44.jpeg)
上一章节我们说到了在Vue中的数据劫持(Observer)的基本原理是通过ES5的API Object.defineProperty来实现的。
本章目标
- Vue初始化的时候,为什么会主动渲染模板数据
- Vue的发布订阅模式
单纯的只是实现数据劫持是远远不够的,我们需要在数据发生变化的时候需要去更新视图(html),那么Vue中是通过Watcher类就是用来观察数据的变化并且去通知patch去更新dom
Vue初始化的时候,为什么会主动渲染模板数据
通过Observer类我们知道,我们只有访问或者修改data上的属性的时候,才会触发get和set方法,这时候才会触发dom更新,那么当我们第一次new Vue实例的时候,我们并没有对data数据进行任何的访问和获取,那html上的模板数据第一次是如何渲染出来的呢?
这时候我们就不得不提到Watcher类了。
在Watcher类中,有一个很关键的方法,get(),这个方法在Watcher类的构造方法中被调用
this.value = this.lazy? undefined : this.get()
那么在什么情况下会调用get方法呢,通过我前面的文章Vue生命周期详解 中知道,在mounted之前,会实例化一个Watcher
![28ca959160bb823a2a6e927ed9e2f37f.png](https://i-blog.csdnimg.cn/blog_migrate/3981a2b7a8a2f2c06c6be57098ed9b65.jpeg)
可以看出来我们在实例化Watcher的时候,第4个参数我们没有传入lazy值,所以这个时候回过头看
this.value = this.lazy? undefined : this.get()
this.get方法会被调用。
再来看下get方法的源码
![eec395c194881c3c76053ba32f27fb96.png](https://i-blog.csdnimg.cn/blog_migrate/6d3f248a21e655f70f3819ced6fc49fe.jpeg)
它调用了this.getter方法,而
![9380f2a84dd8a1a8e3f9e99028deb692.png](https://i-blog.csdnimg.cn/blog_migrate/3ee4abd8557aee06608dcc5629d5b3b0.jpeg)
这个this.getter方法,实际就是我们传入的第二个参数expOrFn
Watcher的第二个参数为expOrFn它可以是一个string 或者function
![e4ddb804e50fd8970399cb2a1905c4f1.png](https://i-blog.csdnimg.cn/blog_migrate/2b914317222f7f945e05723970c1e110.jpeg)
我们实例化的时候传入的实参是updateComponent,它是一个function类型
![717e2b09e5e55790c286caeab3d40405.png](https://i-blog.csdnimg.cn/blog_migrate/d0a137c757bc45161e8cea5f77b41690.jpeg)
所以最终是调用的updateComponent()方法,这个方法里面再调用了。vm.__update()方法。
我们再在当前的文件下找到了_update方法的定义
![faf349509a32b7a2bb3b683816246275.png](https://i-blog.csdnimg.cn/blog_migrate/16a78d43dd7b3332afb6369d8a214c35.jpeg)
挂载在Vue原型下的方法,可以看出来我圈出来的两行代码。都会触发一个叫vm.__patch__的方法
patch英文的意思就是补丁。它也是虚拟dom的核心方法。内部做了模板的解析。
到了这一步,我们实际上已经找了虚拟dom算法的位置了。这块还是比较复杂的,小编我还没来得及阅读这块的源码,这里就不再深入了。
Vue的发布订阅模式
我们现在知道了了Vue是通过Observer类去劫持数据,Watcher类实现数据的监听并通知更新dom,其实在Vue中还有一个很重要的概念,那就是发布订阅模式(Dep类)
Dep类是连接Watcher和Observer的桥梁。具体我们通过源码去看下它们三者之间的关系
![8f0ace6f553c8b851f0b7e7244194c03.png](https://i-blog.csdnimg.cn/blog_migrate/dd9369888df194ab4b7b8c1dbd99ce70.jpeg)
它里面有一个subs数组,这个数据就是用来收集watcher,再来看下notify方法,可以看到它最终循环了subs中所有的watcher对象然后依次调用了它们的update方法。
我们也同样很容易想到这个notify通知所有的Watcher方法是在当数据发生变化的时候去调用的,我们找下源码
![3d892aa71a7855db5435bba07fc7fd75.png](https://i-blog.csdnimg.cn/blog_migrate/44a3ac14dcf131513ffb48aa8baffc7c.jpeg)
再回过头我们去看下watcher的update方法。
![cd8194dde8e2d3d04043ab5f19354655.png](https://i-blog.csdnimg.cn/blog_migrate/e0dde84e872ae6656ddf087684804526.jpeg)
先来看run 方法
![17748963e1a807c26474a6341043409e.png](https://i-blog.csdnimg.cn/blog_migrate/d1ff12920a583ead46e8222a2b44fff0.jpeg)
最终调用的是cb方法,cb顾名思义,它是callback,回调方法。也就是说,在我们初始化Watcher的时候,并不会去主动调用这个cb方法,而是当数据发生变化的时候触发了set,然后再去调用每一个watcher对应的cb回调。
最后再来看下在Dep类下有一个静态属性target
![6c519d7b168bca5fbc19f5540e74d2da.png](https://i-blog.csdnimg.cn/blog_migrate/8b3a264b1010fd842960313fd1d5c96d.jpeg)
在watcher类中调用get方法的时候,先调用了pushTarget(this)
![b9910a04ae54cc88794292a13138c1b5.png](https://i-blog.csdnimg.cn/blog_migrate/df5e404c5325736dbb6227ee8d936ac5.jpeg)
这个this就是指向的我们当前的Watcher实例。
为什么给Dep加一个静态属性呢?
利用这个中间变量, 缓存已有的target, 在 pushTarget 函数, 使用传入的target 代替 Dep.target; 然后使用 Dep.target
最后使用 popTarget 还原 ; 主要是为了Observe中的get方法, 判断当前是否有watcher(Dep.target), 如果有就在dep增加这个属性的依赖, Dep.target.depend( dep1 )
比如 methods 的每一个方法可以是 一个 watcher, 这个wactcher可能会依赖多个 data里面的属性
这里真的很绕 ^_^
总结:
- Dep类是用来连接Watcher和Observer的桥梁。当数据发生变化的时候,通知Watcher去更新
- 我们在第一次实例化Watcher的时候,会去调用__patch__方法去更新模板上的视图。
这里是畅哥聊技术 《从源码中学Vue》系列文章,更多精彩内容持续更新中,敬请期待。
未完待续。。。