通言:以前都是看网上别人的关于vue数据响应式原理理解,都是长篇大论的,不是很好理解,不能有效概括。直到学习了某位老师的课程我恍然大悟。得出结论:数据响应式就是指数据的改变以后通知函数的执行。
讲一下实现的逻辑过程:
首先
js代码:
var user = {
name: '合约路',
birth: '2002-5-7',
};
// 显示姓氏
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;
}
showFirstName();
showLastName();
showAge();
-------------截至-------------
//上面函数执行完形成初始页面
user.name = 'sag'; //不能做到直接更新页面
showFirstName();//依赖函数
showLastName();//依赖函数
上面代码我们可以看出,传统模式下改变数据不能直接更新页面,而调用下面方法才能让页面进行重新渲染。数据的更新依赖于函数的执行,这里我们称上面两个调用的函数为依赖函数。
其实这样写不好,逻辑看起来太分散(不利于维护)。
搞明白这个问题其实就明白了数据响应式的原理了。
方法:通过Object.defineProperty对数据进行劫持。下面以name为例来进行数据劫持。
var internalValue = obj.name;
Object.defineProperty(obj,'name',{
get(){//访问该属性应该返回的值
return internalValue
},
set(v){
//这里可以理解为设置数据以后的回调函数。
internalValue = v;
showFirstName();
showLastName();
}
})
这样写看起来还是不好,我一旦对象属性多起来了,难道我要写100个这个玩意么?
再优化,我设置一个监听函数,遍历对象下所有属性然后添加
function observe(obj){
for(const key in obj){
let internalValue = obj[key];
let funcs = []; //用于依赖函数存储
Object.defineProperty(obj,key,{
get(){
//abc表示对应的事件进行存储
funcs.push(abc); // 每次定义的时候将受依赖函数放到数组中
return internalValue
},
set(v){
internalValue = v;
for (var i = 0; i < funcs.length; i++) {
funcs[i]();//挨个执行
}
}
})
}
}
上述一个问题就出现了,我去哪里以及什么时机拿到函数名字来执行呢?
//自己注册对应函数,存储到window全局变量下,并初始化执行,最后重置这个属性。
function autorun(fn){
window.__func = fn;//注册
fn(); //触发对应函数
window.__func = null;
}
//设此时fn = showFirstName
function showFirstName() {
document.querySelector('#firstName').textContent = '姓:' + user.name[0];
//user.name 会触发它对应的object.defineProperty() GET函数。
}
get(){
//user.name表示对应的事件进行存储
funcs.push(“showFirstName”);
},
以此得到完整代码
/**
observe.js
* 观察某个对象的所有属性
* @param {Object} obj
*/
function observe(obj){
for(const key in obj){
let internalValue = obj[key];
let funcs = []; //用于函数存储
Object.defineProperty(obj,key,{
get(){
// 依赖收集,记录:是哪个函数在用我
if (window.__func && !funcs.includes(window.__func)) {
funcs.push(window.__func);
}
return internalValue
},
set(v){
internalValue = v;
// 派发更新,运行:执行用我的函数
for (var i = 0; i < funcs.length; i++) {
funcs[i]();
}
}
})
}
}
function autorun(fn){
window.__func = fn;
fn();
window.__func = null;
}
//入口函数
var user = {
name: '合约路',
birth: '2002-5-7',
};
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;
}
autorun(showFirstName);
autorun(showLastName);
autorun(showAge);
额外的 双向绑定原理
<input type="text" oninput="user.name = this.value" />
<input type="date" onchange="user.birth = this.value" />
//对于表单元素来讲 数据的改变也会导致视图更新。
完结,下一章讲一讲vue3.0数据响应式方面的干货
项目完整地址:
https://github.com/xuxiaoyang883/vue_data_reactive
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。