前言
我将参考下面这张图 实现一个简单的伪MVVM模式 主要针对compile, observer, watcher, dep 四个文件
目录结构
- js
- compile.js
- dep.js
- observer.js
- vue.js
- watcher.js
- index.html
index.html
<div id="app">
<section>
<input type="text" v-model="message.a">
<div>
{{message.a}} - {{message.a}} - {{message.b}}
</div>
</section>
<section>
<div>123</div>
<p><span></span></p>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</section>
<section>
<button v-on:click="clickMe">click me !</button>
<h2>{{title}}</h2>
</section>
</div>
<script src="./js/dep.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/observer.js"></script>
<script src="./js/compile.js"></script>
<script src="./js/vue.js"></script>
<script>
let vm = new Vue ({
el: '#app',
data: {
message: {
a: 'hello MVVM',
b: 'new vue'
},
title: 'vue'
},
mounted() {
setTimeout(() => {
this.title = 'vue 3000'
}, 3000)
},
methods: {
clickMe() {
console.log('clickMe ~ title', this.title)
this.title = 'vue code'
}
}
})
</script>
vue.js
class Vue {
constructor(options) {
this.$el = options.el
this.$data = options.data
this.methods = options.methods
if (this.$el) {
/** 核心-数据劫持 */
new Observer(this.$data)
/** 核心-编译模板 */
new Compile(this.$el, this)
/** 辅助-数据代理到this */
this.proxyKeys()
/** 辅助-生命周期mounted(所有的事情处理好执行) */
options.mounted.call(this)
}
}
proxyKeys() {
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get() {
return this.$data[key]
},
set(newValue) {
this.$data[key] = newValue
}
})
})
}
}
compile.js
class Compile {
constructor(el, vm) {
this.el = this.isNodeElement(el) ? el : document.querySelector(el)
this.vm = vm
if (this.el) {
let fragment = this.nodeToFragment(this.el)
this.compile(fragment)
this.el.appendChild(fragment)
}
}
/** 辅助方法 */
isNodeElement(node) { return node.nodeType === 1 }
isDirective(name) { return name.includes('v-') }
isEventDirective(name) { return name.indexOf('on:') > -1 }
/** 核心方法 */
/**
* @description: 编译元素
*/
compileElement(node) {
let nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach(attr => {
if (this.isDirective(attr.name)) {
if (this.isEventDirective(attr.name)) {
const [, eventType] = attr.name.split(':')
let method = attr.value
compileUtil['event'](this.vm, node, eventType, method)
} else { // v-model
let expr = attr.value
const [, type] = attr.name.split('-')
compileUtil[type](this.vm, node, expr)
}
}
})
}
/**
* @description: 编译文本
*/
compileText(node) {
let textExpr = node.textContent
if (RegText.test(textExpr)) {
compileUtil['text'](this.vm, node, textExpr)
}
}
/**
* @description: 编译
*/
compile(fragment) {
let childNodes = fragment.childNodes
Array.from(childNodes).forEach(node => {
if (this.isNodeElement(node)) {
this.compileElement(node)
this.compile(node)
} else {
this.compileText(node)
}
})
}
/**
* @description: 转为文档碎片
*/
nodeToFragment(el) {
let fragment = document.createDocumentFragment()
let firstChild = el.firstChild
while(firstChild) {
fragment.appendChild(firstChild)
firstChild = el.firstChild
}
return fragment
}
}
const RegText = /\{\{([^}]+)\}\}/g
/** 指令工具包 */
const compileUtil = {
/** 辅助方法 */
getValue(vm, expr) {
expr = expr.split('.')
return expr.reduce((prev, next) => {
return prev[next]
}, vm.$data)
},
getTextValue(vm, textExpr) {
return textExpr.replace(RegText, (...argments) => {
let expr = argments[1]
return this.getValue(vm, expr)
})
},
setModelValue(vm, expr, value) {
expr = expr.split('.')
return expr.reduce((prev, next, currentIndex) => {
if (currentIndex == expr.length-1) {
prev[next] = value
}
return prev[next]
}, vm.$data)
},
/** 核心方法 */
text(vm, node, textExpr) {
let textValue = this.getTextValue(vm, textExpr)
let updaterFn = this.updater['textUpdater']
updaterFn && updaterFn(node, textValue)
textExpr.replace(RegText, (...argments) => {
new Watcher(vm, argments[1], () => {
updaterFn && updaterFn(node, this.getTextValue(vm, textExpr))
})
})
},
model(vm, node, expr) {
let value = this.getValue(vm, expr)
let updaterFn = this.updater['modelUpdater']
updaterFn && updaterFn(node, value)
new Watcher(vm, expr, () => {
updaterFn && updaterFn(node, this.getValue(vm, expr))
})
// 监听事件
node.addEventListener('input', e => {
let newValue = e.target.value
this.setModelValue(vm, expr, newValue)
})
},
event(vm, node, eventType, method) {
node.addEventListener(eventType, (e) => {
e.preventDefault && e.preventDefault()
let methodFn = vm.methods[method]
methodFn && methodFn.call(vm)
})
},
updater: {
modelUpdater(node, value) {
node.value = value
},
textUpdater(node, value) {
node.textContent = value
}
}
}
dep.js
class Dep {
constructor() {
this.subs = []
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
Dep.target = null
obsever.js
class Observer {
constructor(data) {
this.data = data
this.observer(data)
}
observer(data) {
if (!data || typeof data !== 'object') { return }
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
this.observer(data[key])
})
}
defineReactive(data, key, value) {
let self = this
let dep = new Dep()
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
Dep.target && dep.addSub(Dep.target)
return value
},
set(newValue) {
if (newValue != value) {
self.observer(newValue)
value = newValue
dep.notify()
}
}
})
}
}
watcher.js
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
// 存储老值
this.oldValue = this.getValue()
}
getObserverValue(vm, expr) {
expr = expr.split('.') // [message.a]
return expr.reduce((prev, next) => {
return prev[next]
}, vm.$data)
}
getValue() {
Dep.target = this
let value = this.getObserverValue(this.vm, this.expr)
Dep.target = null
return value
}
update() {
let newValue = this.getObserverValue(this.vm, this.expr)
if (newValue != this.oldValue) {
this.oldValue = newValue
this.cb()
}
}
}
效果图
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!