一、用Object.defineProperty实现数据劫持
1.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再进行后续处理。
2.用一个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实现数据劫持
1.原理
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时候就报错。
1.在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支持代理数组的变化。
是不是很简单啊?喜欢的小伙伴留言点赞关注吧!