Vue 响应式的发展历程
Vue 的响应式系统经历了显著的演进,从早期基于 Object.defineProperty 的实现到如今 Vue 3 中采用 ES6 的 Proxy 对象。这一转变不仅提升了系统的灵活性和性能,还简化了开发者的使用体验。接下来,我们将深入探讨这两种技术的实现过程及其优缺点,并通过具体的示例代码来具体说明。
Object.defineProperty
实现过程
- 声明数据:创建一个普通对象,包含需要响应式的属性。
- 包装数据成为响应式对象的属性:将这些属性转换为响应式属性。
- 定义属性
Object.defineProperty():使用Object.defineProperty定义 getter 和 setter 方法,确保每次访问或修改属性时都能触发相应的逻辑。 - 进行 get、set 方法访问、修改属性:当访问或修改这些属性时,触发相应的 getter 和 setter。
- 通过事件绑定,进行 DOM 的更新:通过事件监听器更新 DOM,确保视图与数据同步。
示例代码:
接下来我通过简单的点击事件来给大家描述一下它的实现过程。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>响应式API</title>
</head>
<body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>响应式API</title>
</head>
<body>
<div id="container">1</div>
<div id="count">2</div>
<button id="button">点击+1</button>
<button id="btn">点击*2</button>
<script>
// JSON对象, JSON数据
var obj = {
value:1, // 相当与count
count:2 // 包装成为响应式对象
}
let value = 1;
let count = 2; // 数据
// 拦截器 修改值之前
// 属性定义 定义一下value属性
Object.defineProperty(obj,'value',{
get:function(){
console.log('读了value属性');
return value; // 原来的职责
},
set:function(newValue){
value = newValue; // 原来的职责
document.getElementById('container').innerHTML = newValue;
}
})
// 属性定义 定义一下count属性
Object.defineProperty(obj,'count',{
get:function(){
console.log('读了count属性');
return count; // 原来的职责
},
set:function(newCount){
count = newCount; // 原来的职责 数据改变了
document.getElementById('count').innerHTML = newCount;
}
})
document.getElementById('button')
.addEventListener('click',function(){
obj.value++;
})
document.getElementById('btn')
.addEventListener('click',function(){
obj.count *= 2;
})
</script>
</body>
</html>
很明显这里对不同属性进行访问、修改操作的时候,都要进行Object.definedProperty()相同的操作。同时我们应该注意进行set操作时对内部属性的更新(示例中value = newValue; count = newCount; )要不然每次执行更新时都是访问以前的旧值,如下。
Proxy代理对象
Proxy是vue3响应式API(ref)的底层实现原理之一。
-
ref:- 在 Vue 3 中,
ref是一个函数,它接收一个内部值并返回一个带有.value属性的对象。这个对象是通过Proxy实现的响应式对象。 - 当你使用
ref包装一个值时,Vue 会创建一个Proxy来拦截对该值的访问和修改,确保任何对该值的操作都能触发视图更新。
- 在 Vue 3 中,
-
reactive:reactive函数接收一个普通对象并返回一个响应式代理对象,内部也是基于Proxy实现的。- 它允许你将整个对象变成响应式的,而不仅仅是单个属性。
实现过程
- 定义
watch函数:创建一个函数用于初始化代理对象。 - 创建代理对象:使用
Proxy包装目标对象。 - 暴露
watch函数:使watch函数可以在全局范围内使用。 - 初始化数据对象:定义初始数据结构。
- 创建响应式对象:通过
watch函数将普通对象转换为响应式对象。 - 添加事件监听器:为按钮添加点击事件监听器,触发对代理对象的操作。
- 优点:
- 全面监听。
- 灵活性强。
示例代码:
根据上述示例修改,使用Proxy代理对象实现。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy</title>
</head>
<body>
<div id="container">1</div>
<div id="count">2</div>
<button id="cutton">+1</button>
<button id="btn">*2</button>
<script>
// 匿名函数->立即执行函数 + 回调函数(事件处理函数、定时器、文件操作)
(function(){
// 函数作用域
// 设计模式 观察者模式
function watch(target,func){
// es6 proxy 实例化代理对象
const proxy = new Proxy(target,{
get:function(target,prop){
console.log(`读了${target}${prop}属性`);
console.log(target[prop]);
return target[prop];
},
// get获取修改前的值,javascript 引擎会执行运算,原值修改后,
// 试图将新值赋给newobj的时候,就会执行set 此时newValue就是修改后的值
set:function(target,prop,newValue){
console.log(`修改了${target}${prop}属性`);
target[prop] = newValue;
func(prop,newValue);
}
})
return proxy;
}
// 暴露全局
this.watch = watch;
})()
let obj = {
value:1,
count:2
}
let obj2 = {
}
// 代理对象
var newObj = watch(obj,function(key,newValue){
if(key==='value'){
document.getElementById('container').innerHTML = newValue;
}
if(key==='count'){
document.getElementById('count').innerHTML = newValue;
}
})
// 事件处理函数
document.getElementById('cutton')
.addEventListener('click',function(){
// 交给代理对象去处理
newObj.value++;
})
document.getElementById('btn')
.addEventListener('click',function(){
// 交给代理对象去处理
newObj.count *= 2;
})
</script>
</body>
</html>
newObj是一个由Proxy构造函数创建的对象,它包装了原始的对象obj。当尝试修改newObj.count时,实际上是在与Proxy对象交互,而不是直接与obj交互。
- 执行过程描述
当用户点击按钮时,事件处理函数被触发,进而调用 newObj.value++ 或 newObj.count *= 2。此时:
- get 捕获器触发:尝试读取
newObj.value或newObj.count触发Proxy的get捕获器,控制台输出相应信息,并返回当前属性值。 - 计算新值:JavaScript 引擎计算表达式的结果。
- set 捕获器触发:尝试设置新的属性值,触发
Proxy的set捕获器,控制台输出相应信息,更新属性值,并通过回调函数更新DOM。
总结
Vue 的响应式系统在 Vue 3 中借助 Proxy 对象实现了更强大和灵活的功能,解决了 Object.defineProperty 存在的局限性。Proxy 不仅可以全面监听属性变化,还能轻松处理动态属性和复杂的数据结构。通过上述代码示例,我们可以清晰地看到两种方式的具体实现及其差异。对于现代应用开发而言,Proxy 提供了一个更加优雅和高效的解决方案。z