Vue.js是一款MVVM框架喝检,上手快速簡單易用狂巢,通過響應(yīng)式在修改數(shù)據(jù)的時(shí)候更新視圖率碾。Vue.js的響應(yīng)式原理依賴于Object.defineProperty等限,尤大大在Vue.js文檔中就已經(jīng)提到過,這也是Vue.js不支持IE8 以及更低版本瀏覽器的原因释漆。Vue通過設(shè)定對象屬性的 setter/getter 方法來監(jiān)聽數(shù)據(jù)的變化悲没,通過getter進(jìn)行依賴收集,而每個(gè)setter方法就是一個(gè)觀察者男图,在數(shù)據(jù)變更的時(shí)候通知訂閱者更新視圖示姿。
將數(shù)據(jù)data變成可觀察(observable)的
那么Vue是如何將所有data下面的所有屬性變成可觀察的(observable)呢?
function observe(value, cb) {
Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}
function defineReactive (obj, key, val, cb) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
/*....依賴收集等....*
return val
},
set:newVal=> {
val = newVal;
cb();/*訂閱者收到消息的回調(diào)*/
}
})
}
class Vue {
constructor(options) {
this._data = options.data;
observe(this._data, options.render)
}
}
let app = new Vue({
el: '#app',
data: {
text: 'text',
text2: 'text2'
},
render(){
console.log("render");
}
})
為了便于理解逊笆,首先考慮一種最簡單的情況栈戳,不考慮數(shù)組等情況,代碼如上所示难裆。在initData中會調(diào)用observe這個(gè)函數(shù)將Vue的數(shù)據(jù)設(shè)置成observable的子檀。當(dāng)_data數(shù)據(jù)發(fā)生改變的時(shí)候就會觸發(fā)set,對訂閱者進(jìn)行回調(diào)(在這里是render)乃戈。
那么問題來了褂痰,需要對app._data.text操作才會觸發(fā)set。為了偷懶症虑,我們需要一種方便的方法通過app.text直接設(shè)置就能觸發(fā)set對視圖進(jìn)行重繪缩歪。那么就需要用到代理。
代理
我們可以在Vue的構(gòu)造函數(shù)constructor中為data執(zhí)行一個(gè)代理proxy谍憔。這樣我們就把data上面的屬性代理到了vm實(shí)例上匪蝙。
_proxy.call(this, options.data);/*構(gòu)造函數(shù)中*/
/*代理*/
function _proxy (data) {
const that = this;
Object.keys(data).forEach(key => {
Object.defineProperty(that, key, {
configurable: true,
enumerable: true,
get: function proxyGetter () {
return that._data[key];
},
set: function proxySetter (val) {
that._data[key] = val;
}
})
});
}
我們就可以用app.text代替app._data.text了主籍。
為什么要依賴收集
先看下面這段代碼
new Vue({
template:
`<div>
<span>text1:</span> {{text1}}
<span>text2:</span> {{text2}}
<div>`,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3'
}
});
按照之前的方法進(jìn)行綁定則會出現(xiàn)一個(gè)問題——text3在實(shí)際模板中并沒有被用到,然而當(dāng)text3的數(shù)據(jù)被修改(this.text3 = 'test')的時(shí)候逛球,同樣會觸發(fā)text3的setter導(dǎo)致重新執(zhí)行渲染千元,這顯然不正確。
先說說Dep
當(dāng)對data上的對象進(jìn)行修改值的時(shí)候會觸發(fā)它的setter颤绕,那么取值的時(shí)候自然就會觸發(fā)getter事件诅炉,所以我們只要在最開始進(jìn)行一次render,那么所有被渲染所依賴的data中的數(shù)據(jù)就會被getter收集到Dep的subs中去屋厘。在對data中的數(shù)據(jù)進(jìn)行修改的時(shí)候setter只會觸發(fā)Dep的subs的函數(shù)涕烧。
定義一個(gè)依賴收集類Dep。
class Dep {
constructor () {
this.subs = [];
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
Watcher
訂閱者汗洒,當(dāng)依賴收集的時(shí)候會addSub到sub中议纯,在修改data中數(shù)據(jù)的時(shí)候會觸發(fā)dep對象的notify,通知所有Watcher對象去修改對應(yīng)視圖溢谤。
class Watcher {
constructor (vm, expOrFn, cb, options) {
this.cb = cb;
this.vm = vm;
/*在這里將觀察者本身賦值給全局的target瞻凤,只有被target標(biāo)記過的才會進(jìn)行依賴收集*/
Dep.target = this;
/*Github:https://github.com/answershuto*/
/*觸發(fā)渲染操作進(jìn)行依賴收集*/
this.cb.call(this.vm);
}
update () {
this.cb.call(this.vm);
}
}
開始依賴收集
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data, options.render);
let watcher = new Watcher(this, );
}
}
function defineReactive (obj, key, val, cb) {
/*在閉包內(nèi)存儲一個(gè)Dep對象*/
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
if (Dep.target) {
/*Watcher對象存在全局的Dep.target中*/
dep.addSub(Dep.target);
}
},
set:newVal=> {
/*只有之前addSub中的函數(shù)才會觸發(fā)*/
dep.notify();
}
})
}
Dep.target = null;
將觀察者Watcher實(shí)例賦值給全局的Dep.target,然后觸發(fā)render操作只有被Dep.target標(biāo)記過的才會進(jìn)行依賴收集世杀。有Dep.target的對象會將Watcher的實(shí)例push到subs中阀参,在對象被修改觸發(fā)setter操作的時(shí)候dep會調(diào)用subs中的Watcher實(shí)例的update方法進(jìn)行渲染。
數(shù)據(jù)綁定原理
前面已經(jīng)講過Vue數(shù)據(jù)綁定的原理了瞻坝,現(xiàn)在從源碼來看一下數(shù)據(jù)綁定在Vue中是如何實(shí)現(xiàn)的蛛壳。
首先看一下Vue.js官網(wǎng)介紹響應(yīng)式原理的這張圖。
這張圖比較清晰地展示了整個(gè)流程所刀,首先通過一次渲染操作觸發(fā)Data的getter(這里保證只有視圖中需要被用到的data才會觸發(fā)getter)進(jìn)行依賴收集衙荐,這時(shí)候其實(shí)Watcher與data可以看成一種被綁定的狀態(tài)(實(shí)際上是data的閉包中有一個(gè)Deps訂閱者,在修改的時(shí)候會通知所有的Watcher觀察者)浮创,在data發(fā)生變化的時(shí)候會觸發(fā)它的setter忧吟,setter通知Watcher,Watcher進(jìn)行回調(diào)通知組件重新渲染的函數(shù)斩披,之后根據(jù)diff算法來決定是否發(fā)生視圖的更新溜族。
Vue在初始化組件數(shù)據(jù)時(shí),在生命周期的beforeCreate與created鉤子函數(shù)之間實(shí)現(xiàn)了對data垦沉、props煌抒、computed、methods乡话、events以及watch的處理摧玫。
initData
這里來講一下initData耳奕,可以參考源碼instance下的state.js文件绑青,
initData主要是初始化data中的數(shù)據(jù)诬像,將數(shù)據(jù)進(jìn)行Observer,監(jiān)聽數(shù)據(jù)的變化闸婴,其他的監(jiān)視原理一致坏挠,這里以data為例。
function initData (vm: Component) {
/*得到data數(shù)據(jù)*/
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
/*判斷是否是對象*/
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
/*遍歷data對象*/
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
//遍歷data中的數(shù)據(jù)
while (i--) {
/*保證data中的key不與props中的key重復(fù)邪乍,props優(yōu)先降狠,如果有沖突會產(chǎn)生warning*/
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(keys[i])) {
/*判斷是否是保留字段*/
/*這里是我們前面講過的代理,將data上面的屬性代理到了vm實(shí)例上*/
proxy(vm, `_data`, keys[i])
}
}
// observe data
/*從這里開始我們要observe了庇楞,開始對數(shù)據(jù)進(jìn)行綁定榜配,這里有尤大大的注釋asRootData,這步作為根數(shù)據(jù)吕晌,下面會進(jìn)行遞歸observe進(jìn)行對深層對象的綁定蛋褥。*/
observe(data, true /* asRootData */)
}
其實(shí)這段代碼主要做了兩件事,一是將_data上面的數(shù)據(jù)代理到vm上睛驳,另一件事通過observe將所有數(shù)據(jù)變成observable烙心。
proxy
接下來看一下proxy代理。
/*添加代理*/
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
這里比較好理解乏沸,通過proxy函數(shù)將data上面的數(shù)據(jù)代理到vm上淫茵,這樣就可以用app.text代替app._data.text了。
observe
接下來是observe蹬跃,這個(gè)函數(shù)定義在core文件下observer的index.js文件中匙瘪。
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
/*
嘗試創(chuàng)建一個(gè)Observer實(shí)例(__ob__),如果成功創(chuàng)建Observer實(shí)例則返回新的Observer實(shí)例蝶缀,如果已有Observer實(shí)例則返回現(xiàn)有的Observer實(shí)例辆苔。
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
/*判斷是否是一個(gè)對象*/
if (!isObject(value)) {
return
}
let ob: Observer | void
/*這里用__ob__這個(gè)屬性來判斷是否已經(jīng)有Observer實(shí)例,如果沒有Observer實(shí)例則會新建一個(gè)Observer實(shí)例并賦值給__ob__這個(gè)屬性扼劈,如果已有Observer實(shí)例則直接返回該Observer實(shí)例*/
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
/*這里的判斷是為了確保value是單純的對象驻啤,而不是函數(shù)或者是Regexp等情況。*/
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
/*如果是根數(shù)據(jù)則計(jì)數(shù)荐吵,后面Observer中的observe的asRootData非true*/
ob.vmCount++
}
return ob
}
Vue的響應(yīng)式數(shù)據(jù)都會有一個(gè)ob的屬性作為標(biāo)記骑冗,里面存放了該屬性的觀察器,也就是Observer的實(shí)例先煎,防止重復(fù)綁定贼涩。
Observer
接下來看一下新建的Observer。Observer的作用就是遍歷對象的所有屬性將其進(jìn)行雙向綁定薯蝎。
/**
* Observer class that are attached to each observed
* object. Once attached, the observer converts target
* object's property keys into getter/setters that
* collect dependencies and dispatches updates.
*/
export class {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
/*
將Observer實(shí)例綁定到data的__ob__屬性上面去遥倦,之前說過observe的時(shí)候會先檢測是否已經(jīng)有__ob__對象存放Observer實(shí)例了,def方法定義可以參考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16
*/
def(value, '__ob__', this)
if (Array.isArray(value)) {
/*
如果是數(shù)組,將修改后可以截獲響應(yīng)的數(shù)組方法替換掉該數(shù)組的原型中的原生方法袒哥,達(dá)到監(jiān)聽數(shù)組數(shù)據(jù)變化響應(yīng)的效果缩筛。
這里如果當(dāng)前瀏覽器支持__proto__屬性,則直接覆蓋當(dāng)前數(shù)組對象原型上的原生數(shù)組方法堡称,如果不支持該屬性瞎抛,則直接覆蓋數(shù)組對象的原型。
*/
const augment = hasProto
? protoAugment /*直接覆蓋原型的方法來修改目標(biāo)對象*/
: copyAugment /*定義(覆蓋)目標(biāo)對象或數(shù)組的某一個(gè)方法*/
augment(value, arrayMethods, arrayKeys)
/*如果是數(shù)組則需要遍歷數(shù)組的每一個(gè)成員進(jìn)行observe*/
this.observeArray(value)
} else {
/*如果是對象則直接walk進(jìn)行綁定*/
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
/*walk方法會遍歷對象的每一個(gè)屬性進(jìn)行defineReactive綁定*/
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
/*數(shù)組需要遍歷每一個(gè)成員進(jìn)行observe*/
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
Observer為數(shù)據(jù)加上響應(yīng)式屬性進(jìn)行雙向綁定却紧。如果是對象則進(jìn)行深度遍歷桐臊,為每一個(gè)子對象都綁定上方法,如果是數(shù)組則為每一個(gè)成員都綁定上方法晓殊。
如果是修改一個(gè)數(shù)組的成員断凶,該成員是一個(gè)對象,那只需要遞歸對數(shù)組的成員進(jìn)行雙向綁定即可巫俺。但這時(shí)候出現(xiàn)了一個(gè)問題:如果我們進(jìn)行pop懒浮、push等操作的時(shí)候,push進(jìn)去的對象根本沒有進(jìn)行過雙向綁定识藤,更別說pop了砚著,那么我們?nèi)绾伪O(jiān)聽數(shù)組的這些變化呢? Vue.js提供的方法是重寫push痴昧、pop稽穆、shift、unshift赶撰、splice舌镶、sort、reverse這七個(gè)數(shù)組方法豪娜。修改數(shù)組原型方法的代碼可以參考observer/array.js以及observer/index.js餐胀。
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
//.......
if (Array.isArray(value)) {
/*
如果是數(shù)組,將修改后可以截獲響應(yīng)的數(shù)組方法替換掉該數(shù)組的原型中的原生方法瘤载,達(dá)到監(jiān)聽數(shù)組數(shù)據(jù)變化響應(yīng)的效果否灾。
這里如果當(dāng)前瀏覽器支持__proto__屬性,則直接覆蓋當(dāng)前數(shù)組對象原型上的原生數(shù)組方法鸣奔,如果不支持該屬性墨技,則直接覆蓋數(shù)組對象的原型。
*/
const augment = hasProto
? protoAugment /*直接覆蓋原型的方法來修改目標(biāo)對象*/
: copyAugment /*定義(覆蓋)目標(biāo)對象或數(shù)組的某一個(gè)方法*/
augment(value, arrayMethods, arrayKeys)
/*如果是數(shù)組則需要遍歷數(shù)組的每一個(gè)成員進(jìn)行observe*/
this.observeArray(value)
} else {
/*如果是對象則直接walk進(jìn)行綁定*/
this.walk(value)
}
}
}
/**
* Augment an target Object or Array by intercepting
* the prototype chain using __proto__
*/
/*直接覆蓋原型的方法來修改目標(biāo)對象或數(shù)組*/
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
/**
* Augment an target Object or Array by defining
* hidden properties.
*/
/* istanbul ignore next */
/*定義(覆蓋)目標(biāo)對象或數(shù)組的某一個(gè)方法*/
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
import { def } from '../util/index'
/*取得原生數(shù)組的原型*/
const arrayProto = Array.prototype
/*創(chuàng)建一個(gè)新的數(shù)組對象挎狸,修改該對象上的數(shù)組的七個(gè)方法扣汪,防止污染原生數(shù)組方法*/
export const arrayMethods = Object.create(arrayProto)
/**
* Intercept mutating methods and emit events
*/
/*這里重寫了數(shù)組的這些方法,在保證不污染原生數(shù)組原型的情況下重寫數(shù)組的這些方法锨匆,截獲數(shù)組的成員發(fā)生的變化崭别,執(zhí)行原生數(shù)組操作的同時(shí)dep通知關(guān)聯(lián)的所有觀察者進(jìn)行響應(yīng)式處理*/
[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function (method) {
// cache original method
/*將數(shù)組的原生方法緩存起來,后面要調(diào)用*/
const original = arrayProto[method]
def(arrayMethods, method, function mutator () {
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
let i = arguments.length
const args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
/*調(diào)用原生的數(shù)組方法*/
const result = original.apply(this, args)
/*數(shù)組新插入的元素需要重新進(jìn)行observe才能響應(yīng)式*/
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
inserted = args
break
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
/*dep通知所有注冊的觀察者進(jìn)行響應(yīng)式處理*/
ob.dep.notify()
return result
})
})
從數(shù)組的原型新建一個(gè)Object.create(arrayProto)對象,通過修改此原型可以保證原生數(shù)組方法不被污染茅主。如果當(dāng)前瀏覽器支持proto這個(gè)屬性的話就可以直接覆蓋該屬性則使數(shù)組對象具有了重寫后的數(shù)組方法舞痰。如果沒有該屬性的瀏覽器,則必須通過遍歷def所有需要重寫的數(shù)組方法暗膜,這種方法效率較低,所以優(yōu)先使用第一種鞭衩。
在保證不污染不覆蓋數(shù)組原生方法添加監(jiān)聽学搜,主要做了兩個(gè)操作,第一是通知所有注冊的觀察者進(jìn)行響應(yīng)式處理论衍,第二是如果是添加成員的操作瑞佩,需要對新成員進(jìn)行observe。
但是修改了數(shù)組的原生方法以后我們還是沒法像原生數(shù)組一樣直接通過數(shù)組的下標(biāo)或者設(shè)置length來修改數(shù)組坯台,可以通過Vue.set以及splice方法炬丸。
Watcher
Watcher是一個(gè)觀察者對象。依賴收集以后Watcher對象會被保存在Deps中蜒蕾,數(shù)據(jù)變動的時(shí)候會由Deps通知Watcher實(shí)例稠炬,然后由Watcher實(shí)例回調(diào)cb進(jìn)行視圖的更新。
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: ISet;
newDepIds: ISet;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
/*_watchers存放訂閱者實(shí)例*/
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
/*把表達(dá)式expOrFn解析成getter*/
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
/*獲得getter的值并且重新進(jìn)行依賴收集*/
get () {
/*將自身watcher觀察者實(shí)例設(shè)置給Dep.target咪啡,用以依賴收集首启。*/
pushTarget(this)
let value
const vm = this.vm
/*
執(zhí)行了getter操作,看似執(zhí)行了渲染操作撤摸,其實(shí)是執(zhí)行了依賴收集毅桃。
在將Dep.target設(shè)置為自身觀察者實(shí)例以后,執(zhí)行g(shù)etter操作准夷。
譬如說現(xiàn)在的的data中可能有a钥飞、b、c三個(gè)數(shù)據(jù)衫嵌,getter渲染需要依賴a跟c读宙,
那么在執(zhí)行g(shù)etter的時(shí)候就會觸發(fā)a跟c兩個(gè)數(shù)據(jù)的getter函數(shù),
在getter函數(shù)中即可判斷Dep.target是否存在然后完成依賴收集楔绞,
將該觀察者對象放入閉包中的Dep的subs中去论悴。
*/
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
/*如果存在deep,則觸發(fā)每個(gè)深層對象的依賴墓律,追蹤其變化*/
if (this.deep) {
/*遞歸每一個(gè)對象或者數(shù)組膀估,觸發(fā)它們的getter,使得對象或數(shù)組的每一個(gè)成員都被依賴收集耻讽,形成一個(gè)“深(deep)”依賴關(guān)系*/
traverse(value)
}
/*將觀察者實(shí)例從target棧中取出并設(shè)置給Dep.target*/
popTarget()
this.cleanupDeps()
return value
}
/**
* Add a dependency to this directive.
*/
/*添加一個(gè)依賴關(guān)系到Deps集合中*/
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)
}
}
}
/**
* Clean up for dependency collection.
*/
/*清理依賴收集*/
cleanupDeps () {
/*移除所有觀察者對象*/
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
/*
調(diào)度者接口察纯,當(dāng)依賴發(fā)生改變的時(shí)候進(jìn)行回調(diào)。
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
/*同步則執(zhí)行run直接渲染視圖*/
this.run()
} else {
/*異步推送到觀察者隊(duì)列中,由調(diào)度者調(diào)用饼记。*/
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
/*
調(diào)度者工作接口香伴,將被調(diào)度者回調(diào)。
*/
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
/*
即便值相同具则,擁有Deep屬性的觀察者以及在對象/數(shù)組上的觀察者應(yīng)該被觸發(fā)更新即纲,因?yàn)樗鼈兊闹悼赡馨l(fā)生改變。
*/
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
/*設(shè)置新的值*/
this.value = value
/*觸發(fā)回調(diào)渲染視圖*/
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
/*獲取觀察者的值*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
/*收集該watcher的所有deps依賴*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
/*將自身從所有依賴收集訂閱列表刪除*/
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
/*從vm實(shí)例的觀察者列表中將自身移除博肋,由于該操作比較耗費(fèi)資源低斋,所以如果vm實(shí)例正在被銷毀則跳過該步驟。*/
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
Dep
來看看Dep類匪凡。其實(shí)Dep就是一個(gè)發(fā)布者膊畴,可以訂閱多個(gè)觀察者,依賴收集之后Deps中會存在一個(gè)或多個(gè)Watcher對象病游,在數(shù)據(jù)變更的時(shí)候通知所有的Watcher唇跨。
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
/*添加一個(gè)觀察者對象*/
addSub (sub: Watcher) {
this.subs.push(sub)
}
/*移除一個(gè)觀察者對象*/
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
/*依賴收集,當(dāng)存在Dep.target的時(shí)候添加觀察者對象*/
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
/*通知所有訂閱者*/
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
/*依賴收集完需要將Dep.target設(shè)為null衬衬,防止后面重復(fù)添加依賴买猖。*/
defineReactive
接下來是defineReactive。defineReactive的作用是通過Object.defineProperty為數(shù)據(jù)定義上getter\setter方法滋尉,進(jìn)行依賴收集后閉包中的Deps會存放Watcher對象政勃。觸發(fā)setter改變數(shù)據(jù)的時(shí)候會通知Deps訂閱者通知所有的Watcher觀察者對象進(jìn)行試圖的更新。
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
/*在閉包中定義一個(gè)dep對象*/
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
/*如果之前該對象已經(jīng)預(yù)設(shè)了getter以及setter函數(shù)則將其取出來兼砖,新定義的getter/setter中會將其執(zhí)行奸远,保證不會覆蓋之前已經(jīng)定義的getter/setter。*/
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
/*對象的子對象遞歸進(jìn)行observe并返回子節(jié)點(diǎn)的Observer對象*/
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/*如果原本對象擁有g(shù)etter方法則執(zhí)行*/
const value = getter ? getter.call(obj) : val
if (Dep.target) {
/*進(jìn)行依賴收集*/
dep.depend()
if (childOb) {
/*子對象進(jìn)行依賴收集讽挟,其實(shí)就是將同一個(gè)watcher觀察者實(shí)例放進(jìn)了兩個(gè)depend中懒叛,一個(gè)是正在本身閉包中的depend,另一個(gè)是子元素的depend*/
childOb.dep.depend()
}
if (Array.isArray(value)) {
/*是數(shù)組則需要對每一個(gè)成員都進(jìn)行依賴收集耽梅,如果數(shù)組的成員還是數(shù)組薛窥,則遞歸。*/
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
/*通過getter方法獲取當(dāng)前值眼姐,與新值進(jìn)行比較诅迷,一致則不需要執(zhí)行下面的操作*/
const 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 (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
/*如果原本對象擁有setter方法則執(zhí)行setter*/
setter.call(obj, newVal)
} else {
val = newVal
}
/*新的值需要重新進(jìn)行observe,保證數(shù)據(jù)響應(yīng)式*/
childOb = observe(newVal)
/*dep對象通知所有的觀察者*/
dep.notify()
}
})
}