簡單介紹
我的理解,所謂的雙向綁定,其實就是將Model和View綁定在一起,任何一方改變的同時,改變另外一方。
在流行框架中,react是單向綁定(只支持Model改變=>View改變),要實現雙向綁定得加value和onChange事件從而實現(View改變=>調起事件=>改變Model)。
而vue是雙向綁定的,因爲它事先已經幫我們綁定好了事件。
什麼是Model
我理解爲Model就是一個JS對象,用來存儲頁面中的數據。
什麼是View
我理解是頁面中所顯示的DOM對象的集合。
怎麼實現雙向綁定呢?
- Object.defineProperty()
點擊查看實現效果
Model => View 實現的原理:
當Model改變時,得到事件響應(數據劫持),獲取到Dom節點,我們就可以通過Dom.value來改變View。而Object.defineProperty主要幫我們來獲得這個過程的事件響應,或者常說的數據劫持,可以劫持到改變後的新值。
View => Model 實現原理:
當View改變時,調起onKeyup之類的事件,然後改變響應的Model,這個其實是很簡單的。 - Proxy() – vue3中啓用了該方式
點擊查看實現效果
實現原理與上面基本相似,但是爲什麼vue3中會使用它呢?這個後面會解釋。
Object.defineProperty()
這個建議去看一下紅寶書的介紹可以幫助快速理解。或者點擊MND
主要使用到了它的訪問器屬性:get和set
- get
當獲取對象屬性值時觸發。這個起到的作用不大。 - set
當改變對象屬性值時觸發。比如Model對象的某個屬性值發生了變化,就會調起set方法,我們可以在set方法中改變對應View中對應的某個Dom節點的值。
我們開始實現吧!!!
Object.defineProperty()版本
首先我們準備一下界面,比較簡單,左側是input框,右邊是model轉成的字符串。我們會使用到console來改變model的屬性值,來測試是否會改變DOM
源碼在這
demo在這
界面代碼(index.html)如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>bidreactional binding</title>
</head>
<style>
div {
width: 40%;
float: left;
border: 1px dashed;
padding: 20px;
height: 100vh;
}
</style>
<body>
<div>
<p>View:</p>
<input id="view" />
</div>
<div>
<p>Model:</p>
<span id="model"></span>
</div>
<script src="./index.js"></script>
</body>
</html>
接下來是實現綁定邏輯,我們都寫在了index.js中,都有對應的註釋。
// 獲取DOM節點
var view = document.getElementById('view');
var model = document.getElementById('model');
// 設置model對象
var data = {};
// 設置get函數的中轉站,封裝後可以去掉
let temp = 0;
//在data對象中定義number屬性,並給他賦值兩個訪問器屬性,來代理或者說劫持number的值的獲取與設置
Object.defineProperty(data, "number", {
//可枚舉,這個主要是用來將Model顯示在前端的,可以省去
enumerable:true,
// 獲取值時的處理方法 就相當於代理執行獲取值的操作,返回什麼都又他決定,這裏不能return data.number會造成無限循環的
get: function () {
return temp;
},
// data的number值發生變化時調用
set: function (value) {
// 改變View節點的值
view.value = value;
// 將值存在temp中,在get時要用到
temp = value;
// 這個主要是用來將Model顯示在前端的,可以省去
model.innerHTML = `"data":${JSON.stringify(data)}`;
},
})
// 綁定事件,當view改變時將改變的值賦值給data對象中的number屬性
view.addEventListener("keyup", function (event) {
data.number = event.target.value;
})
以上就可以實現一個基於單個輸入框的雙向綁定了。這裏放代碼鏈接和demo鏈接!假設我現在有三個輸入框怎麼辦呢?我要整個流程再來三次嗎?其實不用的,我們可以對流程封裝一下。
源碼在這
demo在這
index.html
.....
<div>
<p>View:</p>
<p>username: <input id="username" /></p>
<p>password: <input id="password" /></p>
<p>sex: <input id="sex" /></p>
</div>
......
index.js
// model 是用來顯示model字符串的 可省去
var model = document.getElementById('model');
// model對象
var data = {};
// 所有input的id
const keys = ["username", "password", "sex"];
// 給每個id都實現上雙向綁定
keys.forEach(item => {
// 封裝後綁定View和Model的方法
bindViewAndModel(item, "", document.getElementById(item))
})
function bindViewAndModel(key, val, dom) {
// Model => View
Object.defineProperty(data, key, {
// 這裏只是爲了前端展示model 可以省去
enumerable: true,
get: function () {
return val; // 去掉了temp
},
set: function (newValue) {
val = newValue;
dom.value = newValue;
// 這裏只是爲了前端展示model 可以省去
model.innerHTML = JSON.stringify(data);
},
})
// View => Model
dom.addEventListener("keyup", function (event) {
data[key] = event.target.value;
})
}
到這裏我們就能實現多個input框的雙向綁定了,可以在這裏測試一下。
其實這裏還會設計不同的表單元素的變化,這裏就不再深入了。
接下來探究一下proxy。爲什麼會用proxy替換掉Object.defineProperty()呢?因爲defineProperty無法監聽數組變化(這裏我還在試驗中),也只能劫持對象的屬性。而proxy而劫持整個對象。
Proxy版本雙向綁定
源碼在這
demo在這
index.html 同上
index.js
// 這裏只是爲了前端展示model 可以省去
var model = document.getElementById('model');
// 所有dom的id
const domKeys =["username","password","sex"];
// 枚舉信息 根據 {domkey:dom}
const domEnum = {};
// model
var data = {};
// proxy 代理整個data
const proxy = new Proxy(data, {
// taget 即爲代理的對象 prop爲屬性值
get: function (target, prop) {
return target[prop];
},
// value爲新值
set: function (target, prop, value) {
target[prop] = value;
domEnum[prop+'Dom'].value = target[prop];
// 這裏只是爲了前端展示model 可以省去
model.innerHTML = JSON.stringify(data);
}
})
// 加上key事件
domKeys.forEach(item=>{
const dom = document.getElementById(item);
domEnum[item+'Dom'] = dom;
dom.addEventListener("keyup", function (event) {
proxy[item] = event.target.value;
})
})
總結一下
本文主要幫助理解了雙向綁定的原理,以及實現雙向綁定的兩種方法。如有不對之處還望幫忙指出~~