什么是响应式
所谓vue的响应式呢,就是在组件data的数据一旦变化,立刻触发视图的更新
我们先看一个案例
<template>
<div id="app">
<p @click="change">{{ name }}</p>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
<button @click="add">add</button>
</div>
</template>
<script>
export default {
name: "app",
data() {
return {
name: "vue",
list: ["a", "b", "c"],
};
},
methods: {
change() {
this.name = "zhangsan";
},
add() {
this.list.push("d");
},
},
};
</script>
但是组件的data数据变化,立刻触发视图更新,这个事情是怎么实现的,如何知道data数据发生变化了呢?
带着这个疑问我们就探讨下data数据变化后,我们是怎么立刻第一时间知道的
核心API - Object.defineProperty
vue2实现响应式的最核心的API
我们现看下 Object.defineProperty 的基本用法
let data = {};
let name = 'zhangsan';
Object.defineProperty(data, 'name', {
enumerable: true,
configurable: true,
get: function() {
console.log('get');
return name
},
set: function(newVal) {
console.log('set', newVal);
name = newVal;
}
});
console.log(data.name); // get zhangsan
data.name = 'lisi'; // set lisi
console.log(data.name); // get lisi
可以看到在修改name值后,可以打印出 set lisi,这说明我们已经监听到了数据的变化了
对象的监听
// 触发试图更新
function updateView() {
console.log("视图更新");
}
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// 核心 API
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
if (newValue !== value) {
// 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue;
// 触发更新视图
updateView();
}
},
});
}
// 监听对象属性
function observer(target) {
if (typeof target !== "object" || target === null) {
// 不是对象或数组
return target;
}
// 重新定义各个属性
for (let key in target) {
defineReactive(target, key, target[key]);
}
}
const data = {
name: 'zhangsan',
age: 18
};
// 监听数据
observer(data);
console.log(data.name);
data.name = 'lisi';
console.log(data.name);
// console.log(data.age);
// data.age = 19;
// console.log(data.age);
复杂对象的深度监听
const data = {
name: 'zhangsan',
age: 18,
info: {
address: '武汉' // 需要深度监听
}
};
// 触发试图更新
function updateView() {
console.log("视图更新");
}
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// ⭐️ 深度监听 value是个对象{address: '武汉'}
observer(value)
// 核心 API
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
if (newValue !== value) {
// ⭐️ 深度监听 新设置的值如果是数组或对象,所以这里也需要调用observer
observer(newValue);
// 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue;
// 触发更新视图
updateView();
}
},
});
}
// 监听对象属性
function observer(target) {
if (typeof target !== "object" || target === null) {
// 不是对象或数组
return target;
}
// 重新定义各个属性
for (let key in target) {
defineReactive(target, key, target[key]);
}
}
observer(data);
data.info.address = '上海'
data.age = {num: 19};
data.age.num = 20;
data.x = '123'; // 新增属性,监听不到,不会触发视图更新 - 需要使用Vue.set
delete data.name; // 删除属性,监听不到,不会触发视图更新 - 需要使用Vue.delete
数组的监听
const data = {
name: 'zhangsan',
age: 18,
info: {
address: '武汉'
},
nums: [1, 2, 3]
};
// ⭐️ 重新定义数组原型
const oldArrayProperty = Array.prototype;
// ⭐️ 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
// arrProto.push = function() {}
// arrProto.pop = function() {}
// ⭐️ 举例几个常用的方法
["push", "pop", "shift", "unshift", "splice"].forEach((methodName) => {
arrProto[methodName] = function () {
updateView(); // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments);
// Array.prototype.push.call(this, ...arguments)
};
});
// 触发试图更新
function updateView() {
console.log("视图更新");
}
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// 深度监听 value:nums
observer(value)
// 核心 API
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
if (newValue !== value) {
// 深度监听 新设置的值如果是数组或对象,所以这里也需要调用observer
observer(newValue);
// 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue;
// 触发更新视图
updateView();
}
},
});
}
// 监听对象属性
function observer(target) {
if (typeof target !== "object" || target === null) {
// 不是对象或数组
return target;
}
// ⭐️ 如果是数组 nums
if (Array.isArray(target)) {
target.__proto__ = arrProto;
} else {
// 重新定义各个属性
for (let key in target) {
defineReactive(target, key, target[key]);
}
}
}
observer(data);
data.nums.push(4);
Object.defineProperty的缺点
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增属性/删除属性(Vue.set/Vue.delete)
- 无法原生监听数组,需要特殊处理
总结
我们现在知道了vue2是怎么做到响应式监听的,当data的数据发生改变了,我们就可以知道现在需要触发视图更新了,那么视图又是怎么更新的呢?带着这个疑问,我们下次见。