造轮子学习之旅day2-数据响应式

356 阅读2分钟

学无止境 每日反思

今日主题: 数据响应式

什么是响应式

数据模型发生变化可以发出响应(处理逻辑) 在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

详情见MDN

Reflect是一个内置的对象,提供拦截JavaScript的方法,它不是一个函数对象,因此它是不可构造的,它所有的属性和方法都是静态的。

参考文章