学无止境 每日反思
今日主题: 数据响应式
什么是响应式
数据模型发生变化可以发出响应(处理逻辑) 在MVVM中ViewModel的需求是数据变化后视图相应作出响应
功能实现
Vue使用的数据劫持方式,Vue2使用的是DefineProperty,一次劫持一个属性,Vue3使用响应式原理是Proxy,可以一次劫持一个对象。
Proxyh/Reflect是在ES2015规范中加入的,Proxy可以更好的拦截对象行为,Reflect可以更优雅的操作对象。
Proxy和Reflect的优势
- 针对整个对象定制,不需要对象keys进行遍历
- 支持数组,使用DefineProperty是需要拦截数组的方法,进行重载
- Proxy的第二个参数有13中拦截方法,更加丰富
- Proxy作为新标准收到浏览器厂商的关注和优化
- 可以通过递归进行对象嵌套
DefineProperty(Vue2)
Vue2中通过Object.defineProperty重新定义getter和setter方法实现。
let effective
function effect(fun){
effective = func
}
function reactive(data){
if(typeof data !== 'object' || data === null){
return data
}
Object.keys(data).forEach(function(key)){
let value = data[key]
reactive(value) // 递归调用,性能上有损失
Object.defineProperty(data,key,{
enumerable: false,
configurable: true,
get: ()=>{
return value
},
set: newVal => {
if(newVal !== value){
effective()
value = newVal
}
}
})
}
return data
}
module.exports = {
effect, reactive
}
数组响应式实现原理
通过函数劫持的方式解决
const oldArrayPrototype = Array.prototype
// Object.create()方法创建一个新对象,使用现有的对象来提供创建新对象的__proto__
const proto = Object.create(oldArrayProperty)
const arrFun = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
arrFun.forEach(method=> {
// 劫持数组方法
proto[method] => function(){
effective()
oldArrayPrototype[method].call(this,...arguments)
}
})
if(Array.isArray(data)){
data.__proto__ = proto
}
Proxy(Vue3)
Vue3使用ES6的Proxy方式,对于深层监听不需要使用递归。当get时判断值为对象时将对象将对象做响应式处理返回即可。
function reactive(data){
if(typeof data !== 'object' || data === null){
return data
}
const observed = new Proxy(data, {
get(target, key, receiver){
let result = Reflect.get(target, key, receiver)
return typeof !== 'object' ? result: reactive(result)
},
set(target, key, value, receiver){
effective()
const ret = Reflect.set(target, key, value, receiver)
return ret
},
deleteProperty(target, key){
const ret = Reflect.deleteProperty(target, key)
return ret
}
})
return observed
}
补充 Reflect
Reflect是一个内置的对象,提供拦截JavaScript的方法,它不是一个函数对象,因此它是不可构造的,它所有的属性和方法都是静态的。