【vue】用图告诉你响应式原理
栏目: JavaScript · 发布时间: 6年前
内容简介:如果自己去实现数据驱动的模式,如何解决一下几个问题:我们需要知道数据的获取和改变,数据劫持是最基础的手段。在Obeserver中,我们可以看到代码如下:通过Object.defineProperty这个方法,我们可以在数据发生改变或者获取的时候,插入一些自定义操作。同理,vue也是在这个方法中做依赖收集和派发更新的。
如果自己去实现数据驱动的模式,如何解决一下几个问题:
- 通过什么手段去知道我的数据变了?
- 通过什么东西去同步更新视图?
数据劫持——obvserver
我们需要知道数据的获取和改变,数据劫持是最基础的手段。在Obeserver中,我们可以看到代码如下:
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// ...
},
set: function reactiveSetter (newVal) {
// ...
}
})
复制代码
通过Object.defineProperty这个方法,我们可以在数据发生改变或者获取的时候,插入一些自定义操作。同理,vue也是在这个方法中做依赖收集和派发更新的。
绑定和更新视图——watcher
从初始化开始,我们渲染视图的时候,便会生成一个watcher,他是监视视图中参数变化以及更新视图的。代码如下:
// 在mount的生命钩子中
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
复制代码
当然,我们可以保留疑问:
- watcher是怎么去更新视图的
- 数据又是怎么和watcher联动起来的
具体的绑定和更新的流程,我们到后续的依赖收集中讲解。
我们先来讲讲响应式系统中涉及到的设计模式。
发布订阅模式
在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件
以此避免发布者和订阅者之间产生依赖关系
vue的响应式流程
vue的响应式系统借鉴了数据劫持和发布订阅模式。
Vue用Dep作为一个中间者,解藕了Observer和Watcher之间的关系,使得两者的职能更加明确。
那具体是如何来完成依赖收集和订阅更新的呢?
依赖收集过程
- 依赖收集的流程
举个例子
<div id="app">
{{ message }}
{{ message1 }}
<input type="text" v-model="message">
<div @click="changeMessage">改变message</div>
</div>
复制代码
var app = new Vue({
el: '#app',
data: {
message: '1',
message1: '2',
},
methods: {
changeMessage() {
this.message = '2'
}
},
watch: {
message: function(val) {
this.message1 = val
}
}
})
复制代码
依赖收集流程图:
如何看懂这个依赖收集流程?关键在watcher代码中:
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
// 省略
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
复制代码
调用的这个this.getter有两种,一种是key值的getter方法,还有一种是expOrFn,比如mounted中传入的updateComponent。
- 如何防止重复收集
我们不妨想想什么才算是重复收集了?
笔者想到一种情况:就是dep数组中,出现了多个一样的watcher。
比如renderWatch就容易被重复收集,因为我们在html模版中,会重复使用data中的某个变量。那他是如何去重的呢?
1、只有watch在执行get时,触发的取数操作,才会被收集
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
// ...
}
return value
},
set: function reactiveSetter (newVal) {
// ...
dep.notify()
}
})
复制代码
当只有Dep.target这个存在的时候才进行依赖收集。Dep.target这个值只有在watcher执行get方法的时候才会存在。
2、在dep.depend的时候会判断watch的id
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
复制代码
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
复制代码
我们会发现,在depend过程中,会有一个newDepIds去记录已经存入的dep的id,当一个watcher已经被该dep存过时,便不再会进行依赖收集操作。
派发更新过程
收集流程讲完了,不妨在听听更新流程。
- 订阅更新的流程 老例子
<div id="app">
{{ message }}
{{ message1 }}
<input type="text" v-model="message">
<div @click="changeMessage">改变message</div>
</div>
复制代码
var app = new Vue({
el: '#app',
data: {
message: '1',
message1: '2',
},
methods: {
changeMessage() {
this.message = '3'
}
},
watch: {
message: function(val) {
this.message1 = val
}
}
})
复制代码
依赖收集的最终结果:
当触发click事件的时候,便会触发订阅更新流程。
订阅更新流程图:
当renderWatch执行更新的时候,回去调用beforeUpdate生命钩子,然后执行patch方法,进行视图的变更。
- 如何防止重复更新
如何去防止重复更新呢?renderWatch会被很多dep进行收集,如果视图多次渲染,会造成性能问题。
其实问题的关在在于—— queueWatcher
function queueWatcher (watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
queue.push(watcher)
// ...
if (!waiting) {
waiting = true
// ...
nextTick(flushSchedulerQueue)
}
}
}
复制代码
其实queueWatcher很简单,将所有watch收集到一个数组当中,然后去重。
这样至少可以避免renderWatch频繁更新。
比如上述例子中的,message和message1都有一个renderWatch,但是只会执行一次。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C++语言的设计和演化
[美] Bjarne Stroustrup / 裘宗燕 / 机械工业出版社 / 2002-1 / 48.00元
这本书是C++的设计者关于C++语言的最主要著作之一。作者综合性地论述了C++的历史和发展,C++中各种重要机制的本质意义和设计背景,这些机制的基本用途和使用方法,讨论了C++所适合的应用领域及其未来的发展前景。一起来看看 《C++语言的设计和演化》 这本书的介绍吧!