常识 共享 合作 bodog官网

    bodog官网专心于网页资料下载,供给博狗博彩、网页规划、ps资料、图片资料等,服务于【个人站长】【网页规划师】和【web开发从业者】的代码资料与规划资料网站。

    bodog官网供给网页资料下载、博狗博彩
    常识 共享 合作!

    完结一个双向数据绑定的简易MVVM结构

    作者:佳明妈 来历:oschina 2017-05-31 人气:
    reactJs、vueJs、Angular.js等MVVM结构大火,先用jquery测验一下双向数据绑定然后再自己着手完结一下双向数据绑定的mvvm结构,
    reactJs、vueJs、Angular.js等MVVM结构大火,其间vueJs、Angular.js双向数据绑定功用关于数据交互频频的运用场景真的是十分的酸爽,reactJs默许不支撑双向数据绑定,可是完结双向绑定也不难,这儿不研讨这些结构的运用。下面咱们来自己着手完结一下双向数据绑定的mvvm结构。

    用jquery完结一个数据双向绑定

    jquery这个神器,仍然活泼很多年,未来仍然能够活泼很多年,咱们这儿就用jquery完结一下双向数据绑定。jquery完结双向数据绑定选用DOM工作的订阅和发布机制。

    jquery完结的原文:https://www.oschina.net/translate/easy-two-way-data-binding-in-javascript

    function DataBinder( object_id ) {
      // Use a jQuery object as simple PubSub
      var pubSub = jQuery({});
    
      // We expect a `data` element specifying the binding
      // in the form: data-bind-<object_id>="<property_name>"
      var data_attr = "bind-" + object_id,
          message = object_id + ":change";
    
      // Listen to change events on elements with the data-binding attribute and proxy
      // them to the PubSub, so that the change is "broadcasted" to all connected objects
      jQuery( document ).on( "change", "[data-" + data_attr + "]", function( evt ) {
        var $input = jQuery( this );
    
        pubSub.trigger( message, [ $input.data( data_attr ), $input.val() ] );
      });
    
      // PubSub propagates changes to all bound elements, setting value of
      // input tags or HTML content of other tags
      pubSub.on( message, function( evt, prop_name, new_val ) {
        jQuery( "[data-" + data_attr + "=" + prop_name + "]" ).each( function() {
          var $bound = jQuery( this );
    
          if ( $bound.is("input, textarea, select") ) {
            $bound.val( new_val );
          } else {
            $bound.html( new_val );
          }
        });
      });
    
      return pubSub;
    }
    关于上面这个完结来说,下面是一个User模型的最简略的完结办法: 
    function User( uid ) {
      var binder = new DataBinder( uid ),
    
          user = {
            attributes: {},
    
            // The attribute setter publish changes using the DataBinder PubSub
            set: function( attr_name, val ) {
              this.attributes[ attr_name ] = val;
              binder.trigger( uid + ":change", [ attr_name, val, this ] );
            },
    
            get: function( attr_name ) {
              return this.attributes[ attr_name ];
            },
    
            _binder: binder
          };
    
      // Subscribe to the PubSub
      binder.on( uid + ":change", function( evt, attr_name, new_val, initiator ) {
        if ( initiator !== user ) {
          user.set( attr_name, new_val );
        }
      });
    
      return user;
    }
    现在咱们假如想要将User模型特点绑定到UI上,咱们只需求将合适的数据特性绑定到对应的HTML元素上。
    // javascript
    var user = new User( 123 );
    user.set( "name", "Wolfgang" );
    
    // html
    <input type="number" data-bind-123="name" />
    
    这样输入值会自动映射到user目标的name特点,反之亦然。到此这个简略完结就完结啦!

    自己完结一个简易MVVM结构

    体会过运用jquery完结一个双向绑定的功用后,咱们来研讨下,自己完结一个简易MVVM结构

    咱们知道的,常见的数据绑定的完结办法

    1、数据绑架(vue):经过Object.defineProperty() 去绑架数据每个特点对应的getter和setter
    2、脏值检测(angular):经过特定工作比方input,change,xhr恳求等进行脏值检测。
    3、发布-订阅形式(backbone):经过发布音讯,订阅音讯进行数据和视图的绑定监听。详细代码完结能够参阅我github个人库房overwrite->my-observer

    一言不合先上代码

    <!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>example</title>
      <script src="./mvvm.js" charset="utf-8"></script>
    </head>
    <body>
      <div id="mvvm">
        <h2>{{b}}</h2>
        <input type="text" x-model="a">
        <input type="text" name="" value="" x-model="a">
        <p x-html="a">{{ a }}</p>
        <button type="button" name="button" x-on:click="testToggle">change b</button>
      </div>
    </body>
    <script>
    var vm = new MVVM({
      el: '#mvvm',
      data: {
        a: 'test model',
        b: 'hello MVVM',
        flag: true
      },
      methods: {
        testToggle: function () {
          this.flag = !this.flag;
          this.b = this.flag ? 'hello MVVM' : 'test success'
        }
      }
    });
    </script>
    </html>
    

    作用图

    完结一个双向数据绑定的简易MVVM结构

    看完作用图之后,接下来咱们直接搞工作吧

    一、MVVM结构全体流程图

    要完结一个咱们自己的mvvm库,咱们首要需求做的工作不是写代码,而是收拾一下思路,捋清楚之后再着手肯定会让你事半功倍。先上流程图,咱们对着流程图来捋思路
    MVVM结构全体流程图

    如上图所示,咱们能够看到,全体完结分为四步

    1、完结一个Observer,对数据进行绑架,告诉数据的改变
    2、完结一个Compile,对指令进行解析,初始化视图,而且订阅数据的改变,绑定好更新函数
    3、完结一个Watcher,将其作为以上两者的一个中介点,在接纳数据改变的一同,让Dep增加当时Watcher,并及时告诉视图进行update
    4、完结MVVM,整合以上三者,作为一个进口函数

    二、mvvm结构技能点完结

    1、完结Observer

    这儿咱们需求做的工作便是完结数据绑架,并将数据改变给传递下去。那么这儿将会用到的办法便是Object.defineProperty()来做这么一件事。先不管三七二十一,咱先用用Object.defineProperty()试试手感。

    function observe (data) {
      if (!data || typeof data !== 'object') {
        return;
      }
      Object.keys(data).forEach(key => {
        observeProperty(data, key, data[key])
      })
    }
    function observeProperty (obj, key, val) {
      observe(val);
      Object.defineProperty(obj, key, {
        enumerable: true,   // 可枚举
        configurable: true, // 可从头界说
        get: function () {
          return val;
        },
        set: function (newVal) {
          if (val === newVal || (newVal !== newVal && val !== val)) {
            return;
          }
          console.log('数据更新啦 ', val, '=>', newVal);
          val = newVal;
        }
      });
    }
    

    调用

    var data = {
      a: 'hello'
    }
    observe(data);

    作用如下

    看完是不是发现JavaScript供给给咱们的Object.defineProperty()办法功用巨强壮巨好用呢。

    其实到这,咱们现已算是完结了数据绑架,完好的Observer则需求将数据的改变传递给Dep实例,然后接下来的工作就丢给Dep去告诉下面完结接下来的工作了,完好代码如下所示

    /**
     * @class 发布类 Observer that are attached to each observed
     * @param {[type]} value [vm参数]   
      * bodog官网http://www.3mbodoglv.com/ 收拾发布
     */
     function observe(value, asRootData) {
       if (!value || typeof value !== 'object') {
         return;
       }
       return new Observer(value);
     }
    
    function Observer(value) {
      this.value = value;
      this.walk(value);
    }
    
    Observer.prototype = {
      walk: function (obj) {
        let self = this;
        Object.keys(obj).forEach(key => {
          self.observeProperty(obj, key, obj[key]);
        });
      },
      observeProperty: function (obj, key, val) {
        let dep = new Dep();
        let childOb = observe(val);
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get: function() {
            if (Dep.target) {
              dep.depend();
            }
            if (childOb) {
              childOb.dep.depend();
            }
            return val;
          },
          set: function(newVal) {
            if (val === newVal || (newVal !== newVal && val !== val)) {
              return;
            }
            val = newVal;
            // 监听子特点
            childOb = observe(newVal);
            // 告诉数据改变
            dep.notify();
          }
        })
      }
    }
    /**
     * @class 依靠类 Dep
     */
    let uid = 0;
    function Dep() {
      // dep id
      this.id = uid++;
      // array 存储Watcher
      this.subs = [];
    }
    Dep.target = null;
    Dep.prototype = {
      /**
       * [增加订阅者]
       * @param  {[Watcher]} sub [订阅者]
       */
      addSub: function (sub) {
        this.subs.push(sub);
      },
      /**
       * [移除订阅者]
       * @param  {[Watcher]} sub [订阅者]
       */
      removeSub: function (sub) {
        let index = this.subs.indexOf(sub);
        if (index !== -1) {
          this.subs.splice(index ,1);
        }
      },
      // 告诉数据改变
      notify: function () {
        this.subs.forEach(sub => {
          // 履行sub的update更新函数
          sub.update();
        });
      },
      // add Watcher
      depend: function () {
        Dep.target.addDep(this);
      }
    }
    // 结合Watcher
    /** 
    * Watcher.prototype = {
    *   get: function () {
    *     Dep.target = this;
    *     let value = this.getter.call(this.vm, this.vm);
    *     Dep.target = null;
    *     return value;
    *   },
    *   addDep: function (dep) {
    *     dep.addSub(this);
    *   }
    * }
    */

    至此,咱们现已完结了数据的绑架以及notify数据改变的功用了。

    2、完结Compile

    按理说咱们应该紧接着完结Watcher,究竟从上面代码看来,Observer和Watcher相关很多啊,可是,咱们在捋思路的时分也应该知道了,Watcher和Compile也是有一腿的哦。所以咱先把Compile也给完结了,这样才干更好的让他们3P。

    Compile需求做的工作也很简略
    a、解析指令,将指令模板中的变量替换成数据,对视图进行初始化操作
    b、订阅数据的改变,绑定好更新函数
    c、接纳到数据改变,告诉视图进行view update

    咱先试着写一个简略的指令解析办法,完结解析指令初始化视图。

    js部分

    function Compile (el, value) {
      this.$val = value;
      this.$el = this.isElementNode(el) ? el : document.querySelector(el);
      if (this.$el) {
        this.compileElement(this.$el);
      }
    }
    Compile.prototype = {
      compileElement: function (el) {
        let self = this;
        let childNodes = el.childNodes;
        [].slice.call(childNodes).forEach(node => {
          let text = node.textContent;
          let reg = /{{((?:.|
    )+?)}}/;
          // 假如是element节点
          if (self.isElementNode(node)) {
            self.compile(node);
          }
          // 假如是text节点
          else if (self.isTextNode(node) && reg.test(text)) {
            // 匹配第一个选项
            self.compileText(node, RegExp.$1.trim());
          }
          // 解析子节点包括的指令
          if (node.childNodes && node.childNodes.length) {
            self.compileElement(node);
          }
        })
      },
      // 指令解析
      compile: function (node) {
        let nodeAttrs = node.attributes;
        let self = this;
    
        [].slice.call(nodeAttrs).forEach(attr => {
          var attrName = attr.name;
          if (self.isDirective(attrName)) {
            var exp = attr.value;
            node.innerHTML = typeof this.$val[exp] === 'undefined' ? '' : this.$val[exp];
            node.removeAttribute(attrName);
          }
        });
      },
      // {{ test }} 匹配变量 test
      compileText: function (node, exp) {
        node.textContent = typeof this.$val[exp] === 'undefined' ? '' : this.$val[exp];
      },
      // element节点
      isElementNode: function (node) {
        return node.nodeType === 1;
      },
      // text纯文本
      isTextNode: function (node) {
        return node.nodeType === 3
      },
      // x-XXX指令断定
      isDirective: function (attr) {
        return attr.indexOf('x-') === 0;
      }
    }

    html部分

    <body>
    <div id="test">
      <h2 x-html="a"></h2>
      <p>{{ a }}</p>
    </div>
    </body>
    <script>
    var data = {
      a: 'hello'
    }
    new Compile('#test', data)
    </script>

    成果如图所示

     
    依照过程走的我现已完结了指令解析!
    这儿咱们仅仅完结了指令的解析以及视图的初始化,并没有完结数据改变的订阅以及视图的更新。完好的Compile则完结了这些功用,详细代码如下

    /**
     * @class 指令解析类 Compile
     * @param {[type]} el [element节点]
     * @param {[type]} vm [mvvm实例]
     */
    function Compile(el, vm) {
      this.$vm = vm;
      this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    
      if (this.$el) {
        this.$fragment = this.nodeFragment(this.$el);
        this.compileElement(this.$fragment);
        // 将文档碎片放回实在dom
        this.$el.appendChild(this.$fragment)
      }
    }
    Compile.prototype = {
      compileElement: function (el) {
        let self = this;
        let childNodes = el.childNodes;
        [].slice.call(childNodes).forEach(node => {
          let text = node.textContent;
          let reg = /{{((?:.|
    )+?)}}/;
    
          // 假如是element节点
          if (self.isElementNode(node)) {
            self.compile(node);
          }
          // 假如是text节点
          else if (self.isTextNode(node) && reg.test(text)) {
            // 匹配第一个选项
            self.compileText(node, RegExp.$1);
          }
          // 解析子节点包括的指令
          if (node.childNodes && node.childNodes.length) {
            self.compileElement(node);
          }
        });
      },
      // 文档碎片,遍历过程中会有屡次的dom操作,为进步功能咱们会将el节点转化为fragment文档碎片进行解析操作
      // 解析操作完结,将其增加回实在dom节点中
      nodeFragment: function (el) {
        let fragment = document.createDocumentFragment();
        let child;
    
        while (child = el.firstChild) {
          fragment.appendChild(child);
        }
        return fragment;
      },
      // 指令解析
      compile: function (node) {
        let nodeAttrs = node.attributes;
        let self = this;
    
        [].slice.call(nodeAttrs).forEach(attr => {
          var attrName = attr.name;
          if (self.isDirective(attrName)) {
            var exp = attr.value;
            var dir = attrName.substring(2);
            // 工作指令
            if (self.isEventDirective(dir)) {
              compileUtil.eventHandler(node, self.$vm, exp, dir);
            }
            // 一般指令
            else {
              compileUtil[dir] && compileUtil[dir](node, self.$vm, exp);
            }
    
            node.removeAttribute(attrName);
          }
        });
      },
      // {{ test }} 匹配变量 test
      compileText: function (node, exp) {
        compileUtil.text(node, this.$vm, exp);
      },
      // element节点
      isElementNode: function (node) {
        return node.nodeType === 1;
      },
      // text纯文本
      isTextNode: function (node) {
        return node.nodeType === 3
      },
      // x-XXX指令断定
      isDirective: function (attr) {
        return attr.indexOf('x-') === 0;
      },
      // 工作指令断定
      isEventDirective: function (dir) {
        return dir.indexOf('on') === 0;
      }
    }
    // 界说$elm,缓存当时履行input工作的input dom目标
    let $elm;
    let timer = null;
    // 指令处理调集
    const compileUtil = {
      html: function (node, vm, exp) {
        this.bind(node, vm, exp, 'html');
      },
      text: function (node, vm, exp) {
        this.bind(node, vm, exp, 'text');
      },
      class: function (node, vm, exp) {
        this.bind(node, vm, exp, 'class');
      },
      model: function(node, vm, exp) {
        this.bind(node, vm, exp, 'model');
    
        let self = this;
        let val = this._getVmVal(vm, exp);
        // 监听input工作
        node.addEventListener('input', function (e) {
          let newVal = e.target.value;
          $elm = e.target;
          if (val === newVal) {
            return;
          }
          // 设置定时器  完结ui js的异步烘托
          clearTimeout(timer);
          timer = setTimeout(function () {
            self._setVmVal(vm, exp, newVal);
            val = newVal;
          })
        });
      },
      bind: function (node, vm, exp, dir) {
        let updaterFn = updater[dir + 'Updater'];
    
        updaterFn && updaterFn(node, this._getVmVal(vm, exp));
    
        new Watcher(vm, exp, function(value, oldValue) {
          updaterFn && updaterFn(node, value, oldValue);
        });
      },
      // 工作处理
      eventHandler: function(node, vm, exp, dir) {
        let eventType = dir.split(':')[1];
        let fn = vm.$options.methods && vm.$options.methods[exp];
    
        if (eventType && fn) {
          node.addEventListener(eventType, fn.bind(vm), false);
        }
      },
      /**
       * [获取挂载在vm实例上的value]
       * @param  {[type]} vm  [mvvm实例]
       * @param  {[type]} exp [expression]
       */
      _getVmVal: function (vm, exp) {
        let val = vm;
        exp = exp.split('.');
        exp.forEach(key => {
          key = key.trim();
          val = val[key];
        });
        return val;
      },
      /**
       * [设置挂载在vm实例上的value值]
       * @param  {[type]} vm    [mvvm实例]
       * @param  {[type]} exp   [expression]
       * @param  {[type]} value [新值]
       */
      _setVmVal: function (vm, exp, value) {
        let val = vm;
        exps = exp.split('.');
        exps.forEach((key, index) => {
          key = key.trim();
          if (index < exps.length - 1) {
            val = val[key];
          }
          else {
            val[key] = value;
          }
        });
      }
    }
    // 指令烘托调集
    const updater = {
      htmlUpdater: function (node, value) {
        node.innerHTML = typeof value === 'undefined' ? '' : value;
      },
      textUpdater: function (node, value) {
        node.textContent = typeof value === 'undefined' ? '' : value;
      },
      classUpdater: function () {},
      modelUpdater: function (node, value, oldValue) {
        // 不对当时操作input进行烘托操作
        if ($elm === node) {
          return false;
        }
        $elm = undefined;
        node.value = typeof value === 'undefined' ? '' : value;
      }
    }
    

     

    好了,到这儿两个和Watcher相关的“菇凉”现已进场了

    3、完结Watcher

    作为一个和Observer和Compile都有联系的“蓝银”,他做的工作有以下几点

    a、经过Dep接纳数据改变的告诉,实例化的时分将自己增加到dep中
    b、特点改变时,接纳dep的notify,调用本身update办法,触发Compile中绑定的更新函数,从而更新视图

    这儿的代码比较简略,所以我决议直接上代码

    /**
     * @class 调查类
     * @param {[type]}   vm      [vm目标]
     * @param {[type]}   expOrFn [特点表达式]
     * @param {Function} cb      [回调函数(一半用来做view动态更新)]
     */
    function Watcher(vm, expOrFn, cb) {
      this.vm = vm;
      expOrFn = expOrFn.trim();
      this.expOrFn = expOrFn;
      this.cb = cb;
      this.depIds = {};
    
      if (typeof expOrFn === 'function') {
        this.getter = expOrFn
      }
      else {
        this.getter = this.parseGetter(expOrFn);
      }
      this.value = this.get();
    }
    Watcher.prototype = {
      update: function () {
        this.run();
      },
      run: function () {
        let newVal = this.get();
        let oldVal = this.value;
        if (newVal === oldVal) {
          return;
        }
        this.value = newVal;
        // 将newVal, oldVal挂载到MVVM实例上
        this.cb.call(this.vm, newVal, oldVal);
      },
      get: function () {
        Dep.target = this;  // 将当时订阅者指向自己
        let value = this.getter.call(this.vm, this.vm); // 触发getter,将本身增加到dep中
        Dep.target = null;  // 增加完结 重置
        return value;
      },
      // 增加Watcher to Dep.subs[]
      addDep: function (dep) {
        if (!this.depIds.hasOwnProperty(dep.id)) {
          dep.addSub(this);
          this.depIds[dep.id] = dep;
        }
      },
      parseGetter: function (exp) {
        if (/[^w.$]/.test(exp)) return;
    
        let exps = exp.split('.');
    
        // 简易的循环依靠处理
        return function(obj) {
            for (let i = 0, len = exps.length; i < len; i++) {
                if (!obj) return;
                obj = obj[exps[i]];
            }
            return obj;
        }
      }
    }

    没错便是Watcher这么一个简略的“蓝银”和Observer和Compile两位“菇凉”牵扯不清

    4、完结MVVM部分

    能够说MVVM是Observer,Compile以及Watcher的“boss”了,他才不会去管他们职工之间的联系,只需他们三能给干活,而且干好活就行。他需求安排给Observer,Compile以及Watche做的工作如下

    a、Observer完结对MVVM本身model数据绑架,监听数据的特点改变,并在改变时进行notify
    b、Compile完结指令解析,初始化视图,并订阅数据改变,绑定好更新函数
    c、Watcher一方面接纳Observer经过dep传递过来的数据改变,一方面告诉Compile进行view update

    详细完结如下

    /**
     * @class 双向绑定类 MVVM
     * @param {[type]} options [description]
     */
    function MVVM (options) {
      this.$options = options || {};
      let data = this._data = this.$options.data;
      let self = this;
    
      Object.keys(data).forEach(key => {
        self._proxyData(key);
      });
      observe(data, this);
      new Compile(options.el || document.body, this);
    }
    MVVM.prototype = {
      /**
       * [特点署理]
       * @param  {[type]} key    [数据key]
       * @param  {[type]} setter [特点set]
       * @param  {[type]} getter [特点get]
       */
      _proxyData: function (key, setter, getter) {
        let self = this;
        setter = setter ||
        Object.defineProperty(self, key, {
          configurable: false,
          enumerable: true,
          get: function proxyGetter() {
            return self._data[key];
          },
          set: function proxySetter(newVal) {
            self._data[key] = newVal;
          }
        })
      }
    }

    至此,一个归于咱们自己的mvvm库也算是完结了。因为本文的代码较多,又不太好分小部分抽离出来解说,所以我将代码的解析都直接写到了代码中。文中一些不行谨慎的考虑和过错,还请各位小伙伴们拍砖指出,咱们一同纠正一同学习。

    ↓ 检查全文

    完结一个双向数据绑定的简易MVVM结构由bodog官网收集收拾,您能够自在传达,请自动带上本文链接

    bodog官网便是免费共享,觉得有用就多来支撑一下,没有能帮到您,懒人也只能表示遗憾,期望有一天能帮到您。

    m88 188bet uedbet 威廉希尔 明升 bwin 明升88 bodog bwin 明升m88.com 18luck 188bet unibet unibet Ladbrokes Ladbrokes casino m88明升 明升 明升 m88.com 188bet m88 明陞 uedbet赫塔菲官网 365bet官网 m88 help

    完结一个双向数据绑定的简易MVVM结构-最新谈论