一、在这之前可以看看观察者模式和发布订阅者模式的概念
1、观察者模式demo:
var subject={
observers:[],
notify(num){
this.observers.forEach(observer=>{
observer.update.call(this,num)
})
},
attach(observer){
this.observers.push(observer)
}
}
var observerMother = {
update(msg){
console.log(msg,',妈妈好开心呢')
}
}
var observerSister = {
update(msg){
console.log(msg,',哥哥终于结婚了')
}
}
subject.attach(observerMother)
subject.attach(observerSister)
subject.notify('哥哥要结婚了')
发布订阅者demo:
var pubsub ={
list:{},
subscribe(key,fn){
this.list[key] = this.list[key] || []
this.list[key].push(fn)
},
publish(key,...args){
for(let fn of this.list[key]){
fn.call(this,...args)
}
}
}
pubsub.subscribe('data1',(value)=>{console.log('data1变了,需要更新视图')})
pubsub.subscribe('data1',(value)=>{console.log('watch到data1变了,需要重新请求数据')})
pubsub.subscribe('data2',(value)=>{console.log('data3=fn(data1)需要重新计算')})
pubsub.publish('data1',6)
pubsub.publish('data2',8)
2.从上面可以看出,
-
观察者模式:
一般一对多:一个事件变化通知多个观察者;
一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新 -
发布订阅者模式:
可以多对多: 多个事件对应多个订阅者
3.vue中使用订阅者模式
- vue中每个响应式数据对应一个
dep订阅中心,dep有subs属性是watcher数组 - 当数据变化时,会调用dep实例的notify方法
- new Watcher会传人一个回调cb函数,调用update方法即执行该回调
function Dep(){
this.subs = []
}
Dep.prototype.addsub = function(watcher){
this.subs.push(watcher)
}
Dep.prototype.notify = function(){
this.subs.forEach(watcher=>{watcher.undate()})
}
function Watcher(fn){
this.cb = fn
Dep.target = this //new Watcher()
}
Watcher.prototype.update = function(){
this.cb()
}
二、vue源码
- observe
- Observer
- defineReactive
- Dep
- watcher
总结
- 初始化data等数据,会给每个响应对象创建dep依赖收集中心
- 初始化vue会new 渲染Watcher
- 创建虚拟dom会触发所有数据的 getter方法进行watcher收集,数据变化的时候会让dep触发watcher
- 除了渲染Watcher,还有computed 和watch 会创建watcher
observe
vue在初始化的时候initData,initProps都会调用这个方法;
返回一个Observer对象
function observe(value) {
if (Object.prototype.toString.call(value) === '[object Object]' || Array.isArray(value)) {
return new Observer(value)
}
}
Observer
对象多了一个 __ob__ 的属性,就是对应Observer实例;
给data,props等响应化,对象和数组需要遍历深度响应
const { arrayMethods } = require('./array')
class Observer {
constructor(value) {
Object.defineProperty(value, '__ob__', {
value: this,
enumerable: false,
writable: true,
configurable: true
})
if(Array.isArray(value)) {
value.__proto__ = arrayMethods
this.observeArray(value)
} else {
this.walk(value)
}
}
walk(data) {
let keys = Object.keys(data)
for(let i = 0; i < keys.length; i++) {
const key = keys[i]
const value = data[key]
defineReactive(data, key, value)
}
}
observeArray(items) {
for(let i = 0; i < items.length; i++) {
observe(items[i])
}
}
}
defineReactive
对象内部通过 defineReactive 方法,使用 Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。
function defineReactive(data, key, value) {
const childOb = observe(value)
const dep = new Dep()
Object.defineProperty(data, key, {
get() {
if (Dep.target) { //对应watcher
dep.depend() //收集watcher
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set(newVal) {
if (newVal === value) return
observe(newVal)
value = newVal
dep.notify() //通知watcher
}
})
}
function dependArray(value) {
for(let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
// 数组每一项对应一个dep收集中心
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
// array.js
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'reverse',
'sort'
]
//重写数据上的方法
methodsToPatch.forEach(method => {
arrayMethods[method] = function (...args) {
const result = arrayProto[method].apply(this, args)
const ob = this.__ob__
var inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
default:
break;
}
if (inserted) ob.observeArray(inserted)
ob.dep.notify()
return result
}
})
Dep订阅中心
当页面使用对应属性时,每个属性都拥有自己的dep属性,存放他所依赖的 watcher(依赖收集)
import type Watcher from './watcher'
import { remove } from '../util/index'
let uid = 0
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 () {
// 依赖次数据的watcher执行update()
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// 同一时间,全局唯一的watcher
// 在使用watcher的时候,会把watcher赋值给Dep.target,在响应式数据get的时候push进dep
Dep.target = null
const targetStack = []
//获取watcher的value,会执行pushTarget,给Dep.target赋值
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
Watcher 观察者
- 当属性变化后会通知自己对应的
watcher去更新(派发更新),有三类
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
dep: Dep;
deps: Array<Dep>;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
this.cb = cb
this.id = ++uid
this.deps = []
this.newDeps = []
this.getter = expOrFn
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
get () {
//Dep.target = this
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
...
} finally {
popTarget()
this.cleanupDeps()
}
return value
}
// watcher添加到dep
addDep (dep: Dep) {
dep.addSub(this)
this.newDeps.push(dep)
}
cleanupDeps () {
dep.removeSub(this)
this.newDeps = this.deps
}
// 派发更新
update () {
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true
} else {
this.getAndInvoke(() => {
this.dep.notify()
})
}
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
// queueWatcher维护watcher队列,依次执 watcher.run()
run () {
if (this.active) {
this.getAndInvoke(this.cb)
}
}
getAndInvoke (cb: Function) {
const value = this.get()
cb.call(this.vm, value, oldValue)
}
}
queueWatcher 会执行queue.push(this) 把当前watcher推入队列,最终执行watcher.run()
getAndInvoke 对于渲染 watcher ,this.cb如下:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
flushSchedulerQueue 更新队列
let flushing = false
let index = 0
function flushSchedulerQueue () {
flushing = true
let watcher, id
// watcher队列先后顺序:父先于子,custom watcher先于渲染 `watcher`
queue.sort((a, b) => a.id - b.id)
// 循环执行 watcher.run()
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
}
...
// 更新队列状态清空
resetSchedulerState()
// 触发 更新钩子函数
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
}