Vue2是通过用Object…defineProperty来设置数据的getter和setter实现对数据和以及视图改变的监听的。对于数组和对象这种引用类型来说,getter和setter无法检测到它们内部的变化。那么Vue2是则么来解决这个问题的呢?
通过一个简单的例子来理解Vue2中是如何解决数组和对象的响应式问题。
<html>
<head>
</head>
<body>
<script>
//1. 定义一个data对象来模拟组件中的数据
var data = {
name: 'xwd',
sex: 1,
dog: {
name: 'peter',
age: 5
},
hobby: [
"pingpang", "basktetball"
],
}
//2. 对Data做 reactive化
Observer(data)
function Observer(data) {
// 模拟组件初始化对Data reactive化
if (typeof data != "object" || data == null) {
return data
}
for (let item in data) {
//将数据响应式化
defineReactive(data, item, data[item])
}
return data
}
function defineReactive(target, key, value) {
Object.defineProperty(target, key, {
enumerable: false,
configurable: false,
get() {
//用打印控制台模拟视图发生渲染
console.log("视图渲染使用了数据")
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
//用打印控制台模拟数据变更视图更新
console.log("更新视图")
}
}
})
}
</script>
</body>
对复杂对象对象属性的变更主要有以下几种情况:
function defineReactive(target,key,value) {
Observer(value)
Object.defineProperty(target,key,{
enumerable: false,
configurable: false,
get() {
console.log("视图渲染使用了数据")
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
console.log("更新视图")
}
}
})
}
set(newValue) {
Observer(value)
if (newValue !== value) {
value = newValue;
console.log("更新视图")
}
}
Note: 因为Vue2对数据响应式的添加是在一开始初始化以及set属性的时候,所以在使用过程中当发生data的属性的增加或者删除。Vue不能添加响应式。如果要对运行过程中添加的属性做响应式,必须使用Vue.delete方法或者Vue.Set。
数组内部的变化,包括使用我们常用的数组函数,push,pop等。都不能被setter函数检测到,只有当整个数组被换掉才会被检测到。
Vue2为了解决这个问题采用的方式是提供一组新的数组变异函数。换掉Array的原型用新的变异函数,在自定义的变异函数里做更新视图的操作。
const oldArrayProto = Array.prototype;
const newArrProto = Object.create(oldArrayProto);
['push', 'pop'].forEach(methodName => {
newArrProto[methodName] = function () {
console.log("更新视图")
oldArrayProto[methodName].call(this, ...arguments)
}
});
if (Array.isArray(data)) {
data.__proto__ = newArrProto
}
Note: Vue 不能检测以下数组的变动:
解决方法
<html>
<head>
head>
<body>
123
<script>
//1. 定义一个data对象来模拟组件中的数据
var data = {
name: 'xwd',
sex: 1,
dog: {
name: 'peter'
,
type: 'dog'
},
hobby: [
"pingpang", "basktetball"
],
}
const oldArrayProto = Array.prototype;
const newArrProto = Object.create(oldArrayProto);
['push', 'pop'].forEach(methodName => {
newArrProto[methodName] = function () {
console.log("更新视图")
oldArrayProto[methodName].call(this, ...arguments)
}
});
Observer(data)
function Observer(data) {
if (typeof data != "object" || data == null) {
return data
}
if (Array.isArray(data)) {
data.__proto__ = newArrProto
}
for (let item in data) {
//将数据响应式化
defineReactive(data, item, data[item])
}
return data
}
function defineReactive(target, key, value) {
Observer(value)
Object.defineProperty(target, key, {
enumerable: false,
configurable: false,
get() {
console.log("视图渲染使用了数据")
return value;
},
set(newValue) {
Observer(value)
if (newValue !== value) {
value = newValue;
console.log("更新视图")
}
}
})
}
script>
body>
html>
用 Object.defineProperty这种方法去监听数据和视图的改变,当遇到复杂对象的时候需要对所有的对象进行深度遍历来给属性设置上getter和setter函数,这会导致首屏加载速度很慢。针对这个问题 Vue3 将响应式的实现由
Object.defineProperty换成了Proxy。实现在数据要用的时候再添加响应式提高了首屏加载速度。
1. (完整版)快速掌握Vue2响应式原理【Vue】_哔哩哔哩_bilibili
2. 深入响应式原理 — Vue.js (vuejs.org)