defineProperty和Proxy浅析

518 阅读3分钟

前言

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定义属性失败:

clipboard.png
将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 只能遍历对象属性直接修改;