本文主要是详细了解下
Object.defineProperty和new Proxy这两个方法。旨在了解Vue2和Vue3关于数据代理的区别。
Ojbect.defineProperty
它用于在对象中定义一个新的属性,或修改原有属性的值。该方法提供了对属性描述符的控制,以下是它的一些属性:
value
属性的值
const obj = {}
Object.defineProperty (obj, "property", {
value: 20
})
console.log (obj) // { property: 20 }
writable
若为
false,属性的值不可以被修改
const obj = {}
Object.defineProperty (obj, "property", {
value: 20,
writable: false
})
obj.property = 30
console.log (obj) // { property: 20 }
我们将writable设置为false,可以看到对property属性的修改无效
configurable
若为
false,属性的描述符一旦被定义则不可以更改,该属性也不可以被删除。
const obj = {}
Object.defineProperty (obj, "property", {
value: 20,
configurable: false
})
// 将 configurable设置为false, 我们再对属性描述符进行修改
Object.defineProperty (obj, "property", {
value: 30
})
console.log (obj)
上述案例:首次将 configurable属性设置为false,再次对它的属性描述符进行修改的时候,会报错:Cannot redefine property: property at Function.defineProperty (anonymous)
enumerable
若为
false,则使用for...in和Object.keys()遍历不出该属性
const obj = {
name: "Sun",
sex: "男"
}
Object.defineProperty (obj, "age", {
value: 20,
enumerable: false
})
const arr = Object.keys(obj)
console.log (arr) // [name, sex]
get、set
给属性提供
getter、和setter的方法。当访问属性时会调用get方法,当属性被修改时set方法会被调用
const obj = {}
Object.defineProperty (obj, "property", {
get () {
return this._
},
set (newValue) {
this._ = newValue * 2
}
})
obj.property = 20
console.log (obj.property) // 40
打印结果:40
进行数据代理(没有封装数组方法)
let obj = {
name: "Sun",
age: 35,
sex: "男",
children: {
name: "S",
age: 10,
sex: "男"
},
arr: [1, 2, 3, 4, 5]
}
function reactive (obj) {
for (let key in obj) {
if (typeof obj[key] == 'object' && obj[key] != null) {
reactive (obj[key])
}
defineReactive (obj, key, obj[key])
}
}
function defineReactive (obj, key, value) {
Object.defineProperty (obj, key, {
get () {
console.log ("get", key, value)
return value
},
set (newValue) {
console.log ("set", key, newValue)
value = newValue
}
})
}
reactive (obj)
obj.children.name = "U"
obj.arr.push (10)
打印结果
/**
get children {}
set name U
get arr (5) [(...), (...), (...), (...), (...)]
get 0 1
get 1 2
get 2 3
get 3 4
get 4 5
*/
上述案例使用Object.defineProperty对obj进行深度代理。我们可以看到当使用数组中的push方法时,没有走set方法。这也是Object.defineProperty的一个不好的点,对数组不兼容,因此Vue2才会对数组的方法进行进一步的封装:push、pop、shift、unshift、splice、sort、reverse
注意:在Vue2中,我们使用数组的索引更改其值或删除对象的属性时,无法触发视图的更新。为了解决它,Vue提供了$set、$delete方法
Proxy
是
ES6引入的一个新的特性,它允许我们创建一个对象的代理(proxy),从而能够拦截和自定义对象的基本操作(如:查找,赋值,遍历)。它返回一个新的代理对象。注意:它通常结合
Reflect内置对象一起使用。为什么要一起使用?
- 统一性:
Reflect和Proxy二者的方法相对应- 返回值:与
Proxy中方法所需的返回值一致。如:Reflect.get成功时返回属性值,失败返回undefined
基本语法
let proxy = new Proxy (target, handler)
上述案例:target是要代理的对象,handler也是一个对象,它的属性是当执行一个操作时定义代理的行为的函数。常见的函数如下:
get
拦截对象属性的读取
set
拦截对对象属性的设置
has
拦截
in操作符
let obj = {
name: "Sun",
age: 20,
sex: "男"
}
let proxy = new Proxy (obj, {
has(target, key) {
// key == "age"不参与遍历
return key != "age"
}
})
for (let key in proxy) {
console.log (key, proxy[key])
}
deleteProperty
拦截delete操作符
此外还有一些:apply(拦截函数的调用)、construct(拦截 new 操作符).....
Proxy数据代理
let data = {
name: "张三",
age: 30,
sex: "男",
children: {
name: "张小三",
age: 10
},
arr: [1, 2, 3 ,4]
};
function reactive (obj) {
let handler = {
get (target, key) {
let value = Reflect.get (target, key)
console.log ("get: ", target, key)
return (typeof value == "object" && value != null)? reactive(value): value
},
set (target, key, newValue) {
console.log ("set: ", target, key, newValue)
const res = Reflect.set (target, key, newValue)
return res
}
}
return new Proxy (obj, handler)
}
let proxy = reactive (data)
proxy.children.age = 5
proxy.arr.push (5)
console.log (proxy)
/**
打印结果:
get: {name: '张三', age: 30, sex: '男', children: {…}, arr: Array(4)} children
set: {name: '张小三', age: 10} age 5
get: {name: '张三', age: 30, sex: '男', children: {…}, arr: Array(4)} arr
get: (4) [1, 2, 3, 4] push
get: (4) [1, 2, 3, 4] length
set: (4) [1, 2, 3, 4] 4 5
set: (5) [1, 2, 3, 4, 5] length 5
{
"name": "张三",
"age": 30,
"sex": "男",
"children": {
"name": "张小三",
"age": 5
},
"arr": [
1,
2,
3,
4,
5
]
}
*/
上述案例中我们可以看到使用push方法可以对数组的元素进行添加,表明Proxy对数组兼容还是很友好的。