五十行javascript代碼實現簡單的雙向數據綁定
Vue框架想必從事前端開發的同學都使用過,它的雙向數據綁定機制能給我們帶來很大的方便。今天閑着沒事,嘗試着實現一下雙向數據綁定,接下來給大家分享一下。
Object.defineProperty(obj, prop, descriptor)
Object.defineProperty 方法允許精確添加或修改對象的屬性。它的第一個參數 obj 是要在其上定義屬性的對象,第二個參數 prop 是要定義或修改的屬性的名稱,第三個參數 descriptor 是一個將被定義或修改的屬性的描述符。
返回值: 被傳遞給函數的對象。
來舉個例子:
var o = Object.defineProperty({}, 'name', {
value: 1
});
console.log(o) // {name: 1}
這是最基本的定義一個對象的方式。對於屬性描述符,還有很多其他屬性:
數據描述符和存取描述符均具有以下可選鍵值:
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。
這里只說一下 get 和 set:
看一下這個例子:
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 定義了一個 get 和 set ,為什么使用 _name_而不是name呢?因為name是正在被定義的屬性,如果在get或者set中使用name無形之中就會發生遞歸,導致棧溢出。_name_這個是自己自定義的,你完全可以設置為$name、__name__等等。
另外,使用對象的字面量形式也可以設置get與set:
var o = {
get name(){
return this._name_;
},
set name(value){
this._name_ = value * 2;
}
};
o.name = 1;
console.log(o.name); // 2
實現雙向數據綁定
要實現雙向數據綁定,肯定要從 get 與 set 下手,在 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
}
})

