响应式框架原理1(数据劫持与代理)

  • 感知数据变化的方法很直接,就是进行数据劫持或数据代理。往往通过 Object.defineProperty 实现。这个方法可以定义数据的 getter 和 setter
            let data = {
                stage: '状态',
                course: {
                    title: '标题',
                    author: '作者',
                    publishTime: '出版时间'
                }
            }
            Object.keys(data).forEach(key => {
                let currentValue = data[key]
                // Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
                Object.defineProperty(data, key, {
                    // 当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false
                    enumerable: true,
                    // 当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
                    configurable: false,
                    get() {
                        console.log(`getting ${key} value now, getting value is:`, currentValue)
                        return currentValue
                    },
                    set(newValue) {
                        currentValue = newValue
                        console.log(`setting ${key} value now, setting value is`, currentValue)
                    }
                })
            })

这段代码对 data 数据的 getter 和 setter 进行定义拦截,当读取或者改变 data 的值时:

            data.course
            data.course = 'Test'!


但是这种实现会有问题,例如:

data.course.title = 'newTitle'

出现这个问题的原因是因为实现代码只进行了一层 Object.defineProperty,或者说只对 data 的第一层属性进行了 Object.defineProperty,对于嵌套的引用类型数据结构:data.course,同样应该进行拦截。

为了达到深层拦截的目的,将 Object.defineProperty 的逻辑抽象为 observe 函数,并改用递归实现:

            let data = {
                stage: '状态',
                course: {
                    title: '标题',
                    author: '作者',
                    publishTime: '出版时间'
                }
            }

            const observe = data => {
                if (!data || typeof data !== 'object') {
                    return
                }
                Object.keys(data).forEach(key => {
                    let currentValue = data[key]

                    observe(currentValue)
                    Object.defineProperty(data, key, {
                        enumerable: true,
                        configurable: false,
                        get() {
                            console.log(`getting ${key} value now, getting value is: `, currentValue)
                            return currentValue
                        },
                        set(newValue) {
                            currentValue = newValue;
                            console.log(`setting ${key} value now, setting value is: `, currentValue)
                        }
                    })
                })
            }
            observe(data)

这样就实现了深层数据拦截:

data.course.title = 'newTitle'

在 set 代理中,并没有对 newValue 再次递归进行 observe(newValue)。也就是说,如果赋值是一个引用类型:

            data.course.title = {
                title: 'newTitle2'
            }

无法实现对 data.course.title 数据的观察。

在尝试对 data.course.title 赋值时,首先会读取 data.course,因此输出:getting course value now, getting value is: {// ...},赋值后,触发 data.course.title 的 setter,输出:setting title value now, setting value is newTitle。

你可能感兴趣的:(响应式框架原理1(数据劫持与代理))