搞懂:MVVM模型以及VUE中的数据绑定数据劫持发布订阅模式

搞懂:MVVM模式和Vue中的MVVM模式

MVVM

  • MVVM : model - view - viewmodel的缩写,说都能直接说出来 model:模型,view:视图,view-Model:视图模型
    • V:视图,即浏览器最前端渲染的页面
    • M:模型,数据模型,就是后端页面渲染依赖的数据
    • VM:稍后再说,由于暂时还不知道怎么工做,什么场景,直接解释有点没用
  • 那就先说说前端场景:
    • 若是数据改变,想要前端页面作出相应的改变,有几种方法:
      • 1.使用原生js
        var dom = document.getElementById('xxx')
            dom.value = xxx;    // 直接修改值
            dom.innerHtml = xxx;    //改变开始 和 结束标签中的html
      • 2.使用jquery
        $('#name').text('Homer').css('color', 'red');
    • 上面能够看出来,jquery确实在dom操做方面简化了不少,链式调用和更加人性化的api在没有mvvm模型出世以前,使用率极高
    • 可是,也能够看出来,数据和页面视图之间存在断层,数据影响视图,甚至是视图中的节点改变数据,这都是极其频繁的页面操做,虽然一再简化这个面向过程的逻辑操做,可是仍是避免不了手动修改的弊端。
    • 有没有一种更好的方式,能够实现这种视图(view)和模型(model)之间的关系
  • VM:
    • 再看看如今VUE框架中怎么作到这种视图和模型的联动javascript

      //html
          <input v-model = 'val' placeholder = 'edit here'>
      
          //script
          export defaults{
              data:function(){
                  return {
                      val:''
                  }
              }
          }

      很简单,很经常使用的v-model指令,那么在input值修改的时候,data中的val变量值也会改变,直接在js中改变val的值的时候,input中的value也会改变??咱们作了什么,咱们怎么将数据和视图联系起来的?自动会关联这两个东西css

    • 可能,这就是VM吧~html

      • vm:viewModel视图模型,就是将数据model和视图view关联了起来,负责将model数据同步到view显示,也同时把view修改的数据同步到model,咱们无需关心中间的逻辑,开发者更多的是直接操做数据,至于更新视图或者会写model,都是咱们写好的视图模型(viewModel)帮咱们处理
    • 概念:视图模型层,是一个抽象化的逻辑模型,链接视图(view)和模型(model),负责:数据到视图的显示,视图到数据的回写前端

VUE中的MVVM(双向绑定)

vue框架中双向绑定是最经常使用的一个实用功能。实现的方式也网上不少文章,vue2.x是Object.DefineProperty,vue3.x是Es6语法的proxy代理语法vue

  • 具体是怎么作到的java

    ps:暂时先看vue2.xjquery

    • Object.setProperty(),设置和修改Javascript中对象属性值,定义对象属性的get和set方法,能够在对象获取值和修改值时触发回调函数,实现数据劫持,而且拿到新的改变后的值
    • 须要根据初始化对象值和修改以后拿到改变后的值,对已绑定模板节点进行数据更新。
  • 第一步:监听对象全部属性值变化(Observer)程序员

    var data = {test: '1'};
      observe(data);
      data.test = '2'; // changed 1 --> 2
    
      function observe(data) {
          if (!data || typeof data !== 'object') {
              return;
          }
          // 取出全部属性遍历
          Object.keys(data).forEach(function(key) {
              defineReactive(data, key, data[key]);
          });
      };
    
      function defineReactive(data, key, val) {
          observe(val); // 监听子属性
          Object.defineProperty(data, key, {
              enumerable: true, // 可枚举
              configurable: false, // 防止重复定义或者冲突
              get: function() {
                  return val;
              },
              set: function(newVal) {
                  console.log('changed ', val, ' --> ', newVal);
                  val = newVal;
              }
          });
      }
    • 第二步:怎么作到对有绑定关系的节点进行更新和初始化值呢?若是一个数据对象绑定了多个dom节点,怎么统一通知全部dom节点呢,这就须要用到发布者-订阅者模式
      • 这里是Observer做为一个察觉数据变化的发布者,发现数据变化时,触发全部订阅者(Watcher)的更新update事件,首先要拥有一个能存储全部订阅者队列,而且能通知全部订阅者的中间件(消息订阅器Dep后端

        function Dep () {
                // 订阅者数组
                this.subs = [];
            }
            Dep.prototype = {
                addSub: function(sub) {
                    this.subs.push(sub);
                },
                notify: function() {
                    //通知全部订阅者
                    this.subs.forEach(function(sub) {
                        sub.update();
                    });
                }
            };
      • 而且在观察者Observer中修改当Object对象属性发生变化时,触发Dep中的notify事件,全部订阅者能够接收到这个改变api

        function defineReactive(data, key, val) {
                observe(val); 
                var dep = new Dep(); 
                Object.defineProperty(data, key, {
                    enumerable: true,
                    configurable: false, 
                    get: function() {
                        return val;
                    },
                    set: function(newVal) {
                        //修改的在这里
                        if(newVal === val){
                            return
                        }
                        // 若是新值不等于旧值发生变化,触发全部订阅中间件的notice方法,全部订阅者发生变化
                        val = newVal
                        console.log('changed ', val, ' --> ', newVal);
                        dep.notify();
                        
                    }
                });
            }
      • 可是有没有发现还有一个问题,Dep订阅中间件中的订阅者数组一直是空的,何时把订阅者添加进来咱们的订阅中间件中间,哪些订阅者须要添加到咱们的中间件数组中

        • 1.咱们但愿的是订阅者Watcher在实例化的时候自动添加到Dep中
        • 2.有且仅有在第一次实例化的时候添加进去,不容许重复添加
        • 3.因为Dep在发布者数据变化时会触发全部订阅则的update事件,因此Watcher实例(订阅者)可以触发update事件,并进行相关操做
        • 怎么能让Watcher在实例化的时候自动添加到Dep订阅者数组中
          function Watcher(vm, exp, cb) {
                  this.cb = cb;       // 构造函数中执行,只有可能在实例化的时候执行一遍
                  this.vm = vm;
                  this.exp = exp;
                  this.value = this.get();  // 将本身添加到订阅器的操做---HACK开始
                  // 在构造函数中调用了一个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() {
                      //get方法中首先缓存了本身自己到target属性
                      Dep.target = this;  
                      // 获取了一下Observer中的值,至关于调用了一下get方法
                      var value = this.vm.data[this.exp]  
                      // get 完成以后清除了本身的target属性???
                      Dep.target = null;  
                      return value;
                  }
                  //很明显,get方法只在实例化的时候调用了,知足了只有在Watcher实例化第一次的时候调用
                  //update方法接收了发布者的notice 发布消息,而且执行回调函数,这里的回调函数仍是经过外部定义(简化版)
                  //可是,好像在get方法中有一个很神奇的操做,缓存本身,而后调用Observer的getter,而后清除本身
                  //这里实际上是一步巧妙地操做把本身添加到Dep订阅者数组中,固然Observer 的getter方法也要变化以下
              };
          
              //Observer.js
              function defineReactive(data, key, val) {
                  observe(val); 
                  var dep = new Dep(); 
                  Object.defineProperty(data, key, {
                      enumerable: true,
                      configurable: true,
                      get: function() {
                          if (Dep.target) {.  
                              dep.addSub(Dep.target); // 关键的在这里,当第一次实例化时,调用Watcher的get方法,get方法内部会获取Object的属性,会触发这个get方法,在这里将Watcher 添加到Dep的订阅者数组中
                          }
                          return val;
                      },
                      set: function(newVal) {
                          if (val === newVal) {
                              return;
                          }
                          val = newVal;
                          dep.notify(); 
                      }
                  });
              }
              Dep.target = null;
      • 看似好像发布者订阅者模式实现了,数据劫持也实现了,在数据改变的时候,触发Object.setProperty中定义的set函数,set函数触发Dep订阅者中间件的notice方法,触发全部订阅者的update方法,而且订阅者在实例化的时候就加入到了Dep订阅者的数组内部,让咱们来看看怎么用

        • html部分,
          <body>
                  <!-- 这里其实仍是会直接显示{{name}} -->
                  <h1 id="name">{{name}}</h1>
              </body>
        • 封装一个方法(类)将Observer,Watcher,关联起来
          function SelfVue (data, el, exp) {
                  //初始化data属性
                  this.data = data;
                  //将其设置为观察者
                  observe(data);
                  //手动设置初始值
                  el.innerHTML = this.data[exp]; 
                  //初始化watcher,添加到订阅者数组中,而且回调函数是从新渲染页面,触发update方法时经过回调函数重写html节点
                  new Watcher(this, exp, function (value) {
                      el.innerHTML = value;
                  });
                  return this;
              }
        • 使用:
          var ele = document.querySelector('#name');
              var selfVue = new SelfVue({
                  name: 'hello world'
              }, ele, 'name');
          
              //设定延时函数,直接修改数据值,看可否绑定到页面视图节点
              window.setTimeout(function () {
                  console.log('name值改变了');
                  selfVue.data.name = 'canfoo';
              }, 2000);
      • 到上面为止:基本实现了数据(model)到视图(view)层的单向数据绑定,只有v-model是使用到了双向绑定,不少vue的数据绑定的理解,和难点也就在上面的单向绑定

      • 那么:model->view单向绑定彷佛已经成功了,那么view -> model呢?

        • 这个在于若是视图层的value改变了,如何修改已经绑定的model层的对象属性呢?
        • 这个指令在vue中是:v-model,指令部分会在以后的学习中继续讲解
        • 可是,视图view节点在value属性改变时,通常会触发change或者input事件,并且也通常是一些可输入视图节点,直接将事件写在change事件或者input事件里面,而且修改Object里面的值
          var dom = document.getElementById('xx')
              dom.addEventListener('input',function(e){
                  selfVue.data.xxx = e.target.value
              })
        • 具体input事件和v-model指令这种用法怎么联系起来,以后会慢慢学习

总结:

  • MVVM实际上是如今不少前端框架的实现基础,除了vue 的数据劫持和观察订阅模式,其余框架的例如脏数据检测,或者直接使用观察者订阅者模式,都是一些很巧妙的实现方式,使程序员可以更多的关注数据层面或者逻辑层面的代码,而不须要手动去作更新二者之间关系的繁琐操做
  • vue的数据劫持和发布者订阅者模式理解起来一开始看起来理解有点费劲,大概了解如何作的,学习其方法,固然手写彻底流程的写出来,我也很难
  • 学习的路上,你们一块儿加油,多问一个为何

很是感谢:下面的文章给了我不少的帮助,感谢各位前行者的辛苦付出,能够点击查阅更多信息

廖雪峰老师的MVVM讲解

由浅入深讲述MVVM

很详细的讲解vue的双向绑定

相关文章
相关标签/搜索