我們一直都在問vue雙向數據綁定的原理,今天簡單手寫一個雙向數據綁定,也深入理解一下其中原理。
因為目前vue技術也在不斷更新,現在已經更新到3.0,內部方法邏輯也改了很多,那麼作為開發的我們,其實掌握其中原理,能自己寫出簡單的雙向數據綁定代碼,就可以了,更深層次的學習那畢竟是更好的,如果自己確實有充足的時間,可以去扒源碼。
文章我們分部分來寫,本文代碼並非源碼,但可以通過其原理自己實現相同的功能。
今天我們的目的:先拋去vue的監聽(watcher)方法。枚舉遍歷vue中的data數據,通過Object.defineProperty
方法,利用其中的get
和set
重新定義屬性值,也就是對vue中數據進行劫持和更改,從而達到目的。
在此之前,我們先要熟悉我們今天的思路原理:
js中我們定義class類,類中有不同的方法,首先我們會拿到vue中所有的data數據,對data數據進行遞歸遍歷,我們對每個數據加入Object.defineProperty
方法,利用get
和set
進行劫持更改,實現更改data中的數據源。
開始:
建立兩個文件夾:
一個是vue.js(寫vue方法);
一個是index.html(引入vue進行使用)。
以下為了講解方便,我會直接放入我本人寫完的代碼,講解也在代碼的註釋中,很詳細,認真看就好。
先說index.html文件:
這個文件就是個簡單的html結構,引入我們寫的vue.js文件,然後和vue官網的引入書寫方式是一樣的,直接用。
<!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>Document</title>
</head>
<body>
<!-- 引入稍後寫的vue.js文件 -->
<script src="./vue.js"></script>
<script>
// 實例化vue ,書寫方式與我們正常引入的vue使用方式一樣
var vue = new Vue({
// 定義data數據,定義兩個變量,一個message,一個list對象
data: {
message: 'Hello Vue!',
list: {
name: 'xiaoming'
}
}
})
// 本次沒有重定向this,所以先用 vue.$data 拿到vue中data的數據(等同於 this. ),然後進行賦值更改
// 這個操作稍後我們會寫,會對this進行操作。目前我們先使用 $data 代替this
vue.$data.message = "你好" // 將 message 更改成 你好
vue.$data.list.name = "小明" // 將 name 更改成 小明
console.log(vue.$data.message) // 打印出來,如果data中的數據更改成功,說明我們實現了這個功能
console.log(vue.$data.list.name)
</script>
</body>
</html>
再看下比較重要的vue.js文件,有一些js知識點需要大家提前會用:
1. js中類的概念(class)
2. constructor構造函數
3. Object.keys()枚舉方法
4. forEach()方法
5. 遞歸
6. Object.defineProperty(obj, prop, desc)三個參數分別是:需要定義屬性的當前對象、當前需要定義的屬性名、描述符(get和set)
// vue.js文件
// 定義vue類,當被引入時,可直接使用
class Vue {
// 構造函數constructor,在html中,new Vue()中的所有,都會傳進來。
constructor(opa) {
this.$opa = opa // 賦值到$opa
this.$data = opa.data // 將opa中的 data 賦值
this.obsever(opa.data) // 得到data後,傳入 obsever函數進行 枚舉每條數據,然後進行遍歷
}
obsever(value) {
// 先判斷傳進來的是否是對象,如果為空 或非對象,則不向下進行。
if (!value || typeof value !== 'object') {
return
}
// Object.keys() 枚舉方法,返回每條對象的key值,返回的對象是數組,本例中返回:['message','list']
// forEach 遍歷每個key值,將每條數據放入 defineReactive()函數中,通過Object.defineProperty進行處理
Object.keys(value).forEach(key => {
this.defineReactive(value, key, value[key]) // 第一個參數是 value對象,第二個參數是枚舉出來的key,第三個參數是key對應的value值
})
}
defineReactive(value, key, val) {
// 如果傳入來的val依然是個object對象,則需要重新遞歸執行obsever()方法,如本次案例中,html文件的data中有兩個參數:message和list
// 我們通過forEach遍歷data後,得到的一個是message 鍵值對,一個是list對象。我們的目的是需要得到每一個鍵值對,對其中的值進行更改
// 如果遍歷後又是對象,需要再次將這個對象拆開,得到鍵值對,所以需要遞歸再次進行遍歷,直到拆到都是鍵值對為止。
this.obsever(val) // 遞歸操作
// Object.defineProperty()第三個參數是比較重要的,可以決定數據能否被讀取,被遍歷等。
Object.defineProperty(value, key, {
get() { // 進行讀取數據
return val
},
set(i) { // 進行改數據
// 如果傳入進來的數據val,等於修改的i,則返回
if (i === val) {
return
}
// 閉包
val = i
console.log(key + "賦值成功" + '=' + val) // 修改成功,控制檯打印
}
})
}
}
運行index.html,打開控制檯,運行成功
空閒時,會繼續更新源碼知識,我們共同學習。如有錯誤請指出,核實後會在第一時間更改。