圖解Vue?響應(yīng)式流程及原理

閱讀本文能夠幫助你什么?
- 在學(xué)習(xí)vue源碼的時(shí)候發(fā)現(xiàn)組件化過(guò)程很繞?
- 在響應(yīng)式過(guò)程中
Observer、Dep、Watcher三大對(duì)象傻傻分不清? - 搞不清楚對(duì)象、數(shù)組依賴收集、派發(fā)更新的流程?
dep、watcher互調(diào)造成混亂? - 學(xué)了一遍好像懂了又好像不全懂的感覺?而且缺乏大體流程概念?
- 或者像我一樣,有段時(shí)間沒看vue源碼好像有點(diǎn)遺忘?但是想快速回顧卻無(wú)從下手?
本文主要分為1. 組件化;2. 響應(yīng)式原理;3. 彩蛋(computed和watch)進(jìn)行講解。本文調(diào)試源碼的vue版本是v2.6.14。整篇將采用源碼講解 + 流程圖的方式詳細(xì)還原整個(gè)Vue響應(yīng)式原理的全過(guò)程。你可以了解到Dep.target、pushTarget、popTarget;響應(yīng)式中的三大Watcher;Dep、Wathcer多對(duì)多的,互相收集的關(guān)系。
這篇是進(jìn)階的 Vue 響應(yīng)式源碼解析,文章比較長(zhǎng),內(nèi)容比較深,大家可以先mark后看??床欢牟灰獜?qiáng)行看,可以先看看其他作者的偏簡(jiǎn)單一點(diǎn)的源碼解析文章,然后好好消化。等過(guò)段時(shí)間再回來(lái)看這篇,相信你由淺入深后再看本文,一定會(huì)有意想不到的收獲~
一、組件化流程
在講解整個(gè)響應(yīng)式原理之前,先介紹一下Vue中另一個(gè)比較核心的概念——組件化,個(gè)人認(rèn)為這也是學(xué)習(xí)響應(yīng)式的前置核心。搞懂組件化,響應(yīng)式學(xué)習(xí)如虎添翼!
1. 整個(gè)new Vue階段做了什么?
- 執(zhí)行init操作。包括且不限制
initLifecycle、initState等 - 執(zhí)行mount。進(jìn)行元素掛載
- compiler步驟在runtime-only版本中沒有。
- compiler步驟對(duì)template屬性進(jìn)行編譯,生成render函數(shù)。
- 一般在項(xiàng)目中是在
.vue文件開發(fā),通過(guò)vue-loader處理生成render函數(shù)。
執(zhí)行render。生成vnode
<div id="app">{{ message }}</div>
render (h) {
return h('div', {
attrs: {
id: 'app'
},
}, this.message)
}
- render例子,如下
- 對(duì)應(yīng)手寫的render函數(shù)
- patch。新舊vnode經(jīng)過(guò)diff后,渲染到真實(shí)dom上

2. 普通dom元素如何渲染到頁(yè)面?
- 執(zhí)行
$mount。- 實(shí)際執(zhí)行
mountComponent - 這里會(huì)實(shí)例化一個(gè)Watcher
- Watcher中會(huì)執(zhí)行
get方法,觸發(fā)updateComponent
- 實(shí)際執(zhí)行
- 執(zhí)行
updateComponent。執(zhí)行vm._update(vm._render(), hydrating) - 執(zhí)行
vm.render()。- render其實(shí)調(diào)用
createElment(h函數(shù)) - 根據(jù)tag的不同,生成組件、原生VNode并返回
- render其實(shí)調(diào)用
- 執(zhí)行
vm.update()。createElm()到createChildren()遞歸調(diào)用 - 將VNode轉(zhuǎn)化為真實(shí)的dom,并且最終渲染到頁(yè)面

3. 組件如何渲染到頁(yè)面?
這里以如下代碼案例講解更加清晰~沒錯(cuò),就是這么熟悉!就是一個(gè)初始化的Vue項(xiàng)目
// mian.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App),
}).$mount('#app')
// App.vue
<template>
<div id="app">
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
msg: 'hello world'
}
}
}
</script>
主要講解組件跟普通元素的不同之處,主要有2點(diǎn):
如何生成VNode——創(chuàng)建組件VNodecreateComponent

如何patch——組件new Vue到patch流程createComponent
$vnode:占位符vnode。最終渲染vnode掛載的地方。所有的組件通過(guò)遞歸調(diào)用createComponent直至不再存在組件VNode,最終都會(huì)轉(zhuǎn)化成普通的dom。
{
tag: 'vue-component-1-App',
componentInstance: {組件實(shí)例},
componentOptions: {Ctor, ..., }
}
_vnode:渲染vnode。
{
tag: 'div',
{
"attrs": {
"id": "app"
}
},
// 對(duì)應(yīng)占位符vnode: $vnode
parent: {
tag: 'vue-component-1-App',
componentInstance: {組件實(shí)例},
componentOptions: {Ctor, ..., }
},
children: [
// 對(duì)應(yīng)p標(biāo)簽
{
tag: 'p',
// 對(duì)應(yīng)p標(biāo)簽內(nèi)的文本節(jié)點(diǎn){{ msg }}
children: [{ text: 'hello world' }]
}, {
// 如果還有組件VNode其實(shí)也是一樣的
tag: 'vue-component-2-xxx'
}
]
}
(注意:這一步對(duì)應(yīng)上圖render流程的紫色塊的展開?。。?
區(qū)分普通元素VNode
- 普通VNode:tag是html的保留標(biāo)簽,如
tag: 'div' - 組件VNode:tag是以
vue-component開頭,如tag: 'vue-component-1-App'
(注意:這一步對(duì)應(yīng)上圖patch流程的紫色塊的展開?。?!)

4. Vue組件化簡(jiǎn)化流程
相信你看完細(xì)粒度的Vue組件化過(guò)程可能已經(jīng)暈頭轉(zhuǎn)向了,這里會(huì)用一個(gè)簡(jiǎn)化版的流程圖進(jìn)行回顧,加深理解

二、響應(yīng)式流程
案例代碼
// 案例
export default {
name: 'App',
data () {
return {
msg: 'hello world',
arr = [1, 2, 3]
}
}
}
1. 依賴收集
這里會(huì)從Observer、Dep、Watcher三個(gè)對(duì)象進(jìn)行講解,分 object、array 兩種依賴收集方式。
- 一定要注意!數(shù)組 的依賴收集 跟 對(duì)象的屬性 是不一樣的。對(duì)象屬性經(jīng)過(guò)深度遍歷后,最終就是以一個(gè)基本類型的數(shù)據(jù)為單位收集依賴,但是數(shù)組仍然是一個(gè)引用類型。
- 如果這里不懂,先想一個(gè)問(wèn)題: 我們用
this.msg = 'xxx'能觸發(fā)setter派發(fā)更新,但是我們修改數(shù)組并不是用this.arr = xxx,而是用this.arr.push(xxx)等修改數(shù)組的方法。很顯然,這時(shí)候并不是通過(guò)觸發(fā)arr的setter去派發(fā)更新的。那是怎么做的呢?先帶著這個(gè)問(wèn)題繼續(xù)往下看吧!
三個(gè)核心對(duì)象:Observer(藍(lán))、Dep(綠)、Watcher(紫)

依賴收集準(zhǔn)備階段——Observer、Dep的實(shí)例化
// 以下是initData調(diào)用的方法講解,排列遵循調(diào)用順序
function observe (value, asRootData) {
if (!isObject(value)) return // 非對(duì)象則不處理
// 實(shí)例化Observer對(duì)象
var ob;
ob = new Observer(value);
return ob
}
function Observer (value) {
this.value = value; // 保存當(dāng)前的data
this.dep = new Dep(); // 實(shí)例化dep,數(shù)組進(jìn)行依賴收集的dep(對(duì)應(yīng)案例中的arr)
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
// 這里會(huì)改寫數(shù)組原型。__proto__指向重寫數(shù)組方法的對(duì)象
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
}
// 遍歷數(shù)組元素,執(zhí)行對(duì)每一項(xiàng)調(diào)用observe,也就是說(shuō)數(shù)組中有對(duì)象會(huì)轉(zhuǎn)成響應(yīng)式對(duì)象
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
}
// 遍歷對(duì)象的全部屬性,調(diào)用defineReactive
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
// 如案例代碼,這里的 keys = ['msg', 'arr']
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}
function defineReactive (obj, key, val) {
// 產(chǎn)生一個(gè)閉包dep
var dep = new Dep();
// 如果val是object類型,遞歸調(diào)用observe,案例代碼中的arr會(huì)走這個(gè)邏輯
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
get: function reactiveGetter () {
// 求value的值
var value = getter ? getter.call(obj) : val;
if (Dep.target) { // Dep.target就是當(dāng)前的Watcher
// 這里是閉包dep
dep.depend();
if (childOb) {
// 案例代碼中arr會(huì)走到這個(gè)邏輯
childOb.dep.depend(); // 這里是Observer里的dep,數(shù)組arr在此依賴收集
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 下文派發(fā)更新里進(jìn)行講解
}
});
}
注意 對(duì)象 、 數(shù)組 的不同處理方式。這里以 核心代碼 + 圖 進(jìn)行講解
接下來(lái)核心分析 defineReactive 做了什么。注意 childOb ,這是數(shù)組進(jìn)行依賴收集的地方(也就是為什么我們 this.arr.push(4) 能找到 Watcher 進(jìn)行派發(fā)更新)

依賴收集觸發(fā)階段——Wather實(shí)例化、訪問(wèn)數(shù)據(jù)、觸發(fā)依賴收集
// new Wathcer核心
function Watcher (vm, expOrFn, cb, options, isRenderWatcher) {
if (typeof expOrFn === 'function') {
// 渲染watcher中,這里傳入的expOrFn是updateComponent = vm.update(vm.render())
// this.getter等價(jià)于vm.update(vm.render())
this.getter = expOrFn;
} else {
...
}
// 這里進(jìn)行判斷,lazy為true時(shí)(計(jì)算屬性)則什么都不執(zhí)行,否則執(zhí)行g(shù)et
this.value = this.lazy
? undefined
: this.get(); // 本次為渲染W(wǎng)atcher,執(zhí)行g(shù)et,繼續(xù)往下看~
}
// Watcher的get方法
Watcher.prototype.get = function get () {
// 這里很關(guān)鍵,pushTarget就是把當(dāng)前的Wather賦值給“Dep.target”
pushTarget(this);
var value;
var vm = this.vm;
try {
// 1. 這里調(diào)用getter,也就是執(zhí)行vm.update(vm.render())
// 2. 執(zhí)行vm.render函數(shù)就會(huì)訪問(wèn)到響應(yīng)式數(shù)據(jù),觸發(fā)get進(jìn)行依賴收集
// 3. 此時(shí)的Dep.target為當(dāng)前的渲染W(wǎng)atcher,數(shù)據(jù)就可以理所應(yīng)當(dāng)?shù)陌裌atcher加入自己的subs中
// 4. 所以此時(shí),Watcher就能監(jiān)測(cè)到數(shù)據(jù)變化,實(shí)現(xiàn)響應(yīng)式
value = this.getter.call(vm, vm);
} catch (e) {
...
} finally {
popTarget();
/*
* cleanupDeps是個(gè)優(yōu)化操作,會(huì)移除Watcher對(duì)本次render沒被使用的數(shù)據(jù)的觀測(cè)
* 效果:處于v-if為false中的響應(yīng)式數(shù)據(jù)改變不會(huì)觸發(fā)Watcher的update
* 感興趣的可以自己去debugger調(diào)試,這里就不展開了
*/
this.cleanupDeps();
}
return value
}
Dep.target相關(guān)講解
- targetStack:棧結(jié)構(gòu),用來(lái)保存
Watcher - pushTarget:往
targetStack中push當(dāng)前的Watcher(排在前一個(gè)Watcher的后面),并把Dep.target賦值給當(dāng)前Watcher - popTarget:先把
targetStack最后一個(gè)元素彈出(.pop),再把Dep.target賦值給最后一個(gè)Watcher(也就是還原了前一個(gè)Watcher) - 通過(guò)上述實(shí)現(xiàn),vue保證了
全局唯一的Watcher,準(zhǔn)確賦值在Dep.target中

細(xì)節(jié)太多繞暈了?來(lái)個(gè)整體流程,從宏觀角度再過(guò)一遍(computed部分可看完彩蛋后再回來(lái)重溫一下)

2. 派發(fā)更新
派發(fā)更新區(qū)分對(duì)象屬性、數(shù)組方法進(jìn)行講解
如果想要深入了解組件的異步更新,戳這里,了解Vue組件異步更新之nextTick。本文只針對(duì)派發(fā)更新流程,不會(huì)對(duì)異步更新DOM進(jìn)行展開講解~
這里可以先想一下,以下操作會(huì)發(fā)生什么?
this.msg = 'new val'
this.arr.push(4)
是的,毫無(wú)疑問(wèn)都會(huì)先觸發(fā)他們之中的get,那再觸發(fā)什么呢?我們接下來(lái)看
對(duì)象屬性修改觸發(fā)set,派發(fā)更新。this.msg = 'new val'
...
Object.defineProperty (obj, key, {
get () {...},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
// 判斷新值相比舊值是否已經(jīng)改變
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// 如果新值是引用類型,則將其轉(zhuǎn)化為響應(yīng)式
childOb = !shallow && observe(newVal);
// 這里通知dep的所有watcher進(jìn)行更新
dep.notify();
}
}
...

數(shù)組調(diào)用方法。this.arr.push(4)
// 數(shù)組方法改寫是在 Observer 方法中
function Observer () {
if (hasProto) {
// 用案例講解,也就是this.arr.__proto__ = arrayMethods
protoAugment(value, arrayMethods);
}
}
// 以下是數(shù)組方法重寫的實(shí)現(xiàn)
var arrayProto = Array.prototype; // 保存真實(shí)數(shù)組的原型
var arrayMethods = Object.create(arrayProto); // 以真數(shù)組為原型創(chuàng)建對(duì)象
// 可以看成:arrayMethods.__proto__ = Array.prototype
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
// 一個(gè)裝飾器模型,重寫7個(gè)數(shù)組方法
methodsToPatch.forEach(function (method) {
// 保存原生的數(shù)組方法
var original = arrayProto[method];
// 劫持arrayMethods對(duì)象中的數(shù)組方法
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args);
var ob = this.__ob__; // 當(dāng)我門調(diào)用this.arr.push(),這里就能到數(shù)組對(duì)象的ob實(shí)例
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// 由于數(shù)組對(duì)象在new Observer中實(shí)例化了一個(gè)dep,并通過(guò)childOb邏輯收集了依賴,這里就能在ob實(shí)例中拿到dep屬性
ob.dep.notify();
return result
});
})
- 這里可以聯(lián)合數(shù)組的依賴收集再看一遍,你就恍然大悟了。為什么 對(duì)象的屬性 、數(shù)組 的依賴收集方式不一樣

整個(gè)new Vue階段、到依賴收集、派發(fā)更新的全部流程就到這里結(jié)束了。可以縱觀流程圖看出,Vue應(yīng)用就是一個(gè)個(gè)Vue組件組成的,雖然整個(gè)組件化、響應(yīng)式流程很多,但核心的路徑一旦走通,你就會(huì)恍然大悟。
三、彩蛋篇
1. computed依賴收集
- 案例代碼
<template>
<div id="app">
{{ name }}
</div>
</template>
<script>
export default {
name: 'App',
computed: {
name () {
return this.firstName + this.secondName
}
},
data () {
return {
firstName: 'jing',
secondName: 'boran'
}
}
}
</script>
- 我們先看流程圖。圖有點(diǎn)大~大家可以放大看看,每個(gè)核心步驟都附有文字說(shuō)明

根據(jù)案例概括一下,加深理解
// 訪問(wèn)computed時(shí)觸發(fā)get的核心代碼
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) { // dirty第一次為true
watcher.evaluate(); // 這里是對(duì)computed進(jìn)行求值,對(duì)computed watcher執(zhí)行依賴收集
}
if (Dep.target) {
watcher.depend(); // 這里是對(duì)渲染W(wǎng)atcher進(jìn)行依賴收集
}
return watcher.value
}
}
}
computed中的name其實(shí)就是一個(gè)computed Watcher,這個(gè)Watcher在init階段生成
當(dāng)App組件render的階段,render函數(shù)會(huì)訪問(wèn)到模版中的{{ name }},則會(huì)觸發(fā)computed的求值,也就是執(zhí)行上面代碼computedGetter()。執(zhí)行watcher.evaluate()。也就是執(zhí)行wathcer.get。上文依賴收集的第3點(diǎn):依賴收集觸發(fā)階段有對(duì)get方法進(jìn)行講解,忘了的可以上去回顧一下執(zhí)行watcher.depend()
Watcher.prototype.depend = function depend () {
var i = this.deps.length;
while (i--) {
// 也就是調(diào)用Dep.depend => Watcher.addDep => dep.addSub
this.deps[i].depend();
}
}
// this.firstName和this.secondName的dep.subs dep.subs: [name的computed watcher, App組件的渲染W(wǎng)atcher]
代碼中判斷watcher.dirty標(biāo)志是什么?有什么用?
只有computed的值發(fā)生改變(也就是其依賴的數(shù)據(jù)改變),watcher.dirty才會(huì)被設(shè)為true
只有watcher.dirty為true才會(huì)對(duì)computed進(jìn)行 求值 或 重新求值
總結(jié):也就是組件每次render,如果computed的值沒改變,直接返回value值(是不需要重新計(jì)算的),這也是computed的一個(gè)特點(diǎn)
- 首先
pushTarget把Dep.target從App組件的渲染W(wǎng)atcher改為name的computed Watcher - 其次執(zhí)行cb:
function() { return this.firstName + this.secondName } - 執(zhí)行cb的過(guò)程中,必然會(huì)訪問(wèn)到
firstName、secondName,這時(shí)候就是我們熟悉的依賴收集階段了。firstName、secondName都會(huì)把name這個(gè)computed watcher收集到自己的dep.subs[]中 - 最后
popTarget把name的computed Watcher彈出棧,并恢復(fù)Dep.target為當(dāng)前App組件的渲染W(wǎng)atcher - 遍歷computed watcher的deps。其實(shí)就是firstName、secondName實(shí)例的Dep
dep.depend也就是調(diào)用watcher.addDep(把Dep收集進(jìn)watcher.deps中),再由watcher.appDep調(diào)用dep.addSub(把Watcher收集進(jìn)dep.subs中)- 這樣一來(lái),就完成了firstName、secondName對(duì)App組件的渲染watcher進(jìn)行收集
- 結(jié)果如下。響應(yīng)式數(shù)據(jù)中會(huì)存在兩個(gè)Watcher
- 至于為什么響應(yīng)式數(shù)據(jù)要收集2個(gè)watcher?下文computed派發(fā)更新會(huì)講解
講到這里,我以自己的理解講解下文章開頭引言的問(wèn)題:為什么Watcher、Dep多對(duì)多且相互收集? 這可能也是大家閱讀Vue源碼中一直存在的一個(gè)疑惑(包括我自己剛開始讀也是這樣)
對(duì)的,當(dāng)然是為了computed中的響應(yīng)式數(shù)據(jù)收集渲染W(wǎng)atcher啦!??!
還有!??! 還記得前文中依賴收集的第3點(diǎn)——依賴收集觸發(fā)階段的代碼講解中我寫了很多注釋的cleanupDeps嗎?
// 此時(shí)flag為true,也就是說(shuō)msg2沒有渲染在頁(yè)面中
<div v-if="flag">{{ msg1 }}</div>
<div v-else>{{ msg2 }}</div>
<button @click=() => { this.msg2 = 'change' }>changeMsg2</button>
function cleanupDeps () {
var i = this.deps.length;
while (i--) {
// 這里對(duì)watcher所觀測(cè)的響應(yīng)式數(shù)據(jù)的dep進(jìn)行遍歷
// 對(duì)的,這樣一來(lái),是不是watcher中的deps就發(fā)揮作用了呢?
var dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
// 這里對(duì)當(dāng)前渲染中沒有訪問(wèn)到的響應(yīng)式數(shù)據(jù)進(jìn)行依賴移除
dep.removeSub(this);
}
}
...
}
cleanupDeps的作用就是清除掉當(dāng)前沒有使用到的響應(yīng)式數(shù)據(jù)。怎么清除?我們往下看- 首先看個(gè)案例回答個(gè)問(wèn)題,代碼如下。當(dāng)flag為true時(shí),
msg2并沒有渲染在頁(yè)面中,那么此時(shí)我們點(diǎn)擊按鈕修改msg2的值會(huì)不會(huì)、或者應(yīng)不應(yīng)該觸發(fā)這個(gè)組件的重新渲染呢? - 答案肯定是不會(huì)、不應(yīng)該。所以:
cleanupDeps就是為此而存在的 - 那
cleanupDeps是怎么工作的呢?接著看下面代碼 - 到此,你是否已經(jīng)懂得了watcher中為什么要收集自己觀測(cè)的響應(yīng)式數(shù)據(jù)對(duì)應(yīng)的dep呢?
2. computed派發(fā)更新
派發(fā)相對(duì)來(lái)說(shuō)比較簡(jiǎn)單了~跟響應(yīng)式的派發(fā)更新基本一致,繼續(xù)以案例來(lái)講解吧!
當(dāng)我們修改firstName會(huì)發(fā)生什么?this.firstName = 'change'
首先觸發(fā)firstName的set,最終會(huì)調(diào)用dep.notify()。firstName的dep.subs中有2個(gè)watcher,分別執(zhí)行對(duì)應(yīng)watcher的notify
Watcher.prototype.update = function update () {
if (this.lazy) {
this.dirty = true; // computed會(huì)走到這里,然后就結(jié)束了
} else if (this.sync) {
this.run();
} else {
queueWatcher(this); // 渲染watcher會(huì)走到這里
}
}
computed watcher:將dirty屬性置為true。
渲染watcher會(huì)執(zhí)行派發(fā)更新流程(如本文響應(yīng)式流程——2.派發(fā)更新一致)
nextTick階段執(zhí)行flushSchedulerQueue,則會(huì)執(zhí)行watcher.run()
watcher.run會(huì)執(zhí)行watcher.get方法,也就是重新執(zhí)行render、update的流程
執(zhí)行render又會(huì)訪問(wèn)到name的computed,從而又會(huì)執(zhí)行computedGetter
此時(shí)的watcher.dirty在本步驟3已經(jīng)置為true,又會(huì)執(zhí)行watcher.evaluate()進(jìn)行computed的求值,執(zhí)行watcher.depend()......后續(xù)的流程就是派發(fā)更新的流程了~
3. user Watcher依賴收集
user Watcher的依賴收集相比computed會(huì)簡(jiǎn)單一點(diǎn),這里不會(huì)贅述太多,只說(shuō)核心區(qū)別,還有watch的常用配置immediate、deep、sync
user Watcher在init階段會(huì)執(zhí)行一次watcher.get(),在這里會(huì)訪問(wèn)我們watch的響應(yīng)式數(shù)據(jù),從而進(jìn)行依賴收集?;仡櫹耤omputed,computed在這個(gè)階段什么也沒做。
// 沒錯(cuò),又是這段熟悉的代碼 this.value = this.lazy ? undefined : this.get(); // user Watcher和渲染 Watcher都在new Watcher階段執(zhí)行g(shù)et()
如果userWatcher設(shè)置的immediate: true,則會(huì)在new Watcher后主動(dòng)觸發(fā)一次cb的執(zhí)行
Vue.prototype.$watch = function (expOrFn, cb, options) {
...
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
// immediate則會(huì)執(zhí)行我們傳入的callback
try {
cb.call(vm, watcher.value);
} catch (error) {
}
}
return function unwatchFn () {
watcher.teardown();
}
};
deep邏輯很簡(jiǎn)單,大概講下:深度遍歷這個(gè)對(duì)象,訪問(wèn)到該對(duì)象的所有屬性,以此來(lái)觸發(fā)所有屬性的getter。這樣,所有屬性都會(huì)把當(dāng)前的user Watcher收集到自己的dep中。因此,深層的屬性值修改(觸發(fā)set派發(fā)更新能通知到user Watcher),watch自然就能監(jiān)測(cè)到數(shù)據(jù)改變~感興趣的同學(xué)可以自己去看看源碼中traverse的實(shí)現(xiàn)。
sync。當(dāng)前tick執(zhí)行,以此能先于渲染W(wǎng)athcer執(zhí)行。不設(shè)置同步的watcher都會(huì)放到nextTick中執(zhí)行。
Watcher.prototype.update = function update () {
if (this.lazy) {
this.dirty = true; // 計(jì)算屬性
} else if (this.sync) {
this.run(); // 同步的user Wathcer
} else {
queueWatcher(this); // 普通user Watcher和渲染W(wǎng)atcher
}
}

總體來(lái)說(shuō),Vue的源碼其實(shí)是比較好上手的,整體代碼流程非常的清晰。但是想要深入某一塊邏輯,最好結(jié)合流程圖加debugger方式親自上手實(shí)踐。畢竟真正搞懂一門框架的源碼并非易事,我也是通過(guò)不斷debugger調(diào)試,一遍遍走核心流程,才能較好的學(xué)習(xí)理解vue的實(shí)現(xiàn)原理~
寫在最后,這篇文章也算是自己的一個(gè)知識(shí)沉淀吧,畢竟很早之前就學(xué)習(xí)過(guò)Vue的源碼了,但是也一直沒做筆記?,F(xiàn)在回顧一下,發(fā)現(xiàn)很多都有點(diǎn)忘了,但是缺乏一個(gè)快速記憶、回顧的筆記。如果要直接硬磕源碼重新記憶,還是比較費(fèi)時(shí)費(fèi)力的~作為知識(shí)分享,希望可以幫助到想學(xué)習(xí)源碼,想要進(jìn)階的你,大家彼此共勉,一同進(jìn)步!
以上就是圖解Vue 響應(yīng)式原理的詳細(xì)內(nèi)容,更多關(guān)于Vue 響應(yīng)式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Vue3和element-plus實(shí)現(xiàn)登錄功能(最終完整版)
這篇文章主要介紹了基于Vue3和element-plus實(shí)現(xiàn)一個(gè)完整的登錄功能,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
使用Vue3和Echarts?5繪制帶有立體感流線中國(guó)地圖(推薦收藏!)
最近接到一個(gè)需求是做一個(gè)中國(guó)地圖,下面這篇文章主要給大家介紹了關(guān)于如何使用Vue3和Echarts?5繪制帶有立體感流線中國(guó)地圖的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
vue在使用element組件出現(xiàn)<el-input>標(biāo)簽無(wú)法輸入的問(wèn)題
這篇文章主要介紹了vue在使用element組件出現(xiàn)<el-input>標(biāo)簽無(wú)法輸入的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04

