04-vue3为什么用Proxy API替代 defineProperty API

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。04-vue3为什么用Proxy API替代 defineProperty API,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、Object.defineProperty

定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

为什么能实现响应式:
通过 defineProperty 两个属性,get 和 set

  • get

属性的getter函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象 (由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值

  • set

属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined

定义一个响应式函数defineReactive

function update(){
	app.innerText = obj.foo
}
function defineReactive(obj,key,val){
	Object.defineProperty(obj,key,{
		get(){
			console.log(`get ${key}:${val}`);
			return val	
		},
		set(newVal){
			if(newVal !== val){
				val = newVal
				update()	
			}	
		}
	})
}

调用 definReactive,数据发生变化触发 update方法,实现数据响应式

const obj = {}
definReactive(obj,'foo','')
setTimeout(()=>{
	obj.foo = new Date().toLocaleTimeString()
},1000)

在对象存在多个 key 情况下,需要进行遍历

function observe(obj){
	if(typeof obj !== 'object' || obj == null){
		return	
	}
	Object.keys(obj).forEach(key =>{
		defineReactive(obj,key,obj[key])	
	})
}

如果存在嵌套对象的情况,还需要在defineReactive中进行递归

function defineReactive(obj,key,val){
	observe(val)
	Object.defineProperty(obj,key,{
		get(){
			console.log(`get ${key}:${val}`);
			return val
		},
		set(newVal){
			if(newVal !== val){
				val = newVal
				update()	
			}	
		}
	})
}

当给 key 赋值为对象的时候,还需要在 set 属性中进行递归

set(newVal){
	if(newVal !== val){
		observe(newVal) //新值是对象的情况
		notifyUpdate()	
	}
}

上述例子能够实现对一个对象的基本响应式,但仍然存在诸多问题

现在对一个对象进行删除与添加属性操作,无法劫持到

const obj = {
	foo:"foo",
	bar:"bar"
}
observe(obj)
delete obj.foo //不可以
obj.jar = 'xxx' // 不可以

当我们对一个数组进行监听的时候,并不那么好使了

const arrData = [1,2,3,4];
arrData.forEach((val,index)=>{
	defineProperty(arrData,index,val)
})
arrData.push();// 不可以
arrData.pop();// 不可以
arrDate[0] = 99;// 可以

数组的 api无法劫持到,从而无法实现数据响应式,故增加了 set / delete API ,并且对数组 api 方法进行一个重写。

还有一个问题是,如果存在深层的嵌套对象关系,需要深层的监听,造成性能的浪费

小结

  • 检测不到对象属性的添加和删除
  • 数组API方法无法监听到
  • 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

二、proxy

Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了

定义一个响应式方法reactive

function reactive(obj){
	if(typeof obj !== 'object' && obj != null){
		return obj	
	}
	//Proxy相当于在对象外层加拦截
	const observed = new Proxy(obj,{
		get(target,key,receiver){
			const res = Reflect.get(target,key,receiver);
			console.log(`获取 ${key}:${res}`)
			return res
		},
		set(target,key,value,receiver){
			const res = Reflect.set(target,key,value,receiver)
			console.log(`设置${key}:${value}`)
			return res
		},
		deleteProperty(target,key){
			const res = Reflect.deleteProperty(target,key)
			console.log(删除 `${key}:${res}`)
			return res
		}	
	})
	return observed
}

测试:

const state = reactive({
	foo:'foo'
})
// 获取
state.foo // 可以
state.foo = 'test' // 可以
state.done = 'true' //可以
delete state.done // 可以

嵌套对象情况,有问题

const state = reactive({
	bar:{a:1}
})
state.bar.a = '我是嵌套的值' // 不可以

可以在 get 之上再进行一层代理解决

function reactive(obj){
	if(typeof obj !== 'object' && obj != null){
		return obj	
	}
	const observed = new Proxy(obj,{
		get(target,key,receiver){
			const res = Reflect.get(target,key,receiver)
			console.log(`获取${key}:${res}`)
			return isObject(res) ? reactive(res) : res
		},
		return observed
	})
}

三、 总结

Object.defineProperty 只能遍历对象属性进行劫持

function observe(obj){
	if(typeof obj !== 'object' && obj != null){
		return obj	
	}
	Object.keys(obj).forEach(key =>{
		defineReactive(obj,key,obj[key])	
	})
}

Proxy 直接可以劫持整个对象,并返回一个新对象,可以只操作新的对象达到响应式目的

function reactive(obj){
	if(typeof obj !== 'object' && obj != null){
		return obj	
	}
	const observed = new Proxy(obj,{
		get(t,k,r){
			const res = Reflect.get(t,k,c)
			console.log(`获取${k}:${res}`)
			return res
		},
		set(t,k,v,r){
			const res = Reflect.set(t,k,v,c)
			console.log(`设置${k}:${value}`)
			return res
		},
		deleteProperty(t,k){
			const res = Reflect.deleteProperty(t,k)
			console.log(`删除${k}:${res}`)
			return res
		}	
	})
	return observed 
}

Proxy 可以直接监听数组的变化(push/shift/splice)

const obj = [3,2,1]
const proxyObj = reactive(obj)
obj.push(5)

Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的

正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外set、delete方法)

// 数组重写
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
  arrayProto[method] = function () {
    originalProto[method].apply(this.arguments)
    dep.notice()
  }
});

// set、delete
Vue.set(obj,'bar','newbar')
Vue.delete(obj),'bar')

Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/163296.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!