五十行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
    }
})

相关文章
相关标签/搜索