当前位置:首页 > VUE

vue双向绑定代码实现

2026-02-24 15:52:07VUE

Vue 双向绑定的实现原理

Vue 的双向绑定主要通过数据劫持结合发布者-订阅者模式实现。核心是利用 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)来监听数据变化,并通过事件机制通知视图更新。

Vue 2.x 实现代码示例

数据劫持与监听

function defineReactive(obj, key, val) {
  const dep = new Dep(); // 依赖收集器

  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.addSub(Dep.target); // 收集当前依赖
      }
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      dep.notify(); // 通知所有订阅者更新
    }
  });
}

依赖收集器(Dep)

class Dep {
  constructor() {
    this.subs = []; // 订阅者列表
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  notify() {
    this.subs.forEach(sub => sub.update());
  }
}
Dep.target = null; // 静态属性,指向当前正在处理的 Watcher

订阅者(Watcher)

class Watcher {
  constructor(vm, exp, cb) {
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;
    this.value = this.get(); // 触发依赖收集
  }
  get() {
    Dep.target = this;
    const value = this.vm.data[this.exp]; // 触发 getter
    Dep.target = null;
    return value;
  }
  update() {
    const newValue = this.vm.data[this.exp];
    if (newValue !== this.value) {
      this.value = newValue;
      this.cb.call(this.vm, newValue);
    }
  }
}

编译器(Compiler)

function compile(node, vm) {
  if (node.nodeType === 1) { // 元素节点
    const attrs = node.attributes;
    for (let i = 0; i < attrs.length; i++) {
      if (attrs[i].nodeName === 'v-model') {
        const exp = attrs[i].nodeValue;
        node.addEventListener('input', e => {
          vm.data[exp] = e.target.value;
        });
        new Watcher(vm, exp, value => {
          node.value = value;
        });
      }
    }
  }
}

Vue 3.x 实现代码示例(基于 Proxy)

响应式数据

function reactive(obj) {
  const handlers = {
    get(target, key, receiver) {
      track(target, key); // 依赖收集
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver);
      trigger(target, key); // 触发更新
      return true;
    }
  };
  return new Proxy(obj, handlers);
}

const targetMap = new WeakMap(); // 存储依赖关系
function track(target, key) {
  if (!activeEffect) return;
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  dep.add(activeEffect);
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  effects && effects.forEach(effect => effect());
}

副作用函数(Effect)

let activeEffect = null;
function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

双向绑定示例

HTML 模板

<input id="input" type="text" v-model="message">
<div id="output"></div>

初始化 Vue 实例

vue双向绑定代码实现

const vm = {
  data: reactive({ message: '' }),
  mount() {
    effect(() => {
      document.getElementById('output').textContent = vm.data.message;
    });
    document.getElementById('input').addEventListener('input', e => {
      vm.data.message = e.target.value;
    });
  }
};
vm.mount();

关键点总结

  • Vue 2.x 使用 Object.defineProperty 监听对象属性,需递归遍历对象并劫持每个属性。
  • Vue 3.x 使用 Proxy 直接代理整个对象,无需递归且能监听新增/删除属性。
  • 依赖收集通过 DepWatcher(Vue 2.x)或 effect(Vue 3.x)实现。
  • 视图更新通过事件监听(如 input)和响应式数据变更触发。

标签: 绑定双向
分享给朋友:

相关文章

实现vue双向绑定

实现vue双向绑定

Vue双向绑定的实现原理 Vue的双向绑定主要通过数据劫持结合发布-订阅模式实现。核心是利用Object.defineProperty()或ES6的Proxy来劫持各个属性的getter和setter…

jquery事件绑定

jquery事件绑定

jQuery 事件绑定的基本方法 使用 on() 方法绑定事件是最推荐的方式,兼容性强且支持动态元素。语法如下: $(selector).on(eventName, handlerFunction)…

vue双向实现原理

vue双向实现原理

Vue 双向数据绑定的实现原理 Vue 的双向数据绑定主要通过数据劫持结合发布-订阅模式实现,核心是 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)。…

vue双向绑定如何实现

vue双向绑定如何实现

Vue 双向绑定的实现原理 Vue 的双向绑定是通过数据劫持结合发布者-订阅者模式实现的,核心是 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)。 Vu…

vue实现动态绑定样式

vue实现动态绑定样式

动态绑定样式的方法 在Vue中实现动态绑定样式主要通过v-bind:class和v-bind:style指令完成,支持对象语法、数组语法等多种灵活方式。 对象语法 通过对象可以动态切换class,对…

vue中实现双向绑定

vue中实现双向绑定

Vue 双向绑定的实现方式 Vue 中实现双向绑定主要通过 v-model 指令,它结合了属性绑定和事件监听,适用于表单元素和自定义组件。 表单元素的双向绑定 在表单元素(如 input、texta…