myvue.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>myVue</title>
</head>
<body>
<!-- <script src="./compileUtil.js"></script> -->
<script src="./dep.js"></script>
<script src="./watcher.js"></script>
<script src="./observer.js"></script>
<script src="./myVue.js"></script>
<script src="./compile.js"></script>
<div id="app">
<input type="text" v-model="msg">
<div>
<p>{{msg}} --- {{info}}</p>
</div>
<div onclick="aaa()">1111111</div>
</div>
<script>
const mv = new myVue({
el: '#app',
data: {
msg: "hello",
info: "muji"
}
})
aaa=()=>{
mv.$data.info='22222'
}
</script>
</body>
</html>
compile.js
class compile {
constructor(vm) {
this.el = vm.$el
this.vm = vm
//1. createDocumentFragment()方法,是用来创建一个有虚拟节点的对象
const fragment = this.nodeToFragment()
//2.编译解析文档片段
this.compileFragment(fragment)
//3.将编译后的文档片段追加到模版容器中
this.el.appendChild(fragment)
}
nodeToFragment() {
const fragment = document.createDocumentFragment()
while (this.el.firstChild) {
fragment.appendChild(this.el.firstChild)
}
return fragment
}
compileFragment(fragment) {
const nodeList = fragment.childNodes
nodeList.forEach(node => {
const nType = node.nodeType
if (nType === 1) {
//元素节点,解析指令
this.compileElement(node)
} else if (nType === 3) {
//文档节点,解析{{}}
this.compileText(node)
}
//如果node有子节点,递归调用compile
if (node.childNodes && node.childNodes.length > 0) {
this.compileFragment(node)
}
})
}
compileText(node) {
const con = node.textContent
//匹配 {{xx.xx}}的正则
const reg = /\{\{(.+?)\}\}/g
if (reg.test(con)) {
const newVal = con.replace(reg, (...args) => {
//key -> args[1]
//object -> this.vm.$data
new watcher(this.vm, args[1], () => {
node.textContent = con.replace(reg, (...args) => {
return this.vm.$data[args[1]]
})
})
return this.vm.$data[args[1]]
})
console.log(newVal)
node.textContent = newVal
}
}
compileElement(node) {
//1.获取元素节点的所有属性
const atrs = node.attributes
Array.from(atrs).forEach(atr => {
const { name, value } = atr
if (name.startsWith('v-')) {
const [, b] = name.split('-')
if (b === 'model') {
node.value = this.vm.$data[value]
//数据驱动视图
new watcher(this.vm, value, () => {
node.value = this.vm.$data[value]
})
//dom事件监听,更新数据
node.addEventListener('input', (e) => {
this.vm.$data[value] = e.target.value
})
}
}
})
}
}
dep.js
class dep {
//当数据变化时,通知所有观察者更新视图
constructor() {
this.watcherList = []
}
addWatcher(watcher) {
this.watcherList.push(watcher)
}
//通知所有观察者更新视图
notify() {
this.watcherList.forEach(w => w.update())
}
}
myvue.js
class myVue {
constructor(option) {
this.$data = option.data
this.$el = document.querySelector(option.el)
//1.数据代理
this.proxyData()
//2.数据劫持
new observer(this.$data)
//3.模版编译
new compile(this)
}
//数据代理
proxyData() {
for (const key in this.$data) {
Object.defineProperty(this, key, {
configurable: false,
enumerable: true,
get() {
return this.$data[key]
},
set(newVal) {
this.$data[key] = newVal
}
})
}
}
}
observer.js
class observer {
constructor(data) {
this.oberve(data)
}
//劫持(监听)数据
oberve(data) {
const dependency = new dep()
for (const key in data) {
let val = data[key]
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get() {
console.log('依赖被添加了', val)
//watcher实例化时被追加到依赖收集器中
dep.target && dependency.addWatcher(dep.target)
return val
},
set(newVal) {
//对数据变化进行监听
val = newVal
dependency.notify()
}
})
}
}
}
watch.js
const compileUtil = {
/**
* 获取data中的值 如person.name="小明"
*/
getValue(exp, vm) {
// exp = person.fav
return exp.split(".").reduce((data, curValue) => {
return data[curValue]
}, vm.$data)
},
/**
*
* @param {*} node 元素节点
* @param {string} exp 指令表达式 在data中找值需要
* @param {*} vm Vue实例
*/
text(node, exp, vm) {
let value;
if (exp.indexOf("{{") !== -1) {
// 对{{person.name}}进行替换
value = exp.replace(/\{\{(.+?)\}\}/g, (...args) => {
// 获取到要替换的表达式 args中第二个元素即为需要的元素
// console.log(args);
return this.getValue(args[1], vm)
})
} else {
// 获取指令表达式的值
value = this.getValue(exp, vm);
}
// 更新
this.updater.textUpdater(node, value);
},
html(node, exp, vm) {
let value = this.getValue(exp, vm);
this.updater.htmlUpdater(node, value);
},
model(node, exp, vm) {
let value = this.getValue(exp, vm);
this.updater.modelUpdater(node, value);
},
/**
*
* @param {*} node 元素节点
* @param {*} exp 指令表达式
* @param {*} vm Vue实例
* @param {*} eventName 事件名称
*/
on(node, exp, vm, eventName) {
let fn = vm.$options.methods && vm.$options.methods[exp];
node.addEventListener(eventName, fn.bind(vm), false)
},
/**
*
* @param {*} node 元素节点
* @param {*} exp 指令表达式
* @param {*} vm Vue实例
* @param {*} attrName 属性名称
*/
bind(node, exp, vm, attrName) {
let value = this.getValue(exp, vm);
this.updater.bindUpdater(node, attrName, value);
},
updater: {
// 更新文本内容
textUpdater(node, value) {
node.textContent = value;
},
// 插入html碎片
htmlUpdater(node, value) {
node.innerHTML = value;
},
// 绑定input的value
modelUpdater(node, value) {
node.value = value;
},
// 绑定属性
bindUpdater(node, attrName, attrValue) {
node.setAttribute(attrName, attrValue)
}
}
}
class watcher {
//当观察者观察的数据变化时,更新视图
constructor(vm, key, callback) {
this.vm = vm
this.key = key
this.callback = callback
//当观察者示例化时,将最初的值保存
this.oldVal = this.getOldVal()
}
getOldVal() {
dep.target = this
//本质是对mv实例对象中的mv.$data[key]的访问
const oldVal = compileUtil.getValue(this.key, this.vm)
dep.target = null
return oldVal
}
//更新视图
update() {
this.callback()
}
}