这节将专门讲解vue MVVM响应式处理
Vue采用的是数据劫持+观察者模式实现数据的响应式
数据劫持
Observer类
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this) // 这里添加__ob__标记
if (Array.isArray(value)) {
...// 保留重要代码
protoAugment(value, arrayMethods) // 劫持数组的原型方法
this.observeArray(value) // 劫持数组
} else {
this.walk(value) // 劫持对象
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
- Observer会给每一个劫持的数据添加‘ob’属性
- Observer在给数组进行劫持的时候会先劫持数组原型上面的 methodsToPatch这几个修改数组方法,每一行代码都添加了注释
const arrayProto = Array.prototype //获取Array的原型
export const arrayMethods = Object.create(arrayProto) // 继承Array的原型
const methodsToPatch = [ // 需要拦截的方法
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method] // 拿到原型原来的方法
// 重写原型方法,最后也是调用原型原来的方法,只是在调用前添加了响应式
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args) // 调用原型原来的方法
const ob = this.__ob__
let inserted // 添加的数据
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted) //如果存在新添加的,则响应式拦截新的数据
ob.dep.notify() // 修改完以后视图更新
return result
})
})
- 数组的劫持是对数组的每一项在添加劫持
- 对象的劫持defineReactive
辅助函数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__') ) {
ob = value.__ob__
} else if (
....
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
- 通过ob判断当前对象是否劫持过
- 如果没有这添加劫持
辅助函数defineReactive定义具体拦截
const dep = new Dep() // 声明一个Dep观察者收集器
....
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, { // 通过defineProperty拦截
enumerable: true,
configurable: true,
get: function reactiveGetter () { // 拦截get
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // 添加观察者
...
}
return value
},
set: function reactiveSetter (newVal) { // 拦截set
const value = getter ? getter.call(obj) : val
....
childOb = !shallow && observe(newVal) // 对新的val添加observe
dep.notify() // 通知watch更新
}
})
暴露set方法
export function set (target: Array<any> | Object, key: any, val: any): any {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 数组的splice已经被拦截了
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 这里的set已经在oberve里面劫持了
const ob = (target: any).__ob__
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
暴露delete
export function del (target: Array<any> | Object, key: any) {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (!hasOwn(target, key)) {
return
}
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}
Dep 配合拦截器添加Wacher,触发Wacher
let uid = 0
export default class Dep {
static target: ?Watcher;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) { // 收集Watcher
this.subs.push(sub)
}
removeSub (sub: Watcher) { // 删除Watcher
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this) // Watcher添加Dep
}
}
notify () {
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // Watcher更新
}
}
}
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
// 这两个函数是为了保证同时只能有一个watcher在求值
Watcher
export default class Watcher {
....
constructor (
vm: Component, // vue实例
expOrFn: string | Function, // 观察的对象
cb: Function, // 更新回调
options?: ?Object, // 参数配置
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) { // 在对watch进行观察的时候需要
vm._watcher = this
}
vm._watchers.push(this)
...
this.cb = cb
this.dirty = this.lazy // 主要用于computed的watcher
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.lazy // computed不会立即求值
? undefined
: this.get()
}
get () { // 求值
pushTarget(this)
...
value = this.getter.call(vm, vm)
...
popTarget()
return value
}
// 添加Sub
addDep (dep: Dep) {
....
dep.addSub(this)
}
// 清除Sub
cleanupDeps () {
...
dep.removeSub(this)
}
// 更新
update () {
if (this.lazy) {
this.dirty = true // computed只需要修改dirty
} else if (this.sync) {
this.run() // 异步
} else {
queueWatcher(this) // 队列更新
}
}
// 通过cb更新视图
run () {
...
const value = this.get()
...
this.cb.call(this.vm, value, oldValue)
}
// computed求值函数
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown () {
if (this.active) {
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
- 这里先说一下Watcher有三种 data,computed,watch下节将会逐步讲解
- 把sub的收集放在这里实现主要是利用dep.id过滤
- 更新也分了3种
这里总结一下响应式流程
1 在数据初始化的时候进行数据劫持,这里劫持的有data,props,computed,watch,劫持的是这些数据的get和set
2 在get里面我们会通过Dep收集watcher
3 在set里面我们回去触发watcher
那么有一个问题是watcher是在什么时候初始化的呢?下节将会把这个流程将的更加完善
网友评论