搞懂: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的雙向綁定

相關文章
相關標籤/搜索