vue MVVM双向绑定实例详解(数据劫持+发布者-订阅者模式)

Vue MVVM双向绑定实例详解(数据劫持+发布者-订阅者模式)

一、MVVM模式

MVVM是Model-View-ViewModel的缩写。在前端开发中,MVVM是一种设计模式,它将数据(Model)、业务逻辑(ViewModel)和页面(View)分离开来。其中,ViewModel充当了连接View和Model的纽带,通过ViewModel将数据绑定到View中实现了数据的双向绑定。

二、双向绑定的实现

在Vue中实现双向绑定的方式,主要是通过数据劫持和发布者-订阅者模式的结合来实现的。

1. 数据劫持

在Vue中,通过Object.defineProperty()方法将数据属性转化为getter和setter,来实现数据劫持,当数据属性发生变化时,通过setter方法通知模板进行更新。

下面是一个数据劫持的示例。

var obj = {};
var value = '';
Object.defineProperty(obj, 'msg', {
  get() {
    console.log('get value');
    return value;
  },
  set(newValue) {
    console.log('set value');
    value = newValue;
  }
})
obj.msg = 'hello';
console.log(obj.msg);

运行结果:

set value
get value
hello

输出结果可以看到,当对obj.msg属性进行赋值时,会触发setter方法,然后再通过getter方法获取值,实现了对数据的劫持。

2. 发布者-订阅者模式

在Vue中,通过对属性的getter和setter进行封装,实现属性的订阅和发布,来实现对数据的双向绑定。

下面是一个发布者-订阅者模式的示例。

class Dep {
  constructor() {
    this.subs = [];
  }

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

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

class Watcher {
  constructor() {
    Dep.target = this;
  }

  update() {
    console.log('update view');
  }
}

Dep.target = null;

var dep = new Dep();
var watcher = new Watcher();
dep.addSub(watcher);
dep.notify();

运行结果:

update view

输出结果可以看到,在订阅者Watcher中,通过将Dep.target属性标记为当前Watcher,然后在属性的getter方法中,向依赖的订阅者列表中添加当前Watcher,然后在属性的setter方法中,通过Dep.notify()方法通知依赖该数据的所有订阅者进行更新。

三、数据双向绑定的实现

数据双向绑定实现方式有很多,下面以Vue的实现为例,进行说明。

1. 实现双向绑定的构造函数

首先,我们需要实现一个构造函数,用于观察并监听ViewModel中的数据,当数据发生改变时,触发回调函数。

function Observer(data) {
  this.data = data;
  this.walk(data);
}

Observer.prototype = {
  walk(data) {
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key]);
    })
  },

  defineReactive(data, key, val) {
    let dep = new Dep();
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get() {
        if (Dep.target) {
          dep.addSub(Dep.target);
        }
        return val;
      },
      set(newValue) {
        if (val === newValue) {
          return;
        }
        val = newValue;
        dep.notify();
      }
    })
  }
}

解析:

Observer构造函数接收一个参数data,data表示要建立双向绑定的ViewModel对象。

walk()方法遍历data的所有属性,并调用defineReactive()方法。

defineReactive()方法对data对象的每个属性进行处理。首先创建一个Dep实例,用于存储该属性所依赖的订阅者。然后使用Object.defineProperty()方法将data[key]转换为getter/setter方法,当属性被读取时,如果有订阅者依赖该属性,就将该订阅者添加到Dep实例中,当属性被修改时,通知Dep实例告知所有订阅者进行更新。

2. 实现模板编译

为了一个页面的数据与ViewModel建立双向绑定,需要实现模板编译的功能,将模板中的表达式与ViewModel中的数据绑定起来,这里我们可以使用正则表达式进行解析。

function compile(el) {
  var element = document.querySelector(el);

  var childNodes = element.childNodes;

  Array.prototype.slice.call(childNodes).forEach(node => {
    if (node.nodeType === 3) {
      let text = node.textContent;
      let reg = /\{\{(.*)\}\}/;
      if (reg.test(text)) {
        let matchValue = RegExp.$1.trim();
        new Watcher(vm, matchValue, function(newVal) {
          node.textContent = text.replace(reg, newVal);
        })
      }
    } else if (node.nodeType === 1) {
      let nodeAttrs = node.attributes;
      Array.prototype.slice.call(nodeAttrs).forEach(attr => {
        let attrName = attr.name;
        let attrValue = attr.value;
        if (attrName.indexOf('v-') === 0) {
          let dir = attrName.substring(2);
          if (dir === 'model') {
            new Watcher(vm, attrValue, function(newVal) {
              node.value = newVal;
            })
            node.addEventListener('input', e => {
              let newValue = e.target.value;
              if (attrValue.indexOf('.') > -1) {
                let keys = attrValue.split('.');
                let data = vm;
                keys.forEach((key, index) => {
                  if (index === keys.length - 1) {
                    data[key] = newValue;
                  } else {
                    data = data[key];
                  }
                })
              } else {
                vm[attrValue] = newValue;
              }
            })
          }
        }
      })
    }

    if (node.childNodes && node.childNodes.length) {
      compile(node);
    }
  })
}

解析:

compile()函数接收一个参数el,el表示需要编译的模板元素。首先获取该元素下的所有子节点,通过Array.prototype.slice.call()将NodeList转换为数组。

遍历每个子节点,如果是文本节点(nodeType=3),则使用正则表达式将模板中的表达式解析出来,并创建一个Watcher,并在回调函数中更新视图。如果是元素节点(nodeType=1),则遍历节点的所有属性,并判断属性名是否以'v-'开头,如果是,则进行特殊处理,将该属性值与ViewModel中的数据建立双向绑定,并添加事件监听器,当该元素的value值修改时,将数据同步更新到ViewModel中。

最后,如果该节点还包含子节点,则递归遍历子节点,进行编译。

3. 实现Watcher

Watcher是连接ViewModel和View的桥梁,当ViewModel中的数据发生变化时,Watcher会触发回调函数来更新视图,同时将新的值传递到回调函数中。

function Watcher(vm, exp, cb) {
  this.vm = vm;
  this.exp = exp;
  this.cb = cb;
  this.value = this.get();
}

Watcher.prototype = {
  update() {
    let newVal = this.get();
    let oldVal = this.value;
    if (newVal !== oldVal) {
      this.value = newVal;
      this.cb.call(vm, newVal, oldVal);
    }
  },

  get() {
    Dep.target = this;
    let value = this.getVMValue(this.vm, this.exp);
    Dep.target = null;
    return value;
  },

  getVMValue(vm, exp) {
    let data = vm;
    exp.split('.').forEach(key => {
      data = data[key];
    })
    return data;
  }
}

解析:

Watcher构造函数接收三个参数,vm表示ViewModel对象,exp表示ViewModel中的数据对象的属性,cb为回调函数,在数据发生变化时会触发。

在Watcher中,通过get()方法获取ViewModel中的数据对象的属性的值,并将Watcher对象存储到Dep实例中的subs数组中。

当数据发生变化时,触发Dep的set()方法,通知Dep实例中的所有订阅者进行更新,并调用Watcher的update()方法来更新视图,同时将新的值传递到回调函数cb中。

四、示例说明

下面给出两个示例,分别演示了基本的数据绑定和模板编译如何工作。

示例1:基本双向绑定

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>双向绑定实例1</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model="msg">
    <p>{{ msg }}</p>
  </div>

  <script src="observer.js"></script>
  <script src="watcher.js"></script>
  <script src="compile.js"></script>
  <script>
    let vm = new Observer({
      msg: 'hello world'
    })
    compile('#app', vm)
  </script>
</body>
</html>

当用户在input框中输入文字时,可以看到下面的p标签实时更新为input框中的文字。

示例2:模板编译

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>双向绑定实例2</title>
</head>
<body>
  <div id="app">
    <ul>
      <li v-for="item in list">
        <input type="checkbox" v-model="item.checked">{{ item.title }}
      </li>
    </ul>
    <button v-on:click="addItem">新增</button>
  </div>

  <script src="observer.js"></script>
  <script src="watcher.js"></script>
  <script src="compile.js"></script>
  <script>
    let vm = new Observer({
      list: [
        {title: '任务1', checked: true},
        {title: '任务2', checked: false},
        {title: '任务3', checked: false}
      ]
    })
    compile('#app', vm)
  </script>
</body>
</html>

该示例中,展示了如何在模板中使用v-for、v-model和v-on等指令,并通过模板编译将其和ViewModel中的数据建立起有效的双向绑定关系。

用户可以尝试增加、选择或取消选择其中一个任务,并观察页面的变化。

五、总结

本文主要介绍了Vue中实现数据双向绑定的原理以及基本实现方法,通过对数据劫持和发布者-订阅者模式的结合,实现了数据的双向绑定。同时,我们还实现了模板编译的功能,将ViewModel中的数据与模板建立双向绑定,在数据发生变化时,实现了及时地更新视图。这就实现了Vue的MVVM模式,使得前端开发更加容易维护和扩展。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:vue MVVM双向绑定实例详解(数据劫持+发布者-订阅者模式) - Python技术站

(0)
上一篇 2023年6月8日
下一篇 2023年6月8日

相关文章

  • Node.js学习入门

    Node.js学习入门 Node.js 是一个开源的跨平台 JavaScript 运行时环境,它可以在浏览器之外,直接在服务器端运行 JavaScript 代码。通过 Node.js,我们可以使用 JavaScript 去构建服务器端应用程序、命令行工具、桌面应用等。 下面是学习 Node.js 的完整攻略: 1. 安装 Node.js 首先需要安装 Nod…

    node js 2023年6月8日
    00
  • node.js中优雅的使用Socket.IO模块的方法

    首先,为了优雅地使用Socket.IO模块,我们需要深入了解它的原理和使用方法。 Socket.IO模块简介 Socket.IO是一个实时通信库,它使得实时的双向通信变得轻而易举。它是建立在WebSockets之上的,但也可以在不支持WebSockets的浏览器中工作。 在Node.js中,通过安装Socket.IO模块,在服务端和客户端之间建立连接,可以实…

    node js 2023年6月8日
    00
  • JavaScript使用正则表达式获取全部分组内容的方法示例

    首先,我们需要先了解什么是正则表达式,正则表达式是一种用来匹配字符串文本的特殊模式,利用这种模式,我们可以通过匹配和搜索来进行字符串处理。 下面是使用正则表达式获取全部分组内容的方法示例,具体步骤如下: 1. 创建正则表达式对象 首先,我们需要创建一个正则表达式对象,用于匹配和搜索字符串。 let reg = /正则表达式/; 上述代码中的正则表达式可以根据…

    node js 2023年6月8日
    00
  • 详解如何使用nvm管理Node.js多版本

    当我们在使用 Node.js 进行开发时,有时候需要用到多个不同版本的 Node.js。这时候,我们可以使用 nvm 来方便地管理多个版本的 Node.js。 下面是使用 nvm 管理 Node.js 多个版本的完整攻略: 安装 nvm 首先,我们需要安装 nvm,可以在 https://github.com/nvm-sh/nvm 上找到最新的安装方法。在终…

    node js 2023年6月8日
    00
  • 单线程JavaScript实现异步过程详解

    单线程JavaScript实现异步过程就是通过事件循环机制实现的。该机制通过回调函数的方式,将需要异步执行的代码推入事件队列,等待主线程空闲时再执行。 具体实现过程如下: 首先,我们需要定义一个函数,它能够接受一个回调函数作为参数,这个回调函数会在异步操作结束后被执行。 function loadData(callback) { // 这里是异步操作的代码,…

    node js 2023年6月8日
    00
  • 浅析node.js中close事件

    下面我将为你详细讲解“浅析node.js中close事件”。 什么是close事件? 在Node.js中,close事件是一个简单的事件监听器,它是在流(stream)或者网络套接字(socket)的连接关闭时触发的。例如:当客户端从服务端断开连接时,服务端会收到一个close事件。 close事件的原理 close事件的原理是,当一个连接被关闭时,Node…

    node js 2023年6月8日
    00
  • nodejs教程 安装express及配置app.js文件的详细步骤

    下面是关于“nodejs教程 安装express及配置app.js文件的详细步骤”的完整攻略。 1. 安装express 首先,你需要在本地机器上安装Node.js和npm。接下来,打开命令行或终端,输入以下命令进行全局安装express: npm install -g express 2. 构建应用程序骨架 安装完express后,你可以通过以下命令来构建…

    node js 2023年6月8日
    00
  • 使用imba.io框架得到比 vue 快50倍的性能基准

    使用imba.io框架得到比vue快50倍的性能基准是基于一个开源项目的比较得出的结论。下面是如何进行该测试的攻略: 1. 准备工作 首先,需要确保计算机上已经安装了Node.js和NPM。然后,在命令行中运行以下命令来安装依赖项: npm install -g vue-cli npm install -g imba 这将安装Vue和Imba的命令行工具。 …

    node js 2023年6月8日
    00
合作推广
合作推广
分享本页
返回顶部