Object.defineProperty()
1. 基本用法
作用:对一个对象定义新属性,或修改现有属性
let singo = {}
let singoName = 'XM'
//singo对象添加属性ite,值为singoName
Object.defineProperty(singo, 'ite', {
//默认不可枚举且for in打印打印不出来,可设置:enumerable: true
//默认不可以修改,可设置:wirtable:true
//默认不可以删除,可设置:configurable:true
get: function () {
console.log('调用get')
return singoName
},
set: function (val) {
console.log('调用set')
singoName = val
}
})
//当读取singo对象的namp属性时,触发get方法
console.log(singo.ite)
//singo.ite发现修改成功
singoName = 'XK'
console.log(singo.ite)
// 对singo.ite进行修改,触发set方法
singo.ite = 'XH'
console.log(singo.ite)
我们成功监听了singo上的ite
2.监听多个属性
上面我们只监听单个属性,但是我们是实战项目中会经常会监听多个属性,这时可以通过Object.keys(obj) or (for in)来返回obj对象身上的所有可枚举属性,方法效果这里就不在展示,同时在新增一个方法(obsever),用来让get中return的值并不是直接访问obj[key]而是val。
let singo = {
name: '',
age: 0
}
function defineProperty(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`访问${key}`)
return val
},
set(newVal) {
console.log(`${key}被修改为${newVal}`)
val = newVal
}
})
}
// observer
function observer(obj) {
Object.keys(obj).forEach((key) => {
defineProperty(obj, key, obj[key])
})
}
observer(singo)
console.log(singo.age)
singo.age = 20
console.log(singo.age)
3.深度监听
上述代码基础上,加上递归就可以解决,可以看到observer方法就是我们的监听函数,我们想要的效果是将一个对象传入传入就能对其监视,那我们在defineProperty()函数中添加一个递归判断对象是属性是否也是一个对象
function defineProperty(obj, key, val) {
//如果当前属性也是一个对象,递归进入该对象,进行监听
if(typeof val === 'object'){
observer(val)
}
Object.defineProperty(obj, key, {
get() {
console.log(`访问了${key}属性`)
return val
},
set(newVal) {
// 如果newVal是一个对象,递归进入该对象进行监听
if(typeof newVal === 'object'){
observer(key)
}
console.log(`${key}被修改为${newVal}`)
val = newVal
}
})
}
observer中也需要添加递归停止条件
function Observer(obj) {
//如果传入的不是一个对象,return
if (typeof obj !== "object" || obj === null) {
return
}
Object.keys(obj).forEach((key) => {
defineProperty(obj, key, obj[key])
})
}
4.数组监听
那么如果对象的属性是一个数组呢
let arr = [1, 2, 3]
let obj = {}
Object.defineProperty(obj, 'arr', {
get() {
console.log('get arr')
return arr
},
set(newVal) {
console.log('set', newVal)
arr = newVal
}
})
console.log(obj.arr)//输出get arr [1,2,3] 正常
obj.arr = [1, 2, 3, 4] //输出set [1,2,3,4] 正常
obj.arr.push(3) //输出get arr 不正常,监听不到push
在数组中通过索引访问或者修改数组中已经存在的元素,是可以出发get和set的,但是对于通过push、unshift增加的元素,会增加一个索引,这种情况需要手动初始化,新增加的元素才能被监听到,在Vue2.x中,通过重写Array原型上的方法解决了这个问题。
Proxy
在上面的讲述中,我们还有问题没有解决:那就是当我们要给对象新增加一个属性时,也需要手动去监听这个新增属性。
也正是因为这个原因,使用vue给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。
可以看到,通过Object.definePorperty()监听数组是比较复杂的,需要很多手动处理,这也是为什么在Vue3.0转而采用Proxy。接下来让我们一起看一下Proxy是怎么解决这些问题的。
1.基本使用
语法:const a = new Proxy(target, handler)
Proxy对象由两个部分组成:target、handler
-
target:目标对象 -
handler:是一个对象,声明了代理target的指定行为,支持的拦截操作,一共13种 -
Proxy支持的拦截操作
-
get(target,propKey,receiver):拦截对象属性的读取。
-
set(target,propKey,value,receiver):拦截对象属性的设置,返回一个布尔值(修改成功)。
-
has(target,propKey):拦截 propKey in proxy 的操作,返回一个布尔值。
-
deleteProterty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
-
ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
-
getOwmPropertyDescript(target,propKey):拦截Object.getOwnPropertyDescriptor(proxy,propKey),返回属性的描述对象。
-
defineProperty(target,propKey,propDesc):拦截Object.defineProperty(proxy,propKey,propDesc)、Object.defineProperties(proxy,propDesc),返回一个布尔值。
-
preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
-
getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
-
isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
-
setPrototypeOf(target,proto): 拦截Object.setPrototypeOf(proxy,proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
-
apply(target,object,args):拦截Proxy实例作为函数调用的操作,比如proxy(...args)、proxy.call(object,...args)、proxy.apply(...)。
-
construct(target,args):拦截Proxy实例作为构造函数调用的操作,比如:new proxy(...args)
通过Proxy,我们可以对设置代理的对象上的一些操作进行拦截,外界对这个对象的各种操作,都要先通过这层拦截。(和defineProperty差不多)
// 需要代理的对象
let singo = {
age: 10,
school: '西南大学'
}
let hander = {
get(obj, key) {
// 如过存在就返回属性值,如果没有,就返回默认值''
return key in obj ? obj[key] : ''
},
set(obj, key, val) {
obj[key] = val
return true
}
}
//把handler对象传入Proxy
let proxyObj = new Proxy(singo, hander)
// get
console.log(proxyObj.age)//输出10
console.log(proxyObj.school)//输出西电
console.log(proxyObj.name)//输出默认值''
// set
proxyObj.age = 20
console.log(proxyObj.age)//输出20 修改成功
Proxy代理的是整个对象,而不是对象的某个特定属性,之前我们在使用Object.defineProperty()给对象添加一个属性之后,我们对对象属性的读写操作仍然在对象本身,但是一旦使用Proxy,如果想要读写操作生效,我们就要对Proxy的实例对象proxyObj进行操作。
2.数组使用Proxy
let subject = ['数学']
let handler = {
get(obj, key) {
return key in obj ? obj[key] : '暂无学科'
}, set(obj, key, val) {
obj[key] = val
//set方法成功时应该返回true,否则会报错
return true
}
}
let proxyObj = new Proxy(subject, handler)
// get和set
console.log(proxyObj)//输出 [ '数学' ]
console.log(proxyObj[1])//输出 暂无学科
proxyObj[0] = '语文'
console.log(proxyObj)//输出 [ '语文' ]
// push增加的元素能否被监听
proxyObj.push('英语')
console.log(proxyObj)//输出 [ '语文', '英语' ]