測試例子
<!DOCTYPE html>
<html>
<head>
<title>vue test</title>
</head>
<body>
<div id="app">
<div v-for="i in message" :key="i">
{{i}}
</div>
<!-- <button-counter :title="tt"></button-counter> -->
</div>
<!-- Vue.js v2.6.11 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('button-counter', {
props: ['title'],
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">{{title}}: You clicked me {{ count }} times.</button>'
});
var app = new Vue({
el: '#app',
data: {
message: ['a', 'b', 'c', 'd'],
tt: 'on'
},
mounted() {
window.addEventListener('test', (e) => {
this.message = e.detail;
}, false);
},
})
console.log(app);
// var event = new CustomEvent('test', { 'detail': 5 }); window.dispatchEvent(event);
</script>
</body>
</html>
主要函數定義
- 716:Dep 發布者定義
- 767:Vnode 虛擬節點定義
- 922:Observer 劫持數據的函數定義
- 4419:Watcher 訂閱者定義
- 5073:function Vue() 定義
數據劫持過程
Vue.prototype._init 中,在 callHook(vm, 'beforeCreate'); 后和 callHook(vm, 'created'); 之前調用 initState(vm) 進入劫持邏輯

最后 Object.defineProperty 的代碼詳細看一下
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter(newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
掛載過程
Vue.prototype._init 中,在 callHook(vm, 'created'); 后做 vm.$mount(vm.$options.el); 的邏輯

掛載的過程中解析模版,並對模版進行 parse,optmize,generate 三步動作,編譯出來的東西是一個這樣的結構
{
ast: {
type: 1
tag: "div"
attrsList: [{…}]
attrsMap: {id: "app"}
rawAttrsMap: {id: {…}}
parent: undefined
children: (3) [{…}, {…}, {…}]
start: 0
end: 126
plain: false
attrs: [{…}]
static: false
staticRoot: false
},
render: "with(this){return _c('div',{attrs:{"id":"app"}},[(message + 1 > 1)?_c('div',[_v(_s(message + 1))]):_e(),_v(" "),_c('button',{on:{"click":function($event){message += 1}}},[_v("阿道夫")])])}",
staticRenderFns: []
}
// 所以渲染函數 vm.$options.render 就是下面着樣子的
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[(message + 1 > 1)?_c('div',[_v(_s(message + 1))]):_e(),_v(" "),_c('button',{on:{"click":function($event){message += 1}}},[_v("阿道夫")])])}
})
最終在 mountComponent 函數里完成掛載的動作,這里 callHook(vm, 'beforeMount');,

function mountComponent(
vm,
el,
hydrating // 初始化時這個值是undefined
) {
vm.$el = el;
//...
callHook(vm, 'beforeMount');
var updateComponent;
// ...
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
// 對該vm注冊一個訂閱者,Watcher 的 getter 為 updateComponent 函數,進行依賴搜集。
// Watcher 存在於每一個組件 vm 中
new Watcher(vm, updateComponent, noop, {
before: function before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false;
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm;
注意上面代碼建立 new Watcher() 訂閱者,其內容就是觸發 vm._update(vm._render(), hydrating);。new Watcher 時,自身調用 get,就徹底渲染,真實的節點也掛載到了html上。
update 過程
上文中在生命周期鈎子 beforeMount 之后,建立了訂閱者 new Watcher,執行函數 vm._update(vm._render(), hydrating);。
首先執行 _render 去獲取到最新的 Vnode 虛擬節點

再去 _update 中調用 __patch__ 比對節點並且渲染到真實的 DOM 樹中。

Vnode 比對過程
初次渲染時
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevVnode = vm._vnode;
vm._vnode = vnode;
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
// 初次渲染走這里,直接 createElm 后再 removeVnodes,創建節點后刪除原來的節點完事。
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// 后續更新走這個邏輯,去深搜比對節點並更新
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
// ...
};
初始化時,就直接覆蓋原節點


如果是update 過程
<div id="app">
<!-- <div v-if="message > 0">{{ message + 1 }}</div> -->
<div v-for="i in message">
{{i}}
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: ['a', 'b', 'c', 'd']
},
mounted() {
window.addEventListener('test', (e) => {
this.message = e.detail;
}, false);
}
})
// 接着控制台里輸入
// var event = new CustomEvent('test', { 'detail': ['a', 'c', 'e', 'f', 'b', 'd'] }); window.dispatchEvent(event);
// 能把 message 改為這個數組
</script>

探討key的作用,首先這是 sameVnode 函數,用於比對兩個節點是否是同一個
function sameVnode(a, b) {
// key,tag,isComment相同,並且data都不為空,並且節點類型不是input
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}

子組件渲染過程
若是子元素自身屬性變了,那么直接調用子元素自身訂閱者的更新函數 vm._update(vm._render(), hydrating);
若是父組件變動了的子組件的 props 屬性,子 props上也存在發布者
_props:
title: (...)
get title: ƒ reactiveGetter()
set title: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
__proto__: Object

渲染過程
追問:Dep.target 為什么會指向這個 Watcher 對象?
在 callHook(vm, 'beforeMount') 后,進入 mount 階段,此時初始化 Watcher
function noop (a, b, c) {}
// lifecycle.js
let updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
在初始化 Watcher 的函數里調用 this.get
var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm;
//...
this.cb = cb;
//...
this.expression = expOrFn.toString();
//...
this.getter = expOrFn;
//...
this.value = this.lazy ? undefined : this.get();
};
Watcher.prototype.get,注意 pushTarget,此時就和 Dep 發布者產生了聯系,Dep 的 target 被設置為了這個 wacher,並且在每次監測對象被 get 時,就會往自身的 Dep 里推入這個 wacher。
// dep.js
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
// watcher.js
Watcher.prototype.get = function get() {
pushTarget(this);
var value;
var vm = this.vm;
//...
value = this.getter.call(vm, vm);
//...
popTarget();
this.cleanupDeps();
//...
return value;
};
上文 Watcher.prototype.get 中還要注意 this.getter.call(vm, vm), 執行的其實是上文表達式里的 vm._update(vm._render(), hydrating)。自然也就調用了
調用到了 vm._render() 方法,要返回一個VNode,調試發現 vm.$options.render 其實就是
Vue.prototype._render = function () {
// ...
var vm = this;
var ref = vm.$options;
var render = ref.render;
vnode = render.call(vm._renderProxy, vm.$createElement);
// ...
return vnode
}
// 而render方法其實就是用於輸出一個虛擬節點
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[(message + 1 > 1)?_c('div',[_v(_s(message + 1))]):_e(),_v(" "),_c('button',{on:{"click":function($event){message += 1}}},[_v("阿道夫")])])}
})
然后結果交給 vm._update
Vue.prototype._update = function(vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
// ...
vm._vnode = vnode;
// ...
vm.$el = vm.__patch__(prevVnode, vnode);
// ...
};
結論是 mount 階段 初始化 Watcher,然后在 wathcer初始化后調用 get,get里 pushTarget(this),並且執行自身的getter也就是表達式,表達式的內容就是 vm._update(vm._render(), hydrating) 故而就開始執行 render函數,render 函數就是就是輸出虛擬節點的。
