一、前言

在Vue.js的使用中,不多不少会触及到数据驱动视图的功能,也就是我们常说的“数据双向绑定”,并且在面试中也经常会被问及它的实现原理,今天打算做个由浅入深的总结,回顾知识。

二、MVVM的概念

首先需要理解MVVM到底是什么。要说起这个,还得从最早的MVC开始说起,直接上图两者的区别:

  • 首先我们看MVC:
  • 然后是MVVM:

    可以发现,实际上MVVM是有MVC演变而来,由原来的 Controller(控制处理模块) 变成了ViewModel(第三方观察者),由此我们就知道,所有的页面数据实际上最终都是View和Model之间的变化而来,其中的Model其实通俗来讲就是我们逻辑代码中的data数据。

三、双向绑定的原理

基于以上的说明,我们知道双向绑定的双向,分别指的就是View和data了,如图:

android checkedButton双向绑定 android mvvm双向数据绑定的原理_前端


至于是如何实现的?如上图可知,由Data驱动View时,调用的是ES5中的Object.defineProperty接口,而View驱动Data则是通过绑定input事件实现,下面我们会谈一谈Object.defineProperty的用法。

四、双向绑定的实现方法

首先说明,Object.defineProperty是ES5的用法,在ES6中已经出现了reflect.defineProperty,两者之间是存在一些区别的,前者直接返回的是新对象,后者返回的是布尔判断,想查看更多的资料请看这里:MDN相关介绍

那么下面我们只会对Object.defineProperty做介绍,因为两者的运作原理差别不大。先上代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
  </style>
</head>

<body>
  <div class="partone"><input type="text" name="" id="input"></div>
  <div class="parttwo"><input type="text" id="inputtwo"></div>
  <div>
    <P id="output"></P>
  </div>
  <script>
    let obj = {} //创建一个对象容器用于将多方数据绑定
    
    Object.defineProperty(obj, 'input', {
      get() {
        console.log('get it')
        return obj
      },
      set(word) {
        console.log('set it') //如果该对象属性触发了输入,则会打印set it
        document.getElementById('input').value = word;
        document.getElementById('inputtwo').value = word;
        document.getElementById('output').innerHTML = word;
      }
    })
    Object.defineProperty(obj,'inputtwo',{
      get(){return obj},
      set(newWord){
        document.getElementById('inputtwo').value = newWord;
        document.getElementById('output').innerHTML = newWord;
      }
    })
    //以上是data向View的数据绑定过程,
    //下面是View对data的数据绑定过程
    
    document.addEventListener('keyup', e => {
      obj.input = e.target.value
      obj.imputtwo = e.target.value
    })
  </script>
</body>
</html>

Object.defineProperty()方法主要用于定义新属性或修改原有的属性,这个函数接受三个参数,一个参数是obj,表示要定义属性的对象,一个参数是prop,是要定义或者更改的属性名字,另外是descriptor,描述符,来定义属性的具体描述。如:

Object.defineProperty(obj, prop, descriptor)

其中,descriptor既可以直接定义属性的value及其特性如:writable(是否可写)、enumerable(是否可枚举)等,也可以直接通过getset方法去操作对象的属性。其中,Vue就是用了后者来进行数据双向绑定(data=>view)。

通过将dom的值通过属性方式写入一个对象obj的input属性中,然后input属性又定义了getset方法,一旦该属性被修改,则触发set方法,那么我们就可以在set方法中修改另一个dom的值,这样的相当于借助obj对象进行了数据的监听。

而反向绑定就简单多了,在input框上绑定keyup或其他事件,将值付给相应的data或关联的dom即可。

五、后记

以上说的只是简单的DOM双向绑定,当然Vue是没有这么简单的,Vue本身的采取了发布-订阅的设计模式,因为全局Dom的遍历非常损耗性能,因此在Vue的数据绑定流程中,增加了Watcher、Observer模块来进行监听和分发Dom变动,如官方文档的图示:

android checkedButton双向绑定 android mvvm双向数据绑定的原理_Vue_02


关于这个如何实现,日后再探讨。