Vue最具特色的一项特征大概就是其响应式原理,看过Vue官方文档的朋友应该还是有了解的,如果你觉得看了文档理解起来还是很吃力,那么我们就从后往前的学习其响应式原理。
在Vue api文档中, 实例方法和数据部分,我们可以看到有三种实例方法
- $watch
- $set
- $delete
我们只看set方法
- **参数**:
- `{Object | Array} target`
- `{string | number} propertyName/index`
- `{any} value`
- **返回值**:设置的值。
- **用法**:
这是全局 `Vue.set` 的**别名**。
- **参考**:[Vue.set](https://cn.vuejs.org/v2/api/#Vue-set)
这个set方法其实就是Vue.set的别名,那么,我们再来看看Vue.set
### [Vue.set( target, propertyName/index, value )](https://cn.vuejs.org/v2/api/#Vue-set "Vue.set( target, propertyName/index, value )")
- **参数**:
- `{Object | Array} target`
- `{string | number} propertyName/index`
- `{any} value`
- **返回值**:设置的值。
- **用法**:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 `this.myObject.newProperty = 'hi'`)
注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
不知道你有没有遇到过这种需求,就是动态的向Vue某个组件的实例上增加property.并且需要该property必须是响应式的。
如果你没有碰到过这样的需求,也很正常,毕竟单纯的业务开发的时候,我们可能并用不到这种用法,但是有一种场景,在使用ElementUI
进行开发的时候,如果你在设计动态菜单的时候,会用到这种方法
methods: {
handleCollapseToggle(value) {
if (value) {
this.initPopper();
} else {
this.doDestroy();
}
},
addItem(item) {
this.$set(this.items, item.index, item);
},
removeItem(item) {
delete this.items[item.index];
},
addSubmenu(item) {
this.$set(this.submenus, item.index, item);
},
removeSubmenu(item) {
delete this.submenus[item.index];
},
...
}
到现在,你应该知道如何在vue组件实例中动态增加响应式的属性。那我们从这里开始追踪,到底是如何实现的响应式。
首先在stateMixin
方法里,将observer
中的set
方法赋值给了Vue.prototype.$set
.
所以先来看看observer
中的set
方法
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
抛开对target
的判断,主要部分是
defineReactive(ob.value, key, val)
ob.dep.notify()
我们先来看看defineReactive
的功能和实现
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 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 () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
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
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
defineReactive
是用来在对象上定义响应式属性的。看看是如何定义的,首先将val
,observe
做个转换
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
为什么要转换为Observer
类型呢,这其实是重点之一,Observer
类的作用就是将val
转换为Observer
类型之后,便于附加到target
类型之上,通过getter
和setters
方法来收集依赖和分发更新。
具体实现如下:
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)
}
}
/**
* Walk through all properties 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)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
在该类的构造器中,调用walk
方法以便将所有的属性都getter
和setter
化,而这个过程正是用defineReactive
来处理的。继续来看defineReactive
的内容,看看他是如何getter
和setter
化的
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
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
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
在val
对象上,定义了key
的便利值命名的属性,该属性值可被枚举,可被修改,并且定义了属性的 getter 函数
和setter
函数,首先看看getter
函数,属性的getter
函数会在该属性被访问的时候进行调用。
在getter
方法中,进行depend
,
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
这个Dep.target
是一个Watch
实例,全局唯一,
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []
depend
其实所做的就是,把新建的dep
实例放到全局的监视器Dep.target
里,其实就是把dep
的id追加到watch
的depids
数组里。当然如果之前全局的Watch
里没有监控该 val
那么,还会把该watch
放到该dep
的subs
监听订阅列表里。详情如下:
/**
* Add a dependency to this directive.
*/
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)
}
}
}
Dep
的实现如下,其中id 标识该dep
,subs
数组存储了所有监听该属性变化的Watch
实例
/**
* 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 = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
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()
}
}
}
而defineReactive
中属性的setter
方法就是为了在val
发生变化时,设置新的值,然后通过dep
的notify
方法找到对应的Watch
实例进行update
操作。执行执行的逻辑是Watch
实例的run
方法
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
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.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
最终调用vm
上的cb
函数,这个cb
就是watch
属性中的关乎该key
属性的handler
.