MVC/MVP/MVVM設(shè)計(jì)模式的演化及實(shí)現(xiàn)

前言

演變歷史:
直接操作DOM -> MVC -> MVP -> MVVM

MVC

  • M —— model 數(shù)據(jù)層& 手動(dòng)渲染模板
  • V —— view 視圖層
  • C —— controller 控制層(業(yè)務(wù)邏輯)
    流程:
    V ——> C ——> M ——> V
  1. View 傳送指令 到 controller
  2. controller 完成業(yè)務(wù)邏輯處理后,觸發(fā) Model 改變狀態(tài)
  3. Model 將新的數(shù)據(jù)發(fā)送到 View, View 更新視圖
    所有的通信都是單向的
    見圖
    image.png
實(shí)現(xiàn)代碼
// html
<div id="A" onclick="A.event.change"></div>
//js 
var A = new app({
      //controller
      controller() {
          let self = this
          // 綁定事件
          self.event['change'] = function() {
              self.model.setValue('text', '新的viewA 的值')  // 觸發(fā)model數(shù)據(jù)更新
        }
      },
      //model
      model(){
          this.text = 'viewA 渲染完成';
          // 綁定更新回調(diào)函數(shù)
          this.setValue = function(key, val){
              this[key]  = val  // 更新新值
              this.view.render(this)  // 調(diào)用渲染函數(shù)
          }
      }
      // view
      view(data) {
          var tpl = ''<span>{{text}}</span>'
          this.render= function(tpl,data){ 
           // 渲染視圖
          。。。    
        }
      }
})

MVP 是對(duì)MVC的一種改造,將手動(dòng)渲染步驟從Model 移到Presenter層上

  • M —— model 數(shù)據(jù)層
  • V —— view 視圖層
  • P —— presenter 控制層 & 手動(dòng)渲染模板
    流程:
    V <——> P <——> M
  1. 各部分直接的通信,都是雙向的
  2. View 與 Model 不發(fā)生聯(lián)系,相互對(duì)立
    見圖
image.png

實(shí)現(xiàn)代碼

// html
<div id="A" onclick="A.event.change"></div>
//js 
var A = new app({
     //presenter
      presenter() {
          let self = this
          // 綁定事件
          self.event['change'] = function() {
              self.model.text =  "新的viewA 的值"
              self.view.render(self.model)  
        }
      },
      //model
      model(){
          this.text = 'viewA 渲染完成';
      }
      // view
      view(data) {
          var tpl = ''<span>{{text}}</span>'
          this.render= function(tpl,data){ 
           // 渲染視圖
          。。。    
        }
      }
})

MVVM 是將Presenter 改為ViewModel ,基本與MVP模式一致

唯一區(qū)別是:它采用雙向綁定:View 的變動(dòng),自動(dòng)反映在ViewModel上,反之亦然

  • M —— model 數(shù)據(jù)層
  • V —— view 視圖層
  • VM —— ViewModel 依靠Directive, 修改數(shù)據(jù)& 自動(dòng)渲染模板
    流程:
    V <——> VM <——> M

    image.png
實(shí)現(xiàn)代碼
// html
<div id="A" onclick="change">
    <span >{{text}}</span>
</div>
//js 
new VM({
      //model
      model:{
          text = 'viewA 渲染完成';
      },
      methods: {
        change(){
            this.text = "新的viewA 的值"
        }
    }
})
MVVM模式依靠Directive, 實(shí)現(xiàn)了修改數(shù)據(jù)和模板自動(dòng)渲染,解放了開發(fā)者,只需要關(guān)注View和Model,效率和性能提高,低耦合度,對(duì)立開發(fā),可復(fù)用性

數(shù)據(jù)變動(dòng)檢查方案 (Directive)

一. 手動(dòng)觸發(fā)綁定

在頁面需要改變是,手動(dòng)觸發(fā)檢測(cè),改變Model數(shù)據(jù),并掃描元素,對(duì)有標(biāo)記的元素進(jìn)行修改

let data = {
      value: 'hello'
};

let directive = {
    html: function (html) {
        this.innerHTML = html;
    },
    value: function (html) {
        this.setAttribute('value', value);
    }
};

ViewModelSet('value', 'hello world');

function ViewModelSet(key, value) {
    data[key] = value;
    scan(); 
}

function scan() {
    for (let elem of elems) {
        elem.directive = [];
        for (let attr of elem.attributes) {
            if (attr.nodeName.indexOf('v-') >= 0) {
                directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
            }
        }
    }
}
一. 臟檢測(cè)機(jī)制 (Angrlar)

針對(duì)手動(dòng)綁定進(jìn)行優(yōu)化,只對(duì)修改到的數(shù)據(jù)進(jìn)行更新元素

function scan(elems, val) {
    let list = document.querySelectorAll(`[v-bind=${val}]`); // 只掃描修改到的數(shù)據(jù)涉及的元素
    for (let elem of elems) {
        for (let attr of elem.attributes) {
            let dataKey = elem.getAttribute('v-bind');
            if (elem.directive[attr.nodeValue] !== data[dataKey]) { // 當(dāng)元素值有變時(shí),更新元素
                directive[attr.nodeValue].call(elem, data[dataKey]); 
                elem.directive[attr.nodeValue] = data[dataKey]; // 保存元素當(dāng)前值
            }
        }
    }
}

三. 數(shù)據(jù)劫持結(jié)合訂閱發(fā)布模式 (Vue)

使用 Object.defineProperty 對(duì)數(shù)據(jù)進(jìn)行g(shù)et 和 set 監(jiān)聽,數(shù)據(jù)變動(dòng)時(shí),通知訂閱者調(diào)用更新回調(diào)函數(shù),重新渲染視圖

詳細(xì)內(nèi)容請(qǐng)看我的文章Vue的雙向綁定原理及實(shí)現(xiàn)

四. ES6 Proxy

與方法三類似,換成ES6的寫法

let data = new Proxy({
    get: function(obj, key){
      return obj[key]
  },
    set: function(obj,key,val){
    obj[key] = val
    scan() // 訂閱更新回調(diào)函數(shù)
    return obj[key]  
  }
})

參考文獻(xiàn):
https://segmentfault.com/a/1190000013464776#articleHeader3
http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 嗷喲,6:49的火車 晚點(diǎn)到8:06發(fā) 為什么京津城際和京津高鐵差這么多 等車的一個(gè)多小時(shí) 看完了《半小時(shí)漫畫中國(guó)...
    咸酥閱讀 136評(píng)論 0 0
  • 來源:鳥哥/惠新宸 鏈接:http://www.laruence.com/2015/12/04/3086.html...
    Uzero閱讀 1,202評(píng)論 0 1
  • 同學(xué)從波黑回來,約于深圳一天小聚。晚上去到酒吧,喝到爛醉到深夜。我們約定我和一妹子送一個(gè)醉酒的朋友A回家,其他人各...
    Tramps閱讀 7,289評(píng)論 0 0

友情鏈接更多精彩內(nèi)容