likes
comments
collection
share

面试官:谈谈你对双向绑定的理解

作者站长头像
站长
· 阅读数 73

前言

我们都知道,vue中的双向绑定是其核心特性之一,它使得视图(DOM)和数据模型(JavaScript对象)之间的同步变得非常简单。通过双向绑定,当数据模型发生变化时,视图会自动更新;反之,当用户与视图交互时,数据模型也会自动更新。这种机制大大简化了前端开发中处理数据和视图的复杂性,提高了开发效率。下面本人将为大家介绍一下本人对vue中双向绑定的理解。

为什么要有双向绑定

总的来说可以理解为以下几点:

  1. 简化开发: 双向绑定使得开发者不需要手动编写大量的 DOM 操作代码,例如手动监听 DOM 事件、手动更新 DOM 元素的内容等。这样可以大大简化代码量,提高开发效率。
  2. 提高可维护性: 双向绑定将数据与视图之间的关系变得更加明确和统一,使得代码结构更加清晰,易于理解和维护。
  3. 增强用户体验: 双向绑定可以实时地将用户输入的数据同步到数据模型中,从而实现实时更新视图的效果,提升用户体验。

双向绑定的原理

Vue中的双向绑定是通过数据劫持(Object.defineProperty)和发布-订阅模式实现的。

  1. 数据劫持(Object.defineProperty): Vue会对数据对象进行递归遍历,通过 Object.defineProperty() 方法为每个属性添加 getter 和 setter。当访问或修改属性时,会触发对应的 getter 和 setter 方法,从而实现对属性的拦截和监控。
  2. 发布-订阅模式: Vue利用观察者模式实现了一个简单的发布-订阅系统。当数据发生变化时,会触发对应属性的 setter 方法,setter 方法会通知订阅该属性的所有观察者对象进行更新,从而实现视图的自动更新。

如何实现双向绑定

    // 数据模型
let data = {
  message: 'Hello, Vue!'
};

// 观察者模式
class Watcher {
  constructor(vm, key, callback) {
    this.vm = vm;
    this.key = key;
    this.callback = callback;
    this.value = this.get();
  }

  get() {
    // 将当前 Watcher 对象设置为全局的依赖
    Dep.target = this;
    let value = this.vm[this.key];
    // 清空全局的依赖
    Dep.target = null;
    return value;
  }

  update() {
    let oldValue = this.value;
    let newValue = this.get();
    if (oldValue !== newValue) {
      this.value = newValue;
      this.callback.call(this.vm, newValue);
    }
  }
}

// 依赖管理
class Dep {
  constructor() {
    this.subscribers = [];
  }

  addSub(sub) {
    this.subscribers.push(sub);
  }

  notify() {
    this.subscribers.forEach(sub => sub.update());
  }
}

// 数据劫持
function observe(obj) {
  if (!obj || typeof obj !== 'object') {
    return;
  }
  Object.keys(obj).forEach(key => {
    let dep = new Dep();
    let value = obj[key];
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 如果有 Watcher 对象依赖该属性,则将其添加到依赖列表中
        if (Dep.target) {
          dep.addSub(Dep.target);
        }
        return value;
      },
      set(newValue) {
        if (value !== newValue) {
          value = newValue;
          // 当属性发生变化时,通知所有依赖该属性的 Watcher 对象进行更新
          dep.notify();
        }
      }
    });
  });
}

// 实现双向绑定
function bind(vm, key, node) {
  let watcher = new Watcher(vm, key, (newValue) => {
    node.value = newValue; // 更新视图
  });
  node.addEventListener('input', function (event) {
    vm[key] = event.target.value; // 更新数据模型
  });
}

// 初始化
function init() {
  observe(data);
  let input = document.getElementById('input');
  bind(data, 'message', input);
  input.value = data.message; // 初始化视图
}

init();

如以上代码所示:

  • 首先我们设置了 observe 函数用于递归遍历数据对象,为每个属性添加 getter 和 setter,实现了数据劫持。
  • 然后创建了一个 Watcher 类表示观察者对象,用于订阅数据模型的属性,并在属性发生变化时执行回调函数。
  • 创建了一个 Dep 类表示依赖管理对象,用于管理属性和观察者之间的关系。
  • 并且创建了 bind 函数用于将数据模型的属性与视图的元素节点进行绑定,实现双向绑定。
  • 最后创建了 init 函数用于初始化,将数据模型与视图进行绑定,并初始化视图的值。

但是它主要还是使用了 Object.defineProperty 来实现数据劫持,这种方法还是有着很大的缺陷的,因此我们现在都是使用 Proxy 来实现数据代理的。后面本人会对这两种方法的不同及优缺点进行讲解。

响应式原理实现

在Vue 3中,使用 setup 函数可以实现双向绑定的方式与之前的 Options API 有所不同。Vue 3引入了 Composition API,setup 函数是 Composition API 的核心之一,它用于组件的设置和初始化。在 setup 函数中,可以通过 refreactive 函数来创建响应式数据,从而实现双向绑定。

<template>
  <div>
    <input type="text" v-model="message" />
    <p>{{ message }}</p>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    // 使用 ref 函数创建响应式数据
    const message = ref('');

    // 返回响应式数据和对应的 setter 函数
    return {
      message,
    };
  },
};
</script>

在上面的示例中:

  • 使用 ref 函数创建了一个名为 message 的响应式数据。
  • 在模板中使用 v-model 指令将输入框的值绑定到 message 上,实现了视图和数据的双向绑定。
  • setup 函数中返回了 message 数据,以及对应的 setter 函数,这样就可以在模板中通过 v-model 实现数据的双向绑定了。

值得注意的是,在Vue 3中,通过 ref 创建的响应式数据是包装过的对象,所以在访问和修改其值时需要通过 .value 进行操作。因此,在模板中直接使用 message 变量即可,不需要再使用 message.value。Vue 3通过这种方式简化了双向绑定的语法,使得代码更加清晰和简洁。

总结

对于vue中的双向绑定原理,它的核心概念就是数据劫持、发布-订阅模式和响应式原理。总的来说,Vue双向绑定是一种优雅而高效的解决方案,极大地简化了前端开发中处理数据和视图的复杂性,是Vue框架的一大亮点和核心特性。

转载自:https://juejin.cn/post/7360887387918499903
评论
请登录