五十行javascript代碼實現簡單的雙向數據綁定

五十行javascript代碼實現簡單的雙向數據綁定

Vue框架想必從事前端開發的同窗都使用過,它的雙向數據綁定機制能給咱們帶來很大的方便。今天閒着沒事,嘗試着實現一下雙向數據綁定,接下來給你們分享一下。javascript

Object.defineProperty(obj, prop, descriptor)

Object.defineProperty 方法容許精確添加或修改對象的屬性。它的第一個參數 obj 是要在其上定義屬性的對象,第二個參數 prop 是要定義或修改的屬性的名稱,第三個參數 descriptor 是一個將被定義或修改的屬性的描述符。html

返回值: 被傳遞給函數的對象。前端

來舉個例子:java

var o = Object.defineProperty({}, 'name', {
	value: 1
});

console.log(o) // {name: 1}

這是最基本的定義一個對象的方式。對於屬性描述符,還有不少其餘屬性:api

數據描述符和存取描述符均具備如下可選鍵值:瀏覽器

  • configurable當且僅當該屬性的 configurable 爲 true 時,該屬性描述符纔可以被改變,也可以被刪除。默認爲 false。
  • enumerable當且僅當該屬性的 enumerable 爲 true 時,該屬性纔可以出如今對象的枚舉屬性中。默認爲 false。

數據描述符同時具備如下可選鍵值:框架

  • value該屬性對應的值。能夠是任何有效的 JavaScript 值(數值,對象,函數等)。默認爲 undefined。
  • writable當且僅當該屬性的 writable 爲 true 時,該屬性才能被賦值運算符改變。默認爲 false。

存取描述符同時具備如下可選鍵值:函數

  • get一個給屬性提供 getter 的方法,若是沒有 getter 則爲 undefined。該方法返回值被用做屬性值。默認爲 undefined。
  • set一個給屬性提供 setter 的方法,若是沒有 setter 則爲 undefined。該方法將接受惟一參數,並將該參數的新值分配給屬性。默認爲 undefined。

這裏只說一下 getset
看一下這個例子:this

var o = Object.defineProperty({}, 'name', {
	get: function () {
		return this._name_;
	},
	set: function (value) {
		this._name_ = value * 2;
	}
});

o.name = 1;
console.log(o.name);  // 2

給屬性 name 定義了一個 getset ,爲何使用 _name_而不是name呢?由於name是正在被定義的屬性,若是在get或者set中使用name無形之中就會發生遞歸,致使棧溢出。_name_這個是本身自定義的,你徹底能夠設置爲$name__name__等等。prototype

另外,使用對象的字面量形式也能夠設置getset

var o = {
    get name(){
        return this._name_;
    },
    set name(value){
        this._name_ = value * 2;
    }
};

o.name = 1;
console.log(o.name);  // 2

實現雙向數據綁定

要實現雙向數據綁定,確定要從 getset 下手,在 set 的函數中從新渲染相關的數據,因此一開始應該是這樣的:

var o = {
    get name(){
        return this._name_;
    },
    set name(value){
        this._name_ = value;
        this.render('name');
    },
    render: function(pro){
	    document.write(this[pro]);
    }
};

在瀏覽器控制檯修改一下o.name 試試:

如何實現表單控件到數據的綁定呢?在 Vue 中,表單元素經過 v-model 綁定一個變量,類型這樣:

<input type="text" v-model="name">

其實 v-model 的元素是綁定了一個 input的自定義事件,咱們不考慮那麼多,就使用原生的 oninput 事件來模擬下。

var o = {
    get name(){
        return this._name_;
    },
    set name(value){
        this._name_ = value;
        console.log(this._name_);
    },
    inputInit: function () {
        var self = this;
        var vModels = document.querySelectorAll('[v-model]');
        for (let i = 0; i < vModels.length; i++) {
            vModels[i].addEventListener('input', function () {
                var property = this.getAttribute('v-model');
                var value = this.value;
                self.name = value;
            })
        }
    }
}.inputInit();

至此一個簡單的雙向數據綁定就差很少了,咱們模仿一下 Vue 的api格式,再將代碼封裝一下:

html:

<input type="text" v-model="name">
<p v-text="name"></p>

javascript:

function Vue(options) {
    var data = options.data || {};
    var dKeys = Object.keys(data);
    var _this = this;
    this.vData = {};
    // 根據data中的變量數量動態的綁定 get 與 set
    for (let i = 0; i < dKeys.length; i++) {
        Object.defineProperty(this.vData, dKeys[i], {
            get: function () {
                return this['__' + dKeys[i] + '__'];
            },
            set: function (value) {
                this['__' + dKeys[i] + '__'] = value;
                _this.render(dKeys[i]);  // 從新渲染相關數據
            }
        })
    }

    for(let i in data) {  // 初始化時設置一變vData,觸發一遍 set
        this.vData[i] = data[i];
    }

    this.inputInit(); // 給表單組件綁定事件監聽
}

Vue.prototype.render = function (pro) {
    var vModels = document.querySelectorAll('[v-model=' + pro + ']');
    var vText = document.querySelectorAll('[v-text=' + pro + ']');
    for (var i = 0; i < vModels.length; i++) {
        vModels[i].value = this.vData[pro];
    }

    for (var j = 0; j < vText.length; j++) {
        vText[j].innerText = this.vData[pro];
    }
};

Vue.prototype.inputInit = function () {
    var self = this;
    var vModels = document.querySelectorAll('[v-model]');
    for (let i = 0; i < vModels.length; i++) {
        vModels[i].addEventListener('input', function () {
            var property = this.getAttribute('v-model');
            var value = this.value;
            self.vData[property] = value;
        })
    }
};

var vm = new Vue({
    data: {
        name: 1
    }
})

相關文章
相關標籤/搜索