Vue2响应式原理

  • Post author:
  • Post category:vue



目录


Object.defineProperty()


监听对象中的简单数据类型


监听对象中的对象(可以深层)


监听对象中的数组


借鉴的帖子:

Object.defineProperty方法(详解)_objectdefineproperty_搞前端的小菜的博客-CSDN博客

b站视频讲解:

Vue2响应式原理【Vue】_哔哩哔哩_bilibili

Object.defineProperty()

我们都知道Vue2响应式是应用了Object.defineProperty()这个方法进行数据代理,所以就需要了解一下Object.defineProperty()这个方法

首先这个方法,默认接收3个参数

  1. 第一个参数就是需要监听的属性所在的对象名,不需要带引号
  2. 第二个参数就是需要监听的属性名,需要带引号
  3. 第三个参数就是一个描述符对象

描述符对象里面可以有什么呢

可以有以下几个属性(前4个了解就行,最重要的是get和set方法)

  1. configurable  表示能否通过delete删除属性从而重新定义属性,默认值为false

  2. enumerable   表示能否通过for in循环访问属性,默认值为false

  3. writable   表示能否修改属性的值。默认值为false

  4. value   包含这个属性的数据值。默认值为undefined

const obj = { name: 'cxk', age: 32 }
Object.defineProperty(obj, 'name', {
    // 表示能否通过delete删除属性从而重新定义属性,默认值为false
    configurable: false,
    // 表示能否通过for in循环访问属性,默认值为false
    enumerable: false,
    // 表示能否修改属性的值。默认值为false
    writable: false,
    // 包含这个属性的数据值。默认值为undefined
    // value: ''
})
// ----------------
delete obj.name
console.log(obj) // {name: 'cxk', age: 32} 因为配置了configurable为false,所以name删除不掉
// ----------------
for (const key in obj) {
    console.log(key)      // age  因为配置了configurable为false,所以无法通过for...in遍历name
    console.log(obj[key]) // 32  同上
}
// ----------------
obj.name = 'kk'
console.log(obj) // {age: 32, name: 'cxk'} 因为配置了writable为false,所以无法进行修改name

还可以有get和set方法

  • 读取对象中的属性/方法,就会调用get方法。默认undefined
  • 只要设置(写入)对象中的属性/方法,就会调用set方法。默认undefined

// 字面量声明一个普通函数
        const obj = {
            name: 'zs',
            age: 24
        }
        // 定义一个新的name值
        let newName = ''
        // 使用Object.defineProperty(),并传入对应的参数
        Object.defineProperty(obj, 'name', {
            // 只要读取对象中的属性/方法,就会调用get方法。默认undefined
            get() {
                console.log('get执行了')
                return newName
            },
            // 只要设置对象中的属性/方法,就会调用set方法。默认undefined
            // 能够接收一个参数,就是修改后的新值
            set(newVal) {
                console.log('set执行了')
                newName = newVal
            }
        })
        obj.name = 'cxk' // set执行了
        console.log(obj.name) // get执行了  cxk

通过get和set方法,我们就实现了一个最简单的响应式

监听对象中的简单数据类型

首先判断是否为对象,然后遍历对象,调用Object.defineProperty(),传入每个属性

const obj = {
            name: 'cxk',
            age: 30,
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        // console.log(observer(10)) // 10
        // console.log(observer(null)) // null
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦

监听对象中的对象(可以深层)

首先我们还用上面的进行测试,发现是监听不到的,看注释

const obj = {
            name: 'cxk',
            age: 30,
            skill: {
                sing: '唱',
                dance: '跳',
            }
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        // console.log(observer(10)) // 10
        // console.log(observer(null)) // null
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                    // else {
                    //     console.log('测试对象是否会走这个')
                    // }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦
        // 测试下对象中的对象
        obj.skill.sing = '太美' // 就不会打印
        // 因为走到defineReactive()函数,我们传入的是defineReactive(obj,'skill',{ sing: '唱',dance: '跳' })
        // 我们虽然改变了sing的值,但是走到set的时候,判断的是对象内存地址,地址是没有改变的,所以它就没有走判断里面的
        // 也就不会赋值,不会打印。测试也很简单,我们在if下面加个else即可,看是否会打印else里面的(我注掉了,确实打印了)

我们进行一下改进,只需要在defineReactive()函数里,再调用一下observer()方法即可,把对象中的对象传进去。可以进行深层监听

const obj = {
            name: 'cxk',
            age: 30,
            skill: {
                dance: '跳',
                sing: {
                    song: '喂喂喂'
                }
            }
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        // console.log(observer(10)) // 10
        // console.log(observer(null)) // null
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            // 如果还要监听对象中的对象,则就需要再重新调用一下observer()
            observer(value)
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦
        // 测试下对象中的对象,还有深层的
        obj.skill.dance = '闻鸡起舞' // 视图更新啦
        obj.skill.sing.song = '太美' // 视图更新啦

这样看起来是解决了监听对象中的对象的问题。但是如果我们把对象的简单数据类型,改为复杂数据类型,然后再进行值的修改,再进行监听呢

const obj = {
            name: 'cxk',
            age: 30,
            skill: {
                dance: '跳',
                sing: {
                    song: '喂喂喂'
                }
            }
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            // 如果还要监听对象中的对象,则就需要再重新调用一下observer()
            observer(value)
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦
        // 测试下对象中的对象,还有深层的
        obj.skill.dance = '闻鸡起舞' // 视图更新啦
        obj.skill.sing.song = '太美' // 视图更新啦
        // 改变对象的简单数据类型为复杂数据类型,然后再进行改变,看是否能侦听到
        obj.age = { fangling: 30 } // 视图更新啦,这步是能监听到的,因为是监听简单数据类型
        obj.age.fangling = 26 // 我们再进行修改,是监听不到的。因为我们在设置的时候,没有把fangling设置上去,所以就没监听到

显然,没有监听到。想要监听到也很简单,就是在设置的时候,再调用一下

const obj = {
            name: 'cxk',
            age: 30,
            skill: {
                dance: '跳',
                sing: {
                    song: '喂喂喂'
                }
            }
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            // 如果还要监听对象中的对象,则就需要再重新调用一下observer(),可以深层
            observer(value)
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 如果还要监听对象中的简单数据类型修改为对象后,然后再修改值的对象中的属性,则就需要再重新调用一下observer()
                    observer(newVal)
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦
        // 测试下对象中的对象,还有深层的
        obj.skill.dance = '闻鸡起舞' // 视图更新啦
        obj.skill.sing.song = '太美' // 视图更新啦
        // 改变对象的简单数据类型为复杂数据类型,然后再进行改变,看是否能侦听到
        obj.age = { fangling: 30 } // 视图更新啦
        obj.age.fangling = 26 // 视图更新啦

走到这,就感觉出来好麻烦。除了深度监听意外,对象属性的新增和删除,它是监听不到的。所以vue2推出了set方法,让我们手动更新

删除

const obj = {
            name: 'cxk',
            age: 28
        }
        // 测试删除
        let newName = ''
        Object.defineProperty(obj, 'name', {
            get() {
                console.log('get执行了')
                return newName
            },
            set(newVal) {
                console.log('set执行了')
                newName = newVal
            }
        })
        // 删除name属性
        delete obj.name // 发现什么也没有输出,说明它监听不到对象属性的删除操作

新增

const obj = {
            name: 'cxk',
            age: 28
        }
        // 测试属性删除
        let newName = ''
        Object.defineProperty(obj, 'name', {
            get() {
                console.log('get执行了')
                return newName
            },
            set(newVal) {
                console.log('set执行了')
                newName = newVal
            }
        })
        delete obj.name // 发现什么也没有输出,说明它监听不到对象属性的删除操作
        // 新增属性操作
        obj.skill = '唱跳两年半' // 也是侦听不到,因为它是对对象的某一属性进行侦听,一开始都没有,肯定也侦听不到的

监听对象中的数组

vue2官网上标明了数组不能修改的操作,一共有两个

我们进行一下测试,发现数组通过下标修改,其实是可以监听到的,可能vue2舍弃了

const obj = {
            name: 'cxk',
            age: 30,
            skill: ['sing', 'dance', 'rap']
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            // 如果还要监听对象中的对象,则就需要再重新调用一下observer(),可以深层
            observer(value)
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 如果还要监听对象中的简单数据类型修改为对象后,然后再修改值的对象中的属性,则就需要再重新调用一下observer()
                    observer(newVal)
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦')
                    }
                }
            })
        }

        // 1. 根据数组下标是能监听到的,但是vue2官网说监听不到,可能是舍弃了
        obj.skill[0] = '太美' // 视图更新啦
        // 2. 直接修改length,修改数组,也是监听不到
        obj.skill.length = 5 // 没有输出,监听不到

欢迎姥爷观看,喜欢的给赞一下呗



版权声明:本文为qq_52845451原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。