vue的响应式原理,是发布-订阅模式的应用。学习vue响应式原理前,先来了解下,到底什么是发布-订阅模式。vue
有一天去商城买鞋子,结果没货了。店员拿出一个本对你说,能够先登记电话,到货再通知你。react
这个例子中,店员是消息的发布者,店员有个小本本,只要货到了,就会通知小本本上的客户。客户是消息的订阅者。能够在将来某个时候接收信息发布者(售货员)的消息,而不用一直轮训消息的变化。用代码实现这个例子。git
const sales = {
//售货员的小本本
noteBook :[],
//在本上登记电话
add(customer){
let isExist = this.noteBook.find((item) =>{
return item.phone == customer.phone
})
if(!isExist){
this.noteBook.push(customer)
}
},
//删除小本本上的记录
delete(customer){
let getIndex = this.noteBook.findIndex((v) =>{
return v.phone == customer.phone
})
if(getIndex!==-1){
this.noteBook.splice(getIndex,1)
}
},
//通知顾客
notify(){
this.noteBook.forEach((item) =>{
item.upDate();
})
}
}
const customer1 = {
phone:'12345678',
upDate(){
console.log(`customer1电话号码${this.phone}`)
},
}
const customer2 = {
phone:'87654321',
upDate(){
console.log(`customer2电话号码${this.phone}`)
},
}
//记录客户1信息
sales.add(customer1)
//记录客户2信息
sales.add(customer2)
//到货了,通知客户
sales.notify()
复制代码
Object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 get 是一个给属性提供的 getter 方法,当咱们访问了该属性的时候会触发 getter 方法;set 是一个给属性提供的 setter 方法,当咱们对该属性作修改的时候会触发 setter 方法。github
一旦对象拥有了 getter 和 setter,咱们能够简单地把这个对象称为响应式对象编程
vue在何时,把哪些对象建立为响应对象呢。设计模式
initState 在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法(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) {
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)
}
}
复制代码
initDatabash
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
.......
// observe data
observe(data, true /* asRootData */)
}
复制代码
observe闭包
observe 的功能就是用来监测数据的变化。若是没有_ob_属性,而且不是Observer的实例,就为数据添加一个Observer类。observe定义在(src/core/observer/index.js)编程语言
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类 主要作了三件事。1.为当前值添加_ob_属性。2.当前值为数组调用observeArray方法,循环调用observe方法,给里面的每一个值变为响应对象。3.当前值是对象,调用walk方法
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
...
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
复制代码
walk函数里,调用defineReactive方法,给响应式对象动态添加 getter 和 setter
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
复制代码
defineReactive.
对数据进行响应式化,为数据添加getter/setter,为每一个数据添加一个订阅者列表的过程,。这个列表是getter闭包中的属性,会记录依赖这个数据的组件。 响应式化后的数据至关于消息的发布者。
new Dep()建立一个数组,里面保存全部对这个数据依赖的订阅者
export function defineReactive (
....
) {
const dep = new Dep()
....
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true
//依赖收集
get: function reactiveGetter () {
....
},
// 派发更新
set: function reactiveSetter (newVal) {
.....
}
})
}
复制代码
每一个组件都对应一个Watcher订阅者。当每一个组件的渲染函数被执行时,都会将本组件的Watcher放到本身所依赖的响应式数据的订阅者列表里,这就至关于完成了订阅。
当对数据对象的访问会触发他们的 getter 方法,那么这些对象何时被访问呢?
在Vue 的 mount 过程是经过 mountComponent 函数。(core/instance/lifecycle.js)。
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
复制代码
当实例化一个渲染Watcher时,.首先进入 watcher 的构造函数逻辑,而后会执行它的 this.get() 方法(src/core/observer/watcher.js).而后调用pushTarget
pushTarget(this)
复制代码
pushTarget(src/core/observer/dep.js )把当的渲染watcher赋值给 Dep.target
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
复制代码
在warcher构造函数里,继续执行this.getter,对应就是 updateComponent 函数,这实际上就是在执行:
vm._update(vm._render(), hydrating)
复制代码
vm._render(),执行这个方法会对vm上的数据访问,这时就触发了数据对象的getter.
src/core/observer/index.js
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
},
复制代码
触发getter,调用dep.depend,也就是调用 Dep.target.addDep,这时Dep.target已经被赋值为当前的渲染watcher。
src/core/observer/watcher.js
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.addSub,那么就会执行 this.subs.push(sub),也就是说把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs 作准备。
当响应式数据发生变化的时候,也就是触发了setter时,setter会负责通知该数据的订阅者列表里的Watcher,Watcher会触发组件从新渲染来更新视图