自己实现vue双括号
实现 Vue 双括号插值的基本原理
Vue 的双括号插值({{ }})是数据绑定的核心功能之一,其本质是将模板中的表达式解析并与数据对象动态关联。以下是手动实现类似功能的步骤:
数据劫持与响应式
通过 Object.defineProperty 或 Proxy 实现数据劫持,监听数据变化:
function observe(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
let value = data[key];
const dep = new Dep(); // 依赖收集器
observe(value); // 递归劫持嵌套对象
Object.defineProperty(data, key, {
get() {
Dep.target && dep.addSub(Dep.target); // 收集依赖
return value;
},
set(newVal) {
if (newVal !== value) {
value = newVal;
observe(newVal); // 新值也可能是对象
dep.notify(); // 触发更新
}
}
});
});
}
依赖收集与发布订阅
实现一个简单的发布订阅模式来管理依赖:
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;
this.oldValue = vm[key]; // 触发 getter
Dep.target = null;
}
update() {
const newValue = this.vm[this.key];
if (newValue !== this.oldValue) {
this.cb(newValue);
this.oldValue = newValue;
}
}
}
模板解析与编译
通过正则匹配 {{ }} 并替换为数据:
function compile(el, vm) {
const fragment = document.createDocumentFragment();
let child;
while ((child = el.firstChild)) {
fragment.appendChild(child);
}
const regex = /\{\{(.*?)\}\}/g;
function replaceText(node) {
if (node.nodeType === 3) { // 文本节点
const text = node.textContent;
if (regex.test(text)) {
const key = RegExp.$1.trim();
new Watcher(vm, key, val => {
node.textContent = text.replace(regex, val);
});
node.textContent = text.replace(regex, vm[key]);
}
}
if (node.childNodes) {
node.childNodes.forEach(replaceText);
}
}
replaceText(fragment);
el.appendChild(fragment);
}
初始化实例
整合以上逻辑到主类中:
class MiniVue {
constructor(options) {
this.$data = options.data();
observe(this.$data);
this.proxyData();
compile(options.el, this);
}
proxyData() {
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return this.$data[key];
},
set(val) {
this.$data[key] = val;
}
});
});
}
}
使用示例
<div id="app">{{ message }}</div>
<script>
new MiniVue({
el: document.getElementById('app'),
data: () => ({ message: 'Hello World' })
});
</script>
关键点说明
- 数据劫持:通过
Object.defineProperty拦截属性的读写操作。 - 依赖收集:在
getter中收集依赖(Watcher),在setter中触发更新。 - 模板编译:递归遍历 DOM 节点,替换
{{ }}为实际数据并建立绑定。 - 性能优化:实际 Vue 还包含虚拟 DOM、异步更新队列等机制,此处仅展示核心原理。







