<!DOCTYPE html>
<html>
<head>
<title>Mini Vue</title>
</head>
<body>
<div id="app">
<input v-model="message" />
<p>{{ message }}</p>
</div>
<script>
// 1. 响应式系统(上面的代码)
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
track(obj, key)
return obj[key]
},
set(obj, key, value) {
obj[key] = value
trigger(obj, key)
return true
}
})
}
let activeEffect = null
const targetMap = new WeakMap()
function watchEffect(effect) {
activeEffect = effect
effect()
activeEffect = null
}
function track(obj, key) {
if (!activeEffect) return
let depsMap = targetMap.get(obj)
if (!depsMap) targetMap.set(obj, (depsMap = new Map()))
let dep = depsMap.get(key)
if (!dep) depsMap.set(key, (dep = new Set()))
dep.add(activeEffect)
}
function trigger(obj, key) {
const depsMap = targetMap.get(obj)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) dep.forEach(effect => effect())
}
// 2. 极简编译:查找 v-model 和 {{ }}
function compile(el, data) {
// 查找所有子元素
const nodes = el.querySelectorAll('*')
// 处理 v-model
el.querySelectorAll('[v-model]').forEach(input => {
const key = input.getAttribute('v-model')
// 初始化 value
input.value = data[key]
// 双向绑定
input.addEventListener('input', (e) => {
data[key] = e.target.value
})
// 数据变化时更新 input
watchEffect(() => {
input.value = data[key]
})
})
// 处理 {{ message }}
Array.from(el.childNodes).forEach(node => {
if (node.nodeType === 3) { // 文本节点
const text = node.textContent
const match = text.match(/\{\{(.*)\}\}/)
if (match) {
const key = match[1].trim()
// 初次渲染
node.textContent = node.textContent.replace(match[0], data[key])
// 响应式更新
watchEffect(() => {
node.textContent = node.textContent.replace(data[key], data[key])
})
}
}
})
// 处理子元素中的 {{ }}
nodes.forEach(node => {
Array.from(node.childNodes).forEach(child => {
if (child.nodeType === 3) {
const text = child.textContent
const match = text.match(/\{\{(.*)\}\}/)
if (match) {
const key = match[1].trim()
const getValue = () => data[key]
watchEffect(() => {
child.textContent = text.replace(/\{\{.*\}\}/, getValue())
})
}
}
})
})
}
// 3. 启动
const obj = { message: 'Hello, Mini Vue!' }
const data = reactive(obj)
const app = document.getElementById('app')
compile(app, data)
</script>
</body>
</html>