1.前言1.前言1.前言随着 vue3.x 的消息越来越多, proxy 的讨论也。相对于 Object.defineProperty , proxy 有什么区别,有什么优势,以及可以应用在什么地方。该文章就简单的介绍下vue3.xproxyObject.definePropertyproxy2.Object.defineProperty2.Object.defineProperty2.Object.defineProperty讲 proxy 之前,先回顾下 Object.defineProperty 。大家都知道, vue2.x 以及之前的版本是使用 Object.defineProperty 实现数据的双向绑定的,至于是怎样绑定的呢?下面简单实现一下proxyObject.definePropertyvue2.xObject.defineProperty
function observer(obj) {

if (typeof obj ==='object') {

for (let key in obj) {

defineReactive(obj, key, obj[key])

}

}
}

function defineReactive(obj, key, value) {

//针对value是对象,递归检测

observer(value)

//劫持对象的key

Object.defineProperty(obj, key, {

get() {

console.log('获取:'+key)

return value

},

set(val) {

//针对所设置的val是对象

observer(val)

console.log(key+"-数据改变了")

value = val

}

})
}

let obj={

name:'守候',

flag:{

book:{

name:'js',

page:325

},

interest:['火锅','旅游'],

}
}

observer(obj)
function observer(obj) {

if (typeof obj ==='object') {

for (let key in obj) {

defineReactive(obj, key, obj[key])

}

}
}

function defineReactive(obj, key, value) {

//针对value是对象,递归检测

observer(value)

//劫持对象的key

Object.defineProperty(obj, key, {

get() {

console.log('获取:'+key)

return value

},

set(val) {

//针对所设置的val是对象

observer(val)

console.log(key+"-数据改变了")

value = val

}

})
}

let obj={

name:'守候',

flag:{

book:{

name:'js',

page:325

},

interest:['火锅','旅游'],

}
}

observer(obj)在浏览器的 console 执行一下,似乎能正常运行console但是实际上, Object.defineProperty 问题有以下几个Object.defineProperty问题1.删除或者增加对象属性无法监听到问题1.删除或者增加对象属性无法监听到问题1.删除或者增加对象属性无法监听到比如增加一个属性 gender ,由于在执行 observer(obj) 的时候,没有这个属性,所以这个无法监听到。删除的属性也是无法监听到genderobserver(obj)增加属性的时候, vue 需要使用 $set 进行操作, $set 的内部也是使用 Object.defineProperty 进行操作vue$set$setObject.defineProperty问题2.数组的变化无法监听到问题2.数组的变化无法监听到由上图得知,虽然数组属性实际上是修改成功了,但是不能被监听到问题3. 由于是使用递归遍历对象,使用 Object.defineProperty 劫持对象的属性,如果遍历的对象层级比较深,花的时间比较久,甚至有性能的问题Object.defineProperty3.proxy3.proxy3.proxy对于 proxy ,在 mdn 上的描述是: 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)proxy简单来说就是,可以在对目标对象设置一层拦截。无论对目标对象进行什么操作,都要经过这层拦截听上去似乎, proxy 比 Object.defineProperty 要好用,并且简单很多,实际上就是如此。下面用 proxy 对上面的代码进行改写试下proxyObject.defineProperty
function observerProxy(obj){

let handler = {

get (target, key, receiver) {

console.log('获取:'+key)

// 如果是对象,就递归添加 proxy 拦截

if (typeof target[key] === 'object' && target[key] !== null) {

return new Proxy(target[key], handler)

}

return Reflect.get(target, key, receiver)

},

set (target, key, value, receiver) {

console.log(key+"-数据改变了")

return Reflect.set(target, key, value, receiver)

}

}

return new Proxy(obj, handler)
}


let obj={

name:'守候',

flag:{

book:{

name:'js',

page:325

},

interest:['火锅','旅游'],

}
}

let objTest=observerProxy(obj)
function observerProxy(obj){

let handler = {

get (target, key, receiver) {

console.log('获取:'+key)

// 如果是对象,就递归添加 proxy 拦截

if (typeof target[key] === 'object' && target[key] !== null) {

return new Proxy(target[key], handler)

}

return Reflect.get(target, key, receiver)

},

set (target, key, value, receiver) {

console.log(key+"-数据改变了")

return Reflect.set(target, key, value, receiver)

}

}

return new Proxy(obj, handler)
}


let obj={

name:'守候',

flag:{

book:{

name:'js',

page:325

},

interest:['火锅','旅游'],

}
}

let objTest=observerProxy(obj)也是一样的效果而且,能做到 Object.defineProperty 做不到的事情,比如增加一个属性 gender ,能够监听到Object.definePropertygender操作数组,也能监听到最后敲一下黑板,简单总结一下两者的区别1. Object.defineProperty 拦截的是对象的属性,会改变原对象。 proxy 是拦截整个对象,通过 new 生成一个新对象,不会改变原对象。Object.definePropertyproxy2. proxy 的拦截方式,除了上面的 get 和 set ,还有 11 种。选择的方式很多 Proxy ,也可以监听一些 Object.defineProperty 监听不到的操作,比如监听数组,监听对象属性的新增,删除等。proxyObject.defineProperty4.proxy 使用场景4.proxy 使用场景4.proxy 使用场景关于 proxy 的使用场景,受限于篇幅,这里就简单列举几个,的可以移步我的 github 笔记或者 mdn 。proxygithub mdn 看到这里,两者的区别,和 proxy 的优势已经知道个大概了。但是在开发上,有哪些场景可以使用到 proxy 呢,下面列举个可能会遇上的情况proxyproxy4-1.负索引数组4-1.负索引数组在使用 splice(-1) , slice(-1) 等 API 的时候,当输入负数的时候,会定位到数组的最后一项,但是在普通数组上,并不能使用负数。 [1,2,3][-1] 这个代码并不能输出 3 。要让上面的代码输出 3 , 也可以使用 proxy 实现。splice(-1)slice(-1)[1,2,3][-1]proxy
let ecArrayProxy = {
get (target, key, receiver) {

let _index=key<0?target.length+Number(key):key

return Reflect.get(target, _index, receiver)
}
}
let arr=new Proxy([1,2,3],ecArrayProxy)
let ecArrayProxy = {
get (target, key, receiver) {

let _index=key<0?target.length+Number(key):key

return Reflect.get(target, _index, receiver)
}
}
let arr=new Proxy([1,2,3],ecArrayProxy) 4-2.表单校验4-2.表单校验在对表单的值进行改动的时候,可以在 set 里面进行拦截,判断值是否合法set
let ecValidate = {
set (target, key, value, receiver) {

if (key === 'age') {

//如果值小于0,或者不是正整数

if (value<0||!Number.isInteger(value)) {

throw new TypeError('请输入正确的年龄');

}

}

return Reflect.set(target, key, value, receiver)
}
}

let obj=new Proxy({age:18},ecValidate)
obj.age=16
obj.age='少年'
let ecValidate = {
set (target, key, value, receiver) {

if (key === 'age') {

//如果值小于0,或者不是正整数

if (value<0||!Number.isInteger(value)) {

throw new TypeError('请输入正确的年龄');

}

}

return Reflect.set(target, key, value, receiver)
}
}

let obj=new Proxy({age:18},ecValidate)
obj.age=16
obj.age='少年' 4-3.增加附加属性4-3.增加附加属性比如有一个需求,保证用户输入正确身份证号码之后,把出生年月,籍贯,性别都添加进用户信息里面众所周知,身份证号码第一和第二位代表所在省(自治区,直辖市,特别行政区),第三和第四位代表所在市(地级市、自治州、盟及国家直辖市所属市辖区和县的汇总码)。第七至第十四位是出生年月日。低17位代表性别,男单女双。
const PROVINCE_NUMBER={

44:'广东省',

46:'海南省'
}
const CITY_NUMBER={

4401:'广州市',

4601:'海口市'
}

let ecCardNumber = {
set (target, key, value, receiver) {

if(key === 'cardNumber'){

Reflect.set(target, 'hometown', PROVINCE_NUMBER[value.substr(0,2)]+CITY_NUMBER[value.substr(0,4)], receiver)

Reflect.set(target, 'date', value.substr(6,8), receiver)

Reflect.set(target, 'gender', value.substr(-2,1)%2===1?'男':'女', receiver)

}

return Reflect.set(target, key, value, receiver)
}
}
let obj=new Proxy({cardNumber:''},ecCardNumber)
const PROVINCE_NUMBER={

44:'广东省',

46:'海南省'
}
const CITY_NUMBER={

4401:'广州市',

4601:'海口市'
}

let ecCardNumber = {
set (target, key, value, receiver) {

if(key === 'cardNumber'){

Reflect.set(target, 'hometown', PROVINCE_NUMBER[value.substr(0,2)]+CITY_NUMBER[value.substr(0,4)], receiver)

Reflect.set(target, 'date', value.substr(6,8), receiver)

Reflect.set(target, 'gender', value.substr(-2,1)%2===1?'男':'女', receiver)

}

return Reflect.set(target, key, value, receiver)
}
}
let obj=new Proxy({cardNumber:''},ecCardNumber) 4-4.数据格式化4-4.数据格式化比如有一个需求,需要传时间戳给到后端,但是前端拿到的是一个时间字符串,这个也可以用 proxy 进行拦截,当得到时间字符串之后,可以自动加上时间戳。proxy
let ecDate = {
set (target, key, value, receiver) {

if(key === 'date'){

Reflect.set(target, 'timeStamp', +new Date(value), receiver)

}

return Reflect.set(target, key, value, receiver)
}
}
let obj=new Proxy({date:''},ecDate)
let ecDate = {
set (target, key, value, receiver) {

if(key === 'date'){

Reflect.set(target, 'timeStamp', +new Date(value), receiver)

}

return Reflect.set(target, key, value, receiver)
}
}
let obj=new Proxy({date:''},ecDate) 参考链接参考链接参考链接ProxyProxy面试官: 实现双向绑定Proxy比defineproperty优劣如何?面试官: 实现双向绑定Proxy比defineproperty优劣如何?简单通俗的理解Vue3.0中的Proxy简单通俗的理解Vue3.0中的Proxy小结小结小结proxy 和 Object.defineproperty 的一些区别,以及 proxy 的优势,使用场景,暂时就介绍到这里了。这篇文章介绍的不算深入,理解圈起来不难。对于 proxy ,笔者也打算继续深入学习,如果往后有收获,也会第一时间分享。如果文章有什么错误,或者有什么建议,欢迎评论区留言。proxyObject.definepropertyproxy