这是我参与更文挑战的第27天,活动详情查看: 更文挑战
前言
vue3已经发布很久了,我们知道vue3在这个版本最大的变化是更改了双向绑定的实现,把defineProperty
改成了proxy
。那为什么vue要做这样的改变?proxy究竟有何优势?今天好好来说说。
defineProperty
es5新增了defineProperty
, 可以对一个对象新增一个属性或者修改某个属性,然后返回该对象。
语法
Object.defineProperty(obj, prop, descriptor)
obj
目标对象
prop
属性名,一般是字符串,也可以是symbol
descriptor
对属性名的属性描述,对象格式
-
value
: 属性的值 -
writable
: 属性是否可被赋值, 默认是false
-
enumerable
: 属性是否可被枚举,默认是false
-
configurable
属性是否可被配置,可删除,默认是false
-
set
setter函数,当属性被修改时,会调用此函数,不能与writable
同时使用。 -
get
getter函数,当属性被访问时,会调用此函数,不能与value
同时使用。
例子
// 针对name做getter/setter
let obj = Object.defineProperty({}, 'name', {
get () {
return '答案cp3'
},
set () {
console.log('setter函数被调用了!')
}
})
console.log(obj.name) // 答案cp3
obj.name = 'test' // setter函数被调用了!
可以看到,我们定义了set
函数和get
函数 获取属性的时候调用get
函数, 设置属性的时候调用set
函数。
proxy
es6新增了proxy
, 实现对象的代理,对基本操作(增删改查等)的拦截。
语法
new Proxy(target, handler)
target
需要代理的目标对象
handler
对代理对象的各种操作行为,对象形式,key是各种函数。
支持以下方法:
get
getter函数 当属性被访问时,会调用此函数set
setter函数 当属性被修改时,会调用此函数has
当使用in操作符时会调用此函数getPrototypeOf
获取原型链,拦截Object.getPrototypeOf
setPrototypeOf
设置原型链,拦截Object.setPrototypeOf
isExtensible
判断对象是否可以扩展,拦截Object.isExtensible
preventExtensions
设置对象不可扩展,拦截Object.preventExtensions
getOwnPropertyDescriptor
返回属性的属性描述,拦截Object.getOwnPropertyDescriptor
defineProperty
定义属性,拦截Object.defineProperty
deleteProperty
删除属性,拦截delete
操作符ownKeys
返回一个由可枚举元素组成的数组, 拦截Object.getOwnPropertyNames
和Object.getOwnPropertySymbols
apply
拦截target
是函数的调用construct
拦截new
操作符,这要求target
必须是函数。
这些方法名和
Reflect
对象的方法名是一样的,我们可以在内部直接调用Reflect
.
例子
// 基本用法
let obj = new Proxy({}, {
get () {
return '答案cp3'
},
set () {
console.log('setter函数被调用了!')
}
})
// 对{}做代理
console.log(obj.name) // 答案cp3
obj.name = 'test' // setter函数被调用了!
// 模拟私有属性
// 使用 has 方法隐藏某些属性,不被 in 运算符发现
var handler = {
get (target, key) {
if (key.startsWith('_')) {
console.error('不支持访问私有属性!')
return false
}
return Reflect.get(target, key)
},
set (target, key, val) {
if (key.startsWith('_')) {
console.error('不支持设置私有属性!')
return false
}
return Reflect.set(target, key, val)
},
has (target, key) {
if (key.startsWith('_')) {
console.error('不支持检测私有属性!')
return false
}
return Reflect.has(target, key)
}
};
var target = { _name: '我是私有属性', name: '答案cp3'};
var proxy = new Proxy(target, handler);
console.log('_name' in proxy); // false
proxy._name = 123; // false
console.log(proxy._name); // false
console.log('name' in proxy); // true
proxy.name = 123; // true
console.log(proxy.name); // 123
区别
下面来讲讲二者在vue的区别:
-
defineProperty
只能针对定义好的属性进行监听,无法对新增属性或者删除属性监听,而proxy
是对整个对象进行监听,新增属性或者删除属性都能响应。下面用简单代码展示:
let name = '答案cp3' let obj = { name } for (let key in obj) { if (obj.hasOwnProperty(key)) { Object.defineProperty(obj, key, { get () { console.log('get函数被调用了') return name }, set (val) { console.log('set函数被调用了') name = val } }) } } obj.name = 'cp3' // set函数被调用了 obj.age = 18 // 没有打印set函数被调用了 console.log(obj.name) // get函数被调用了 console.log(obj.age) // 没有打印
可以看到 新增的
age
属性是没有defineProperty
成功的,所以vue只能提供$set
方法来处理。对于
proxy
,就没有这个问题。通过例子来看看let name = '答案cp3' let obj = new Proxy({ name }, { get (target, key) { console.log('get函数被调用了') return target[key] }, set (target, key, val) { console.log('set函数被调用了') target[key] = val } }) obj.name = 'cp3' // set函数被调用了 obj.age = 18 // set函数被调用了 console.log(obj.name) // get函数被调用了 console.log(obj.age) // get函数被调用了
-
vue的
defineProperty
没有实现对数组的监听,只提供了push
,pop
,sort
,reserve
,splice
,unshift
,shift
等七种方法能触发数组监听。proxy
是对整个对象进行监听,新增元素或者删除元素都能响应。let arr = new Proxy([1,2,3], { get (target, key) { console.log('get函数被调用了') return Reflect.get(target, key) }, set (target, key, val) { console.log('set函数被调用了') return Reflect.set(target, key, val) } }) console.log(arr[0]) // get函数被调用了 arr[3] = 4 // set函数被调用了 arr.push(5)// set函数被调用了
-
另外对于
defineProperty
,只有2个get
,set
方法,而proxy
上方法高达13种,基本能满足各种需求。 -
proxy
有个不好的问题,就是兼容性不好,因为它是es6新出的api。
最后
今天简单讲了下vue3的proxy与vue2的defineProperty的对比,proxy能实现的功能比defineProperty多很多,希望今天讲的对你们有帮助~