前端 MVVM 原理

author: 陳家賓
email: 617822642@qq.com
date: 2018/3/1

MVVM 背景

都說懶惰令人進步,MVVM 的進化史,正印證了這句話,是一步步讓開發人員更懶惰更簡單的歷史:html

直接 DOM 操做 -> MVC -> MVP -> MVVM

最開始的前端交互,是很直接的 DOM 操做,最出名的這類庫當數 jQuery 了,封裝了 DOM API,讓一切 DOM 操做都變得簡單。前端

但當頁面數據和交互多的時候,散亂的代碼將使項目變得難以維護,讓人發狂。因此纔有了 MV* 模式的發展。node

MV* 模式

MVC & MVP & MVVM 三者對比僞代碼: 點我

MVC

  • Model:數據模型 & 手動渲染模板
  • View:模板
  • Controller:修改數據

MVP

  • Model:數據模型
  • View:模板
  • Presenter:修改數據 & 手動渲染模板

MVP 是 MVC 模式的一種改造(這裏不說改進,是由於二者其實很類似,沒有本質上的變化),將 手動渲染 步驟** 從 Model 移到了 Presenter,讓 View 和 Model 更獨立更存粹了,但從另外一個角度來講,也加大了 Presenter 的工做。git

MVVM

  • Model:數據模型
  • View:帶特殊屬性的 html 模板
  • ViewModel:依靠 Directive,修改數據 & 自動渲染

MVVM 模式依靠 Directive,實現了 模板自動渲染,極大地解放了開發者的雙手,此時開發者只需關注 View 和 Model,效率和可維護性方面達到了飛躍式的進步。github

下面將着重介紹下神奇的 Directive。mvc

數據變動檢測方案(Directive)

(一)手動觸發綁定

在頁面須要改變時,手動觸發檢測,改變 model 數據,並掃描元素,對有特殊標記的元素進行修改mvvm

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]);
            }
        }
    }
}

(二)髒檢測機制

針對手動綁定進行優化,只對修改到的數據進行更新元素優化

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

(三)前端數據對象劫持(Hijacking)

在上面的基礎更進一步,使用 Object.defineProperty 對數據進行 get & set 監聽,當數據有變時,自動執行 scan 掃描並更新元素。this

原來是在改變數據時,還要手動 scan。如今只須要直接改變數據,會自動 scan,更新元素。spa

defineGetAndSet(data, 'value');

data.value = 'hello world';

function defineGetAndSet(obj, propName) {
    Object.efineProperty(obj, propName, {
        get: function () {
            return this.bVal;
        },
        set: function (newVal) {
            this.bVal = newVal;
            scan();
        },
        enumerable: true,
        configurable: true
    });
}

(四)ECMAScript 6 Proxy

與方法三相似,換了種寫法,這裏應用了 ES6 裏的 Proxy

let data = new Proxy({
    get: function (obj, key) {
        return obj[key];
    },
    set: function (obj, key, val) {
        obj[key] = val;
        scan();
        return obj[key];
    }
});

以上。

參考資料

  1. 《現代前端 技術解析》,張成文,2017 年 4 月第 1 版d
  2. 《MVC,MVP 和 MVVM 的圖示》,阮一峯,2015年2月 1日,http://www.ruanyifeng.com/blo...
相關文章
相關標籤/搜索