vue 响应式原理解读


前言

最近在学习vue源码,在网上看到很多大神的博客,看起来感觉还是很吃力的,所以自己也记录一下加深理解,俗话说:“好记性,不如烂笔头”。


整体过程

vue实例化对象的具体过程:

  1. 新创建一个实例后,Vue调用compile将el转换成vnode。
  2. 调用initState, 创建props, data的钩子以及其对象成员的Observer(添加getter和setter)。
  3. 执行mount挂载操作,在挂载时建立一个直接对应render的Watcher,并且编译模板生成render函数,执行vm._update来更新DOM。
  4. 每当有数据改变,都将通知相应的Watcher执行回调函数,更新视图
    • 当给这个对象的某个属性赋值时,就会触发set方法 。
    • set函数调用,触发Dep的notify()向对应的Watcher通知变化。
    • Watcher调用update方法。

在这里插入图片描述

在这个过程中具体讲解:

  • Obsever是给数据添加依赖的Dep.
  • Dep是data每个对象包括子对象都拥有一个该对象, 当所绑定的数据有变更时, 通过dep.notify()通知Watcher。
  • Compile是HTML指令解析器,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
  • Watcher是连接Observer和Compile的桥梁,Compile解析指令时会创建一个对应的Watcher并绑定update方法 , 添加到Dep对象上。

Object.defineProperty

如果不了解这个属性的话可以查看链接:添加链接描述
在vue2.0中大家用该都了解Vue的响应式原理是通过Object.defineprototype来实现的,被Object.defineprototype绑定过得对象,会变成【响应式】化,也就是改变这个对象,会触发get和set事件,进而触发某些视图的更新,举个例子:

let data = {
      msg: 'hello',
      count: 1222
    }
    let vm = {};
    Object.defineProperty(vm, 'msg', {
      enumerable: true,
      configurable: true,
      get() {
        console.log('get', data.msg);
      },
      set(newVlue) {
        console.log('get', newVlue);
        if (newVlue == data.msg) {
          return
        }
        data.msg = newVlue;
        document.querySelector('#container').textContent = data.msg
      }
    })
    vm.msg = 444;

Observer【响应式】

Observer就是用来管理上边Object.defineProperty的过程,我们用一下代码来描述:

class Observer {
      constructor() {
      //响应式绑定数据的方法
        observe(this.data)
      }
    }

    function observe(data) {
      const keys = Object.keys(data);
      for (let i = 0; i < keys.length; i++) {
      //将data中的每个属性都进行绑定。
        defineReactive(obj, keys[i])
      }
    }

    

Dep【依赖管理】

那么什么是依赖?

那Dep究竟是用来做什么的呢? 我们通过defineReactive方法将data中的数据进行响应式后,虽然可以监听到数据的变化了,那我们怎么处理通知视图就更新呢?

Dep就是帮我们收集【究竟要通知到哪里的】。比如下面的代码案例,我们发现,虽然data中有text和message属性,但是只有message被渲染到页面上,至于text无论怎么变化都影响不到视图的展示,因此我们仅仅对message进行收集即可,可以避免一些无用的工作。

那这个时候message的Dep就收集到了一个依赖,这个依赖就是用来管理data中message变化的。

<div>
    <p>{{message}}</p>
</div>

data:{
  msg:"hello world",
  count:1
}

当时用watch属性时,也就是开发者自定义的监听某个data中属性的变化,比如监听message的变化,message变化时就会通知watch这个钩子,让它去执行这个函数。

这个时候message的Dep就收集到了两个依赖,第二个依赖就是用来管理watch中message变化的。

 watch:{
   message:function(newValue,oldValue){
   console.log(newValue+','+oldValue)
 }

当开发者自定义computed计算属性时,如下messageT属性,是依赖message的变化的。因此message变化时我们也要通知到computed,让它去执行回调函数。

这个时候message的Dep就收集到了三个依赖,这个依赖就是用来管理computed中message变化的。

  computed:{
  	message(){
  	  return this.message+'....'
  	}
  }

图示如下:一个属性可能有多个依赖,每个响应式数据都有一个Dep来管理它的依赖。
在这里插入图片描述

如何收集依赖

我们如何知道data中的某个属性被使用了,答案就是Object.defineProperty,因为读取某个属性就会触发get方法。可以将代码进行如下改造:

 function defineReactive() {
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
          const value = getter ? getter.call(obj) : val
          if (Dep.target) {
            dep.depend()
            if (childOb) {
              childOb.dep.depend()
              if (Array.isArray(value)) {
                dependArray(value)
              }
            }
          }
          return value
        },
        set: function reactiveSetter(newVal) {
          const value = getter ? getter.call(obj) : val
          /* eslint-disable no-self-compare */
          if (newVal === value || (newVal !== newVal && value !== value)) {
            return
          }
          /* eslint-enable no-self-compare */
          if (process.env.NODE_ENV !== 'production' && customSetter) {
            customSetter()
          }
          // #7981: for accessor properties without setter
          if (getter && !setter) return
          if (setter) {
            setter.call(obj, newVal)
          } else {
            val = newVal
          }
          childOb = !shallow && observe(newVal)
          dep.notify()
        }
      })
    }

那所谓的依赖究竟是什么呢?上面的图中已经暴露了答案,就是Watcher。

源码分析:

// Dep是订阅者Watcher对应的数据依赖
	var Dep = function Dep () {
	  //每个Dep都有唯一的ID
	  this.id = uid++;
	  //subs用于存放依赖
	  this.subs = [];
	};
	
	//向subs数组添加依赖
	Dep.prototype.addSub = function addSub (sub) {
	  this.subs.push(sub);
	};
	//移除依赖
	Dep.prototype.removeSub = function removeSub (sub) {
	  remove(this.subs, sub);
	};
	//设置某个Watcher的依赖
	//这里添加了Dep.target是否存在的判断,目的是判断是不是Watcher的构造函数调用
	//也就是说判断他是Watcher的this.get调用的,而不是普通调用
	Dep.prototype.depend = function depend () {
	  if (Dep.target) {
	    Dep.target.addDep(this);
	  }
	};
	
	Dep.prototype.notify = function notify () {
	  var subs = this.subs.slice();
	  //通知所有绑定 Watcher。调用watcher的update()
	  for (var i = 0, l = subs.length; i &lt; l; i++) {
	    subs[i].update();
	  }
	};

Watcher【中介】

Watcher就是类似中介的角色,比如message就有三个中介,当message变化,就通知这三个中介,他们就去执行各自需要做的变化。

Watcher能够控制自己属于哪个,是data中的属性的还是watch,或者是computed,Watcher自己有统一的更新入口,只要你通知它,就会执行对应的更新方法。

因此我们可以推测出,Watcher必须要有的2个方法。一个就是通知变化,另一个就是被收集起来到Dep中去。

class Watcher {
    addDep() {
        // 我这个Watcher要被塞到Dep里去了~~
    },
    update() {
        // Dep通知我更新呢~~
    }, 
}

总结

回顾一下,Vue响应式原理的核心就是Observer、Dep、Watcher。

Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher。

在数据被改的时候,触发set方法,通过对应的所有依赖(Watcher),去执行更新。比如watch和computed就执行开发者自定义的回调方法。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值