1.主文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./eventEmitter.js"></script>
<script src="./vue.js"></script>
<script src="./observer.js"></script>
<script src="./compiler.js"></script>
</head>
<body>
<div id="app">
<p v-text='age'></p>
<span>{{msg}}</span>
<div>
<span>{{age}}</span>
<span>{{age}}</span>
</div>
<input type="text" v-model="msg">
</div>
<script>
const vm = new Vue({
el:'#app',
data: {
msg:'abc',
age:10
}
});
console.log(vm)
</script>
</body>
</html>
2.vue.js(模拟vue实例,进行数据代理)
function Vue(options) {
this.$options = options
this.$data = options.data || {}
const el = options.el
this.$el = typeof el === 'string' ? document.querySelector(el) : el
this.proxyData()
new Observer(this.$data)
new Compiler(this)
}
Vue.prototype.proxyData = function() {
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this,key,{
enumerable: true,
configurable: false,
get() {
return this.$data[key]
},
set(value) {
if(value === this.$data[key]) return
this.$data[key] = value
}
})
})
}
3.observer.js(数据劫持)
function Observer(data) {
this.$data = data
Object.keys(data).forEach(key => {
this.defineReactive(this.$data,key,this.$data[key])
})
}
Observer.prototype.defineReactive = function(data, key, value) {
Object.defineProperty(data,key,{
enumerable: true,
configurable: false,
get() {
return value
},
set(val) {
if(val === value) return
value = val
bus.$emit(key)
}
})
}
4.eventEmitter.js(发布订阅操作)
function EventEmitter() {
this.subs = {}
}
EventEmitter.prototype.$on = function(EventType, handler) {
this.subs[EventType] = this.subs[EventType] || []
this.subs[EventType].push(handler)
}
EventEmitter.prototype.$emit = function(EventType,...rest) {
if(this.subs[EventType]) {
this.subs[EventType].forEach(handler => {
handler.call(this,...rest)
});
}
}
const bus = new EventEmitter()
5.compiler.js (编译模板)
function Compiler(vm) {
this.$vm = vm
this.compile(this.$vm.$el)
}
Compiler.prototype.compile = function (el) {
Array.from(el.childNodes).forEach(node => {
if (this.isTextNode(node)) {
this.compilerTextNode(node)
}
if(this.isElementNode(node)) {
this.compilerElementNode(node)
this.compile(node)
}
})
}
Compiler.prototype.compilerTextNode = function (node) {
const txt = node.textContent
const reg = /\{\{(.+)\}\}/
if(reg.test(txt)) {
const key = RegExp.$1.trim()
node.textContent = this.$vm.$data[key]
bus.$on(key , ()=> {
node.textContent = this.$vm.$data[key]
})
}
}
Compiler.prototype.compilerElementNode = function (node) {
Array.from(node.attributes).forEach( attr => {
const attrName = attr.name
const value = attr.value
if(this.isDirective(attrName)) {
if(attrName === 'v-text') {
node.textContent = this.$vm.$data[value]
bus.$on(value , ()=> {
node.textContent = this.$vm.$data[value]
})
}
if(attrName === 'v-model') {
node.value = this.$vm.$data[value]
bus.$on(value , ()=> {
node.value = this.$vm.$data[value]
})
node.oninput = () => {
this.$vm.$data[value] = node.value
}
}
}
})
}
Compiler.prototype.isTextNode = function (node) {
return node.nodeType === 3
}
Compiler.prototype.isElementNode = function (node) {
return node.nodeType === 1
}
Compiler.prototype.isDirective = function (attrName) {
return attrName.startsWith('v-')
}