Vue使用的是 MVVM 模式,当Model层改变,View 层也会改变,Vue使用数据劫持来实现对数据的检测,本篇文章将实现一个简易的方法来模仿Vue中的数据劫持。
数据劫持主要分为两类,一类是对象,另一类是数组,首先介绍对象的数据劫持
1 对象的数据劫持
1.1 模拟使用Vue时的数据
劫持 data 对象
const vm = new Vue({
el: '#app',
data: {
name: 'Matt',
jobInfo: {
address: 'nanjing',
title: 'worker'
}
}
})
1.2 定义observe方法
只有对象类型才进行劫持,而基本类型不进行劫持,直接略过
function observe(data) {
// 判断data的类型,只有对象类型才进行劫持
let type = typeof data
if (type != 'object' && data == null) {
return false
}
return new Observe(data)
}
1.2 定义 Observe 类
class Observe {
constructor(data) {
this.walk(data)
}
// 遍历 data
walk(data) {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
// 对数据进行劫持
defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
return value
},
set(newValue) {
value = newValue
}
})
}
}
1.3 引发的两个问题
1.3.1 data中如果有值为对象不会被劫持
运行以后,发现只有data中的属性进行了劫持,而 jobInfo 中的属性没有被劫持
解决办法:
defineReactive(obj, key, value) {
// 在劫持之前,对属性值进行递归 observe
observe(value)
Object.defineProperty(obj, key, {
get() {
return value
},
set(newValue) {
value = newValue
}
})
}
1.3.2 直接更改属性为对象,也无法被劫持
// 修改 data 中的属性
data.name = {
fulltName:'Matt',
}
解决办法:
defineReactive(obj, key, value) {
// 在劫持之前,对属性值进行递归 observe
observe(value)
Object.defineProperty(obj, key, {
get() {
return value
},
set(newValue) {
// 更改数据时,对属性值进行递归 observe
observe(newValue)
value = newValue
}
})
}
2 数组的数据劫持
2.1 劫持数组的问题
当对数组进行劫持时,会对每一项进行劫持,性能会有影响
data.name = [1,2,3,4,5]
2.2 劫持数组每一项
因为对数组每一项进行劫持会有性能影响,所以,vue只对数组中对象进行劫持
class Observe {
constructor(data) {
// 判断数组
if(Array.isArray(data)) {
this.walkArray(data)
}else {
this.walk(data)
}
}
walk(data) {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
// 新增的数组遍历
walkArray(data) {
for(let key in data) {
observe(data[key])
}
}
defineReactive(obj, key, value) {
observe(value)
Object.defineProperty(obj, key, {
get() {
return value
},
set(newValue) {
// 如果新值也为对象,也需要进行数据劫持
observe(newValue)
value = newValue
}
})
}
}
2.3 无法劫持新增项
此时,array.push()等新增的项,无法进行劫持
let data = {
name: 'Matt',
jobInfo: {
address: 'nanjing',
title: 'worker'
},
arr:[{age:
18}]
}
data.arr.push({sex:'boy'})
2.4 更改数组原有方法
export let myArray = Object.create(Array.prototype)
// 这几个方法可以新增项,因此需要进行重写
let methods = ['push','unshift','pop','shift','reverse','sort','splice']
methods.forEach(method => {
myArray[method] = function (...args) {
// arr.push(1,2) 此时this==arr
Array.prototype[method].call(this,...args)
let insert
// 新增的一样要有检测
switch(method) {
case 'push':
case 'unshift':
insert = args
break;
case 'splice':
insert = args.slice(2)
break;
default:
break
}
if(insert) {
// 新增的每一项都需要再次进行 observe __ob__是在Observe中得到
this.__ob__.arrayObserve(insert)
}
}
})
constructor(data) {
// 对对象中所有属性进行劫持
Object.defineProperty(data ,'__ob__',{
value:this,
enumerable:false // 防止爆栈
})
if(Array.isArray(data)) {
data.__proto__ = myArray
this.arrayObserve(data)
}else {
this.walk(data)
}
}
3 vue中数据劫持存在的问题
3.1 无法检测对象变动
①直接在对象上添加属性
②删除对象上的属性
data.time = 'sunday'
3.2 无法检测数组变动
①用索引改变值 vm.items[indexOfItem] = newValue
②修改数组长度 vm.items.length = newLength