上一章介绍了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中打印结果是:
调用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
此时打印就没问题了:
这样就完成了对象深层次劫持,但是如果给属性赋值了新对象,数据劫持还是会出现问题
完善给属性赋值新对象也进行数据劫持
o1.b = {
b2:"b2"
}
o1.b.b2
打印结果是:
这时,只需要在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
}
}
})
}
这样就没问题了
以上我们已经基本完成了vue中的响应式数据劫持了,vue中还提供了添加属性vue.$set()方法使对象添加的新属性也是响应式,其实实现很简单,定义一个set函数即可如下:
定义set方法,给对象添加的新属性使其也被劫持
function set (target,key,value) {
defineReactive(target,key,value)
}
// 调用方法添加的属性即被劫持
set(o1,'c','c')