vue源码理解(响应式原理+依赖收集)

191 阅读3分钟

首先理解两个点

1. 响应式原理
2. 依赖收集

响应式原理

vue 采 用 的 是 Object.defineProperty()对属性进行监听,故IE8以下浏览器不支持。

// 搞个vue 对象出来
new Vue({
    template:
    `<div>
        <span>{{data1}}</span>
    </div>`,
    data:{
        data1:'data1',
    }
})

修改data1的值

// 修改data1 操作
this.data1 = '我被修改了'

此时,vue中的data1属性并不能监听到被修改了.

但是,我们可以通过Object.defineProperty()监听data1的变化

  // obj:监听的对象  key:监听的属性 enumerable:是否可枚举 configurable:是否可配置
    
    let obj = new Vue().data,key = 'data1',val = obj[data1];
    
    Object.defineProperty(obj,key,val,{
        enumerable:true,
        configurable:true,
        // 获得监听属性
        get(){
        	return val;
        },
        // 修改设置属性值
        set(newVal){
            if(val === newVal) return
        }
    })

这样,每次修改data1的值,vue这个对象中的data1就能做出相应改变了

依赖收集

首先知道,为甚么有依赖收集这么个东西, 看下面的代码,承接上面

// 搞个vue 对象出来
new Vue({
    template:
    `<div>
        <span>{{data1}}</span>
    </div>`,
    data:{
        data1:'data1',
        data2:'data2'
    }
})

// 把刚刚的 Object.defineProperty封装到一个方法里面去
function defineReactive(obj,key,val){
  Object.defineProperty(obj,key,{
      enumerable:true,
      configurable:true,
      // 获得监听属性
      get(){
          return val;
      },
      // 修改设置属性值
      set(newVal){
          if(val === newVal) return
      }
  })
}

// vue中的data有data1,data2多个属性,所以我们需要遍历监听多个属性
 let obj = new Vue().data
  Object.keys(obj).forEach(key=>{
      this.defineReative(obj,key,obj[key])
  }) 

问题来了,这时,我们模板中只用到了data1,我们实现监听属性变化时,data2也被监听了,此时我们修改data2的值

this.data2 = '修改data2的值'

由于添加了监听,修改data2,模板即使没使用到data2,但也是会强制渲染一次,这样就浪费了资源

所以我们需要在模板渲染的时候,收集需要监听的属性,也叫做依赖收集

首先,我们需要一个订阅者Dep类,到时候被监听的这些属性叫做观察者 watcher

// 订阅者 存放观察者
class Dep{
    constructor(){
        this.subs = [] // watchers 全部放到这里面
    }

    // 添加 watcher 对象
    addSub(sub){
        this.subs.push(sub)
    }
    // 通知所有的watcher更新视图
    notify(){
        this.subs.forEach(watcher=>{
            // 订阅它的观察者全部都要更新
            watcher.update();
        })
    }
}

然后,我们再需要一个观察者类

// 观察者 
class Watcher{
    constructor(){
        /* 在 new 一个watcher的时候,将this赋值给Dep.target */
        Dep.target = this //  Dep.target 其实就是 Watcher 的实例 然后赋到全局中去
    }
    update(){
        console.log("视图更新啦~")
    }
}
Dep.target = null 

接着,我们开始监听属性了

// 进行依赖收集
function defineReative(obj,key,val){
    // 闭包存储一个订阅者 Dep
    let dep = new Dep()
    Object.defineProperty(obj,key,{
        enumerable:true,
        configurable:true,
        get(){
            if(Dep.target){
                 // 获取当前值,先添加依赖到订阅者中去,也就是收集依赖
                dep.addSub(Dep.target)//将当前的target添加到订阅者中去 获取这个值的时候添加依赖 当视图渲染的时候,会去查找相关依赖
            }
            return val; //返回该值
        },
        set(newVal){
            if(val === newVal) return
            dep.notify();//通知订阅者改变
        }
    })
}

我们又多个属性,需要进行遍历

function observer(obj){
    // 对每一个对象进行响应式
    Object.keys(obj).forEach(key=>{
        this.defineReative(obj,key,obj[key])
    })    
}

最后一步,new 一个 Vue 实例

class Vue{
    constructor(options){
        this._data = options.data
        observer(this._data) //把数据搞成响应式
        new Watcher();//此处可能有疑问,为啥添加依赖在监听后,因为在读取模板的数据时,才会触发getter属性,此时才收集依赖。对模板中存在的数据才添加依赖
        // 模拟渲染的过程
        console.log('模拟渲染的过程',this._data.text)
    }
}

执行 observer() 方法的时候,模板渲染回去执行Dep中的getter()方法收集依赖,修改data中的属性时候,会处罚setter()方法,相同值返回,不同值通知那些依赖于这个属性的那些function还有模板做出相应变化。