Vue学习笔记 -- Vue的响应式原理

Vue学习笔记 – Vue的响应式原理

今天通过王红元老师的教学视频和一些博主的技术分享,学习了Vue的响应式原理,话不多说直接进入正题

alt 图片

这是Vue官网中提供的响应式原理示意图,总结起来我们最常见到的响应式原理的答案就是:

使用Object.defineProperty将所有属性使用setter和getter进行劫持,在读取数据和写入数据时进行拦截处理

这是我自己总结的响应式流程:

tlNclD.png

然而这只是响应式原理中的一小部分,下面时是我用自己的话总结的响应式原理:

Vue的响应式原理是通过使用订阅者-发布者模式,配合Object.defineProperty将所有data中的属性进行写入与 获取的劫持,将每一个属性对应一个Dep对象,解析并存储某一属性所对应的vm实例,当属性值发生改变时,Watcher会通知该属性所关联的所有vm实例进行数据更新,者就是响应式的基本原理

一、什么是Object.defineProperty

Object.defineProperty(obj, prop, descriptor)是js对象操作的常用api之一,他对应的三个参数分别是: 需要被定义属性的对象, 要定义或修改的属性,数据描述符或存取描述符

configurable

​ 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除

enumerable

​ 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。

value

​ 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。

writable

​ 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。

get

​ 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。

set

​ 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。


二、什么是订阅者-发布者模式

订阅者-发布者模式简单来说就是:

当我们使用微信关注公众号后,公众号会定期想你推送新消息,在这个场景下我们就是订阅者而公众号就是发布者

我们先定义一个发布者的对象:

  class Dep {
    constructor() {
      //订阅者
      this.subscribs = []
    }

    addSub(sub) {
      this.subscribs.push(sub)
    }

    notify() {
        //对订阅者进行遍历,逐一通知修改
      this.subscribs.forEach(item => {
        item.update()
      })
    }
  }

接下来我们定义数个订阅者

  var sub1 = {
    update() {
      console.log('sub1发生改变')
    }
  }
  var sub2 = {
    update() {
      console.log('sub2发生改变')
    }
  }
  var sub3 = {
    update() {
      console.log('sub3发生改变')
    }
  }

定义之后我们将所有订阅者存入发布者对象中,然后进行发布

  var dep = new Dep();
  dep.add(sub1);
  dep.add(sub2);
  dep.add(sub3);

  dep.notify();

这样dep就会通知已经进行订阅的用户进行数据修改并更新视图,也就完成了发布者的基本功能

这里我们直接定义一个订阅者:

  class Watcher {
    constructor(node, name, vm) {
      this.node = node;
      this.name = name;
      this.vm = vm;
      Dep.target = this;
      this.update();
      Dep.target = null;
    }

    update() {
      this.node.nodeValue = this.vm[this.name] //get
    }
  }

当数据发生改变时,直接使用new Watcher(node, name, this.vm)对数据进行修改


三、响应式原理

在Vue中我们应该先定义一个Vue的对象

  class Vue {
    constructor(options) {
      this.$options = options;
      this.$data = options.data;
      this.$el = options.el;

      //  先将data挂载到响应式系统中
      new Observe(this.$data);
    }
  }

创建发布者对象

  class Dep {
    constructor() {
      //订阅者
      this.subscribs = []
    }

    addSub(sub) {
      this.subscribs.push(sub)
    }

    notify() {
        //对订阅者进行遍历,逐一通知修改
      this.subscribs.forEach(item => {
        item.update()
      })
    }
  }

定义一个Observe对象,对data中的属性进行劫持

 class Observe {
    constructor(data) {
      this.data = data;
      Object.keys(this.data).forEach(key => {
        this.defineReactive(this.data, key, data[key])
      })
    }
    defineReactive(data, key, val) {
      const dep = new Dep();
      Object.defineProperty(data, key, {
        configurable: true,
        enumerable: true,
        set(newValue) {
          if(newValue === val) {
            return
          }
          val = newValue;
            //通知修改属性
          dep.notify()
        },
        get() {
          if(Dep.target) {
            dep.addSub(Dep.target)
          }
          return val
        }
      })
    }
  }

将data中的所有属性使用proxy进行代理

  class Vue {
    constructor(options) {
      this.$options = options;
      this.$el = options.el;
      this.$data = options.data;

      //将数据挂载到响应式系统
      new Observe(this.$data)
        
	  //将data代理到this中
      Object.keys(this.$data).forEach(key => {
        this._proxy(key)
      })
    }

    _proxy(key) {
      Object.defineProperty(this, key, {
        configurable: true,
        enumerable: true,
        set(newValue) {
          this.$data[key] = newValue
        },
        get() {
          return this.$data[key]
        }
      })
    }
  }

配置订阅者对象

  class Watcher {
    constructor(node, name, vm) { //节点, 属性名, Vue实例
      this.node = node;
      this.name = name;
      this.vm = vm;
      Dep.target = this;
      this.update();
      Dep.target = null
    }

    update() {
      //修改视图数据
      this.node.nodeValue = this.vm[this.name]
    }
  }

配置正则处理规则

const reg = /\{\{(.*)\}\}/; //{{}}

配置视图解析对象

  class Compiler {
    constructor(el, vm) {
      this.el = document.querySelector(el);
      this.vm = vm;

      this.frag = this._createFragment();
      //视图解析创建虚拟节点时会将原节点删除,因此需要重新加入节点
      this.el.appendChild(this.frag)
    }
     //创建虚拟节点并对原节点进行解析处理
    _createFragment() {
      //创建虚拟dom根节点
      const frag = document.createDocumentFragment()

      let child;
      //循环搜索节点,解析视图
      while(child = this.el.firstChild) {
        this._compile(child);
        frag.appendChild(child)
      }
      return frag
    }
    //对节点进行解析,并且添加监听和视图修改操作
    _compile(node) {
      console.log(node);
      if(node.nodeType === 1){ //标签节点
        const attrs = node.attributes;
        if(attrs.hasOwnProperty('v-model')) {
          const name = attrs['v-model'].nodeValue;
          node.addEventListener('input', e => {
            this.vm[name] = e.target.value;
          })
        }
      }

      if(node.nodeType === 3) {
        console.log("node: " + node.nodeValue);
        console.log(reg.test(node.nodeValue));
        if (reg.test(node.nodeValue)) {
          const name = RegExp.$1.trim()
          console.log('name: ' + name);
          new Watcher(node, name, this.vm)
        }
      }
    }
  }

完善Vue对象的创建过程

 class Vue {
    constructor(options) {
      this.$options = options;
      this.$el = options.el;
      this.$data = options.data;

      //将数据挂载到响应式系统
      new Observe(this.$data);
	// 将data进行代理处理
      Object.keys(this.$data).forEach(key => {
        this._proxy(key)
      });

    //  将el进行解析并添加订阅者
      new Compiler(this.$el, this)
    }

总结

总结起来响应式的过程大致如下:

  • 定义Vue对象,将Vue对象中的data属性值进行响应式挂载, Observe
  • 在Observe中对每一个属性进行劫持处理,添加setter和getter方法,,在watcher的 update()方法被调用时,会自动执行getter方法,此时将这个Watcher对象(即订阅者)添加到发布者中
  • 每当数据变化时就会触发该属性对应的Dep对象中的notify()方法,通知所有成员进行数据更新
  • 订阅者此时触发update()方法,改变了Watcher中对应node的nodeValue,也就是视图显示的数据
  • 就这样形成了数据的双向绑定,即视图值修改,数据值即修改,反之亦然

写在后面

所有文章收发与我的个人博客幻尘の屋

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值