我们都知道,Vue的响应式原理是Object.defineProperty
进行get
,set
代理实现的,但他在代码中具体是怎样实现的呢?
相关链接:
整体
Vue的响应式实现主要有三块重点内容,分别为Observer
,Dep
和watcher
我们常说的Object.defineProperty
就是通过observe函数
、Observer类
、defineReactive函数
的递归执行实现,defineProperty
写在defineReactive
函数中;而数据与视图的相互绑定(数据改变页面也跟着改变),是通过Dep类
(发布者)和watcher类
(观察者)实现的
整体思路
- 本文重点: 此处为本篇文章讲的主要内容,可以结合后边代码一起看*
在开始之前,我们首先要知道,在一个组件中,data
初始化是在watcher
之前执行的,整体思路也可以分为两个部分,1:处理data
时和2:组件初始化时
- 处理
data
时:会调用observe
函数,该函数会将data
的所有的属性(包括子属性)都进行Object.defineProperty
进行get
和set
代理,并为每个属性创建一个Dep
(发布者),在属性.get
时,让Dep
收集watcher
(dep.depend
方法)(.get
的调用时机在2中),在属性.set
时,调用dep.notify
方法通知观察者(watcher
,也是上边Dep
收集的依赖)更新组件视图(.set
的调用时机在3中); - 组件初始化时:会调用
new Watcher
实例化watcher
(观察者),组件的初始化时肯定会用到data
中的数据,也就一定会调用上边代理过的Object.defineProperty.get
收集watcher
(dep
和watcher
相互收集依赖),到这里,dep
与watcher
的关系就已经绑定好了 - 😃 修改
data
中的值时(更新时):会执行Object.defineProperty.set
,因为在1中,每个值都肯定会有一个对应的dep
,而且在2中,dep
和watcher
都已经相互绑定上了,所以这时dep中可以拿到需要更新的组件的watcher
,调用dep.notify
方法通知这些watcher
更新组件视图;
下边的提示有助于更好的理解整体思路:
每个属性都会有一个
dep
,每个组件也都会有一个watcher
1中定义的get
,set
不会立即执行(虽然大家都知道,但我觉得看的时候容易混淆)
watcher
中可以存储多个dep
,dep
中可以存储多个watcher
(当前流程只会存储一个watcher),二者是多对多的关系
找到代码位置
该段不重要,可以跳过本段直接看下边
observe
函数位置
首先找到observe
函数位置,跳过引入时的各种操作,我们先找确定到初始化data的位置,从new Vue()
(位置:src/core/instance/index.js
)*1
开始
该方法调用this._init(options)
方法(位置:src/core/instance/init.js
)*2
-> 调用initState(vm)
-> 调用initData(vm)
(两个函数位置::src/core/instance/state.js
)*3
我们要找的observe()
函数就在initData
里边,Object.defineProperty
就是在这里实现的
/**
* 首先说明一下属性名的意义
* @params {Object} options 为用户new Vue({...options})时候传的options
* @params {vm} vm Vue
**/
// src/core/instance/index.js *1
// new Vue({...Options}) 就是new的他
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)
}
// src/core/instance/init.js *2
Vue.prototype._init = function (options?: Object) {
// 跳过99%的代码
...
initState(vm) // 初始化data,props,watch,computed,methods
...
if (vm.$options.el) { // 执行$mount()方法
vm.$mount(vm.$options.el)
}
}
// src/core/instance/state.js *3.1
function initState (vm: Component) {
...
if (opts.data) { // opts为用户new Vue({...options})时候传的options
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
}
// *3.2
// 删掉冗余代码,只保留本流程需要的主要代码
function initData (vm: Component) {
// 1.将var data = 对象格式的data
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 2.循环data中的属性,判断是否和props、methods冲突,然后用proxy(自定义的,非ProxyAPI)方法,将data中的属性放在vm上,以便使用时可以this.xxx取到
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
// 检查methods和props中是否已经定义过了,会先检查methods,再检查props
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
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) // 通过Object.defineProperty(vm,key,{get,set})将vm._data代理到this[key]
}
}
// 要说的重点函数
observe(data, true /* asRootData */)
}
new Watcher(初始化时组件watcher)位置
new Watcher
的位置在本篇中其实不重要,我们只要知道data
初始化是在watcher
之前执行的就可以了,下面代码中可以明显的看到new Watcher
是晚于data
执行的
// src/core/instance/init.js (对应找observe函数位置的*2)
Vue.prototype._init = function (options?: Object) {
// 跳过99%的代码
...
initState(vm) // 初始化data,props,watch,computed,methods
...
if (vm.$options.el) { // 执行$mount()方法
vm.$mount(vm.$options.el)
}
}
// src/platforms/web/runtime/index.js
// 创建$mount函数的位置
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined // 获取el节点
return mountComponent(this, el, hydrating)
}
// src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 调用生命周期函数:beforeMount
callHook(vm, 'beforeMount')
let updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 在此执行的new Watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// 初次渲染
if (vm.$vnode == null) {
vm._isMounted = true
// 调用生命周期函数:mounted
callHook(vm, 'mounted')
}
return vm
}
源码解析
以下将分为Observe
、dep
、watcher
来解释
Observe
src/core/observer/index.js
整体:
Observe系列函数的主要功能有两个
- 一个是为属性创建Dep实例
- 另一个是区分value是数组还是对象,然后做不同的处理(对象使用Object.defineProperty对对象属性进行劫持,数组是劫持
push/pop/splice
等方法)
流程:
从(2.)observe函数
开始,该函数主要判断了一下该属性(首次传入时为data对象)需不需要执行new Observer
,如果需要,就调用(1.)new Observer
,在new Observer
中,区分数组还是对象,对其进行不同的处理,数组就代理其push/pop/shift/splice
等方法,对象就执行Observer.walk
方法,调用defineReactive
(3.),对其创建dep
实例并进行Object.defineProperty
代理,重点为defineReactive
函数
1. class Observer {}
Observer
主要是用来区分数组还是对象然后执行不同的操作,并给数据添加Dep
实例
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
// 创建dep,dep为被观察者,每个属性都有自己的dep,当dep改变时,会通知所有的watcher(观察者)更新视图
this.dep = new Dep()
this.vmCount = 0
// 为value添加"__ob__"属性,值为this
def(value, '__ob__', this)
if (Array.isArray(value)) {
// 对数组处理,由于数组没办法进行defineProperty,所以采用拦截数组的push,pop...等方法来实现
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 对数组中的属性循环调用observe
this.observeArray(value)
} else {
// 如果是对象则使用walk遍历每个属性
this.walk(value)
}
}
/**
* 处理对象的方法
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* 对数组中的属性循环调用observe
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
2. observe函数
在初始化data
时,首先进入的就是observe
函数,该函数主要功能是判断是否需要执行new Observer(value)
详细:
- 该函数首先保证进入的
value
为一个对象/数组 - 然后判断
value
中是否有__ob__
属性(如果有就说明已经被劫持过了,在(1.)中添加的该属性) 如果有,直接返回__ob__
- 调用
new Observe(value)
方法创建实例,最后返回该实例
// 逐行解析
function observe (value: any, asRootData: ?boolean): Observer | void {
// 保证只有对象会进入到这个函数
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 如果这个数据身上有__ob__,代表已经有ob实例,直接返回那个ob实例
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else {
// 是对象(包括数组)的话就深入进去遍历属性,observe每个属性
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
3. defineReactive函数
该函数的主要目的是对传入的属性做响应式绑定和为该属性生成一个Dep
实例
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 为该属性生成一个Dep实例(发布者)
const dep = new Dep()
// Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符(可以获取到defineProperty定义的内容)
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
// 设置为不可修改就直接return
return
}
// 获取已经定义的get、set(如果已经定义了的话)
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 对该属性(val)重新执行observe方法,整体上实现递归,便利处理所有的子属性
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可修改
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// Dep.target:此处简单解释下,Dep.target是一个“全局变量”,他会在组件运行时将watcher(观察者)存入到这里面
// if里的代码目的是将dep与watcher关联起来
if (Dep.target) {
// dep.depend 将watcher和dep相互关联起来
dep.depend()
// 对子集进行处理
if (childOb) {
childOb.dep.depend()
// 如果value是对象,那就让生成的Observer实例当中的dep也收集依赖
if (Array.isArray(value)) { // 数组处理
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
// 新老值相同不做处理
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
// 测试环境执行的暂时不看
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
// 如果已经定义过setter执行setter
if (setter) {
setter.call(obj, newVal)
} else {
// 默认就执将val置换为新的val
val = newVal
}
// observe这个新set的值,对其做代理
childOb = !shallow && observe(newVal)
// 执行dep.notify方法,通知订阅了该dep的watcher们更新了(执行dep.notify方法,新欢调用dep.subs总的所有watcher中update方法)
dep.notify()
}
})
}
Dep、Watcher
5. class Dep {}
Dep
:充当发布者的角色,data
中的每个对象与属性包括子属性都会有一个Dep
,Dep
的功能很明确,一个是收集依赖,一个是发布更新
收集依赖:通过Dep.depend
方法收集依赖,将watcher
收集到subs
数组中(Dep.target
存的就是需要收集的watcher
)
发布更新:在Dep
对应的属性更新时,会调用Dep
的notify
方法,通知subs
数组中的watcher
进行更新
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++ // 一个自增的id
this.subs = [] // 用来存放该Dep对应的watcher
}
// 向Dep中添加watcher,一般不会直接调用此方法,而是通过Dep.depend后在watcher实例中调用该方法
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 删除subs中的watcher
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 调用watcher中的addDep,让watcher收集dep,并且在watcher的函数中会调用Dep的addSub方法已达到互相收集对方
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 调用watcher中的更新方法
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// class之外的内容,创建的是全局的属性,用于存放当前的watcher
// 在watcher中会调用pushTarget方法,然后会调用 *组件需要用到的属性对应的实例Dep的depend方法* ,最后再调用popTarget方法清除Dep.target
Dep.target = null
const targetStack = [] // targetStack一般用于计算属性watcher用到的data中属性的依赖收集,暂时不需要考虑
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
4. class Watcher {}
watcher
:(此处只说名组件watcher)充当观察者角色,在组件初始化的时候创建watcher
的实例,在创建实例过程中通过Watcher.get
函数,内部调用Dep
的pushTarget
方法,将target
置位该watcher
,再调用getter
方法,在getter
方法执行时,页面中所用到的属性就会被调用defineProperty.get
的内容,defineProperty.get
中dep.depend()
方法就会将watcher
(组件)和dep
(属性)关联起来,在属性改变时(defineProperty.set
),执行dep.notify()
方法,就会通知dep
中存的所有watcher
,执行update
方法更新组件
// pushTarget和popTarget方法在Dep中有讲
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上存储该watcher
vm._watchers.push(this)
// options:配置项
if (options) {
this.deep = !!options.deep // 深度监听(用于watch)
this.user = !!options.user // 是否是用户传入
this.lazy = !!options.lazy // 表示为计算属性
this.sync = !!options.sync // 表示是否改变了值之后立即触发回调。如果用户定义为true,则立即执行 this.run
this.before = options.before // before: 更新前调用的函数
} else {
// 不传全部默认false
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 // 该值是否为脏值(用于计算属性)
// 用于存储dep
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production' // expOrFn
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else { // 一般用于watch中执行
this.getter = parsePath(expOrFn) // 返回一个函数,函数调用后会返回expOrFn字符串对应的属性(走defineProperty的get)
// 没有就置为空并报错
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
)
}
}
// lazy:用于计算属性的
// 如果是计算属性,则不需要在这里就求值,在外界get这个计算属性时再求值
// 如果非计算属性,调用get方法收集依赖
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this) // 将该watcher(this)push到dep.target上
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) // 组件watcher在执行getter方法时,会走组件中所有用到的属性的defineProperty.get
} 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() // 将watcher pop出dep.target
this.cleanupDeps()
}
return value
}
// 为当前watcher收集dep(一般在dep的depend方法中调用该函数)
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 () {
// if (xxx) ...
// 组件watcher只会走这一句话
queueWatcher(this) // 排序并通过nextTick执行要更新的watcher
}
}