这是我参与更文挑战的第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 -
setsetter函数,当属性被修改时,会调用此函数,不能与writable同时使用。 -
getgetter函数,当属性被访问时,会调用此函数,不能与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是各种函数。
支持以下方法:
getgetter函数 当属性被访问时,会调用此函数setsetter函数 当属性被修改时,会调用此函数has当使用in操作符时会调用此函数getPrototypeOf获取原型链,拦截Object.getPrototypeOfsetPrototypeOf设置原型链,拦截Object.setPrototypeOfisExtensible判断对象是否可以扩展,拦截Object.isExtensiblepreventExtensions设置对象不可扩展,拦截Object.preventExtensionsgetOwnPropertyDescriptor返回属性的属性描述,拦截Object.getOwnPropertyDescriptordefineProperty定义属性,拦截Object.definePropertydeleteProperty删除属性,拦截delete操作符ownKeys返回一个由可枚举元素组成的数组, 拦截Object.getOwnPropertyNames和Object.getOwnPropertySymbolsapply拦截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多很多,希望今天讲的对你们有帮助~