Skip to content

Latest commit

 

History

History
180 lines (125 loc) · 6.34 KB

实现一个简单的双向数据绑定.md

File metadata and controls

180 lines (125 loc) · 6.34 KB

实现一个简单的双向数据绑定

双向数据绑定是使用 Vue 构建 JavaScript 表单的强大模式。

例如,假设您有一个 input 元素和一个 JavaScript 变量 value。双向数据绑定意味着:

  • 当用户键入 input 时,value 将被更新以匹配 input 中的值。
  • 更新 value 时,input 元素的内容将更新以匹配 value

Vue 通过 v-model 属性支持双向数据绑定。例如:

<template>
  <input v-model="value" />
  <button @click="value = 'Hello Vue'">Reset</button>
</template>

<script>
  export default {
    data() {
      return { value: 'Hello Vue' }
    }
  }
</script>

原理

要实现一个双向绑定,我们需要了解以下几点。

MVVM

Vue 的双向数据绑定(MVVM)由三个重要部分构成:

  • Model 代表数据层,可定义修改数据和编写业务逻辑。
  • View 代表视图层(UI 组件),负责将数据渲染成页面。
  • ViewModel 负责监听数据层数据变化,控制视图层行为交互。简单讲,就是同步数据层和视图层的对象。ViewModel 通过双向绑定把 View 和 Model 层连接起来,且同步工作无需人为干涉,使开发人员只关注业务逻辑,无需频繁操作 DOM,不需关注数据状态的同步问题。

很像面试总结说法,大概了解即可。

重点理解 ViewModel,它的主要职责是:

  • 数据变化更新视图
  • 视图变化更新数据

以下是 Vue 官网提供的 MVVM 示例图:

image.png

通过 MVVM 示例图,我们可以清晰的看到,Vue ViewModel 通过 DOM 监听和指令来实现。

具体的说,Vue 内部在实现 ViewModel 层上,分为两部分:

  • 监听器(Observer)— 对所有数据的属性进行监听
  • 解析器(Compiler)— 对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

指令

Vue 指令数据绑定有两种:

  • 双向绑定的 v-model
  • 单向绑定的 :value

而 Vue v-model 指令其实是属性 + 事件的语法糖。

简单的说,双向绑定由两个单向绑定组成:

  • 模型 —> 视图数据绑定
  • 视图 —> 模型事件绑定

图片

通过 :value 实现了模型到视图的数据绑定,@event 实现了视图到模型的事件绑定:

<input :value="searchText" @input="searchText = $event.target.value" />
<!-- 等同于 -->
<input v-model="searchText" />

其他的表单元素绑定如下:

  • input 元素若是 text / textarea 类型,使用 value 属性和 input 事件。
  • input 元素若是 radio / checkbox 类型,使用 checked 属性和 change 事件。
  • select 元素使用 value 属性和 change 事件。

Object.defineProperty 和 Proxy

最后一点,就是实现响应式数据基础了。

Vue 2.x 基于 Object.defineProperty 实现响应式系统,而 Vue 3 使用 Proxy。

这里就有个问题,Vue 为什么从 Object.defineProperty 替换为 Proxy?

可以从优缺点入手。

替换掉 Object.defineProperty 肯定是它有一些缺点:

  • 无法监听数组下标的变化,导致通过数组下标添加元素,无法实时响应
  • 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择

Proxy 的优势就是为了解决 Object.defineProperty 的一些劣势,除此之外:

  • Proxy 可以直接监听数组的变化。
  • Proxy 有多达 13 种拦截方法,不限于 applyownKeysdeletePropertyhas 等,这些是 Object.defineProperty 不具备的。
  • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的。而 Object.defineProperty 只能遍历对象属性直接修改。
  • Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是新标准的性能红利。
  • 当然,Proxy 的劣势就是兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本(3.0)才能用 Proxy 重写。

关于 Proxy 和 Reflect 还有不认识的可以看看阮一峰老师的 ProxyReflect,也可以看看现代 JavaScript 教程的 Proxy 和 Reflect

实现

一个需要绑定的 input 标签和用于显示数据的 span 标签:

<input id="input" /> <span id="span"></span>

Vue 2.x 使用 ES5 Object.defineProperty

const input = document.getElementById('input')
const span = document.getElementById('span')
const obj = {}

// 数据劫持
Object.defineProperty(obj, 'text', {
  get() {
    console.log('获取数据')
  },
  set(value) {
    console.log('数据更新')
    span.textContent = value
    this.value = value
  }
})

// 监听
input.addEventListener('input', (e) => {
  obj.text = e.target.value
})

Vue 3 使用 ES6 Proxy,它可以直接劫持整个对象,并返回一个新对象,不管是操作便利程度还是底层功能上都比 Object.defineProperty 强。

const input = document.getElementById('input')
const span = document.getElementById('span')
const obj = {}

// 数据劫持
const ret = new Proxy(obj, {
  get(target, key, receiver) {
    console.log(target, key, receiver)
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    console.log(target, key, value, receiver)
    if (key === 'text') {
      input.value = value
      span.textContent = value
    }
    return Reflect.set(target, key, value, receiver)
  }
})

// 监听
input.addEventListener('input', (e) => {
  ret.text = e.target.value
})

以上是极简的双向数据绑定的示例。

更具体的实现,后面有空在写一写。

更多资料

Vue 3.0 进阶之双向绑定探秘