手写vue2数据劫持逻辑

225 阅读2分钟

上一章介绍了vue2响应式数据实现关键api:Object.defineproperty的用法,下面我们一起来手写vue2的数据劫持逻辑

创建一个my-vue.js文件

在文件中创建一个函数用于对象属性劫持,代码如下:

定义defineReactive函数

// 定义一个响应式函数
/**
*
* @param {定义的对象} obj
* @param {定义的属性键} key
* @param {键对应的值} value
*/
function defineReactive函数(obj, key, value) {
    Object.defineProperty(obj, key, {
        get() {
            console.log('get', { key, value })
            return value
        },
        set(newValue) {
            console.log('set', { key, newValue })
            if (value !== newValue) {
            value = newValue
            }
        }
    })
}

定义一个observe函数遍历对象属性,使其每个属性都被劫持

function observe(obj) {
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
    })
}
const o1 = {
    a:'a',
    b:{b1:'b1'}
}
observe(o1)
o1.b
o1.b.b1

以上两个函数已经可以完成对象中第一层属性劫持了,但是不能完成深层次劫持,比如以上代码node中打印结果是:

image.png 调用b1属性未被劫持到

用到递归完成深度劫持

// 定义一个响应式函数
/**
*
* @param {定义的对象} obj
* @param {定义的属性键} key
* @param {键对应的值} value
*/
function defineReactive(obj, key, value) {
    // 递归当obj[key]是对象时,调用observe函数,这样对象中的没一层属性都能完成数据劫持(typeof(null)==='object'所以要排除)
    if (typeof(value) === 'object' && value !== null) {
        observe(value)
    }
    Object.defineProperty(obj, key, {
        get() {
            console.log('get', { key, value })
            return value
        },
        set(newValue) {
            console.log('set', { key, newValue })
            if (value !== newValue) {
            value = newValue
            }
        }
    })
}
function observe(obj) {
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
    })
}
const o1 = {
    a:'a',
    b:{b1:'b1'}
}
observe(o1)
o1.b
o1.b.b1

此时打印就没问题了:

image.png 这样就完成了对象深层次劫持,但是如果给属性赋值了新对象,数据劫持还是会出现问题

完善给属性赋值新对象也进行数据劫持

o1.b = {
    b2:"b2"
}
o1.b.b2

打印结果是:

image.png 这时,只需要在defineReactive函数set方法中再调用一次observe函数就可以了

function defineReactive(obj, key, value) {
    // 递归当obj[key]是对象时,调用observe函数,这样对象中的没一层属性都能完成数据劫持(typeof(null)==='object'所以要排除)
    if (typeof(value) === 'object' && value !== null) {
        observe(value)
    }
    Object.defineProperty(obj, key, {
        get() {
            console.log('get', { key, value })
            return value
        },
        set(newValue) {
            console.log('set', { key, newValue })
            if (value !== newValue) {
                // 修改值如果是对象则调用observe函数进行数据劫持
                if (typeof(newValue) === 'object' && newValue !== null) {
                observe(newValue)
                }
                value = newValue
            }
        }
    })
}

这样就没问题了

image.png 以上我们已经基本完成了vue中的响应式数据劫持了,vue中还提供了添加属性vue.$set()方法使对象添加的新属性也是响应式,其实实现很简单,定义一个set函数即可如下:

定义set方法,给对象添加的新属性使其也被劫持

function set (target,key,value) {
    defineReactive(target,key,value)
}
// 调用方法添加的属性即被劫持
set(o1,'c','c')

image.png