專案中vue比較多,大概知道實現,最近翻了一下雙向繫結的程式碼,這裡寫一下閱讀後的理解。
專案目錄
拉到vue的程式碼之後,首先來看一下專案目錄,因為本文講的是雙向繫結,所以這裡主要看雙向繫結這塊的程式碼。
入口
從入口開始:src/core/index.js
index.js
比較簡單,第一句就引用了Vue
進來,看下Vue
是啥
import Vue from `./instance/index`
複製程式碼
Vue建構函式
來到 src/core/instance/index.js
一進來,嗯,沒錯,定義了Vue
建構函式,然後呼叫了好幾個方法,這裡我們看第一個initMixin
import { initMixin } from `./init`
function Vue (options) {
if (process.env.NODE_ENV !== `production` &&
!(this instanceof Vue)
) {
warn(`Vue is a constructor and should be called with the `new` keyword`)
}
this._init(options)
}
initMixin(Vue)
...
export default Vue
複製程式碼
初始化
來到 src/core/instance/init.js
這個給Vue
建構函式定義了_init
方法,每次new Vue
初始化例項時都會呼叫該方法。
然後看到_init
中間的程式碼,呼叫了好多初始化的函式,這裡我們只關注data
的走向,所以這裡看一下initState
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
...
initState(vm)
...
}
}
複製程式碼
來到 src/core/instance/state.js
initState
呼叫了initData
,initData
呼叫了observe
,然後我們再往下找observe
import { observe } from `../observer/index`
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
...
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
...
}
function initData (vm: Component) {
let data = vm.$options.data
...
observe(data, true /* asRootData */)
}
複製程式碼
Observer(觀察者)
來到 src/core/observer/index.js
這裡,例項化Observer
物件
首先,new Observer
例項化一個物件,引數為data
export function observe (value: any, asRootData: ?boolean): Observer | void {
let ob: Observer | void
ob = new Observer(value)
return ob
}
複製程式碼
然後我們來看下Observer
建構函式裡面寫了什麼,這裡給每個物件加了value
和例項化了一個Dep
,然後data
為陣列的話則遞迴,否則執行walk
。
walk
這裡是對物件遍歷執行defineReactive
export class Observer {
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()
if (Array.isArray(value)) {
...
this.observeArray(value)
} else {
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
複製程式碼
然後,我們來看defineReactive
做了什麼,嗯,這裡就是Observer
的核心。
用Object.defineProperty
對物件進行配置,重寫get&set
get
:對原來get
執行,然後執行dep.depend
新增一個訂閱者
set
:對原來set
執行,然後執行dep.notify
通知訂閱者
Dep
是幹啥的呢?Dep
其實是一個訂閱者的管理中心,管理著所有的訂閱者
import Dep from `./dep`
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
const getter = property && property.get
const setter = property && property.set
let childOb = !shallow && observe(val)
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) {
const value = getter ? getter.call(obj) : val
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
dep.notify()
}
})
}
複製程式碼
Dep(訂閱者管理中心)
那麼,到這裡了,疑問的是什麼時候會觸發Observer
的get
方法來新增一個訂閱者呢?
這裡的條件是有Dep.target
的時候,那麼我們找一下程式碼中哪裡會對Dep.target
賦值,找到了Dep
定義的地方
來到 src/core/observer/dep.js
pushTarget
就對Dep.target
賦值了,然後來看一下到底是哪裡呼叫了pushTarget
export default class Dep {
...
}
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
複製程式碼
然後,找到了Watcher
呼叫了pushTarget
,那麼我們來看一下Watcher
的實現
來到 src/core/observer/watcher.js
這裡可以看到每次new Watcher
時,就會呼叫get
方法
這裡執行兩步操作
第一:呼叫pushTarget
第二:呼叫getter
方法觸發Observer
的get
方法將自己加入訂閱者
export default class Watcher {
vm: Component;
constructor (
vm: Component
) {
this.value = this.lazy
? undefined
: this.get()
}
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
}
return value
}
}
複製程式碼
接著,Dep.target
有了之後,接下來就要看一下dep.depend()
這個方法,所以還是要到Dep
來看下這裡的實現。
來到 src/core/observer/dep.js
這裡呼叫了Dep.target.addDep
的方法,引數是Dep
的例項物件,那麼我們看下addDep
export default class Dep {
addSub (sub: Watcher) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
}
複製程式碼
Watcher(訂閱者)
又來到 src/core/observer/watcher.js
到這,addDep
其實又呼叫時Dep
例項的addSub
方法,引數也是把Watcher
例項傳遞過去
然後,我們看上面的addSub
,這裡就是把Watcher
例項push
到dep
的subs
陣列中儲存起來
到這裡,就完成了把Watcher
加入到Dep
這裡訂閱器管理中心這裡,後面的管理就由Dep
來統一管理
export default class Watcher {
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)
}
}
}
}
複製程式碼
走完了新增訂閱器,接著再來看下Observer
的set
方法,這裡呼叫了dep.notify
,我們來看一下這個方法
來到 src/core/observer/dep.js
這裡,就是對subs
中的所有Watcher
,呼叫其update
方法來更新資料
export default class Dep {
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
複製程式碼
這裡,我們就來看看Watcher
是怎麼更新的
又來到 src/core/observer/watcher.js
update
呼叫的是run
方法,run
方法這裡先用get
拿到新的值,然後把新&舊值做為引數給cb
呼叫
export default class Watcher {
update () {
this.run()
}
run () {
if (this.active) {
const value = this.get()
const oldValue = this.value
this.value = value
this.cb.call(this.vm, value, oldValue)
}
}
}
複製程式碼
這裡的cb
其實是例項化的時候傳進來的,這裡我們看一下什麼時候會例項化Watcher
回到一開始的initState:src/core/instance/state.js
initState
的最後還呼叫了initWatch
,然後再createWatcher
,最後$watch
的時候就例項化了Watcher
物件,這裡就把cb
傳到了Watcher
例項中,當監聽的資料改變的時候就會觸發cb
函式
import Watcher from `../observer/watcher`
export function initState (vm: Component) {
...
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
createWatcher(vm, key, handler)
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
return vm.$watch(expOrFn, handler, options)
}
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
const watcher = new Watcher(vm, expOrFn, cb, options)
}
複製程式碼
寫在最後
這裡的思路,其實就是翻著原始碼走的,寫的時候都是按自己的理解思路來的,存在問題的話歡迎指出~