Skip to content

实现一个数据劫持器

LYF edited this page Sep 27, 2020 · 5 revisions

这是我以前做的本地的笔记,现在移到这儿

class Observer {
    constructor(data) {
        this.observe(data)
    }
    observe(data) {
        // 只有是对象,才能劫持
        if (!data || typeof data !== 'object') return
        Object.keys(data).forEach(key => {
            const val = data[key]
            this.defineReactive(data, key, val)
            // 递归劫持
            this.observe(val)
        })
    }
    defineReactive(data, key, val) {
        const self = this
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get() {
                return val
            },
            set(newVal) {
                if (newVal !== val) {
                    console.log(`set ${newVal} .....`)
                    // 新设置的值也可能是对象,所以也要用observe方法搞一遍
                    self.observe(newVal)
                    val = newVal
                }
            }
        })
    }
}

实现数据劫持,核心是三个点:

  1. Object.defineProperty
  2. 闭包
  3. 递归劫持

Object.defineProperty结合闭包,把val封闭到闭包里,每次set数据的时候,用newVal更新val的值即可,每个val或者newVal可能是一个对象,那么我们要递归地进行劫持

测试

new Observer(data)
data.name = '张无忌'                                     // set 张无忌 .....
console.log(data.name)                                   // 张无忌
data.name = 'pod4g'                                      // set pod4g .....
console.log(data.name)                                   // pod4g
data.props.skills.push('typescript')                     // 不会触发set勾子,因为`Object.defineProperty`无法劫持数组,这也是为什么vue额外提供了操作数据的方法,在这些方法里才能做劫持
data.props.skills = [...data.props.skills, 'typescript'] // 只有改变skills的引用才能触发set: set java,javascript,node.js,mysql,html,css,typescript .....
console.log(data)
data.props.data.addr = '首都'                            // set 首都 .....
console.log(data)

vue不能检测:

  1. 对象属性的添加或者移除
  2. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  3. 当你修改数组的长度时,例如:vm.items.length = newLength

其中第1、3是能力问题,Object.defineProperty确实无法检测对象属性的增添,length属性由于configurable: false,故也无法对其descriptor进行重写

2是设计问题,Object.defineProperty其实是可以检测数组的索引变化的,但是vue没有这么做,原因是为了性能考虑,尤大的原话是「性能代价和获得的用户体验收益不成正比」,ROI太低了。。

关于vue不能检测数组的文章:

  1. https://juejin.im/post/6844904046722023438
  2. https://cn.vuejs.org/v2/guide/reactivity.html#%E6%A3%80%E6%B5%8B%E5%8F%98%E5%8C%96%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9
  3. https://juejin.im/post/6844904046722023438
Clone this wiki locally