实现Vue2响应式原理主要就是通过数据劫持,依赖收集,派发更新的方式来实现的。
数据劫持:使用 Object.defineProperty 方法添加对象,重写了原有的 get 和 set 方法;
依赖收集:在渲染视图时 将watcher和具体的属性,通过发布订阅者模式管理,这样数据改变之后就能更精准的更新视图,也就是需要用到数据的地方,称为依赖;
派发更新:通过dep来执行watcher的notify方法。
在 getter() 中收集依赖,在 setter() 中触发依赖 // 对象类型
在 getter() 中收集依赖,在 拦截器 中触发依赖 // 数组类型
使用Object.defineProperty做响应式的缺点:
解决方案:使用this.$set 、this.$delete
this.$set(this.arr, index, value)
Vue.set(this.arr, index, value)
this.$delete(this.arr, index)
Vue.delete(this.arr, index)
// 操作数组的函数
splice(),push(), pop(), shift(), unshift(), sort(), reverse()
相应式实例代码:
let person = {
name:'anna',
age:18
}
let p = {}
Object.defineProperty(p, "name", {
// 获取 name 时调用
get(){
return person.name;
},
// 设置 name 时调用
set(value){
console.log("修改 name 属性")
person.name = value
}
});
Object.defineProperty(p, "age", {
// 获取 age 时调用
get(){
return person.age;
},
// 设置 age 时调用
set(value){
console.log("修改 age 属性")
person.age = value
}
});
此时,p对象就完成了对person对象的代理,当读取p.name时,实际上是在读取person.name,当修改p.name时,实际上person中name属性的值也会随之更新。
但是,在Vue2中,无法通过p对象对person对象进行增和删的操作,实际上person对象是捕获不到的,所以即便通过p对象删除和增加属性,person对象内的属性是不会更新的。
基于Vue2的缺点,在Vue3 中得到了解决。
和Vue2不同的是,它的核心是es6的 Proxy 结合 Reflect 实现的,使用代理,本质上是通过 `Proxy` 劫持了数据对象的读写,当我们访问数据时,会触发 getter 执行依赖收集;修改数据时,会触发 setter 派发通知;这样就可以不直接操作对象,也可以监听到所有对象的增删改查,同时也提升了效率。
es6新增了Proxy类,即代理的作用。如果要监听一个对象,可以先创建代理对象,之后对对象的所有操作,都由代理对象完成,它可以监听我们的所有操作。支持的拦截操作共 `13` 种。
// 基本使用
const p = new Proxy( target, handler );
target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
let person = {
name: "anna",
age: 18,
};
let p = new Proxy(person, {
get(target, key) {
return target[key];
},
set(target, key, val) {
return (target[key] = val);
},
});
console.log(p);
//测试 get 是否可以拦截成功
console.log(p.name); // 输出 anna
console.log(p.age); // 输出 18
console.log(p.job); // 输出 undefined
//测试 set 是否可以拦截成功
p.age = 25;
console.log(p.age);
Proxy代理的是整个对象,而不是对象的某个特定属性,不需要我们通过遍历来逐个进行数据绑定。
`Reflect`对象与`Proxy`对象一样,也是 ES6 为了操作对象而提供的新 API,它提供拦截 JavaScript 操作的方法,这些方法与 Proxy 的方法一一对应,也是 `13` 种。Reflect就是为了让this指向代理对象。
let person = {
name: "anna",
age: 18,
};
const objProxy = new Proxy(person, {
get(target, key, receiver) {
console.log("属性被访问");
return Reflect.get(target, key)
},
set(target, key, newValue, receiver) {
console.log("属性被修改");
Reflect.set(target, key, newValue)
}
})
objProxy.name = 'lily'
objProxy.age = 25
console.log(objProxy.name);
console.log(objProxy.age);
基本数据类型通过ref实现响应式,引用数据类型通过reactive实现响应式,可以拦截对象中任意操作的变化,包括属性的读写、属性添加、属性删除,以及数组下标的修改。
setup() {
// 没有响应式
let msg = "hello";
function changeMsg() {
msg = "hello world";
}
// 通过 ref 定义响应式变量:基本数据类型
// ref() 返回带有 value 属性的对象
let counter = ref(0);
function changeCounter() {
counter.value++;
}
// 通过 reactive 定义引用类型的数据:对象和数组,引用类型数据
let obj = reactive({
name: "anna",
age: 18,
person: {
name: "lily",
},
});
function changeObj() {
obj.person.name = "bob";
obj.name = "bob";
}
// toRefs 使解构后的数据重新获得响应式
// 通过 es6 扩展解构运算符进行解构使得对象中的属性不是响应式的
return {
msg,
changeMsg,
counter,
changeCounter,
obj,
changeObj,
...toRefs(obj),
};
},
{{ msg }}
hello
{{ counter }}
{{ obj.person.name }}
{{ obj.name }}
{{ person.name }}
{{ name }}
利用Proxy实现数据劫持的优势:
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 new Proxy() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Vue 3.0与Vue 2.0的区别仅是数据劫持的方式由 Object.defineProperty 更改为Proxy代理。
优点:
缺点:
优点:
缺点:
vue2是监听对象的属性,vue3是监听对象