首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >通过js实现vue双向绑定技术

通过js实现vue双向绑定技术

原创
作者头像
小焱
发布2025-07-15 13:40:24
发布2025-07-15 13:40:24
1080
举报
文章被收录于专栏:前端开发前端开发

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Vue双向绑定原理演示</title>

<script src="https://cdn.tailwindcss.com"></script>

<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">

<script>

tailwind.config = {

theme: {

extend: {

colors: {

primary: '#4F46E5',

secondary: '#6366F1',

accent: '#818CF8',

neutral: '#F3F4F6',

},

fontFamily: {

inter: ['Inter', 'system-ui', 'sans-serif'],

},

}

}

}

</script>

<style type="text/tailwindcss">

@layer utilities {

.content-auto {

content-visibility: auto;

}

.transition-all-300 {

transition: all 0.3s ease;

}

.bg-gradient-primary {

background: linear-gradient(135deg, #4F46E5 0%, #818CF8 100%);

}

}

</style>

</head>

<body class="font-inter bg-gray-50 min-h-screen flex flex-col">

<header class="bg-gradient-primary text-white shadow-lg">

<div class="container mx-auto px-4 py-6">

<h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold flex items-center">

<i class="fa fa-code mr-3"></i>Vue 双向绑定原理演示

</h1>

<p class="mt-2 text-white/80 text-lg">使用原生 JavaScript 实现数据与视图的双向绑定</p>

</div>

</header>

<main class="flex-grow container mx-auto px-4 py-8">

<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">

<!-- 左侧:双向绑定演示 -->

<div class="bg-white rounded-xl shadow-xl p-6 transition-all-300 hover:shadow-2xl">

<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center">

<i class="fa fa-magic text-primary mr-2"></i>双向绑定演示

</h2>

<div class="space-y-6">

<div class="form-group">

<label for="name" class="block text-sm font-medium text-gray-700 mb-1">你的名字</label>

<div class="relative">

<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">

<i class="fa fa-user"></i>

</span>

<input type="text" id="name" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all-300" placeholder="请输入你的名字">

</div>

</div>

<div class="form-group">

<label for="message" class="block text-sm font-medium text-gray-700 mb-1">你的留言</label>

<div class="relative">

<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">

<i class="fa fa-comment"></i>

</span>

<textarea id="message" rows="3" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all-300" placeholder="请输入你的留言"></textarea>

</div>

</div>

<div class="bg-neutral rounded-lg p-4 border border-gray-200">

<h3 class="font-medium text-gray-700 mb-2">预览效果</h3>

<div id="preview" class="text-gray-600">

<p>你好,<span id="nameDisplay" class="font-medium text-primary">-</span></p>

<p class="mt-2">你的留言是:<span id="messageDisplay" class="italic text-gray-700">-</span></p>

</div>

</div>

</div>

</div>

<!-- 右侧:双向绑定实现原理 -->

<div class="bg-white rounded-xl shadow-xl p-6 transition-all-300 hover:shadow-2xl">

<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center">

<i class="fa fa-code-fork text-primary mr-2"></i>实现原理

</h2>

<div class="space-y-4">

<div class="p-4 bg-blue-50 border-l-4 border-primary rounded-r">

<h3 class="font-medium text-primary">1. 数据劫持</h3>

<p class="text-gray-700 mt-1">使用 Object.defineProperty() 来劫持数据的 getter 和 setter</p>

</div>

<div class="p-4 bg-blue-50 border-l-4 border-primary rounded-r">

<h3 class="font-medium text-primary">2. 发布-订阅模式</h3>

<p class="text-gray-700 mt-1">每个数据属性都有一个 dep(依赖)对象,用于收集依赖和发布更新</p>

</div>

<div class="p-4 bg-blue-50 border-l-4 border-primary rounded-r">

<h3 class="font-medium text-primary">3. 观察者</h3>

<p class="text-gray-700 mt-1">Watcher 对象会订阅数据变化,并更新对应的 DOM 元素</p>

</div>

<div class="p-4 bg-blue-50 border-l-4 border-primary rounded-r">

<h3 class="font-medium text-primary">4. 解析器</h3>

<p class="text-gray-700 mt-1">Compile 对象解析 DOM 中的指令和插值表达式,初始化视图</p>

</div>

</div>

<div class="mt-6">

<h3 class="font-medium text-gray-800 mb-3">核心代码</h3>

<div class="bg-gray-800 text-white p-4 rounded-lg overflow-x-auto text-sm font-mono">

<pre>

// 实现数据劫持

class Observer {

constructor(data) {

this.observe(data);

}

observe(data) {

if (!data || typeof data !== 'object') return;

Object.keys(data).forEach(key => {

this.defineReactive(data, key, data[key]);

this.observe(data[key]); // 递归劫持子属性

});

}

defineReactive(obj, key, value) {

const dep = new Dep();

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get() {

Dep.target && dep.addSub(Dep.target);

return value;

},

set(newVal) {

if (newVal === value) return;

value = 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, expr, cb) {

this.vm = vm;

this.expr = expr;

this.cb = cb;

this.oldValue = this.getOldValue();

}

getOldValue() {

Dep.target = this;

const oldValue = compileUtil.getValue(this.expr, this.vm);

Dep.target = null;

return oldValue;

}

update() {

const newValue = compileUtil.getValue(this.expr, this.vm);

if (newValue !== this.oldValue) {

this.cb(newValue);

}

}

}

</pre>

</div>

</div>

</div>

</div>

<div class="mt-8 bg-white rounded-xl shadow-xl p-6">

<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center">

<i class="fa fa-info-circle text-primary mr-2"></i>关于此演示

</h2>

<div class="prose max-w-none">

<p>这个演示展示了 Vue.js 双向数据绑定的核心原理,通过原生 JavaScript 实现了以下功能:</p>

<ul class="list-disc pl-5 mt-2">

<li>数据劫持:使用 Object.defineProperty() 监听数据变化</li>

<li>发布-订阅模式:实现依赖收集和通知机制</li>

<li>DOM 更新:当数据变化时自动更新视图</li>

<li>事件监听:当视图变化时自动更新数据</li>

</ul>

<p class="mt-3">实际的 Vue.js 框架比这个示例复杂得多,它还包含虚拟 DOM、组件系统、路由等高级功能。这个演示只是为了帮助理解双向绑定的基本原理。</p>

</div>

</div>

</main>

<footer class="bg-gray-800 text-white py-6">

<div class="container mx-auto px-4">

<div class="flex flex-col md:flex-row justify-between items-center">

<div class="mb-4 md:mb-0">

<p>© 2025 Vue 双向绑定原理演示</p>

</div>

<div class="flex space-x-4">

<a href="#" class="text-gray-400 hover:text-white transition-all-300">

<i class="fa fa-github"></i>

</a>

<a href="#" class="text-gray-400 hover:text-white transition-all-300">

<i class="fa fa-twitter"></i>

</a>

<a href="#" class="text-gray-400 hover:text-white transition-all-300">

<i class="fa fa-linkedin"></i>

</a>

</div>

</div>

</div>

</footer>

<script>

// 实现一个简单的 Vue 双向绑定

class Vue {

constructor(options) {

this.$el = options.el;

this.$data = options.data;

this.$methods = options.methods;

// 数据代理,使得可以通过 vm.xxx 访问 vm.$data.xxx

this.proxyData(this.$data);

// 1. 数据劫持

new Observer(this.$data);

// 2. 编译模板

if (this.$el) {

new Compile(this.$el, this);

// 3. 初始化生命周期钩子

if (options.mounted) {

options.mounted.call(this);

}

}

}

proxyData(data) {

Object.keys(data).forEach(key => {

Object.defineProperty(this, key, {

get() {

return data[key];

},

set(newVal) {

data[key] = newVal;

}

});

});

}

}

// 数据劫持

class Observer {

constructor(data) {

this.observe(data);

}

observe(data) {

if (!data || typeof data !== 'object') {

return;

}

Object.keys(data).forEach(key => {

this.defineReactive(data, key, data[key]);

// 递归劫持子属性

this.observe(data[key]);

});

}

defineReactive(obj, key, value) {

// 每个属性都有一个依赖收集器

const dep = new Dep();

// 递归劫持子属性

this.observe(value);

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get() {

// 收集依赖

Dep.target && dep.addSub(Dep.target);

return value;

},

set(newVal) {

if (newVal === value) {

return;

}

value = newVal;

// 新值可能是对象,需要继续劫持

this.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, expr, cb) {

this.vm = vm;

this.expr = expr;

this.cb = cb;

// 保存旧值

this.oldValue = this.get();

}

get() {

// 将当前 watcher 实例保存到 Dep.target

Dep.target = this;

// 获取值的过程中会触发属性的 getter,从而收集依赖

const value = CompileUtil.getValue(this.expr, this.vm);

// 清空 Dep.target,防止重复收集

Dep.target = null;

return value;

}

update() {

const newValue = CompileUtil.getValue(this.expr, this.vm);

if (newValue !== this.oldValue) {

this.cb(newValue);

}

}

}

// 编译模板

class Compile {

constructor(el, vm) {

this.el = this.isElementNode(el) ? el : document.querySelector(el);

this.vm = vm;

if (this.el) {

// 1. 将真实 DOM 转换为文档片段

const fragment = this.node2Fragment(this.el);

// 2. 编译文档片段

this.compile(fragment);

// 3. 将编译后的文档片段添加回原 DOM

this.el.appendChild(fragment);

}

}

// 判断是否是元素节点

isElementNode(node) {

return node.nodeType === 1;

}

// 判断是否是指令

isDirective(attrName) {

return attrName.startsWith('v-');

}

// 判断是否是事件指令

isEventDirective(dir) {

return dir.startsWith('on');

}

// 将 DOM 转换为文档片段

node2Fragment(el) {

const fragment = document.createDocumentFragment();

let firstChild;

// 将 DOM 元素的所有子节点移动到文档片段中

while (firstChild = el.firstChild) {

fragment.appendChild(firstChild);

}

return fragment;

}

// 编译文档片段

compile(fragment) {

const childNodes = fragment.childNodes;

Array.from(childNodes).forEach(node => {

if (this.isElementNode(node)) {

// 编译元素节点

this.compileElement(node);

// 递归编译子节点

this.compile(node);

} else {

// 编译文本节点

this.compileText(node);

}

});

}

// 编译元素节点

compileElement(node) {

const attrs = node.attributes;

Array.from(attrs).forEach(attr => {

const attrName = attr.name;

if (this.isDirective(attrName)) {

// v-text, v-model, v-html 等

const expr = attr.value;

const dir = attrName.substring(2);

if (this.isEventDirective(dir)) {

// 事件指令,如 v-on:click

CompileUtil.eventHandler(node, this.vm, expr, dir);

} else {

// 普通指令

CompileUtil[dir] && CompileUtil[dir](node, this.vm, expr);

}

// 移除指令属性

node.removeAttribute(attrName);

}

});

}

// 编译文本节点

compileText(node) {

const text = node.textContent;

const reg = /\{\{([^}]+)\}\}/g;

if (reg.test(text)) {

CompileUtil.text(node, this.vm, text);

}

}

}

// 编译工具

const CompileUtil = {

// 获取表达式的值

getValue(expr, vm) {

return expr.split('.').reduce((data, current) => {

return data[current];

}, vm.$data);

},

// 设置表达式的值

setValue(expr, vm, inputVal) {

expr.split('.').reduce((data, current, index, arr) => {

if (index === arr.length - 1) {

data[current] = inputVal;

}

return data[current];

}, vm.$data);

},

// 处理 v-text 指令

text(node, vm, expr) {

let value;

if (expr.indexOf('{{') !== -1) {

// 处理插值表达式 {{xxx}}

value = expr.replace(/\{\{([^}]+)\}\}/g, (match, p1) => {

// 为每个插值表达式创建一个 watcher

new Watcher(vm, p1.trim(), (newValue) => {

this.updateText(node, newValue);

});

return this.getValue(p1.trim(), vm);

});

} else {

// 处理普通 v-text 指令

new Watcher(vm, expr, (newValue) => {

this.updateText(node, newValue);

});

value = this.getValue(expr, vm);

}

this.updateText(node, value);

},

// 更新文本内容

updateText(node, value) {

node.textContent = value;

},

// 处理 v-model 指令

model(node, vm, expr) {

// 设置初始值

const value = this.getValue(expr, vm);

// 为 input 元素添加事件监听

node.addEventListener('input', (e) => {

const newValue = e.target.value;

this.setValue(expr, vm, newValue);

});

// 创建 watcher,当数据变化时更新视图

new Watcher(vm, expr, (newValue) => {

node.value = newValue;

});

// 设置初始值

node.value = value;

},

// 处理事件指令

eventHandler(node, vm, expr, dir) {

const eventType = dir.split(':')[1];

const fn = vm.$methods && vm.$methods[expr];

if (eventType && fn) {

node.addEventListener(eventType, fn.bind(vm));

}

}

};

// 创建 Vue 实例

document.addEventListener('DOMContentLoaded', () => {

const vm = new Vue({

el: '#app',

data: {

name: '',

message: ''

},

methods: {

updatePreview() {

console.log('更新预览');

}

},

mounted() {

console.log('Vue 实例已挂载');

// 手动实现与表单元素的双向绑定

const nameInput = document.getElementById('name');

const messageInput = document.getElementById('message');

const nameDisplay = document.getElementById('nameDisplay');

const messageDisplay = document.getElementById('messageDisplay');

// 从数据到视图的绑定

new Watcher(vm, 'name', (newValue) => {

nameDisplay.textContent = newValue || '-';

});

new Watcher(vm, 'message', (newValue) => {

messageDisplay.textContent = newValue || '-';

});

// 从视图到数据的绑定

nameInput.addEventListener('input', (e) => {

vm.name = e.target.value;

});

messageInput.addEventListener('input', (e) => {

vm.message = e.target.value;

});

}

});

});

</script>

</body>

</html>

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档