zoukankan      html  css  js  c++  java
  • 五十行javascript代码实现简单的双向数据绑定

    五十行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__等等。

    另外,使用对象的字面量形式也可以设置getset

    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
        }
    })

    爱写代码的孩子运气不会太差。 github:http://github.com/lavyun 新浪微博:http://weibo.com/u/5921186675
  • 相关阅读:
    CodePlus#4 最短路
    最大子矩阵问题———悬线法
    Luogu P3393 逃离僵尸岛
    SCOI2011 糖果
    关于页面的跳转添加参数(比如id啥的)
    npm 常用命令
    移动开发中的一些基本的思想,和需要注意的细节技巧之处
    Mock模拟后台数据接口--再也不用等后端的API啦
    普及知识
    移动端JD首页H5页面
  • 原文地址:https://www.cnblogs.com/libin-1/p/6759414.html
Copyright © 2011-2022 走看看