先简单了解下object.defineProperty方法
// Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
// get 方法 使用该属性的时候会调用get方法
// set 方法 修改或者赋值该属性时调用该方法
Object.defineProperty(obj, key, {
enumerable: true, //可枚举的
configurable: true, //可删除,可配置
get: ()=>{
// 使用调用
},
set:newVal=> {
// 赋值调用
}
})
ok,接下来进入正题
现上个图
Vue.js在这里主要做了三件事:
- 通过 Observer 对 data 做监听,并且提供了订阅某个数据项变化的能力。
- 把 template 编译成一段 document fragment,然后解析其中的 Directive,得到每一个 Directive 所依赖的数据项和update方法。
- 通过Watcher把上述两部分结合起来,即把Directive中的数据依赖通过Watcher订阅在对应数据的 Observer 的 Dep 上。当数据变化时,就会触发 Observer 的 Dep 上的 notify 方法通知对应的 Watcher 的 update,进而触发 Directive 的 update 方法来更新 DOM 视图,最后达到模型和视图关联起来。
理解:
- Observer: 数据的观察者,让数据对象的读写操作都处于自己的监管之下
- Watcher: 数据的订阅者,数据的变化会通知到Watcher,然后由Watcher进行相应的操作,例如更新视图
- Dep: Observer与Watcher的纽带,当数据变化时,会被Observer观察到,然后由Dep通知到Watcher
示意图如下
// src/core/instance/index.js
function Vue (options) {
this._init(options)
}
// src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
// expose real self
vm._self = vm
// 初始化生命周期函数
initLifecycle(vm)
// 初始化事件
initEvents(vm)
// 初始化render函数
initRender(vm)
// 调用beforeCreate方法
callHook(vm, 'beforeCreate')
// 初始化inject
initInjections(vm) // resolve injections before data/props
// 初始化state(包括data,props,methods,computed,watch)
initState(vm)
// 初始化provide
initProvide(vm) // resolve provide after data/props
// 调用created
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
// src/core/instance/state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
// 这里做data的初始化
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
// initData
function initData (vm: Component) {
// 获取data
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 遍历data的每一个key
while (i--) {
const key = keys[i]
// 防止props和data里数据重复
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
// 从这里开始执行obesrve方法,对data进行监听
observe(data, true /* asRootData */)
}
从上面我们可以知道当我们执行new Vue()时
1. 首先会执行_init()方法,这个方法内vue会初始化他的生命周期、事件以及他自身的state,这个state包括props、methods、data、computed、watch。
2. 因为我们要说响应式,因此,重点分析下data,而对data的初始化在initData()方法中。
3. 分析initData,这里主要是对data中和props中key值重复性以及数据代理的的处理,我们暂不研究,关于响应式我们从observe(data, true /* asRootData */)这个方法开始
// src/core/observer/index.js
// observe方法
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 不是对象或是VNode则退出
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 这里__ob__属性指的是已被observe监听
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
// 这里的判断是为了确保value是单纯的对象
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 如果是对象且没有__ob__属性,则new Observer()
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
这里 new Observer(value) 就是实现响应式的核心方法之一了,通过它将data转变可以成观察的,而这里正是我们开头说的,用了 Object.defineProperty 实现了data的 getter/setter 操作,通过 Watcher 来观察数据的变化,进而更新到视图中。
接下来我们就来看Observer
Observer
先来observer的构造函数
// src/core/observer/index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
// 如果value是对象,执行walk方法,对每一个key进行defineReactive
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
// 如果是数组,则执行observeArray,对每一项进行observe
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
分析构造函数,主要做了以下的事:
1. 给要监听的value,添加__ob__属性,表示数据已经被observer的标志
2. 如果value是数组,则通过对value中每一个元素调用observe分别进行观察。
3. 如果value是对象,则使用walk遍历value上每个key,对每个key调用defineReactive来获得该key的set/get控制权。
示意图如下:
通过以上的分析,我们知道,真正实现数据的响应式的核心方法在defineReactive上
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 创建一个dep
// get时用来进行依赖收集
// set时用来通知watcher更新dom
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖之前已经定义的getter/setter。
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 执行原有的getter方法
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 进行依赖收集
dep.depend()
if (childOb) {
// 如果value是对象,则对其子集进行依赖收集。
/*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
childOb.dep.depend()
// 如果value是数组,则对数组逐一依赖收集
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
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()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
// 如果之前存在setter,需要先执行
if (setter) {
setter.call(obj, newVal)
} else {
// 修改value的值
val = newVal
}
// 对新生成的数据,进行监听
childOb = !shallow && observe(newVal)
// 通知watcher更新
dep.notify()
}
})
}
分析下其中的getter方法:
1. Dep.target。Dep.target的作用是用来判断是否应该进行依赖收集,什么时候为他赋值呢?我们之后研究。
2. dep对象创建的作用是为了进行依赖收集
那么为什么要判断是否需要依赖收集呢?举个简单的例子
new Vue({
template:
`<div>
<span>name:</span> {{name}}
<span>age:</span> {{age}}
<div>`,
data: {
name: 'lily',
age: '18',
sex: 'male'
}
});
上例中在模版中我们只是用了data中的两个数据,sex这个属性我们没有使用,因此再这种情况下我们没必要对sex进行监听,这样在一定程度上可以提高性能。
这里需要注意getter方法的执行时间是render()的时候,当$el.mount()执行的时候,这时候会实例话一个watcher对象,这个watcher对象就是Dep.target,表示这个数据需要被observe。
cb('beforeMount')
vm._watcher = new Watcher(vm, function () {
vm._update(vm._render(), hydrating);
}, noop);
cb('mounted')
接着分析setter方法
1. 当数据修改完毕,时我们需要重新对这个数据进行监听。
2. 调用dep.notify()方法通知watcher数据更新。
简单来说,dep相当于一个书店,而watcher就相当于书店的订阅者,observer的数据data相当于书店的书
ok,接下来我们了解下具体是怎么进行依赖收集的
Dep
被Observer的data在触发 getter 时,Dep 就会收集依赖的 Watcher ,其实 Dep 就像刚才说的是一个书店,可以接受多个订阅者的订阅,当书有变动时即在data变动时,就会通过 Dep 给 Watcher 发通知进行更新。
// src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// 添加一个观察者对象
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除观察者对象
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 依赖收集,当存在Dep.target的时候添加观察者对象
// 这里的Dep.target其实是Watcher实例
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 通知所有观察者,执行update
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
1. 执行getter时,dep执行dep.depend()方法,进而会执行watcher的addDep方法将wtacher加入到dep的subs列表中
2. 执行setter时,dep执行notify方法,然后遍历subs中的所有watcher执行update方法。
Watcher
Watcher是一个观察者对象。依赖收集以后Watcher对象会被保存在Dep的subs中,数据变动的时候Dep会通知Watcher实例,然后由Watcher实例回调cb进行视图的更新。
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} 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
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
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
)
}
}
// new Watcher时执行get方法
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 这一步对应用到的数据标记Dep.target
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
// 添加依赖,将当前watcher添加到dep的subs中去
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)
}
}
}
// 当依赖发生改变的时候进行回调。
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
}
1. 执行getter时,dep执行dep.depend()方法,进而会执行watcher的addDep方法将wtacher加入到dep的subs列表中
2. 执行setter时,dep执行notify方法,然后遍历subs中的所有watcher执行update方法。