vue源码之响应式原理

251 阅读5分钟

vue源码文件夹作用

文件作用
compiler把模板转换成render函数
coreVue核心库
platforms平台相关代码
serverSSR、服务端渲染
sfc.vue文件编译成js 文件
shared公共代码

vue版本一般分为运行时版本和完整版本
完整版本:多了一个编译器,编译器就是把template模板语法编译成js渲染函数(render)的代码
运行时:不包含编译器,创建vue实例,渲染并处理虚拟DOM

基于vue-cli创建的项目使用的都是runtime版本的vue,关于单文件组件(.vue)是在webpack打包的时候,会把文件编译成js文件,相关的template模板语法会被编译成render函数

vue源码模块化方式

UMD:通用的模块版本,支持多种模块方式
commonjs:支持commonjs版本引入
ES Module:支持静态分析,支持tree shaking

commonjs和ES Module之间的差异
ES ModuleCommonJS
编译时加载运行时加载
this指的是undefindthis指的是当前模块
导出多个单个值导出moudle.export
输出一个值引用输出的是一个值拷贝

读源码找入口是从package.json文件中的scripts中找相关的node 命令

当我们使用下面代码的时候,使用完全版本的vue的时候,会执行template还是会执行render,或者都执行之类的?

new Vue({
    el:"#app",
    template:'',
    render(h){
        return h('div','aaaa')
    }
})
// 会执行render函数,源码中有判断有render执行render函数,没有才会去解析template模板

响应式处理的过程

从Vue实例的init方法中开始的

  • initState() 初始化Vue实例的状态
    • initProps()
    • initMethods()
    • initData() 把data属性注入到data实例上,并且调用observe()使data属性变成响应式的
    • initComputed()
    • initWatch()
  • observe(value)的实现原理
    1. 判断value是否是对象,如果不是对象直接返回
    2. 判断vlaue对象是否有__ob__,如果有直接返回,说明已经做过响应式了
    3. 如果都没有,则创建Observer对象
    4. 返回Observer对象
  • Observer
    1. 首先给value对象定义一个不可枚举的__ob__属性,记录当前的observer对象
    2. 数组的响应式处理,设置数组的七个方法
    3. 对象的响应式处理,调用walk方法(遍历对象的属性)
  • defineReactive
    1. 为每个属性创建对应的dep对象,让dep去收集依赖
    2. 判断当前的属性值是否是对象,是的话,调用observe
    3. 定义getter
      • 收集依赖(为每个属性收集依赖,属性值是对象的时候,为每个子对象收集依赖)
      • 最终返回属性的值
    4. 定义setter
      • 保存新值
      • 如果新值是对象的话,调用observe
      • 值发生变化的时候,派发更新(发送通知),调用dep.notify()

依赖收集

  1. 首先调用watcher对象中的get方法,在get方法中调用pushTarget,把当前watcher对象记录Dep.target属性
  2. 访问data成员的属性的时候去收集依赖,访问这些属性的时候,会触发defineReactive的getter,在getter中会收集依赖
  3. 把属性对应的watcher对象添加到对应的dep的subs数组中
  4. 如果属性的值是一个对象,则先调用observe,返回一个childOb的Observer对象,并继续要为子对象收集依赖,目的是当子对象发生变化的时候,能够发送通知

watcher

  1. 当数据发生变化的时候,会调用dep.notify()发送通知,这个方法会调用watcher的update()方法
  2. 在update()方法中会调用 queueWatcher()方法,在这个函数中会判断watcher是否被处理了,如果没有处理就添加到 queue队列中,并调用flushSchedulerQueue()
  3. 在flushSchedulerQueue中
    • 首先触发beforeUpdate钩子函数
    • 调用watcher.run()方法,在这个方法中调用get()->getter()-->updateComponent (对应render watcher来说的)
    • 清空上一次的依赖,和重置一些变量状态
    • 触发actived钩子函数 触发updated钩子函数

$set的实现原理

  1. 判断target是否是数组,并且所传入的key是否是合法的,用splice数组进行响应式替换
  2. 判断key 是否是已经在target上,是的话直接赋值
  3. 判断target的__ob__属性,来判断target的是不是响应式对象,如果不是响应式对象的话 直接赋值
  4. 最后target是响应式对象,key不在target上的时候,直接调用defineReactive,把key设置为响应式

最后通过 ob.dep.notify() 发送通知

$delete的实现原理

  1. 判断target是否是数组,并且所传入的key是否是合法的,用splice对数组进行删除
  2. 判断key 是否是已经在target上,不存在的话,直接返回,不处理
  3. 判断key 是否是已经在target上,存在的话,往下走,存在的话 使用delete target[key]删除对象的属性

判断target 是否是响应式的,不是响应式的,删除之后直接return,是响应式的就通过 ob.dep.notify() 发送通知

delete target[key]删除数组和对象是有区别的,删除对象是连同属性一起删除,删除数组的时候是把数组的那一项变为undefined

Watcher类

Watcher分为三种 Computed Watcher 用户Watcher(侦听器,$watch方法) 渲染Watcher 渲染watcher 会默认吧lazysync设置为false

没有静态方法,因为$watch 方法中要使用Vue实例
创建顺序:计算属性watcher、用户Watcher、渲染watcher

异步更新队列-$nextTick()的实现原理

这个方法获取dom上的最新数据,当我们微任务执行的时候,dom元素还没有渲染到浏览器上,如何获取值的呢?当nextTick中的回调函数执行前,数据已经被改变了,当重新改变这个数据的时候,会立即通知watcher去渲染视图,而watcher中首先做的事情就是更改dom树,dom的更新操作要在当前事件循环机制完成之后,nextTick内部如果使用微任务的话,在获取数据的时候是在dom树上获取数据的,此时dom还没有渲染到浏览器上来

有静态方法和实例方法

  1. 首先用一个callBacks数组收集nextTick传过来的回调函数
  2. 判断是否在pending,是否在执行中,没有被处理的时候进入,pending置为true,同时执行timerFunc()
  3. timerFunc()的作用就是遍历callBacks数组中的所有cb,依次进行调用
  4. timerFunc中有很多兼容性判断
    • 判断浏览器支不支持Promise,使用Promise来遍历和执行cb的调用操作,优先微任务来操作
    • 判断浏览器不是IE和支持MutationObserver,使用MutationObserver回调的微任务操作
    • 判断是不是支持setImmdiate 只有IE和nodejs
    • 使用setTimeout执行