全手打原創,轉載請標明出處:https://www.cnblogs.com/dreamsqin/p/14982040.html, 多謝,=。=~(如果對你有幫助的話請幫我點個贊啦)
作爲一個Web前端開發人員,使用Vue框架進行項目開發已經有一陣子,掐指一算,是時候認真探索一下Vue的底層了,以前的瞭解比較偏理論,這一次打算在弄清基本原理的前提下自己手寫Vue中的核心部分,也許這樣我纔敢說自己“深入理解”了Vue。上一篇聊了聊大家熟知的理論部分,本篇就來手擼數據響應化代碼,即數據遍歷並重寫
setter
及getter
~
Object.defineProperty(obj, prop, descriptor)
它是實現數據響應式的核心,該方法可以直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,並返回此對象。
obj
:要定義或修改的屬性對象;prop
:要定義或修改的屬性名稱;descriptor
:要定義或修改的屬性描述,詳細說明可參見我之前寫的《Javascript基礎鞏固系列——標準庫Object對象》中屬性描述對象章節;
下面是一個自定義setter和getter的簡單例子:
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 訪問temperature屬性時會調用get方法,控制檯打印:'get!'
arc.temperature = 11 // 修改temperature屬性時會調用set方法,爲archive數組添加日誌條目:{val: 11};
arc.temperature = 13; // 修改temperature屬性時會調用set方法,爲archive數組添加日誌條目:{val: 13};
arc.getArchive(); //調用getArchive方法返回archive數組:[{ val: 11 }, { val: 13 }]
通過Object.defineProperty讀取和設置DOM節點內容
在上一篇做原理解析的時候有提到Vue是通過Object.defineProperty
重寫data
對象中各個屬性的setter
及getter
,用於實現【響應式】和【依賴收集】。那麼我們先做一件事,通過Object.defineProperty
讀取和設置DOM節點內容,以此體會一下數據驅動視圖的概念。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>defineProperty</title>
</head>
<body>
<div id="app">
<p id="name"></p>
</div>
<script>
var obj = {};
Object.defineProperty(obj, 'name', {
get: function() {
// 訪問obj對象的name屬性時,獲取id爲name的節點內容
return document.querySelector('#name').innerHTML;
},
set: function(value) {
// 修改obj對象的name屬性時,設置id爲name的節點內容爲修改後的值
document.querySelector('#name').innerHTML = value;
}
})
// 數據驅動視圖變更
obj.name = 'dreamsyang';
</script>
</body>
</html>
運行結果如下,可以看到obj
對象的name
屬性值被修改後DOM節點內容也同步更新:
自建數據響應式框架
有了上面的例子做鋪墊應該對響應式有些許感覺,接下來我們自己搭建一個Vue響應式框架,需要達到的效果就是在new
一個Vue實例之後實現數據初始化,達到響應式效果~爲了區別於Vue,我重新命名爲MVue
。
新建MVue.js
文件用於MVue
類封裝,參數接收配置對象
/*** MVue.js ***/
// new MVue({ data: {...} })
class MVue {
constructor(options) {
// 數據緩存
this.$options = options;
this.$data = options.data;
// 數據遍歷
this.observe(this.$data);
}
}
通過observe
實現data
數據遍歷
/*** MVue.js ***/
// new MVue({ data: {...} })
class MVue {
constructor(options) {...}
observe(data) {
// 確定data存在並且爲對象
if (!data || typeof data !== 'object') {
return;
}
// 遍歷data對象
Object.keys(data).forEach(key => {
// 重寫對象屬性的getter和setter,實現數據的響應化
this.defineReactive(data, key, data[key]);
})
}
}
通過defineReactive
重寫getter
和setter
實現數據響應化
/*** MVue.js ***/
// new MVue({ data: {...} })
class MVue {
constructor(options) {...}
observe(data) {...}
defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get: function() {
return val;
},
set: function(newVal) {
// 判斷屬性值是否發生變化
if (newVal === val) {
return;
}
val = newVal;
// 預留視圖更新
console.log(`${key}屬性更新了:${val}`);
}
})
}
}
自建框架測試demo1
完成上述步驟後先看看目前的效果,寫個小demo測試一下:
<!-- demo1.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo1</title>
</head>
<body>
<script src="MVue.js"></script>
<script>
const app = new MVue({
data: {
name: 'dreamsyang',
infoObj: {
location: 'chongqing',
}
}
})
app.$data.name = 'hello, dreamsyang!';
app.$data.infoObj.location = 'oh, chongqing!';
</script>
</body>
</html>
運行結果如下,可以看到app.$data.infoObj.location = 'oh, chongqing!'
並未觸發setter
中的打印,其主要原因是我們在遍歷data
時不是深度遍歷:
通過遞歸實現深度遍歷
我們只需要在defineReactive
執行的開始再次調用observe
即可,如果val
不爲對象,就會結束執行,如果爲對象就會深度遍歷。
/*** MVue.js ***/
// new MVue({ data: {...} })
class MVue {
constructor(options) {
// 數據緩存
this.$options = options;
this.$data = options.data;
// 數據遍歷
this.observe(this.$data);
}
observe(data) {
// 確定data存在並且爲對象
if (!data || typeof data !== 'object') {
return;
}
// 遍歷data對象
Object.keys(data).forEach(key => {
// 重寫對象屬性的getter和setter,實現數據的響應化
this.defineReactive(data, key, data[key]);
})
}
defineReactive(obj, key, val) {
// 解決數據嵌套,遞歸實現深度遍歷
this.observe(val);
Object.defineProperty(obj, key, {
get: function() {
return val;
},
set: function(newVal) {
// 判斷屬性值是否發生變化
if (newVal === val) {
return;
}
val = newVal;
// 預留視圖更新
console.log(`${key}屬性更新了:${val}`);
}
})
}
}
再次執行demo1
結果如下,可以看到正常打印了:
參考資料
1、Object.defineProperty
:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty;
2、Vue源碼:https://github.com/vuejs/vue;