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文件操作汇总实例详解

    当你需要为你的 Node.js 应用程序创建、读取或更新文件时,你需要了解 Node.js 文件系统模块的 API。Node.js 提供了许多文件操作方法,例如创建、打开、读取、写入、删除和关闭文件等操作。本文将详细介绍 Node.js 文件操作常用的 API 及其使用方法。 核心模块 Node.js 中提供了 fs 核心模块,我们可以通过 require(…

    node js 2023年6月8日
    00
  • vue-element-admin开发教程(v4.0.0之前)

    《Vue Element Admin》是一个开源的基于Vue和Element的后台管理系统解决方案,它通过提供数据表格、表单、图表等组件,大大提高了前端开发效率。本文将提供vue-element-admin开发教程(v4.0.0之前)的完整攻略。 环境搭建 在开始使用vue-element-admin开发前,需要环境的搭建。建议使用最新版本的Node.js和…

    node js 2023年6月8日
    00
  • node.js实现的装饰者模式示例

    下面是如何实现“node.js装饰者模式示例”的攻略。 什么是装饰者模式 装饰者模式是一种结构设计模式,经常用于在不修改现有对象的情况下,向其添加操作。这种模式可帮助拆分逻辑,使其更加可重用。在装饰者模式中,新的功能是通过将其添加到源对象上而非继承方式来实现的。 装饰者模式的实现 下面是一个实现装饰者模式的示例: // 创建一个简单的对象 const som…

    node js 2023年6月8日
    00
  • node版本下报错build: `vue-cli-service build`问题及解决

    当使用vue-cli-service打包vue项目时,可能会遇到”node版本下报错build: vue-cli-service build问题”,这通常是由于node版本过低或过高导致的。下面是解决该问题的几个步骤。 1. 查看当前node和npm版本 首先,需要查看当前node和npm版本是否正确。可以通过以下命令进行查看: node -v npm -v…

    node js 2023年6月8日
    00
  • node.js中的console.time方法使用说明

    关于“node.js中的console.time方法使用说明”这个话题,我可以给你提供以下完整攻略。 1. console.time方法是什么? 在Node.js中,console.time方法用于测试代码块执行时间。这个方法可以计时开始和结束之间的时间间隔,并输出执行时间。在需要了解某个代码块或函数执行的性能时,这个方法会非常有用。 2. console.…

    node js 2023年6月8日
    00
  • 深入探讨javascript函数式编程

    深入探讨Javascript函数式编程 Javascript 函数式编程是一种将函数作为主要构建块的编程范式。与传统的命令式编程不同,函数式编程通过组合函数来完成任务,这使得代码更加简洁、模块化和易于测试。本篇文章将深入探讨Javascript函数式编程的一些关键概念以及如何在实践中应用它们。 函数式编程的核心概念 纯函数 Javascript 中的纯函数是…

    node js 2023年6月8日
    00
  • vue中使用sass及解决sass-loader版本过高导致的编译错误问题

    关于“vue中使用sass及解决sass-loader版本过高导致的编译错误问题”的攻略,我可以提供以下详细的步骤和示例说明: 步骤一: 安装scss-loader和node-sass 在Vue项目中使用Sass,需要安装两个依赖包:sass-loader和node-sass。可以使用以下命令进行安装: npm install sass-loader nod…

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

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

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