前端面試準備(框架原理)--- 雙向綁定的實現

簡單介紹
我的理解,所謂的雙向綁定,其實就是將Model和View綁定在一起,任何一方改變的同時,改變另外一方。
在流行框架中,react是單向綁定(只支持Model改變=>View改變),要實現雙向綁定得加value和onChange事件從而實現(View改變=>調起事件=>改變Model)。
而vue是雙向綁定的,因爲它事先已經幫我們綁定好了事件。
什麼是Model
我理解爲Model就是一個JS對象,用來存儲頁面中的數據。
什麼是View
我理解是頁面中所顯示的DOM對象的集合。

怎麼實現雙向綁定呢?

  1. Object.defineProperty()
    點擊查看實現效果
    Model => View 實現的原理:
    當Model改變時,得到事件響應(數據劫持),獲取到Dom節點,我們就可以通過Dom.value來改變View。而Object.defineProperty主要幫我們來獲得這個過程的事件響應,或者常說的數據劫持,可以劫持到改變後的新值。
    View => Model 實現原理:
    當View改變時,調起onKeyup之類的事件,然後改變響應的Model,這個其實是很簡單的。
  2. Proxy() – vue3中啓用了該方式
    點擊查看實現效果
    實現原理與上面基本相似,但是爲什麼vue3中會使用它呢?這個後面會解釋。

Object.defineProperty()
這個建議去看一下紅寶書的介紹可以幫助快速理解。或者點擊MND

主要使用到了它的訪問器屬性:get和set

  1. get
    當獲取對象屬性值時觸發。這個起到的作用不大。
  2. 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;
    })
})

總結一下

本文主要幫助理解了雙向綁定的原理,以及實現雙向綁定的兩種方法。如有不對之處還望幫忙指出~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章