项目中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)
}
写在最后
这里的思路,其实就是翻着源码走的,写的时候都是按自己的理解思路来的,存在问题的话欢迎指出~