We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
一个数据发生变化,与此数据相关的事件随即被触发。(观察者模式)
defineProperty:用于设置对象属性的getter/setter
const obj = { val: 1 } let val = obj.val Object.defineProperty(obj, 'val', { get(){ //obj.val的值将被查询 return val }, set(newVal){ //obj.val的值将被修改 val = newVal } })
转为响应式:
const obj = { val: 1 } //以下内容又可作为函数defineReactive() let val = obj.val Object.defineProperty(obj, 'val', { get(){ //obj.val的值将被查询 + depend() //obj.val被查询了,说明有事件依赖了obj.val return val }, set(newVal){ //obj.val的值将被修改 val = newVal + notify() //obj.val被修改了,通知依赖该属性的事件更新 } })
将对象转为响应式:
const obj = { val: 1 } function observe(obj){ Object.keys(obj).forEach(key => defineReactive(obj, key)) }
depend()/notify()
一个对象属性可以有多个观察者,这些观察者应当被集中(如放入数组中)方便通知
因此增加数组dep存放观察者:
function defineReactive (obj, key){ + const dep = [] let val = obj.val Object.defineProperty(obj, 'val', { get(){ //obj.val的值将被查询 - //depend() + if( window.currentWatcher ) dep.push( window.currentWatcher ) return val }, set(newVal){ //obj.val的值将被修改 val = newVal - //notify() + dep.forEach( watcher => watcher.update() ) } }) }
以上代码新增了一个概念:Watcher,即观察者。 对Watcher有两点要求:
update()
class Watcher { constructor(obj, key, cb){ this.vm = obj this.getter = obj => obj[key] this.cb = cb this.get() } get(){ window.currentWatcher = this this.getter(this.vm) //触发getter window.currentWatcher = null } update(){ this.cb() } }
这个Watcher怎么用:
const obj = { val: 1 } //转换为响应式:设置getter/setter defineReactive(obj, 'val') //进行观察 new Watcher(obj, 'val', ()=>{ console.log('watcher 1:', obj.val) }) new Watcher(obj, 'val', ()=>{ console.log('watcher 2:', obj.val) })
computed和watch主要有两点不同:
其使用方式可以设计为:
const obj = { val: 1 } defineReactive(obj, 'val') compute(obj, 'computedKey', ()=>{ return obj.val + 1 }) console.log( obj.computedKey ) //2
则compute实现方式可以是:
compute
class Watcher { + constructor(obj, keyOrFn, cb, lazy){ this.vm = obj - //this.getter = obj => obj[key] + this.getter = typeof keyOrFn === "function" ? keyOrFn : obj => obj[keyOrFn] this.cb = cb - //this.get() + this.dirty = this.lazy = lazy + this.value = this.lazy ? undefined : this.get() } get(){ window.currentWatcher = this - //this.getter(this.vm) + let value = this.getter(this.vm) //触发getter window.currentWatcher = null + return value } update(){ + this.lazy ? + (this.dirty = true) : //update触发后,需要进行计算 this.cb() } + evaluate(){ + this.value = this.get() + this.dirty = false + } } function compute(obj, key, fn){ const watcher = new Watcher(obj, fn, ()=>{}, true) Object.defineProperty(obj, key, { get(){ if(watcher){ if(watcher.dirty){ watcher.evaluate() } return watcher.value } } }) }
以上就是Vue 2的响应式原理,一句话总结:
使用Object.defineProperty()拦截对象属性读写,建立观察者模式,实现监听对象属性变化。
Object.defineProperty()
Vue不能监听到对象新增属性及变化
Object.defineProperty()只能为已存在属性设置getter/setter以监听变化,无法监听对象属性的新增
鉴于Vue 2使用的Object.defineProperty()的先天不足,Vue next使用ES6新特性Proxy解决了上面问题。
Proxy
const obj = {} const proxyObj = new Proxy(obj, { get(target, key, receiver){ //对象将被查询 return Reflect.get(target, key, receiver) }, set(target, key, value, receiver){ //对象将被修改 return Reflect.set(target, key, value, receiver) } })
Proxy相比Object.defineProperty()的优势在于,Proxy实例能够监听本身的变化,因此可以监听到对象属性的增删改查。
API升级了,原理仍是相似的。
function reactive (target){ return new Proxy(target, { get(target, key, receiver){ const res = Reflect.get(target, key, receiver) track(target, key) //depend() return (res !== null && typeof res === 'object') ? reactive(res) : res }, set(target, key, value, receiver){ const result = Reflect.set(target, key, value, receiver) trigger(target, key) return result } }) } const obj = {} const proxyObj = reactive(obj)
track()/trigger()
在Vue 2,为了存放多个观察者,创建了闭包dep数组来存放观察者。
使用ES6,有更合适的结构Set,来存储观察者。
Set
另一方面,闭包dep一旦被外部引用,就需要手动清除引用以避免内存泄漏 ,因此不便访问及管理。
使用ES6,可以存放到全局WeakMap中进行管理。
const targetMap = new WeakMap() //功能等同于 //if( window.currentWatcher ) dep.push( window.currentWatcher ) function track(target, key){ const effect = window.currentEffect //currentWatcher if(!effect) return let depsMap = targetMap.get(target) //对象target所有属性key的dep集合 if (depsMap === void 0) { depsMap = new Map() targetMap.set(target, depsMap) } let dep = depsMap.get(key) //属性key对应的dep,用于存放观察者 if (dep === void 0) { dep = new Set() depsMap.set(key, dep) } if(!dep.has(effect)){ dep.add(effect) } } //功能等同于 //dep.forEach( watcher => watcher.update() ) function trigger(target, key){ const depsMap = targetMap.get(target) if (depsMap === void 0) return false const effects = new Set() const computedRunners = new Set() //区分computed、effect depsMap.get(key).forEach(effect => { effect.computed ? computedRunners.add(effect) : effects.add(effect) }) const run = effect => { effect.scheduler === void 0 ? effect() : effect.scheduler(effect) } computedRunners.forEach(run) //computed先于effect执行 effects.forEach(run) }
以上代码新增了一个概念:effect,类似Vue 2中的Watcher。 对effect有两点要求:
computed
scheduler()
function effect(fn, options = {}){ const effect = function effect(...args) { window.currentEffect = effect try{ return fn(...args) }finally{ window.currentEffect = null } } effect.scheduler = options.scheduler effect.computed = options.computed if(!options.lazy) effect() return effect }
这个effect()怎么用:
effect()
const obj = reactive({ val: 1 }) //转换为响应式:设置getter/setter //进行观察 effect(()=> { console.log('watcher 1:', obj.val) }) effect(()=> { console.log('watcher 2:', obj.val) })
function computed(getter){ let dirty = true let value const runner = effect(getter, { lazy: true, computed: true, scheduler(){ dirty = true } }) return { effect: runner, get value(){ if(dirty) { value = runner() dirty = false } return value } } }
这个computed()怎么用:
computed()
const obj = reactive({ val: 1 }) const cObj = computed(()=>{ console.log('computed', obj.val) return obj.val }) console.log(cObj.value)
WeakMap
The text was updated successfully, but these errors were encountered:
No branches or pull requests
什么是“响应式”
一个数据发生变化,与此数据相关的事件随即被触发。(观察者模式)
Vue 2 响应式原理
defineProperty:用于设置对象属性的getter/setter
转为响应式:
将对象转为响应式:
实现
depend()/notify()
一个对象属性可以有多个观察者,这些观察者应当被集中(如放入数组中)方便通知
因此增加数组dep存放观察者:
以上代码新增了一个概念:Watcher,即观察者。
对Watcher有两点要求:
update()
方法响应变化实现Watcher
这个Watcher怎么用:
实现computed
computed和watch主要有两点不同:
其使用方式可以设计为:
则
compute
实现方式可以是:以上就是Vue 2的响应式原理,一句话总结:
缺点
Vue不能监听到对象新增属性及变化
Object.defineProperty()
只能为已存在属性设置getter/setter以监听变化,无法监听对象属性的新增Vue next 响应式原理
鉴于Vue 2使用的
Object.defineProperty()
的先天不足,Vue next使用ES6新特性Proxy
解决了上面问题。Proxy
Proxy
相比Object.defineProperty()
的优势在于,Proxy实例能够监听本身的变化,因此可以监听到对象属性的增删改查。API升级了,原理仍是相似的。
转为响应式:
实现
track()/trigger()
在Vue 2,为了存放多个观察者,创建了闭包dep数组来存放观察者。
使用ES6,有更合适的结构
Set
,来存储观察者。另一方面,闭包dep一旦被外部引用,就需要手动清除引用以避免内存泄漏 ,因此不便访问及管理。
使用ES6,可以存放到全局WeakMap中进行管理。
以上代码新增了一个概念:effect,类似Vue 2中的Watcher。
对effect有两点要求:
computed
属性为真且实现scheduler()
方法响应变化实现effect()
这个
effect()
怎么用:实现computed()
这个
computed()
怎么用:小结
Proxy
完善Vue 2使用的Object.defineProperty()
的不足WeakMap
对创建的闭包进行弱引用,便于管理The text was updated successfully, but these errors were encountered: