Vue 响应式
What?
- 数据与函数关联
- 数据发生改变,自动调用依赖该数据的函数
How?
- 依赖 defineProperty 做数据劫持
- 靠 get 收集依赖,保存有哪个函数在用
- 修改属性的值时,在 set 中调用与依赖该属性的每个函数
Example
修改单一属性值,并执行更新方法
js
let user = {
name: "王花花",
};
function showName() {
const doc = document.querySelector(".name");
doc.textContent = `姓名: ${user.name}`;
}
// 定义一个内部属性,用于保存劫持的值
let internalValue = user.name;
let func = null;
Object.defineProperty(user, "name", {
get() {
console.log("获取 name");
func = showName; // 收集: 相关函数
return internalValue;
},
set(value) {
console.log("设置 name 为", value);
internalValue = value; // 派发: 执行函数
func();
},
});
user.name; // 获取 name
user.name = "李明花"; // 设置 name 为 李明花
let user = {
name: "王花花",
};
function showName() {
const doc = document.querySelector(".name");
doc.textContent = `姓名: ${user.name}`;
}
// 定义一个内部属性,用于保存劫持的值
let internalValue = user.name;
let func = null;
Object.defineProperty(user, "name", {
get() {
console.log("获取 name");
func = showName; // 收集: 相关函数
return internalValue;
},
set(value) {
console.log("设置 name 为", value);
internalValue = value; // 派发: 执行函数
func();
},
});
user.name; // 获取 name
user.name = "李明花"; // 设置 name 为 李明花
以上代码实现单一指定的数据发生变化时调用执行相关函数,如果多个呢? 例如:
js
let user = {
name: "王花花",
gender: "男",
age: "20",
};
function showName() {
const doc = document.querySelector(".name");
doc.textContent = `姓名: ${user.name}`;
}
function showGender() {
const doc = document.querySelector(".gender");
doc.textContent = `性别: ${user.gender}`;
}
function showAge() {
const doc = document.querySelector(".age");
doc.textContent = `年龄: ${user.age}`;
}
showName();
let user = {
name: "王花花",
gender: "男",
age: "20",
};
function showName() {
const doc = document.querySelector(".name");
doc.textContent = `姓名: ${user.name}`;
}
function showGender() {
const doc = document.querySelector(".gender");
doc.textContent = `性别: ${user.gender}`;
}
function showAge() {
const doc = document.querySelector(".age");
doc.textContent = `年龄: ${user.age}`;
}
showName();
把它定义为一个名为 observe 函数,接收一个对象,遍历对象对所有属性进行数据劫持,在 get 中收集对应依赖函数,在 set 中派发执行所有依赖函数。
js
/**
* @description: 观察对象属性值变化
* @param {*} data 观察对象
*/
function observe(data) {
for (const key in data) {
let internalValue = data[key];
let funcs = [];
Object.defineProperty(data, key, {
get() {
// 收集依赖: 哪个函数在用我
funcs.push(fn); // fn未知!
return internalValue;
},
set(value) {
internalValue = value;
// 派发更新,执行用我的函数
for (let i = 0; i < funcs.length; i++) {
const fn = funcs[i];
fn();
}
},
});
}
}
/**
* @description: 观察对象属性值变化
* @param {*} data 观察对象
*/
function observe(data) {
for (const key in data) {
let internalValue = data[key];
let funcs = [];
Object.defineProperty(data, key, {
get() {
// 收集依赖: 哪个函数在用我
funcs.push(fn); // fn未知!
return internalValue;
},
set(value) {
internalValue = value;
// 派发更新,执行用我的函数
for (let i = 0; i < funcs.length; i++) {
const fn = funcs[i];
fn();
}
},
});
}
}
js
// ...
// 这部分代码抽离为公共方法
let internalValue = user.name;
Object.defineProperty(user, "name", {
get() {
console.log("获取 name");
func = showName; // 收集: 相关函数
return internalValue;
},
set(value) {
console.log("设置 name 为", value);
internalValue = value;
func(); // 派发:执行函数
},
});
// ...
// ...
// 这部分代码抽离为公共方法
let internalValue = user.name;
Object.defineProperty(user, "name", {
get() {
console.log("获取 name");
func = showName; // 收集: 相关函数
return internalValue;
},
set(value) {
console.log("设置 name 为", value);
internalValue = value;
func(); // 派发:执行函数
},
});
// ...
fn 未知 - 谁在用我?
- 不要直接调用 showName
- 找个地方保存,比如 window.__func = showName
js
function observe(data) {
for (const key in data) {
let internalValue = data[key];
let funcs = [];
Object.defineProperty(data, key, {
get() {
// 收集依赖: 哪个函数在用我
// funcs.push(fn); // fn未知!
const fn = window.__func;
if (fn && !funcs.includes(fn)) {
funcs.push(fn);
}
return internalValue;
},
set(value) {...},
});
}
}
function autoRun(fn) {
window.__func = fn; // 存储依赖
fn(); // 获取依赖并收集
window.__func = null; // 收集完置空
}
function observe(data) {
for (const key in data) {
let internalValue = data[key];
let funcs = [];
Object.defineProperty(data, key, {
get() {
// 收集依赖: 哪个函数在用我
// funcs.push(fn); // fn未知!
const fn = window.__func;
if (fn && !funcs.includes(fn)) {
funcs.push(fn);
}
return internalValue;
},
set(value) {...},
});
}
}
function autoRun(fn) {
window.__func = fn; // 存储依赖
fn(); // 获取依赖并收集
window.__func = null; // 收集完置空
}
js
// ...
windows.__func = showName;
showName();
windows.__func = null;
windows.__func = showGender;
showGender();
windows.__func = null;
windows.__func = showAge;
showAge();
windows.__func = null;
// 封装一下 autoRun(fn)
autoRun(showName);
autoRun(showGender);
autoRun(showAge);
// ...
windows.__func = showName;
showName();
windows.__func = null;
windows.__func = showGender;
showGender();
windows.__func = null;
windows.__func = showAge;
showAge();
windows.__func = null;
// 封装一下 autoRun(fn)
autoRun(showName);
autoRun(showGender);
autoRun(showAge);
demo
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue-defineProperty</title>
</head>
<style>
.container {
display: flex;
justify-content: center;
flex-direction: column;
margin: auto;
align-items: center;
height: 50vh;
}
</style>
<body>
<div class="container">
<span class="name">姓名:</span>
<span class="gender">性别:</span>
<span class="age">年龄:</span>
</div>
<script src="./vue.js"></script>
<script src="./index.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue-defineProperty</title>
</head>
<style>
.container {
display: flex;
justify-content: center;
flex-direction: column;
margin: auto;
align-items: center;
height: 50vh;
}
</style>
<body>
<div class="container">
<span class="name">姓名:</span>
<span class="gender">性别:</span>
<span class="age">年龄:</span>
</div>
<script src="./vue.js"></script>
<script src="./index.js"></script>
</body>
</html>
js
let user = {
name: "王花花",
gender: "男",
age: "20",
};
function showName() {
const doc = document.querySelector(".name");
doc.textContent = `姓名: ${user.name}`;
}
function showGender() {
const doc = document.querySelector(".gender");
doc.textContent = `性别: ${user.gender}`;
}
function showAge() {
const doc = document.querySelector(".age");
doc.textContent = `年龄: ${user.age}`;
}
observe(user);
autoRun(showName)
autoRun(showGender)
autoRun(showAge)
let user = {
name: "王花花",
gender: "男",
age: "20",
};
function showName() {
const doc = document.querySelector(".name");
doc.textContent = `姓名: ${user.name}`;
}
function showGender() {
const doc = document.querySelector(".gender");
doc.textContent = `性别: ${user.gender}`;
}
function showAge() {
const doc = document.querySelector(".age");
doc.textContent = `年龄: ${user.age}`;
}
observe(user);
autoRun(showName)
autoRun(showGender)
autoRun(showAge)
js
/**
* @description: 观察对象属性值变化
* @param {*} data 观察对象
* @return {*}
*/
function observe(data) {
for (const key in data) {
let internalValue = data[key];
let funcs = [];
Object.defineProperty(data, key, {
get() {
const fn = window.__func;
if (fn && !funcs.includes(fn)) {
funcs.push(fn);
}
return internalValue;
},
set(value) {
internalValue = value;
for (let i = 0; i < funcs.length; i++) {
const fn = funcs[i];
fn();
}
},
});
}
}
/**
* @description: 存储依赖、首次触发收集依赖、收集完置空
* @param {*} fn 用到数据的函数
*/
function autoRun(fn) {
window.__func = fn; // 存储依赖
fn(); // 获取依赖并收集
window.__func = null; // 收集完置空
}
/**
* @description: 观察对象属性值变化
* @param {*} data 观察对象
* @return {*}
*/
function observe(data) {
for (const key in data) {
let internalValue = data[key];
let funcs = [];
Object.defineProperty(data, key, {
get() {
const fn = window.__func;
if (fn && !funcs.includes(fn)) {
funcs.push(fn);
}
return internalValue;
},
set(value) {
internalValue = value;
for (let i = 0; i < funcs.length; i++) {
const fn = funcs[i];
fn();
}
},
});
}
}
/**
* @description: 存储依赖、首次触发收集依赖、收集完置空
* @param {*} fn 用到数据的函数
*/
function autoRun(fn) {
window.__func = fn; // 存储依赖
fn(); // 获取依赖并收集
window.__func = null; // 收集完置空
}
学习视频:https://ke.qq.com/course/5892689/13883885517138513#term_id=106109971