刘博文
极简主义
刘博文
360导航 - 高级前端工程师
前奇舞团成员
95后
整体逻辑
变化侦测
模板编译
virtualDOM
Observer
Dep
Watcher
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
function defineReactive (data, key, val) {
// 递归观测子属性
observer(val)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
return val
},
set: function (newVal) {
if(val === newVal){
return
}
// 对新值进行观测
observer(newVal)
val = newVal
}
})
}
getter时,收集依赖
setter时,触发依赖
存 Dep 中
专门用来存储依赖用的
每个属性都有一个Dep
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()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
function defineReactive (data, key, val) {
observer(val)
let dep = new Dep() // 新增
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.addSub() // 新增
return val
},
set: function (newVal) {
if(val === newVal){
return
}
observer(newVal)
dep.notify() // 新增
val = newVal
}
})
}
watcher 是变化侦测和更新view的一个纽带
function defineReactive (data, key, val) {
observer(val)
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
if (Dep.target) { // 新增
dep.depend() // 修改
}
return val
},
set: function (newVal) {
if(val === newVal){
return
}
observer(newVal)
dep.notify()
val = newVal
}
})
}
Dep.target 就是watcher实例
// keypath
vm.$watch('a.b.c', function (newVal, oldVal) {
// do something
})
class Watch {
constructor (expOrFn, cb) {
this.getter = parsePath(expOrFn)
this.cb = cb
this.value = this.get()
}
get () {
pushTarget(this)
value = this.getter.call(vm, vm)
popTarget()
}
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 () {
const value = this.get()
this.cb.call(this.vm, value, oldValue)
}
}
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
watcher主动将自己 push 到属性的依赖中
// keypath
vm.$watch('a.b.c', function (newVal, oldVal) {
// do something
})
主动将 watcher push到 a.b.c 的依赖中
只有通过watcher读取属性的值才会收集依赖
模板是通过watcher读取属性的值的~
watcher 是变化侦测和更新view的一个纽带
defineProperty 侦测不了Array
monkey patch
import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
/**
* Intercept mutating methods and emit events
*/
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function (method) {
// cache original 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)
// notify change
ob.dep.notify()
return result
})
})
我们平时都是直接操作数组原生方法的
array.__proto__ = arrayMethods
当数据丢进 Observer 时,如果发现是Array
const keys = Object.getOwnPropertyNames(arrayMethods) for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i]
Object.defineProperty(target, key, {
value: arrayMethods[key],
enumerable: false,
writable: true,
configurable: true
})
}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin urna odio, aliquam vulputate faucibus id, elementum lobortis felis. Mauris urna dolor, placerat ac sagittis quis.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin urna odio, aliquam vulputate faucibus id, elementum lobortis felis. Mauris urna dolor, placerat ac sagittis quis.
By 刘博文
关于 vue 的变化检测原理的分享~