Vue2 响应式原理
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
http-equiv="X-UA-Compatible"
content="IE=edge"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>Document</title>
<link
rel="stylesheet"
href="./index.css"
/>
</head>
<body>
<div class="card">
<p id="firstName"></p>
<p id="lastName"></p>
<p id="age"></p>
</div>
<input
type="text"
oninput="user.name = this.value"
/>
<input
type="date"
onchange="user.birth = this.value"
/>
<script src="./euv.js"></script>
<script src="./index.js"></script>
</body>
</html>
/* index.css */
.card {
width: 300px;
border: 2px solid rgb(74, 125, 142);
border-radius: 10px;
font-size: 2em;
padding: 0 20px;
margin: 0 auto;
background: lightblue;
color: #333;
}
// index.js
var user = {
name: '张三',
birth: '2002-11-26',
};
// 收集依赖变化
observe(user); // 观察
// 显示姓氏
function showFirstName() {
document.querySelector('#firstName').textContent = '姓:' + user.name[0];
}
// 显示名字
function showLastName() {
document.querySelector('#lastName').textContent = '名:' + user.name.slice(1);
}
// 显示年龄
function showAge() {
var birthday = new Date(user.birth);
var today = new Date();
today.setHours(0), today.setMinutes(0), today.setMilliseconds(0);
thisYearBirthday = new Date(
today.getFullYear(),
birthday.getMonth(),
birthday.getDate()
);
var age = today.getFullYear() - birthday.getFullYear();
if (today.getTime() < thisYearBirthday.getTime()) {
age--;
}
document.querySelector('#age').textContent = '年龄:' + age;
}
// user.name = '李四'
// 如果我们想要当属性改变时,页面也会跟着自动改变,那么需要自动调用依赖该函数属性的函数(也就是显示姓,显示名的函数)
// 可以想到 Object.defineProperty() 可以实现这个功能
// const internalValue = user.name
// Object.defineProperty(user, 'name', {
// get: function() {
// console.log('name 属性被读取')
// return internalValue
// },
// set: function(newValue) {
// internalValue = newValue;
// console.log('name 属性被修改')
// showFirstName();
// showLastName();
// },
// })
autorun(showFirstName);
autorun(showLastName);
autorun(showAge);
// euv.js
/**
* 观察某个对象的所有属性,改变数据的函数
* @param {Object} obj
*/
function observe(obj) {
for (const key in obj) {
let internalValue = obj[key];
let funcs = [];
// 只有有依赖的触发收集和更新就会执行
Object.defineProperty(obj, key, {
get: function () {
// 如何知道哪些函数的自动调用依赖某属性?
// 依赖收集,记录:是哪个函数在用我
if (window.__func && !funcs.includes(window.__func)) {
funcs.push(window.__func);
}
return internalValue;
},
set: function (val) {
internalValue = val;
// 派发更新,运行:执行用我的函数
// 遍历依赖函数
for (var i = 0; i < funcs.length; i++) {
funcs[i]();
}
},
});
}
}
// 将需要运行的函数全部传给 autorun,渲染数据的函数
function autorun(fn) {
window.__func = fn;
fn();
window.__func = null;
}
Vue3 响应式原理
reactive.ts
import {track, trigger} from "./effect";
/**
* 创建一个响应式的对象
* @param target 响应式对象的原始对象
*/
export const reactive = <T extends object>(target: T) => {
return new Proxy(target, {
get(target: T, key: string | symbol, receiver: any): any {
// 这里需要使用 Reflect 保证获取代理对象的属性而不是原始对象的属性 这里 target 是原始对象 key 是属性的键 receiver 是代理对象
let res = Reflect.get(target, key, receiver)
// 依赖收集
track(target, key)
return res
},
set(target: T, key: string | symbol, newValue: any, receiver: any): boolean {
let res = Reflect.set(target, key, newValue, receiver)
// 依赖更新
trigger(target, key)
return res
}
})
}
effect.ts
/**
* 副作用函数 effect (track, trigger)
*/
let activeEffect: () => void;
export const effect = (fn: Function) => {
const _effect = function () {
activeEffect = _effect
fn()
}
_effect()
}
const targetMap = new WeakMap()
/**
* 依赖收集
* @param target
* @param key
*/
export const track = (target: object, key: string | symbol) => {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect)
}
/**
* 依赖更新 触发副作用函数
* @param target
* @param key
*/
export const trigger = (target: object, key: string | symbol) => {
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
deps && deps.forEach((effect: () => any) => effect())
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import {reactive} from "./reactive.js";
import {effect} from "./effect.js";
const user = reactive({
name: '张三',
age: 18,
})
effect(() => {
document.querySelector('#app').innerHTML= `<h1>${user.name} - ${user.age}</h1>`
})
</script>
</body>
</html>