MVVM设计模式:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit|ie-comp|ie-stand">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Web App">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, minimum-scale=1, maximum-scale=1">
<title>Document</title>
<style>
</style>
</head>
<body>
<body>
<div id="app">
<input type="text" v-model="message">
{{message}}
</div>
</body>
</body>
<script src="./watcher.js"></script>
<script src="./observer.js"></script>
<script src="./compile.js"></script>
<script src="./mvvm.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
<script type="text/javascript">
let vm = new MVVM({
el: '#app',
data: {
message: '12345'
}
})
</script>
</html>
mvvm.js
class MVVM {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
if (this.$el) {
// 数据劫持
new Observer(this.$data)
this.proxyData(this.$data)
// 模板编译
new Compile(this.$el, this)
}
}
proxyData(data) {
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(newValue) {
data[key] = newValue
}
})
})
}
}
compile.js
class Compile {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el :document.querySelector(el)
this.vm = vm
if (this.el) {
let fragment = this.node2fragment(this.el)
this.compile(fragment)
this.el.appendChild(fragment)
}
}
node2fragment(el) {
let fragment = document.createDocumentFragment()
let firstChild
while(firstChild = el.firstChild) {
fragment.appendChild(firstChild)
}
return fragment // 内存中的节点
}
// 编译指令元素
compileElement(node) {
let attrs = node.attributes
Array.from(attrs).forEach(attr => {
let attrName = attr.name
if (this.isDirective(attrName)) {
let expr = attr.value
let [, type] = attrName.split('-')
CompileUtil[type](node, this.vm, expr)
}
})
}
//编译文本节点
compileText(node) {
let expr = node.textContent
let reg = /\{\{([^}]+)\}\}/g
if (reg.test(expr)) {
CompileUtil['text'](node, this.vm, expr)
}
}
compile(fragment) {
let childNodes = fragment.childNodes
Array.from(childNodes).forEach(node => {
if (this.isElementNode(node)) {
// 元素节点
this.compileElement(node)
this.compile(node)
} else {
// 文本节点
this.compileText(node)
}
})
}
// 辅助函数
isElementNode(node) {
return node.nodeType === 1
}
isDirective(name) {
return name.includes('v-')
}
}
// 编译工具方法
CompileUtil = {
getVal(vm, expr) {
let result
expr = expr.split('.')
expr.reduce((prev, next) => {
result = prev[next]
}, vm.$data)
return result
},
getTextVal(vm, expr) {
return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
return this.getVal(vm, arguments[1])
})
},
setVal(vm, expr, value) {
expr = expr.split('.')
return expr.reduce((prev, next, currentIndex) => {
if (currentIndex === expr.length - 1) {
return prev[next] = value
}
return prev[next]
}, vm.$data)
},
// 编译- 文本处理
text(node, vm, expr) {
let updaterFn = this.updater['textUpdater']
let value = this.getTextVal(vm, expr) // expr存在{{a.b.c.d}}这种结构,需要对这种情况进行取值
expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
new Watcher(vm, arguments[1], (newValue) => {
updaterFn && updaterFn(node, this.getTextVal(vm, expr))
})
})
updaterFn && updaterFn(node, value)
},
// 编译- 指令处理
model(node, vm, expr) {
let updaterFn = this.updater['modelUpdater']
new Watcher(vm, expr, (newValue) => {
updaterFn && updaterFn(node, this.getVal(vm, expr))
})
node.addEventListener('input', (e) => {
let newValue = e.target.value
this.setVal(vm, expr, newValue)
})
let abc = this.getVal(vm, expr)
updaterFn && updaterFn(node, this.getVal(vm, expr))
},
updater: {
textUpdater(node, value) {
node.textContent = value
},
modelUpdater(node, value) {
console.log(444, value);
node.value = value
}
}
}
observer.js
// 数据劫持
class Observer {
constructor(data) {
this.observe(data)
}
observe(data) {
if (!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
this.observe(data[key]) // 深度递归劫持
})
}
defineReactive(obj, key, value) {
let that = this
let dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
Dep.target && dep.addSub(Dep.target)
return value
},
set(newValue) {
if (newValue != value) {
that.observe(newValue)
value = newValue
dep.notify()
}
}
})
}
}
// 发布订阅
class Dep {
constructor() {
this.subs = []
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
watcher.js
// 观察者
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
this.value = this.get()
}
getVal(vm, expr) {
let result
expr = expr.split('.')
expr.reduce((prev, next) => {
result = prev[next]
}, vm.$data)
return result
}
get() {
Dep.target = this
let value = this.getVal(this.vm, this.expr)
Dep.target = null
return value
}
update() {
let newValue = this.getVal(this.vm, this.expr)
let oldValue = this.value
if (newValue != oldValue) {
this.cb(newValue)
}
}
}