Vue响应式数据原理剖析

其实前端架构师每天做的事情与普通开发者并无太大的区别,无外乎就是做两件事情,定目标和如何实现。这个过程往往是循环往复的,我们会不断地修复目标。
我们通过vue的响应式数据设计来理解从目标到实现这个循环往复的过程。
我们知道vue最大的特点就是:视图与数据的双向绑定。
什么是视图呢?dom,虚拟dom,甚至是一段代码字符串也是视图""<h2>content</h2>"。从本质上说,视图也是数据。所以说视图与数据的关联也是就是数据与数据的关联。数据与数据的关联都需要背后的计算过程,因此数据与数据的关联从本质上讲就是创建视图的过程与视图使用的数据发生了关联。
简单说一旦数据发生了改变,那么就必须执行重新创建视图的过程,同理视图发生了改变,也必须触发数据发生改变,这就是视图与数据的双向绑定。要想实现一个目标就离不开监听数据的读取和修改和关联数据和函数。
因此分析vue的源码,我们不难发现其主要就是围绕着以下两个目标去实现:
1. 监听数据的读和写。 2. 关联数据和函数。
那么vue是如何实现监听数据的读取和修改呢?Javascript提供了以下两种解决方案:
由于Proxy是ES6的新特征,因此vue2考虑到兼容器问题,内部使用的是defineProperty实现数据监听。而到了vue3时代,绝大多数浏览器都支持ES6的语法,因此vue3内部采用了Proxy实现。
下面咱们模拟实现vue的响应式数据内部的实现过程。
首先工程结构如下:

1).reactive.js
import {track, trigger} from "./effect.js";
export function reactive(target) {
return new Proxy(target, {
get(target, key, value) {
//依赖收集
track(target, key);
return target[key];
},
set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
trigger(target, key);
return Reflect.set(target, key, value);
}
})
}
2.effect.js
export function track(target,key){
console.log(`%c依赖收集:${key}`,'color:#f00');
}
export function trigger(target,key){
console.log(`%c派发更新:${key}`,'color:#00f');
}
3.index.js
import {reactive} from './reactive.js'
//注意:这里获得的就是代理对象。
const state = reactive({
name: 'zhangsan',
age: 20
})
function fn(){
console.log(state.name);//使用了属性name
console.log(state.age);//使用了属性age
state.name= 'lisi';//修改了属性name
state.age = 18;//修改了属性age
}
fn(); //方法调用
4.package.json
type必须设置为module。
{
"name": "reactivedemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
运行效果:
PS E:\jsdemo\reactivedemo> node index.js
PS E:\jsdemo\reactivedemo> node index.js
依赖收集:name
zhangsan
依赖收集:age
20
派发更新:name
派发更新:age
在浏览器里面执行效果:
创建index.html如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="module" src="index.js">
</script>
</body>
</html>
