vue实现双向数据绑定的原理

1.vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的;

    数据劫持:通过Object.defineProperty( )来进行数据劫持的 

   注:Object.defineProperty( ):它可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举

   

   思路分析: MVVM 主要包含: 1.数据变化更新视图 (就是通过Object.defineProperty( )对属性设置一个set函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更 新的方法放在这里面就可以实现data更新view了。) 2. 视图变化更新数据(通过事件监听)

实现过程

我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

 //实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
  function   defineReactive(data,key,val) {
      observer(val)
      var dep = new Dep();
      Object.defineProperty(data,key,{
          enumerable:true,
          configurable:true,
          get:function () {
              if (Dep.target) { // 判断是否需要添加订阅者
                  dep.addSub(Dep.target); // 在这里添加一个订阅者
              }
              return val
          },
          set:function (newVla) {
              if(val===newVla){
                  return
              }
              val=newVla
              console.log("属性"+key+"已经被监听到了.现在值为:" + newVla.toString())
              dep.notify(); // 如果数据变化,通知所有订阅者
          }
      })

  }

  Dep.target = null;
  function  observer(data) {
      if(!data || typeof data!=="object"){
          return
      }
      Object.keys(data).forEach(function (key) {
          defineReactive(data,key,data[key])
      })

  }

  function Dep() {
      this.subs=[]

  }
  Dep.prototype={
      addSub:function (sub) {
          this.subs.push(sub)
      },
      notify:function () {
          this.subs.forEach(function (sub) {
              sub.update();
          })
      }
  };
//2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
  function  Watcher(vm,exp,cb) {
      this.cb=cb
      this.vm=vm
      this.exp=exp
      this.value=this.get() //将自已添加到订阅器的操作

  }

  Watcher.prototype={
      update:function () {
          this.run()
      },
      run:function () {
         var  value=this.vm.data[this.exp];
         var  oldVal=this.value;
         if(value!==oldVal){
             this.value=value
             this.cb.call(this.vm,value,oldVal)

         }

      },
      get:function () {
          Dep.target=this; //缓存自已
          var  value=this.vm.data[this.exp]  // 强制执行监听器里的get函数
          Dep.target=null // 释放自己
          return value
      }
  }

  function SelfVue (options) {
      var self = this;
      this.vm = this;
      this.data = options;

      Object.keys(this.data).forEach(function(key) {
          self.proxyKeys(key);
      });

      observer(this.data);
      new Compile(options, this.vm);
      return this;
  }
  SelfVue.prototype = {
      proxyKeys: function (key) {
          var self = this;
          Object.defineProperty(this, key, {
              enumerable: false,
              configurable: true,
              get: function proxyGetter() {
                  return self.data[key];
              },
              set: function proxySetter(newVal) {
                  self.data[key] = newVal;
              }
          });
      }
  }
 //3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器

  function nodeToFragment (el) {
      var fragment = document.createDocumentFragment();
      var child = el.firstChild;
      while (child) {
          // 将Dom元素移入fragment中
          fragment.appendChild(child);
          child = el.firstChild
      }
      return fragment;
  }

  function compileElement (el) {
      var childNodes = el.childNodes;
      var self = this;
      [].slice.call(childNodes).forEach(function(node) {
          var reg = /\{\{(.*)\}\}/;
          var text = node.textContent;

          if (self.isTextNode(node) && reg.test(text)) {  // 判断是否是符合这种形式{{}}的指令
              self.compileText(node, reg.exec(text)[1]);
          }

          if (node.childNodes && node.childNodes.length) {
              self.compileElement(node);  // 继续递归遍历子节点
          }
      });
  }
  function compileText (node, exp) {
      var self = this;
      var initText = this.vm[exp];
      this.updateText(node, initText);  // 将初始化的数据初始化到视图中
      new Watcher(this.vm, exp, function (value) {  // 生成订阅器并绑定更新函数
          self.updateText(node, value);
      });
  }
  function updateText(node, value) {
      node.textContent = typeof value == 'undefined' ? '' : value;
  }



  function compile(node) {
      var nodeAttrs=node.attributes;
      var  self=this
      Array.prototype.forEach.call(nodeAttrs,function (attr) {
          var attrName=attr.name
          if(self.isDirective(attrName)){
              var  exp=attr.value
              var dir=attrName.substring(2)
              if (self.isEventDirective(dir)) {  // 事件指令
                  self.compileEvent(node, self.vm, exp, dir);
              } else {  // v-model 指令
                  self.compileModel(node, self.vm, exp, dir);
              }
              node.removeAttribute(attrName);
          }
          
      })

  }

猜你喜欢

转载自www.cnblogs.com/xieli26/p/9701120.html