首先,举一个例子,VUE2中的响应式原理:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用
Object.defineProperty 把这些 property 全部转为
getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持
IE8 以及更低版本浏览器的原因。这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property
被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装
vue-devtools 来获取对检查数据更加友好的用户界面。每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的
setter 触发时,会通知 watcher,从而使它关联的组件重新渲染
也就是说vue对data选项的所有property加了监听,当数据变化时,vue2中每一个实例都有一个watcher进行依赖收集,也就是这个实例所关联的property。property变化时候被setter监听到,然后通知watcher再进行后续处理。
我们先用一个Object.defineProperty实现一个简单的数据劫持:
let a = {
name: "小红",
age: 12,
oinfo: {
school: "狗屁大学",
hobby: ["football", "pingpang"]
},
}
const observer = function (data) {
if (!data || typeof data !== 'object') {
return
}
for (const key in data) {
if (Object.hasOwnProperty.call(data, key)) {
let currentData = data[key]
observer(currentData)
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get() {
console.log(`调用数据时候:${key}数据被劫持劫持`);
return currentData
},
set(value) {
console.log(`设置数据时候:${key}数据被劫持劫持`);
currentData = value
}
})
}
}
}
observer(a)
a.oinfo.hobby = ["1", "2"]
console.log(a.oinfo.hobby);
通过observer这个方法,我们把对象a里面的所有property进行了劫持,在对property进行get和set的时候我们就可以做自己想做的事情了,比如说进行标记的添加之类的,当然这里只是简单的实现,在vue中为了能够更深度的监听数组的变化vue重写了数组的方法。
const extendedArr = Object.create(Array.prototype)
//重写这几个方法,用Object.assign把新的方法混入到Array.prototype上
const methods = ["push", "pop", "shift", "splice", "sort", "reverse"]
methods.forEach(method => {
const oldm = Array.prototype[method]
const newm = function (...args) {
oldm.apply(this, args)
console.log(`劫持方法,爱干嘛干嘛`)
}
extendedArr[method] = newm
})
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
一个代理类的简单例子:
let student = {
name: "小红",
age: 15
}
student = new Proxy(student, {
set(obj, prop, value) {
if (prop == "age" && typeof value !== "number") {
throw new Error("年龄必须是数字")
}
Reflect.set(...arguments)
},
get(obj, prop) {
console.log("get操作");
return Reflect.get(...arguments)
}
})
上面代码把student这个对象进行了代理,判断一下age属性,当age不是number时候就报错。
在VUE3中实现响应式核心变成了代理模式:
当把一个普通的 JavaScript 对象作为 data 选项传给应用或组件实例的时候,Vue 会使用带有 getter 和 setter
的处理程序遍历其所有 property 并将其转换为 Proxy 。这是 ES6 仅有的特性,但是我们在 Vue 3 版本也使用了 Object.defineProperty 来支持 IE 浏览器。两者具有相同的 Surface API,但是 Proxy 版本更精简,同时提升了性能。
let a = {
name: "小红",
age: 12,
oinfo: {
school: "大学",
hobby: ["football", "pingpang"]
},
}
function observerP(data) {
if (!data && Object.prototype.toString.call(data) !== '[object, Object]') {
return
}
Object.keys(data).forEach(key => {
let currentItem = data[key]
if (typeof currentItem == "object") {
observerP(currentItem)
data[key] = new Proxy(currentItem, {
set(obj, prop, value) {
console.log("调用了set");
return Reflect.set(...arguments);
}
})
} else {
Object.defineProperty(data, key, {
set(value) {
console.log("调用了set");
currentItem = value
}
})
}
})
}
observerP(a)
当我们a.oinfo.hobby.push(“fff”)向数组中追加数据时,依然被set函数捕获,所以说Proxy支持代理数组的变化。
是不是很简单啊?喜欢的小伙伴留言点赞关注吧!