基於Object.defineProperty實現雙向數據綁定

雙向數據綁定可算是前端領域經久不衰的熱詞,不論是前端開發仍是面試都會有所涉及。並且不一樣的框架也想盡一切辦法去實現這一特性,好比:
Knockout / Backbone --- 發佈-訂閱模式
Angular --- ‘髒檢查’
Vue --- 'Object.defineProperty'html

那麼 雙向數據綁定究竟是什麼?沒圖說個卵,直接上圖

clipboard.png

簡單的說就是在數據UI之間創建雙向的通訊通道,當用戶經過Function改變了數據,那麼這個改變也會當即反應到UI上;或者說用戶經過UI的操做,那麼這些操做也會隨之引發對應的數據變更。emmmmmm...沒毛病!前端

既然本文標題是討論Object.defineProperty,那麼筆者就把當前火熱的到Boom的國產框架:Vue.js 請出來,而後在瞭解完她實現雙向數據綁定的原理以後,咱們着手實現一個抽象派的雙向數據綁定。那麼那位朋友就說了,什麼叫 抽象派 ?我估計吧,可能就是馬(Vue)和馬骨架的區別吧,TAT...git

在介紹Vue的雙向數據綁定以前,筆者還想多叨叨幾句,若是某一天有人問你:Vue是如何實現雙向數據綁定的? 姑且先在這裏停頓下,思考下這個問題的答案...................
或許有朋友會脫口而出「數據劫持」,說的沒錯!的確就是「數據劫持」,可是還不夠充分和不夠精確。筆者在這裏也談下本身的一點點所見所聞所想:面試

  1. 不夠精確:與其說是 數據劫持,更應該說是對數據對象的SetterGetter實現的劫持。
  2. 不夠充分:爲何說不夠充分?是由於 Object.defineProperty 僅僅是實現了對數據的監控,後續實現對UI的從新渲染並非它作的,因此這裏還涉及到 發佈-訂閱模式(有興趣的朋友戳這裏);過程是,當監控的數據對象被更改後,這個變動會被廣播給全部訂閱該數據的watcher,而後由該 watcher實現對頁面的從新渲染。

下面進入正題,一塊兒瞭解下Vue實現雙向數據綁定的原理,果斷上圖:segmentfault

clipboard.png

首先,Vue的Compile模塊會對Vue的 template 代碼進行編譯解析並生成一系列的watcher,也能夠稱之爲「更新函數」,它負責把變動後的相關數據從新渲染到指定的地方。舉個栗子:數組

<input v-model="message">

Compile會解析出 v-moel 這個指令而且生成 watcher 並鏈接數據中的 message 和當前這個Dom對象,一旦收到這個message被變動的通知,watcher就會根據變動對這個Dom進行從新渲染。框架

固然一個頁面或者一個項目中確定有不少watcher,所以Vue使用了Dep這個對象來存儲每個watcher,當數據發生變動,Observer會調用Dep的notify方法以通知全部訂閱了該數據的watcher,讓它們醒醒該幹活了...函數

Vue的雙向數據綁定也說得差很少了,下面就開始順着這個思路着手寫一個吧,畢竟說得多不如code來得好啊!!!具體的實現效果以下,Let‘s do it
clipboard.pngui

不知道爲何GIF上傳不了,因此只能將就用圖片了,QAQ....this

功能就用文字解釋下:
第一個行的 title 0 直接顯示的是數據,以便觀察;咱們能夠在輸入框中輸入任何int, 而後點擊 「加」能夠實現對數值的 +1 操做,同時輸入框的數值和 title 也會隨之變化;固然,經過輸入數值,title也會跟着變化。

首先把Html代碼呈上來:

<!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>Object.defineProperty實現雙向綁定</title>
</head>

<body>
    <h1 id='h1'></h1>
    <input type="text" id="inp" onkeyup="inputChange(event)">
    <input type="button" value="加" onclick="btnAdd()" />
</body>
<script src="./index.js"></script>

</html>

而後開始一步一步在index.js裏寫代碼吧
1) 首先咱們先定義一個數據源

//數據源
let vm = {
    value: 0
}

2) 而後定義一個Dep,用於存儲watcher

//用於管理watcher的Dep對象
let Dep = function () {
    this.list = [];
    this.add = function(watcher){
        this.list.push(watcher)
    },
    this.notify = function(newValue){
        this.list.forEach(function (fn) {
            fn(newValue)
        })
    }
};

3) 模擬Compile出來的watchers,該demo涉及到兩個地方的從新render,一個是title,另外一個是輸入框。因此寫兩個watcher,而後存入Dep

// 模擬compile,經過對Html的解析生成一系列訂閱者(watcher)
function renderInput(newValue) {
    let el = document.getElementById('inp');
    if (el) {
        el.value = newValue
    }
}

function renderTitle(newValue) {
    let el = document.getElementById('h1');
    if (el) {
        el.innerHTML = newValue
    }
}

//將解析出來的watcher存入Dep中待用
let dep = new Dep();
dep.add(renderInput);
dep.add(renderTitle)

4) 使用 Object.defineProperty 定義一個Observer

function observer(vm, key, value) {
    Object.defineProperty(vm, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            console.log('Get');
            return value
        },
        set: function (newValue) {
            if (value !== newValue) {
                value = newValue
                console.log('Update')

                //將變更通知給相關的訂閱者
                dep.notify(newValue)
            }
        }
    })
}

5) 再將頁面使用的兩個方法寫出來。(Vue使用的是指令對事件進行綁定,可是本文不涉及指令,因此用最原始的方法綁定事件)

//頁面引用的方法
function inputChange(ev) {
    let value = Number.parseInt(ev.target.value);
    vm.value = (Number.isNaN(value)) ? 0 : value;
}

function btnAdd() {
    vm.value = vm.value + 1;
}

主要的代碼都寫好後,下面第一件事就是初始化

//數據初始化方法
function initMVVM(vm) {
    Object.keys(vm).forEach(function (key) {
        observer(vm, key, vm[key])
    })
}

//初始化數據源
initMVVM(vm)

//初始化頁面,將數據源渲染到UI
dep.notify(vm.value);

這樣一個簡單的基於 Object.defineProperty 的雙向數據綁定就完成了。看完的朋友有沒有對雙向數據綁定有了更多的理解了呢?若是沒有理解的話,能夠將代碼複製到本地,而後循着代碼再運行下,或許能容易理解。固然這裏的代碼並不高深,只是從淺層去談論了雙向數據綁定,因此有不足或者表達錯誤的地方,煩請各位朋友多多指正。

這裏是源碼,因爲放不了動圖,因此有興趣的小夥伴能夠拿下來

最後仍是補充一句,Object.defineProperty雖然好用,但並非無懈可擊的,它對數組數據的處理並無想象中的好甚至表現不好,所以Vue團隊專門爲Vue中的數組類型編寫了額外的方法以實現對數組的正確監控
。所以,ES6中的Proxy自告奮勇,拯救了ES5 中 Object.defineProperty對數組數據處理的不足。有興趣的朋友請期待筆者的下一篇博客,討論下用Proxy實現雙向數據綁定。

我們下期再見!!

相關文章
相關標籤/搜索