1. Review & Background
The previous essay discusses in createElm
detail the logic of this method, which is based on VNode
creating real elements, which contain two scenarios:
- If
VNode
is a custom component, call thecreateComponent
method to process; - If it is a normal element, by
nodeOps.createElement
creating a nativeHTML
element;
At the end of the previous article, I sorted out new Vue
the insert
whole process execution stack flow from to to . This process is the whole process Vue
from new Vue
to rendering to the page.
But it Vue
is a responsive system. The previous process is only half completed, and the remaining half is that when the responsive data changes, Vue
the rendering of the system watcher
receives a notification to trigger re-rendering, which is what everyone often says patch 阶段
.
For its Vue
own internal design, as long as it is VNode
the process of rendering to the page, it is called patch
, and it is not divided into two large processes, although the interior is differentiated or rendered for the first time, or the responsive data is changed and rendered. But this is not friendly to people who are beginners in Vue
source code , so I cleverly divided it into two processes and named it;
- The first rendering we call him
初次渲染
, that is, the previous one挂载阶段
, the first time it模板
becomesDOM
Rendered to the page; - The latter part is called
patch 阶段
, the latter stage is the responsive data update triggering re-rendering, your favoritedom diff
is the little cutie at this stage (she abuses you thousands of times, you treat her like first love);
2. Process analysis + sample code
In order to adapt to the update of responsive data in the patch
stage , we added a button to the template button#btn
, the button 点击事件
's handler
will modify the data.forProp.a
attribute:
forPatchComputed
This computed property depends ondata.forProp
;<some-com />
someKey
The prop-bound data received by the component is alsodata.forProp
The test.html code is as follows:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue</title>
</head>
<body>
<div id="app">
<button id="btn" @click="goForPatch">使 forProp.a++</button>
<some-com :some-key="forProp"><div slot="namedSlot">SLOT-CONTENT</div></some-com>
<div>forPatchComputed = {{forPatchComputed}}</div>
<div class="static-div">静态节点</div>
</div>
<script src="./dist1/vue.js"></script>
<script>
const sub = {
template: `
<div style="color: red;background: #5cb85c;display: inline-block">
<slot name="namedSlot">slot-fallback-content</slot>
{{ someKey.a + ' foo' }}
</div>`,
props: {
someKey: {
type: Object,
default () {
return { a: '996-rejected!!' }
}
}
};
debugger
new Vue({
el: '#app',
data: {
forProp: {
a: 100
}
},
methods: {
goForPatch () {
this.forProp.a++
}
},
computed: {
// 计算属性
forPatchComputed () {
return this.forProp.a + ' reject 996'
}
},
components: {
someCom: sub
}
})
</script>
</body>
</html>
复制代码
So when data.forProp
is modified, there are now three watcher
to trigger an update:
forPatchComputed
Corresponding to the computed propertywatcher
, note that the computed property iswatcher
yeslazy
;some-com
corresponding渲染 watcher
;div#app
The template of this root instance corresponds to渲染 watcher
;
3. Triggering responsive updates
In a reactive system, it is an obvious observer pattern, which requires us to figure out who is 观察者
, who is the subject 观察
, and who is responsible for the two 通信
;
- The observer is the
Watcher
instance , andwatcher
it has been seen from the literal that it is the observer (watch
isn't the English translation注视、看
~); - The observed object is naturally the data itself, such as
data.forProp
this object; - Responsible for the communication between the two is an
Dep
instance ,Dep
a dependency, note that a noun is not a verb (what I want to express is被 watcher 依赖
that if依赖别人
it ); each responsive data hasdep
it,dep
which is responsible for collecting and using this datawatcher
, and then the data changes Distribute a notification to make it takewatcher
action ;
3.1 Review reactive implementation
There are three components to the responsiveness of data:
- 在初始化响应式数据的时候将
data
通过defineReactive
方法(核心是Object.defineProperty
)将data.forProp
变成getter
和setter
,当被访问时通过getter
被触发收集依赖,当被需改时触发setter
通知依赖这个数据的watcher
们更新; - 初始化响应式数据时处理
computed
的逻辑:- 2.1 给每个计算属性创建一个
watcher
,而创建Watcher
类的实例时传入的expOrFn
即要求值的函数,就是定义计算属性时声明的方法例如上面的例子:forPatchComputed () { return this.forProp.a + 'reject 996!!!' }
; - 2.2 计算属性是
lazy watcher
,即这个计算属性被访问的时候才会求值; - 2.3 什么时候求值呢?被访问到时候,
forPatchComputed
是根实例的模板对应的render 函数
执行的时候就会拿forPatchComputed
对应的值,这时求值;
- 2.1 给每个计算属性创建一个
- 修改
data.forProp.a
触发setter
,而前面getter
已经知道有三个watcher
依赖了这个forProp
,此时通知他们三个更新;
谁来负责收集依赖 watcher
们和通知 watcher
们更新呢?Dep 类
,在数据响应式初始化的时候给每个数据都创建一个 Dep
的实例,dep.denpend
收集依赖 watcher
,dep.notify
通知 watcher
更新。watcher
更新则是通过 watcher.update()
方法实现的;
function defineRective () {
const dep = new Dep();
Object.defineProperty(target, key {
get () {
if (Dep.target) dep.depend()
}
set () {
dep.notify()
}
})
}
复制代码
下图是点击 button#btn
后因修改 this.forProp.a
而触发 setter
进入到 setter
:
3.2 Dep.prototype.notify
export default class Dep {
// 把 watcher 放大响应式数据的依赖列表中
depend () {
if (Dep.target) {
// Dep.target 是响应式数据 getter 时设置的,值是 watcher 实例
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
// 遍历 dep 中存储的 watcher,执行 watcher.update()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
复制代码
下图是点击按钮后断点调试进入到 dep.notify
,你会发现 this.subs
中有三个 watcher
:
- 第一个是 forPatchComputed 这个
计算属性 watcher
,如图
2. 第二个是 div#app
这个根实例模板对应的渲染 watcher
:
- 第三个则是
<some-com />
自定义组件的渲染 watcher
:
3.3 Wather.prototype.update
export default class Watcher {
constructor (...) {}
update () {
if (this.lazy) {
// 懒执行的 watcher 走这里
// 将 dirty 置为 true,
// 就可以让 计算属性对应的 getter 被访问到的时候
// 再触发重新计算 computed 回调函数的执行结果
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// 更新时一般都在这里,
// 将 watcher 放入到 watcher 队列,
// 然后异步更新队列
queueWatcher(this)
}
}
}
复制代码
3.3.1 计算属性的 update
3.3.2 渲染 watcher 的 update
queueWatcher
是一个重点,我们为它单独开一篇;
四、总结
本篇小作文作为 patch
阶段的第一篇主要做了以下工作:
- 重新修改
test.html
加入了可以修改响应式数据的button#btn
元素,以及绑定点击事件修改data.forProp.a
; - 重新梳理了完整的响应式流程,包含依赖收集、修改数据、派发更新的过程;并且明确了
Watcher
、Dep
以及响应式数据间的依赖和被依赖关系以及三者协作过程; - 通过修改
this.forProp.a
进入到了dep.notify()
,接着看到了作为计算属性
的lazy watcher
和普通 watcher
在watcher.update()
方法中的不同处理方式:-
3.1 lazy watcher 把 this.dirty 置为 true;这就可以使得计算属性的缓存失效,当计算属性再次被访问到的时候,就会重新求值,这个过程我们在说 Watcher 的时候详细介绍过 computed 的缓存原理;
-
3.2 普通
watcher
包括渲染 watcher
和用户 watcher
都会执行queueWatcher
方法进行异步的队列更新;
-