前言
Vue是一个mvvm框架,当数据发生变化的时候,视图同时发生变化,当视图发生变化的时候,数据也会跟着变化。
Vue2.x使用Object.defineProperty()实现数据和视图的双向绑定。
Vue3.x使用Proxy实现数据和视图的双向绑定。
defineProperty
Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, key, options)
obj:监听的对象
key:在对象上要新增或者修改的属性
options:新增或者修改属性的配置
options说明
value:默认undefined,可以是任何有效的 JavaScript 值(数值,对象,函数等)。
get:默认undefined,当属性的值被使用的时候调用该函数,该函数的返回值为属性的值,不传入任何参数。
set:默认undefined,当属性值被修改时会调用该函数,函数的参数为被赋予的新值。
writable:默认false,只有writable设置为true时,属性的值才能被赋值运算符改变。
enumerable:默认false,只有enumerable设置为true时,该属性才会出现在对象的枚举属性中。
configurable:默认false,只有configurable设置为true时,options才能够被改变,同时该属性也能从对应的对象上被删除。
注意:如果options不能同时拥有 value 或 writable 和 get 或 set 键,否则会产生异常
示例
1. writable
将writable设置为false
let obj = {}
Object.defineProperty(obj, 'key', {
value: 'value',
writable: false
})
obj.key='newValue'
console.log('obj.key:' + obj.key)
输出 obj.key:value
将writable设置为true,输出 obj.key:newValue
2. enumerable
将enumerable设置为false
let obj = {}
Object.defineProperty(obj, 'key', {
value: 'value',
enumerable: false
})
Object.keys(obj).forEach((item) => {
console.log(item);
});
输出为空
将enumerable设置为true,输出 key
3. configurable
将configurable设置为false
let obj = {}
Object.defineProperty(obj, 'key', {
value: 'value',
configurable: false
})
delete obj.key
console.log('obj.key:' + obj.key)
删除obj的键值key失败,仍然输出为 obj.key:value
let obj = {}
Object.defineProperty(obj, 'key', {
value: 'value',
configurable: false
})
Object.defineProperty(obj, 'key', {
value: 'name'
})
重新给obj定义属性失败:
将configurable设置为true,删除obj的键值key成功,并且能重新给obj定义属性:
let obj = {}
Object.defineProperty(obj, 'key', {
value: 'value',
configurable: true
})
delete obj.key
console.log('obj.key:' + obj.key)
Object.defineProperty(obj, 'key', {
value: 'name'
})
console.log('obj.key:' + obj.key)
输出为:
obj.key:undefined
obj.key:name
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
let p = new Proxy(obj, handler)
obj:监听的对象或者代理
handler:以函数作为属性的对象,属性中的函数分别定义了在执行各种操作时代理的行为
handler说明
handler分别有如下13个属性:
getPrototypeOf:Object.getPrototypeOf 方法的捕捉器
setPrototypeOf:Object.setPrototypeOf 方法的捕捉器
isExtensible:Object.isExtensible 方法的捕捉器
preventExtensions:Object.preventExtensions 方法的捕捉器
getOwnPropertyDescriptor:Object.getOwnPropertyDescriptor 方法的捕捉器
defineProperty:Object.defineProperty 方法的捕捉器
has:in 操作符的捕捉器
get:属性读取操作的捕捉器
set:属性设置操作的捕捉器
deleteProperty:delete 操作符的捕捉器
ownKeys:Object.getOwnPropertyNames方法和Object.getOwnPropertySymbols 方法的捕捉器
apply:函数调用操作的捕捉器
construct:new 操作符的捕捉器
示例
当handler为空的时候,代理的操作全部直接转发到对象上
let obj = {}
let handler = {}
let p = new Proxy(obj, handler )
p.key = 'value'
console.log('obj.key:' + obj.key)
输出 obj.key:value
给handler添加属性,代理的操作对新生成的代理对象生效,原有对象根据handler的属性变化。
let obj = {}
let handler = {
set(target, p, receiver) {
}
}
let p = new Proxy(obj, handler)
p.key = 'value'
console.log('obj.key:' + obj.key)
输出 obj.key:undefined
给handler的set属性增加函数操作:
let obj = {}
let handler = {
set(target, p, receiver) {
return Reflect.set(...arguments);
}
}
let p = new Proxy(obj, handler)
p.key = 'value'
console.log('obj.key:' + obj.key)
输出 obj.key:value
proxy属性示例:
let obj = {}
let p = new Proxy(obj, {
defineProperty: (target, p1, attributes) => {
console.log(target)
console.log(p1)
console.log(attributes)
return true
}
})
Object.defineProperty(p, 'key', {
value: 'value'
})
输出为
{}
key
{ value: 'value' }
defineProperty简单模拟双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>双向绑定</title>
</head>
<body>
<input type="text" id="modelInput">
<div id="modelView"></div>
<script>
let inputDom = document.getElementById('modelInput')
let divDom = document.getElementById('modelView')
let data = {
name: ''
}
inputDom.addEventListener('input', function (e) {
data.name = this.value
})
Object.defineProperty(data, 'name', {
get() {
},
set(newValue) {
divDom.innerText = newValue
}
})
</script>
</body>
</html>
proxy简单模拟双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>双向绑定</title>
</head>
<body>
<input type="text" id="modelInput">
<div id="modelView"></div>
<script>
let inputDom = document.getElementById('modelInput')
let divDom = document.getElementById('modelView')
let data = {
name: ''
}
let p = new Proxy(data, {
set(target, p, newValue, receiver) {
divDom.innerText = newValue
},
get(target, p, receiver) {
}
})
inputDom.addEventListener('input', function (e) {
p.name = this.value
})
</script>
</body>
</html>
总结
proxy相比defineProperty具有以下优势
- proxy直接对对象进行监听,Object.defineProperty只能对对象的所有属性遍历监听
- proxy可以直接监听数组的变化
- proxy有13 种拦截方法,Object.defineProperty只有get和set2种拦截方法
- proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;