通言:以前都是看網上別人的關於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