我們一直都在問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,打開控制台,運作成功

空閑時,會繼續更新源碼知識,我們共同學習。如有錯誤請指出,核實後會在第一時間更改。