您的当前位置:首页正文

一文搞懂Object.defineProperty和Proxy,Vue3.0为什么采用Proxy?

2024-12-02 来源:个人技术集锦

前言

前几天面试的时候,面试官问到了这个问题,感觉自己答的不是特别好,在这里整理一下~

文章篇幅会比较长,但是看完一定会收获满满~希望你坚持看下去呀~

Object.defineProperty()

作用:在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

1. 基本使用

语法:Object.defineProperty(obj, prop, descriptor)

参数:

let person = {}
let personName = 'lihua'
//在person对象上添加属性name,值为personName
Object.defineProperty(person, 'name', {
  //但是默认是不可枚举的(for in打印打印不出来),可:enumerable: true
  //默认不可以修改,可:wirtable:true
  //默认不可以删除,可:configurable:true
    get: function () {
        return personName
    },
    set: function (val) {
        return name=val
    }
})
//当读取person对象的name属性时,触发get方法
console.log(person.name)
//当修改person对象的name属性时,触发set方法
personName = 'liming'
//检查后发现,修改成功了
console.log(person.name)

通过这种方法,我们成功监听了person上的name属性的变化。

2.监听对象上的多个属性

上面的使用中,我们只监听了一个属性的变化,但是在实际情况中,我们通常需要一次监听多个属性的变化。

这时我们需要配合Object.keys(obj)进行遍历。这个方法可以返回obj对象身上的所有可枚举属性组成的字符数组。(其实用for in遍历也可以

下面是该API一个简单的使用效果:

var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

利用这个API,我们就可以遍历劫持对象的所有属性
但是如果只是上面的思路与该API的简单结合,我们就会发现并达不到效果,下面是我写的一个错误的版本:

Object.keys(person).forEach(function (key) {
    Object.defineProperty(person, key, {
        enumerable: true,
        configurable: true,
        // 默认会传入this
        get() {
            return person[key]
        },
        set(val) {
            console.log(`对person中的${key}属性进行了修改`)
            person[key] = val
            // 修改之后可以执行渲染操作
        }
    })
})
console.log(person.age)

看起来感觉上面的代码没有什么错误,但是试着运行一下吧~你会和我一样栈溢出。这是为什么呢?让我们聚焦在get方法里,我们在访问person身上的属性时,就会触发get方法,返回person[key],但是访问person[key]也会触发get方法,导致递归调用,最终栈溢出。
这也引出了我们下面的方法,我们需要设置一个中转Obsever,来让get中return的值并不是直接访问obj[key]。

let person = {
    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(person)
console.log(person.age)
person.age = 18
console.log(person.age)

3.深度监听一个对象

那么我们如何解决对象中嵌套一个对对象的情况呢?其实在上述代码的基础上,加上一个递归,就可以轻松实现啦~

我们可以观察到,其实Obsever就是我们想要实现的监听函数,我们预期的目标是:只要把对象传入其中,就可以实现对这个对象的属性监视,即使该对象的属性也是一个对象。

我们在defineProperty()函数中,添加一个递归的情况:

function defineProperty(obj, key, val) {
    //如果某对象的属性也是一个对象,递归进入该对象,进行监听
    observer(key)
    Object.defineProperty(obj, key, {
        get() {
            console.log(`访问了${key}属性`)
            return val
        },
        set(newVal) {
            console.log(`${key}属性被修改为${newVal}`)
            val = newVal
        }
    })
}

当然啦,我们也要在observer里面加一个递归停止的条件:

function Observer(obj) {
    //如果传入的不是一个对象,return
    if (typeof obj !== "object" || obj === null) {
        return
    }
    // for (key in obj) {
    Object.keys(obj).forEach((key) => {
        defineProperty(obj, key, obj[key])
    })
    // }

}

其实到这里就差不多解决了,但是还有一个小问题,如果用户输入的属性也是一个对象呢?
预知后事如何 且听明天分解~

显示全文