前言
不同於React的不可變數據,Vue的一套響應式機制成爲Vue的一套核心原則,從2.x基於Object.defineproperty到3.0即將推出的proxy和reflect,弄懂這一套還是很有必要的。
2.x
首先,對於2.x中的響應式原理,官網已經有很詳細的解釋了,這裏我們拋開整個響應式流程,只從JS的角度去研究一下。
let a = 2, obj = {}
Object.defineProperty(obj,'a',{
get(){console.log('get');return a},
set(v) { a = v;console.log('set')}
})
obj.a
VM766:2 get
2
obj.a = 3
VM766:3 set
3
借用此特性,我們可以實現一個Vue的響應式更新的函數:
function bindReactive(target, key, value){
Object.defineProperty(target,key,{
get(){return value},
set(val) {
value = val
render()
}
})
}
function reactive(t){
if(typeof t!=='object') return t
for(let key in t){
bindReactive(t,key,t.key)
}
}
reactive(t)
上面只是簡化版的,爲了能夠遞歸的構建響應式數據,還需要實現遞歸。當然這樣的計算量也會很大,這也是2.x的一個很大的缺點。
function bindReactive(target, key, value){
reactive(value)
Object.defineProperty(target,key,{
get(){return value},
set(val) {
reactive(value)
value = val
render()
}
})
}
function reactive(t){
if(typeof t!=='object') return t
for(let key in t){
bindReactive(t,key,t.key)
}
}
reactive(t)
同時,如果我們新增屬性和刪除屬性也不會走Object.defineProperty。
最後對於數組,我們這樣做是不成的,需要去重新定義數組原型。
const proto = Array.prototype
const arrProto = Object.create(proto)
arrProto['push'] = function(){
render()
proto['push'].call(this,...arguments)
}
vue 3.0
新版本的響應式更新是一大看點,使用到了Proxy和Reflect。
let handle = {get:function(t,k){console.log('get');return t[k]}}
let p = new Proxy({},handle)
> undefined
p.a=3
> 3
p.a
> get
> 3
p
> Proxy {a: 3}
關於reflect,這裏有個小例子:
// 老寫法
'assign' in Object // true
// 新寫法
Reflect.has(Object, 'assign') // true
兩種寫法是等價的,即把Object的操作變成函數式的行爲,和proxy放在一塊使用。
因此基於這個我們實現一套新的響應式:
function reactive(target){
const handler = {
get(target, key){
const result= Reflect.get(target,key)
return result
}
set(target,key,value){
const result = Reflect.set(target, key, val)
// 其他邏輯,包括渲染
return result
}
}
return new Proxy(target, handler)
}
這樣既拜託了了遞歸的不足,數組無法響應式的問題,同時也可以實現屬性的增刪(set包括增刪的功能,還有deleteProperty方法,代碼並沒有體現),以及是否增刪成功的問題(通過Reflect和返回的result)。
唯一的問題就是兼容性,畢竟proxy是沒有辦法polyfill的。