MVVM的基本实现
测试页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{msg}}</h3>
<h1>{{ count }}</h1>
<div v-text='msg'></div>
<div v-text='person.fav'></div>
<div v-html="htmlStr"></div>
<input type="text" v-model="msg">
<input type="number" v-model='person.age'>
<button v-on:click="handClick">add</button>
<button v-on:click="setCount">count++</button>
</div>
</body>
<script src="./observer.js"></script>
<script src="./mvvm.js"></script>
<script>
let vm = new MVVM({
el: '#app',
data: {
count: 0,
htmlStr: '<p style="color: red;">插入成功</p>',
person: {
name: 'mark',
age: 18,
fav: 'girl'
},
msg: '我爱vue'
},
methods: {
handClick() {
this.person.name = 'zhangsan' + Math.floor(Math.random() * 100)
},
setCount() {
this.count++
}
},
})
</script>
</html>
observer.js
class Observer {
constructor(data) {
this.observe(data)
}
observe (data) {
if (data && typeof data === 'object') {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
}
// 劫持监听
defineReactive (obj, key, value) {
// 递归遍历
this.observe(value)
// 创建观察者
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get () {
Dep.target && dep.addSub(Dep.target)
// 订阅数据变化时,往Dep中添加观察者Watcher
return value
},
set: (newVal) => {
this.observe(newVal)
if (newVal !== value) {
value = newVal
}
// 更改数据后告诉Dep,通知变化
dep.notify()
}
})
}
}
class Dep {
constructor() {
this.subs = []
}
// 1、收集观察者
addSub (watcher) {
this.subs.push(watcher)
}
// 通知观察者去更新
notify () {
this.subs.forEach(watcher => watcher.update())
}
}
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm
this.expr = expr.trim()
this.cb = cb
this.oldVal = this.getOldVal()
}
getOldVal () {
Dep.target = this
const oldVal = compileUtil.getVal(this.vm, this.expr)
Dep.target = null
return oldVal
}
update () {
const newVal = compileUtil.getVal(this.vm, this.expr)
if (newVal !== this.oldVal) {
this.cb(newVal)
}
}
}
mvvm.js
/**
* 1、实现一个指令解析器Compile
* 2、实现一个数据的监听器
* 3、实现一个watcher去更新视图
* 4、实现一个proxy
*/
const compileUtil = {
text (vm, node, expr) { // expr: msg
let textValue;
if (expr.indexOf('{{') !== -1) {
// 处理双{{}}
textValue = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
// 绑定观察者,将来数据发生变化,触发这里的回调,进行更新
new Watcher(vm, args[1].trim(), (newVal) => {
this.updater.textUpdater(node, this.getContentVal(vm, expr))
})
return this.getVal(vm, args[1].trim())
})
} else {
textValue = this.getVal(vm, expr)
// 绑定观察者,将来数据发生变化,触发这里的回调,进行更新
new Watcher(vm, expr.trim(), (newVal) => {
this.updater.textUpdater(node, newVal)
})
}
this.updater.textUpdater(node, textValue)
},
html (vm, node, expr) {
const value = this.getVal(vm, expr)
new Watcher(vm, expr, (newVal) => {
this.updater.htmlUpdater(node, newVal)
})
this.updater.htmlUpdater(node, value)
},
model (vm, node, expr) {
const value = this.getVal(vm, expr)
new Watcher(vm, expr, (newVal) => {
this.updater.modelUpdater(node, newVal)
})
node.addEventListener('input', (e) => {
this.setVal(vm, expr, e.target.value)
}, false)
this.updater.modelUpdater(node, value)
},
on (vm, node, expr, eventName) {
let fn = vm.$options.methods[expr]
fn && node.addEventListener(eventName, fn.bind(vm), false)
},
setVal (vm, expr, inputVal) {
expr.split('.').reduce((data, currentVal, index) => {
if (index === expr.split('.').length - 1) {
data[currentVal] = inputVal
}
return data[currentVal]
}, vm.$data)
},
getVal (vm, expr) {
return expr.split('.').reduce((data, currentVal) => {
return data[currentVal]
}, vm.$data)
},
getContentVal (vm, expr) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(vm, args[1].trim())
})
},
updater: {
textUpdater (node, value) {
node.textContent = value
},
htmlUpdater (node, value) {
node.innerHTML = value
},
modelUpdater (node, value) {
node.value = value
}
}
}
class Compile {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el)
this.vm = vm
// 1、获取文档碎片对象,放入内存中,会减少页面的回流和重绘
const fragment = this.node2Fragment(this.el)
// 2、编译模板
this.compile(fragment)
// 3、追加子元素到根元素
this.el.appendChild(fragment)
}
compile (fragment) {
// 1. 获取子节点
const childNodes = fragment.childNodes
childNodes && [...childNodes].forEach(child => {
// 对子节点处理, 元素节点
if (this.isElementNode(child)) {
// 编译元素节点
this.compileElement(child)
} else {
// 编译文本节点
this.compileText(child)
}
if (child.childNodes && childNodes.length) {
this.compile(child)
}
})
}
// 元素节点编译
compileElement (node) {
const attributes = node.attributes
attributes && [...attributes].forEach(attr => {
const { name, value } = attr
// 判断属性是否为指令
if (this.isDerective(name)) {
const [, directive] = name.split('-') // text html model on:click
const [dirName, eventName] = directive.split(':') // text html model on
// 更新数据 数据驱动视图
compileUtil[dirName](this.vm, node, value.trim(), eventName)
// 删除有指令的标签的属性
node.removeAttribute('v-' + directive)
}
})
}
// 文本节点编译
compileText (node) {
const content = node.textContent
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtil['text'](this.vm, node, content.trim())
}
}
// 拿到页面的元素
node2Fragment (el) {
// 创建文档碎片对象
const f = document.createDocumentFragment();
let firstChild
while (firstChild = el.firstChild) {
f.appendChild(firstChild)
}
return f
}
// 判断是否是v-开头,是否为指令
isDerective (attrName) {
return attrName.startsWith('v-')
}
// 判断是否为元素节点
isElementNode (node) {
return node.nodeType === 1
}
}
class MVVM {
constructor(options) {
this.$el = options.el
this.$options = options
this.$data = options.data
if (this.$el) {
// 1、实现数据的观察者
new Observer(this.$data)
// 2、实现一个指令的解析器
new Compile(this.$el, this)
// 3、实现数据代理
this.proxyData(this.$data)
}
}
proxyData (data) {
for (const key in data) {
Object.defineProperty(this, key, {
get () {
return data[key]
},
set (newVal) {
data[key] = newVal
}
})
}
}
}
posted on 2021-08-22 21:58 chinesedon007 阅读(92) 评论(0) 收藏 举报
浙公网安备 33010602011771号