前言
最近看了一下vue2,对vue2的响应式有了更深刻的印象,对vue的实现响应式的原理有了一些思考,所以学习并简易实现一下vue2的更新原理。话不多说,进入正题。
定义代码
我们先看一下定义好的HTML
、CSS
和JS
:
HTML
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>HTML + CSS</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="container">
<div>
<span>姓:</span>
<span class="surname"></span>
</div>
<div>
<span>名:</span>
<span class="name"></span>
</div>
<div>
<span>电话:</span>
<span class="phone"></span>
</div>
</div>
<script src="./myvue.js"></script>
<script src="./index.js"></script>
</body>
</html>
CSS
定义:
.container {
margin: 0 auto;
display: grid;
width: 300px;
height: 200px;
padding: 0 20px;
background-color: #1791f8;
border-radius: 10px;
}
.container > div {
margin: 20px 0;
}
index.js
代码:
let user = {
name: "刘备",
phone: 12312312312,
};
myvue(user);
function setSurname(params) {
document.querySelector(".surname").innerText = user.name[0];
}
function setName(params) {
document.querySelector(".name").innerText = user.name.substring(1);
}
function setPhone(params) {
document.querySelector(".phone").innerText = user.phone;
}
setName();
setSurname();
setPhone();
js
的代码也是比较简单的,定义了三个函数,分别是设置姓、名、电话的函数。然后进行初始化调用,就是vue的初始化展示。
初始状态下,我们看到的效果是这样的,一个盒子进行居中,里边展示相关信息。
实现响应式效果
我们在myvue.js
里定义这样一个函数,对传入的对象进行属性遍历,然后拿到初始值,通过Object.defineProperty
进行对象包装,定义对象每个属性的getter和setter,每次获取值调用getter时,都会进行依赖获取,并对每次重新赋值进行派发更新操作。
function myvue(obj) {
for (const key in obj) {
let initvalue = obj[key];
// 待执行响应的函数 改变UI
let funcArr = [];
Object.defineProperty(obj, key, {
get: function get() {
// 收集依赖
if (window.publicFunc && !funcArr.includes(window.publicFunc)) {
funcArr.push(window.publicFunc);
}
return initvalue;
},
set: function set(val) {
initvalue = val;
// 派发更新
if (funcArr.length > 0) {
funcArr.map((fn) => fn());
}
},
});
}
}
然后 调整一下index.js
的代码,对myvue
引入并注册,然后在window
上设置变量保存函数,并执行相应的代码,最后设置为null
。
let user = {
name: "刘备",
phone: 12312312312,
};
myvue(user);
function setSurname(params) {
document.querySelector(".surname").innerText = user.name[0];
}
function setName(params) {
document.querySelector(".name").innerText = user.name.substring(1);
}
function setPhone(params) {
document.querySelector(".phone").innerText = user.phone;
}
setPhone()
user.name = "张翼德"
window.publicFunc = setName;
setName();
window.publicFunc = setSurname;
setSurname();
window.publicFunc = null;
添加保存,执行,我们可以看到数据有了变化,UI并且更新了
优化
通过上面调整的代码,我们可以进一步提炼,初始化时自动执行函数调用,设置为null。
代码如下:
myvue.js
代码:
function myvue(obj) {
for (const key in obj) {
let initvalue = obj[key];
// 待执行响应的函数 改变UI
let funcArr = [];
Object.defineProperty(obj, key, {
get: function get() {
// 收集依赖
if (window.publicFunc && !funcArr.includes(window.publicFunc)) {
funcArr.push(window.publicFunc);
}
return initvalue;
},
set: function set(val) {
initvalue = val;
// 派发更新
if (funcArr.length > 0) {
funcArr.map((fn) => fn());
}
},
});
}
}
// 定义一个autoRun函数用来处理这些固定的逻辑
function autoRun(fn) {
window.publicFunc = fn;
fn();
window.publicFunc = null;
}
index.js
代码函数调用调整:
let user = {
name: "刘备",
phone: 12312312312,
};
myvue(user);
function setSurname(params) {
document.querySelector(".surname").innerText = user.name[0];
}
function setName(params) {
document.querySelector(".name").innerText = user.name.substring(1);
}
function setPhone(params) {
document.querySelector(".phone").innerText = user.phone;
}
// 初始化调用属性getter和setter 响应函数。
autoRun(setSurname);
autoRun(setName);
autoRun(setPhone);
好了,这样我们就简单完成了一个简易的vue2响应代码。