搞懂:MVVM模式和Vue中的MVVM模式

MVVM

  • MVVM : model - view - viewmodel的缩写,说都能直接说出来 model:模型,view:视图,view-Model:视图模型

    • V:视图,即浏览器最前端渲染的页面
    • M:模型,数据模型,就是后端页面渲染依赖的数据
    • VM:稍后再说,因为暂时还不知道怎么工作,什么场景,直接解释有点没用
  • 那就先说说前端场景:
    • 如果数据改变,想要前端页面做出相应的改变,有几种方法:

      • 1.使用原生js
            var dom = document.getElementById('xxx')
        dom.value = xxx; // 直接修改值
        dom.innerHtml = xxx; //改变开始 和 结束标签中的html
      • 2.使用jquery
              $('#name').text('Homer').css('color', 'red');
    • 上面可以看出来,jquery确实在dom操作方面简化了很多,链式调用和更加人性化的api在没有mvvm模型出世之前,使用率极高
    • 但是,也可以看出来,数据和页面视图之间存在断层,数据影响视图,甚至是视图中的节点改变数据,这都是极其频繁的页面操作,虽然一再简化这个面向过程的逻辑操作,但是还是避免不了手动修改的弊端。
    • 有没有一种更好的方式,可以实现这种视图(view)和模型(model)之间的关系
  • VM:
    • 再看看现在VUE框架中怎么做到这种视图和模型的联动

          //html
      <input v-model = 'val' placeholder = 'edit here'> //script
      export defaults{
      data:function(){
      return {
      val:''
      }
      }
      }

      很简单,很常用的v-model指令,那么在input值修改的时候,data中的val变量值也会改变,直接在js中改变val的值的时候,input中的value也会改变??我们做了什么,我们怎么将数据和视图联系起来的?自动会关联这两个东西

    • 可能,这就是VM吧~

      • vm:viewModel视图模型,就是将数据model和视图view关联了起来,负责将model数据同步到view显示,也同时把view修改的数据同步到model,我们无需关心中间的逻辑,开发者更多的是直接操作数据,至于更新视图或者会写model,都是我们写好的视图模型(viewModel)帮我们处理
    • 概念:视图模型层,是一个抽象化的逻辑模型,连接视图(view)和模型(model),负责:数据到视图的显示,视图到数据的回写

VUE中的MVVM(双向绑定)

vue框架中双向绑定是最常用的一个实用功能。实现的方式也网上很多文章,vue2.x是Object.DefineProperty,vue3.x是Es6语法的proxy代理语法

  • 具体是怎么做到的

    ps:暂时先看vue2.x

    • Object.setProperty(),设置和修改Javascript中对象属性值,定义对象属性的get和set方法,可以在对象获取值和修改值时触发回调函数,实现数据劫持,并且拿到新的改变后的值
    • 需要根据初始化对象值和修改之后拿到改变后的值,对已绑定模板节点进行数据更新。
  • 第一步:监听对象所有属性值变化(Observer)

    var data = {test: '1'};
    observe(data);
    data.test = '2'; // changed 1 --> 2 function observe(data) {
    if (!data || typeof data !== 'object') {
    return;
    }
    // 取出所有属性遍历
    Object.keys(data).forEach(function(key) {
    defineReactive(data, key, data[key]);
    });
    }; function defineReactive(data, key, val) {
    observe(val); // 监听子属性
    Object.defineProperty(data, key, {
    enumerable: true, // 可枚举
    configurable: false, // 防止重复定义或者冲突
    get: function() {
    return val;
    },
    set: function(newVal) {
    console.log('changed ', val, ' --> ', newVal);
    val = newVal;
    }
    });
    }
    • 第二步:怎么做到对有绑定关系的节点进行更新和初始化值呢?如果一个数据对象绑定了多个dom节点,怎么统一通知所有dom节点呢,这就需要用到发布者-订阅者模式

      • 这里是Observer作为一个察觉数据变化的发布者,发现数据变化时,触发所有订阅者(Watcher)的更新update事件,首先要拥有一个能存储所有订阅者队列,并且能通知所有订阅者的中间件(消息订阅器Dep

            function Dep () {
        // 订阅者数组
        this.subs = [];
        }
        Dep.prototype = {
        addSub: function(sub) {
        this.subs.push(sub);
        },
        notify: function() {
        //通知所有订阅者
        this.subs.forEach(function(sub) {
        sub.update();
        });
        }
        };
      • 并且在观察者Observer中修改当Object对象属性发生变化时,触发Dep中的notify事件,所有订阅者可以接收到这个改变

            function defineReactive(data, key, val) {
        observe(val);
        var dep = new Dep();
        Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get: function() {
        return val;
        },
        set: function(newVal) {
        //修改的在这里
        if(newVal === val){
        return
        }
        // 如果新值不等于旧值发生变化,触发所有订阅中间件的notice方法,所有订阅者发生变化
        val = newVal
        console.log('changed ', val, ' --> ', newVal);
        dep.notify(); }
        });
        }
      • 但是有没有发现还有一个问题,Dep订阅中间件中的订阅者数组一直是空的,什么时候把订阅者添加进来我们的订阅中间件中间,哪些订阅者需要添加到我们的中间件数组中

        • 1.我们希望的是订阅者Watcher在实例化的时候自动添加到Dep中
        • 2.有且仅有在第一次实例化的时候添加进去,不允许重复添加
        • 3.由于Dep在发布者数据变化时会触发所有订阅则的update事件,所以Watcher实例(订阅者)能够触发update事件,并进行相关操作
        • 怎么能让Watcher在实例化的时候自动添加到Dep订阅者数组中
              function Watcher(vm, exp, cb) {
          this.cb = cb; // 构造函数中执行,只有可能在实例化的时候执行一遍
          this.vm = vm;
          this.exp = exp;
          this.value = this.get(); // 将自己添加到订阅器的操作---HACK开始
          // 在构造函数中调用了一个get方法
          } Watcher.prototype = {
          update: function() {
          this.run();
          },
          run: function() {
          var value = this.vm.data[this.exp];
          var oldVal = this.value;
          if (value !== oldVal) {
          this.value = value;
          this.cb.call(this.vm, value, oldVal);
          }
          },
          get: function() {
          //get方法中首先缓存了自己本身到target属性
          Dep.target = this;
          // 获取了一下Observer中的值,相当于调用了一下get方法
          var value = this.vm.data[this.exp]
          // get 完成之后清除了自己的target属性???
          Dep.target = null;
          return value;
          }
          //很明显,get方法只在实例化的时候调用了,满足了只有在Watcher实例化第一次的时候调用
          //update方法接收了发布者的notice 发布消息,并且执行回调函数,这里的回调函数还是通过外部定义(简化版)
          //但是,好像在get方法中有一个很神奇的操作,缓存自己,然后调用Observer的getter,然后清除自己
          //这里其实是一步巧妙地操作把自己添加到Dep订阅者数组中,当然Observer 的getter方法也要变化如下
          }; //Observer.js
          function defineReactive(data, key, val) {
          observe(val);
          var dep = new Dep();
          Object.defineProperty(data, key, {
          enumerable: true,
          configurable: true,
          get: function() {
          if (Dep.target) {.
          dep.addSub(Dep.target); // 关键的在这里,当第一次实例化时,调用Watcher的get方法,get方法内部会获取Object的属性,会触发这个get方法,在这里将Watcher 添加到Dep的订阅者数组中
          }
          return val;
          },
          set: function(newVal) {
          if (val === newVal) {
          return;
          }
          val = newVal;
          dep.notify();
          }
          });
          }
          Dep.target = null;
      • 看似好像发布者订阅者模式实现了,数据劫持也实现了,在数据改变的时候,触发Object.setProperty中定义的set函数,set函数触发Dep订阅者中间件的notice方法,触发所有订阅者的update方法,并且订阅者在实例化的时候就加入到了Dep订阅者的数组内部,让我们来看看怎么用

        • html部分,
              <body>
          <!-- 这里其实还是会直接显示{{name}} -->
          <h1 id="name">{{name}}</h1>
          </body>
        • 封装一个方法(类)将Observer,Watcher,关联起来
              function SelfVue (data, el, exp) {
          //初始化data属性
          this.data = data;
          //将其设置为观察者
          observe(data);
          //手动设置初始值
          el.innerHTML = this.data[exp];
          //初始化watcher,添加到订阅者数组中,并且回调函数是重新渲染页面,触发update方法时通过回调函数重写html节点
          new Watcher(this, exp, function (value) {
          el.innerHTML = value;
          });
          return this;
          }
        • 使用:
              var ele = document.querySelector('#name');
          var selfVue = new SelfVue({
          name: 'hello world'
          }, ele, 'name'); //设定延时函数,直接修改数据值,看能否绑定到页面视图节点
          window.setTimeout(function () {
          console.log('name值改变了');
          selfVue.data.name = 'canfoo';
          }, 2000);
      • 到上面为止:基本实现了数据(model)到视图(view)层的单向数据绑定,只有v-model是使用到了双向绑定,很多vue的数据绑定的理解,和难点也就在上面的单向绑定

      • 那么:model->view单向绑定似乎已经成功了,那么view -> model呢?

        • 这个在于如果视图层的value改变了,如何修改已经绑定的model层的对象属性呢?
        • 这个指令在vue中是:v-model,指令部分会在之后的学习中继续讲解
        • 但是,视图view节点在value属性改变时,一般会触发change或者input事件,而且也一般是一些可输入视图节点,直接将事件写在change事件或者input事件里面,并且修改Object里面的值
              var dom = document.getElementById('xx')
          dom.addEventListener('input',function(e){
          selfVue.data.xxx = e.target.value
          })
        • 具体input事件和v-model指令这种用法怎么联系起来,之后会慢慢学习

总结:

  • MVVM其实是现在很多前端框架的实现基础,除了vue 的数据劫持和观察订阅模式,其他框架的例如脏数据检测,或者直接使用观察者订阅者模式,都是一些很巧妙的实现方式,使程序员能够更多的关注数据层面或者逻辑层面的代码,而不需要手动去做更新两者之间关系的繁琐操作
  • vue的数据劫持和发布者订阅者模式理解起来一开始看起来理解有点费劲,大概了解如何做的,学习其方法,当然手写完全流程的写出来,我也很难
  • 学习的路上,大家一起加油,多问一个为什么

非常感谢:下面的文章给了我很多的帮助,感谢各位前行者的辛苦付出,可以点击查阅更多信息

廖雪峰老师的MVVM讲解

由浅入深讲述MVVM

很详细的讲解vue的双向绑定

搞懂:MVVM模型以及VUE中的数据绑定数据劫持发布订阅模式的更多相关文章

  1. vue中如何实现数据的双向绑定

    vue中如何实现数据的双向绑定 实现视图变化数据跟着变:分两步,上面get中的为第二步(即再次读取的时候会调用get方法得到之前设置的值,以此来实现动态改变) 由于直接写obj.name = this ...

  2. 在Vue中由后台数据循环生成多选框CheckBox时的注意事项

    多选框是一种非常常见的功能,有时候我们会根据后台返回的数据进行多选框渲染,之前做项目时遇到循环生成多选框时,v-model绑定的值会随着选中与取消改变,但页面却不会变化 的情况,后来测试了一下,发现多 ...

  3. Vue中双向数据绑定是如何实现的?

    vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变:核心:关于VUE双向数据绑定,其核心是 Ob ...

  4. vue双向绑定(数据劫持+发布者-订阅者模式)

    参考文献:https://www.cnblogs.com/libin-1/p/6893712.html 实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据. 关键点在于data如何更新v ...

  5. Vue框架核心之数据劫持

    本文来自网易云社区. 前瞻 当前前端界空前繁荣,各种框架横空出世,包括各类mvvm框架横行霸道,比如Angular.Regular.Vue.React等等,它们最大的优点就是可以实现数据绑定,再也不需 ...

  6. JS中的发布订阅模式

    一. 你是如何理解发布订阅模式的 JS中的设计模式: 单例模式:处理业务逻辑 构造原型模式:封装类库,组件,框架,插件等 类库:jQuery 只是提供了一些常用的方法,可以应用到任何的项目中,不具备业 ...

  7. javascript中的设计模式之发布-订阅模式

    一.定义 又叫观察者模式,他定义对象间的依照那个一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将的到通知.在javascript中,我们一般用时间模型来替代传统的发布-订阅模式 二 ...

  8. [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

    有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...

  9. 从发布订阅模式入手读懂Node.js的EventEmitter源码

    前面一篇文章setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop详细讲解了浏览器和Node.js的异步API及其底层原理Event Loop.本文会讲一下不 ...

随机推荐

  1. thinkphp5.0 模型的应用

    <?php namespace app\admin\controller; use app\common\controller\BaseController; use think\Db;//数据 ...

  2. 标准库hashlib模块

    hashlib模块用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512, MD5 算法(都是基于hash的算法, ...

  3. 重识TCP/IP协议族与HTTP基础

    不忘初心 砥砺前行, Tomorrow Is Another Day ! 本文概要: TCP/IP协议族的网络分层 TCP三次握手四次挥手 Http简介 报文结构 Http的请求方法及状态码 常用的H ...

  4. solr7.4创建core,导入MySQL数据,中文分词

    #solr版本:7.4.0 一.新建Core 进入安装目录下得server/solr/,创建一个文件夹,如:new_core 拷贝server/solr/configsets/_default/con ...

  5. javascript 控制台调试方法

    console在我们调试js程序的时候是一个非常有效的工具. 日志输出是我们最常用的功能: console.log(); console.info(); console.warn(); console ...

  6. Docker docker-compose 配置lnmp开发环境

    1.安装docker yum -y install dockersystemctl start dockersystemctl enable docker 安装docker-compose https ...

  7. 编译原理-第四章 语法分析-4.7 规范的LR分析

    规范的LR分析 一.规范LR(l)项 二.规范LR(l)项集族 1.构建项目集 2.例 三.规范LR(1)语法分析表 1.构造 2.例1 3.例2 四.LALR语法分析表 1.重要性 2.特点 3.构 ...

  8. 使用 kind 快速搭建 kubernetes 环境

    使用 kind 快速搭建 Kubernetes 环境 Intro kind(Kubernetes IN Docker) 是一个基于 docker 构建 Kubernetes 集群的工具,非常适合用来在 ...

  9. golang教程汇总

    A Tour of Go Go编程基础 Go 语言圣经 中文版

  10. sequel pro无法连接mysql服务器

    1. 添加用户 GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'your_passwd' WITH GRANT OPTION; FLU ...