MVC、MVP及MVVM都是一種架構模式,爲了解決圖形和數據的相互轉化的問題。
Model、View、Controller
mvc有以下兩種形式,無論哪一種都是單向通訊的,
實際項目通常會採用靈活的方式,如backbone.js,至關於兩種形式的結合
MVC實現了功能的分層,可是當你有變化的時候你須要同時維護三個對象和三個交互,這顯然讓事情複雜化了。
Model、 View 、Presenter
MVP切斷的View和Model的聯繫,讓View只和Presenter(原Controller)交互,減小在需求變化中須要維護的對象的數量。
MVP定義了Presenter和View之間的接口,讓一些能夠根據已有的接口協議去各自分別獨立開發,以此去解決界面需求變化頻繁的問題
用更低的成本解決問題
用更容易理解的方式解決問題
Model 、View 、ViewModel
ViewModel大體上就是MVP的Presenter和MVC的Controller了,而View和ViewModel間沒有了MVP的界面接口,而是直接交互,用數據「綁定」的形式讓數據更新的事件不須要開發人員手動去編寫特殊用例,而是自動地雙向同步。
比起MVP,MVVM不只簡化了業務與界面的依賴關係,還優化了數據頻繁更新的解決方案,甚至能夠說提供了一種有效的解決模式。
栗子?:如今用戶下拉刷新一個頁面,頁面上出現10條新的新聞,新聞總數從10條變成20條。那麼MVC、MVP、MVVM的處理依次是:html
- View獲取下拉事件,通知Controller
- Controller向後臺Model發起請求,請求內容爲下拉刷新
- Model得到10條新聞數據,傳遞給Controller
- Controller拿到10條新聞數據,可能作一些數據處理,而後拿處理好的數據渲染View
MVC: 拿到UI節點,渲染10條新聞 MVP: 經過View提供的接口渲染10條新聞 MVVM: 無需操做,只要VM的數據變化,經過數據雙向綁定,View直接變化
mv?都是在處理view和model相互轉化的問題;
mvc解決view和model職責劃分的問題;
mvp解決view和model隔離的問題;
mvvm解決了model與view自動映射的問題。
JavaScript中的Object.defineProperty()和defineProperties()node
<input type="text" id="inpText"> <div class="content"></div> <script> /* 實現一個最簡單的雙向綁定 */ const content = document.querySelector('.content') const data = { name: '' } inpText.addEventListener('input', function() { data.name = this.value }) let value // 臨時變量 Object.defineProperty(data, 'name', { get() { return value }, set(newValue) { if (value === newValue) return value = newValue content.innerHTML = value console.log('data.name=>' + data.name) } }) </script>
代碼演示git
import Dep from './dep.js' export default class Observer { constructor(data) { this.data = data this.init() } init() { const data = this.data Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]) }) } defineReactive(data, key, value) { const dep = new Dep() /** 子屬性爲對象 **/ Observer.create(value) Object.defineProperty(data, key, { enumerable: true, configurable: true, get() { /**添加watcher**/ if (Dep.target) { dep.addSub(Dep.target) } return value }, set(newVal) { if(newVal === value) return value = newVal /** 賦值後爲對象 **/ Observer.create(value) /**通知watcher**/ dep.notify() // console.log('set', key) } }) } static create(value) { if (!value || typeof value !== 'object') return new Observer(value) } }
export default class Dep { constructor(props) { this.subs = [] } addSub(sub) { this.subs.push(sub) } notify() { this.subs.forEach(sub => { sub.update() }) } } Dep.target = null
import Dep from './dep.js' export default class Watcher { constructor(vm, exp, cb) { this.vm = vm this.exp = exp this.cb = cb this.value = this.get() } get() { Dep.target = this const value = this.vm[this.exp] // new Watcher時強制添加 Dep.target = null return value } update() { this.run() } run() { let value = this.vm[this.exp] let oldVal = this.value if (oldVal === value) return this.value = value this.cb && this.cb.call(this.vm , value, oldVal) } }
import Watcher from './watcher.js' export default class Compile { constructor(el, vm) { this.vm = vm this.el = el this.fragment = null this.init() } init() { this.el = this.isElementNode(this.el) ? this.el : document.querySelector(this.el) this.fragment = this.node2fragment(this.el) this.compile(this.fragment) this.el.appendChild(this.fragment) } // 生成dom片斷 node2fragment(el) { let fragment = document.createDocumentFragment() let child = el.firstChild while(child) { fragment.appendChild(child) child = el.firstChild } return fragment } // 編譯 compile(fragment) { let childNode = fragment.childNodes const reg = /\{\{(.*)\}\}/ Array.from(childNode).forEach(child => { const text = child.textContent if (this.isElementNode(child)) { // 元素節點 this.compileElement(child) } else if (this.isTextNode(child) && reg.test(text)) { // 文本節點 this.compileText(child, reg.exec(text)[1]) } if (child.childNodes && child.childNodes.length) { this.compile(child) } }) } // 編譯元素節點 compileElement(node) { const attrs = node.attributes Array.from(attrs).forEach(attr => { let dir = attr.name // 是否爲指令 if (this.isDirective(dir)) { const exp = attr.value dir = dir.substring(2) // 事件指令 if (this.isDirEvent(dir)) { this.compileEvent(node, dir, exp) } // v-model指令 if (this.isDirModel(dir)) { this.compileModel(node, dir, exp) } // ... node.removeAttribute(attr.name) } }) } // 編譯文本節點 compileText(node, exp) { const initText = this.vm[exp] // 初始化文本節點 this.updaterText(node, initText) // 監聽數據變化,更新文本節點 new Watcher(this.vm, exp, value => { this.updaterText(node, value) }) } // 編譯事件 compileEvent(node, dir, exp) { const eventType = dir.split(':')[1] const fn = this.vm.$options.methods && this.vm.$options.methods[exp] if (!eventType || !fn) return node.addEventListener(eventType, fn.bind(this.vm), false) } // 編譯v-model compileModel(node, dir, exp) { let value = this.vm[exp] this.updateModel(node, value) node.addEventListener('input', e=> { let newVal = e.target.value if(value === newVal) return value = newVal this.vm[exp] = value }) } // 更新文本節點 updaterText(node, value) { node.textContent = typeof value === 'undefined' ? '' : value } // 更新v-model節點 updateModel(node, value) { node.value = typeof value === 'undefined' ? '' : value } // 判斷指令 事件 元素 文本 isDirective(dir) { return dir.indexOf('v-') === 0 } isDirModel(dir) { return dir.indexOf('model') === 0 } isDirEvent(dir) { return dir.indexOf('on:') === 0 } isElementNode(node) { return node.nodeType === 1 } isTextNode(node) { return node.nodeType === 3 } }
import Observer from './observer.js' import Compile from './compile.js' const LIFE_CYCLE = [ 'beforeCreate', 'create', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroy' ] export default class MVVM{ constructor(props) { this.$options = props this._data = this.$options.data this._el = this.$options.el // 數據劫持掛載在最外層 this._proxyData() Observer.create(this._data) new Compile(this._el, this) this.setHook('mounted') } _proxyData() { const data = this._data const self = this Object.keys(data).forEach(key => { Object.defineProperty(this, key, { enumerable: true, configurable: true, get() { return data[key] }, set(newVal) { if (data[key] === newVal) return data[key] = newVal self.setHook('updated') } }) }) } setHook(name) { if (!LIFE_CYCLE.find(val => val === name)) return this.$options[name] && this.$options[name].call(this) } }
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>mvvm框架僞代碼</title> </head> <body> <div id="app"> <p>{{title}}</p> <input type="text" v-model="name"> <p>{{name}}</p> <hr> <p>{{count}}<button v-on:click="add" style="transform: translateX(20px)">+數量</button></p> </div> <script type="module"> import MVVM from './my-mvvm/main.js' window.$vm = new MVVM({ el: '#app', data: { title: 'hello mvvm', name: 'jtr', count: 0 }, methods: { add() { this.count++ } }, mounted() { this.timer = setInterval(() => { this.title += '*' }, 500) }, updated() { this.title.length > 20 && clearInterval(this.timer) } }) </script> </body> </html>
代碼演示segmentfault