【Vue】复习笔记

73 阅读4分钟

什么是MVVM

MVVM是Model-View-ViewModel的缩写。MVVM是一种设计思想。Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。

QQ图片20210809194137.png

从图中可以看出,在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和ViewModel 之间的交互是双向的, View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。

ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

MVVM与MVC的区别

mvc和mvvm其实区别并不大。都是一种设计思想。主要就是mvc中Controller演变成mvvm中的viewModel。mvvm主要解决了mvc中大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验的问题。

MVC模型: image.png

Vue 双向绑定原理

image.png

简单来说:

mvvm 双向绑定:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty() 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤:

1)需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter,这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

2)compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

3)Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:在自身实例化时往属性订阅器(dep)里面添加自己,并且自身必须有一个 update() 方法待属性性变动dep.notice() 通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。

4)MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过Observer来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。

简单代码实现

  function myVue(options) {
    this._init(options);
  }
  // 初始化构造函数
  myVue.prototype._init = function (options) {
    this.$options = options;  // options包含的值,el,data,methods
    this.$el = document.querySelector(options.el);  // el是挂载的元素id名, this.$el是id为xx的Element元素
    this.$data = options.data;  // 数据
    this.$methods = options.methods;  // 方法
    
    this._binding = {};   // 保存model和view的映射关系,也就是定义的Watch的实例。当moudle变化时,视图view也变化
    this._obverse(this.$data);
    this._complie(this.$el);
  }
  
  // 实现_obverse函数,对data进行处理,重写data的set和get函数
  myVue.prototype._obverse = function (obj) { // obj=data
    var _this = this;
    Object.keys(obj).forEach(function (key) {
      // 是否拥有这个属性
      if (obj.hasOwnProperty(key)) {
        _this._binding[key] = {    // 按照前面的数据,_binding = {number: _directives: []}
          _directives: []
        }
        var value = obj[key];
        // 如果值还是对象,则遍历处理
        if (typeof value === 'object') {
          _this._obverse(value);
        }
        var binding = _this._binding[key];
        
        // 修改data,重写data的set和get函数
        Object.defineProperty(_this.$data, key, {
          enumerable: true,
          configurable: true,
          get: function () {
            console.log('获取${value}');
            return value;
          },
          set: function (newVal) {
            console.log('更新${value}');
            if (value !== newVal) {
              value = newVal;
              // 当number改变时,触发_binding[number]._directives 中的绑定的Watcher类的更新
              binding._directives.forEach(function (item) {
                item.update();
              })
            }
          }
        })
      }
    })
  }

  // watcher,用来绑定更新函数,实现对DOM元素的更新,(订阅者)
  function Watcher(name, el, vm, exp, attr) {
    this.name = name;   // 指令名称,例如文本节点,该值设为'text''
    this.el = el;      //指令对应的DOM 元素
    this.vm = vm;     // 指令所属myVue实例
    this.exp = exp;    //指令对应的值,'number'
    this.attr = attr;   // 绑定的属性值,例如:'innerHTML''
    this.update();
  }
  
  // Wather自己的updata()方法
  Watcher.prototype.update = function () {
    // 比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新
    this.el[this.attr] = this.vm.$data[this.exp];
  }

  // root为el挂载的id为xx的元素
  myVue.prototype._complie = function (root) {
    var _this = this;
    var nodes = root.children;
    for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      if (node.children.length) {
        this._complie(node);     // 对所有元素进行遍历,并进行处理
      }
      // 如果有v-click属性,比如:我们监听它的onclick事件,触发increment事件,即number++
      if (node.hasAttribute('v-click')) {
        node.onclick = (function () {
          var attrVal = nodes[i].getAttribute('v-click');   // bind是使data的作用域与method函数的作用域保持一致
          return _this.$methods[attrVal].bind(_this.$data)
        })();
      }
      if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
        node.addEventListener('input', (function (key) {
          var attrVal = node.getAttribute('v-model');  // number
          // _this._binding['number']._directives=[一个Watch实例]
          // 其中Watch.prototype.update=function (){
          // node['vaule']=_this.$data['number']; 这就将node的值保持与number一致
          // }
          _this._binding[attrVal]._directives.push(new Watcher('input', node, _this, attrVal, 'value'))
          return function () {
            // 使number 的值与 node的value保持一致,已经实现了双向绑定
            _this.$data[attrVal] = nodes[key].value;
          }
        })(i));
      }
      // 如果有v-bind属性,我们只要使node的值及时更新为data中number的值即可
      if (node.hasAttribute('v-bind')) {
        var attrVal = node.getAttribute('v-bind');
        _this._binding[attrVal]._directives.push(new Watcher('text', node, _this, attrVal, 'innerHTML'))
      }
    }
  }

Vue 的响应式系统

image.png

  • 主要步骤 1)任何一个 Vue Component 都有一个与之对应的 Watcher 实例
    2)Vue 的 data 上的属性会被添加 getter 和 setter 属性
    3)当 Vue Component render 函数被执行的时候, data被读取, getter 方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有 data。(这一过程被称为依赖收集)
    4)data 被改动时(主要是用户操作), 即被写, setter 方法会被调用, 此时 Vue 会去通知所有依赖于此data 的组件去调用他们的 render 函数进行更新

总结

该文章仅为自己学习的笔记,没有其他目的。