前言
MVC、MVP、MVVM区别
MVC、MVP及MVVM都是一种架构模式,为了解决图形和数据的相互转化的问题。
mvc
Model、View、Controller
mvc有如下两种形式,不管哪种都是单向通信的,
实际项目一般会采用灵活的方式,如backbone.js,相当于两种形式的结合
MVC实现了功能的分层,但是当你有变化的时候你需要同时维护三个对象和三个交互,这显然让事情复杂化了。
mvp
Model、 View 、Presenter
MVP切断的View和Model的联系,让View只和Presenter(原Controller)交互,减少在需求变化中需要维护的对象的数量。
MVP定义了Presenter和View之间的接口,让一些可以根据已有的接口协议去各自分别独立开发,以此去解决界面需求变化频繁的问题
用更低的成本解决问题
用更容易理解的方式解决问题
mvvm
Model 、View 、ViewModel
ViewModel大致上就是MVP的Presenter和MVC的Controller了,而View和ViewModel间没有了MVP的界面接口,而是直接交互,用数据“绑定”的形式让数据更新的事件不需要开发人员手动去编写特殊用例,而是自动地双向同步。
比起MVP,MVVM不仅简化了业务与界面的依赖关系,还优化了数据频繁更新的解决方案,甚至可以说提供了一种有效的解决模式。
用一个例子来阐述他们差异
栗子?:现在用户下拉刷新一个页面,页面上出现10条新的新闻,新闻总数从10条变成20条。那么MVC、MVP、MVVM的处理依次是:
- 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自动映射的问题。
MVVM设计模式
极简双向绑定实现
JavaScript中的Object.defineProperty()和defineProperties()
<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>
mvvm伪代码实现
- Oberser.js
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)
}
}
- Dep.js
export default class Dep {
constructor(props) {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
Dep.target = null
- Watcher.js
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)
}
}
- Compile.js
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
}
}
- main.js
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)
}
}
- index.html
<!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>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。