Object.defineProperty 和 new Proxy

138 阅读4分钟

本文主要是详细了解下 Object.definePropertynew Proxy 这两个方法。旨在了解Vue2Vue3关于数据代理的区别。

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...inObject.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.definePropertyobj进行深度代理。我们可以看到当使用数组中的push方法时,没有走set方法。这也是Object.defineProperty的一个不好的点,对数组不兼容,因此Vue2才会对数组的方法进行进一步的封装:push、pop、shift、unshift、splice、sort、reverse
注意:在Vue2中,我们使用数组的索引更改其值或删除对象的属性时,无法触发视图的更新。为了解决它,Vue提供了$set$delete方法

Proxy

ES6引入的一个新的特性,它允许我们创建一个对象的代理(proxy),从而能够拦截和自定义对象的基本操作(如:查找,赋值,遍历)。它返回一个新的代理对象。

注意:它通常结合Reflect内置对象一起使用。为什么要一起使用?

  • 统一性:ReflectProxy二者的方法相对应
  • 返回值:与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文档

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对数组兼容还是很友好的。