剖析vue内部运行机制

掘金小册-剖析Vue.js内部运行机制
Vue.js技术揭秘
Vue源码分析
Vue面试题
尤雨溪讲解vue

Vue的实质

Vue实际上是一个方法类,在原型上扩展了很多方法
源码目录:src/core/instance/index.js
剖析vue内部运行机制_第1张图片

Vue生成Dom的流程

源码目录:src/core/instance/init.js
剖析vue内部运行机制_第2张图片剖析vue内部运行机制_第3张图片初始化数据,将属性变为响应式数据
源码目录:src/core/instance/state.js
剖析vue内部运行机制_第4张图片
new Vue()创建一个类的实例,调用Vue方法类中的_init初始化方法(生命周期、事件、数据、render、数据等),最后,检测到如果有 el 属性,调用 vm.$mount 方法挂载 vm,将模板渲染成最终的dom。

挂载的过程

$mount
--------》
compileToFunctions(将template内容转化为render方法)
--------》
mountComponent
vm._render 调用 createElement 方法返回vnode(虚拟dom)
实例化渲染Watcher,在回调函数中调用 updateComponent
通过 vm._update (调用vm.__patch__方法,通过patch函数利用emptyNodeAt 方法把 oldVnode 转换成 VNode 对象,然后调用 createElm方法(通过虚拟节点创建真实的 DOM 并插入到它的父节点中),创建子元素,添加到虚拟队列中,最后调用insert 方法把 DOM 插入到父节点中)更新 DOM
--------》
生成DOM
图解:
剖析vue内部运行机制_第5张图片

组件patch的整体过程
createComponent–>子组件初始化–>子组件render–>子组件patch
activeInstance为当前激活的vm实例;vm.$vnode为组件的占位vnode;vm._vnode为组件的渲染vnode,也就是根vnode
嵌套组件的插入顺序为先子后父

组件转化为vnode(通过createComponent实现)
有3个关键逻辑:
构造子类构造函数
安装组装钩子函数(installComponentHooks)
实例化vnode

剖析vue内部运行机制_第6张图片

响应式原理

核心
Object.defineProperty(obj,prop,descriptor)
直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
obj是要在其上定义属性的对象;prop是要定义或修改属性的名称;descriptor是将被定义或修改属性的描述符。
其中最重要的是descriptor,它有很多可选键值,get 是一个给属性提供的 getter 方法,当我们访问了该属性的时候会触发 getter 方法;set 是一个给属性提供的 setter 方法,当我们对该属性做修改的时候会触发 setter 方法。一旦对象拥有了 getter 和 setter,我们可以简单地把这个对象称为响应式对象
具体过程

  1. initState初始化数据(props,methods,data,computed,watcher)
  2. proxy把props和data上的属性代理到vm实例上
  3. observe给非VNode对象类型添加Observe类,通过defineReactive方法给对象的属性添加getter和setter,用于依赖收集和派发更新
    剖析vue内部运行机制_第7张图片剖析vue内部运行机制_第8张图片

依赖收集
源码目录:src/core/observer/dep.js

/* @flow */

import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []//存储与当前dep有关的watcher
  }
  //添加一个watcher
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  //移除
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)// 将当前的 dep 与 当前渲染 watcher 关联起来
    }
  }

  /** 
   * 每一个属性 都会包含一个 dep 实例
   * 这个 dep 实例会记录下 参与计算或渲染的 watcher
   */
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    //依次触发 this.subs 中的 watcher 的 update 方法,起到更新的作用
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
//当前watcher
Dep.target = null
//定义一个容器,用于存放watcher
const targetStack = []
//将当前操作的 watcher 存储到 全局容器中, 参数 target 就是当前 watcher
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}
//移除watcher
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
/**
 * 在 watcher 调用 get 方法的时候, 调用 pushTarget( this )
 * 在 watcher 的 get 方法结束的时候, 调用 popTarget()
 */

Dep类的作用实际上就是建立数据与watcher的桥梁,Dep.target静态属性,确保这个watcher为全局唯一。
依赖收集就是订阅数据变化的watcher的收集;
依赖收集的目的是为了当这些响应式数据发生变化,触发它们的setter的时候,能知道应该通知哪些订阅者去做相应的处理。

派发更新
源码目录:src/core/observer/dep.js
dep.notify(),通知所有的订阅者,整个派发更新的过程
当数据发生变化的时候,触发setter逻辑,把在依赖过程中订阅的所有观察者,也就是watcher,都触发它们的update过程。这个过程又利用了队列做了进一步优化,在nextTick后执行所有watcher的run,最后执行它们的回调函数。
整个派发更新过程:
剖析vue内部运行机制_第9张图片
剖析vue内部运行机制_第10张图片
nextTick
把要执行的任务推入到一个队列中,在下一个tick执行
数据改变后触发watcher的update,但是watchers的flush是在nextTick后,所以重新渲染是异步的

响应式数据中对于对象新增删除属性以及数组的下标访问修改和添加数据等的变化观测不到,因为这些操作不能触发setter
通过Vue.set以及数组的API可以解决这些问题,本质上它们内部手动去做了依赖更新的派发
手动调用ob.dep.notify();重新渲染
源码目录:src/core/observer/scheduler.js
剖析vue内部运行机制_第11张图片

计算属性和侦听属性
计算属性的本质是computed watcher
侦听属性的本质是user watcher
计算属性适合用在模板渲染中,某个值是依赖了其他的响应式对象甚至是计算属性计算而来
侦听属性适用于监测某个值的变化去完成一段复杂的业务逻辑
源码目录:src/core/observer/watcher.js

/* @flow */

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError,
  noop
} from '../util/index'

import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'

import type { SimpleSet } from '../util/index'

let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  vm: Component;
  expression: string; // 关联表达式 或 渲染方法体
  cb: Function; // 在定义 Vue 构造函数的时候, 传入的 watch 
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean; // 计算属性, 和 watch 来控制不要让 Watcher 立即执行
  sync: boolean;
  dirty: boolean;
  active: boolean;
                        // 在 Vue 中使用了 二次提交的概念
                        // 每次在数据 渲染 或 计算的时候 就会访问响应式的数据, 就会进行依赖收集
                        // 就将关联的 Watcher 与 dep 相关联,
                        // 在数据发生变化的时候, 根据 dep 找到关联的 watcher, 依次调用 update
                        // 执行完成后会清空 watcher
  deps: Array<Dep>;
  depIds: SimpleSet;
  
  newDeps: Array<Dep>;
  newDepIds: SimpleSet;
  
  before: ?Function; // Watcher 触发之前的, 类似于 生命周期


  getter: Function; // 就是 渲染函数 ( 模板或组件的渲染 ) 或 计算函数 ( watch )
  
  value: any; // 如果是渲染函数, value 无效; 如果是计算属性, 就会有一个值, 值就存储在 value 中

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)

    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()

    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''

    
    // parse expression for getter
    if (typeof expOrFn === 'function') { // 就是 render 函数
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }


    // 如果是 lazy 就什么也不做, 否则就立即调用 getter 函数求值 ( expOrFn ),初始化渲染
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps() // "清空" 关联的 dep 数据
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      
      this.newDepIds.add(id)
      this.newDeps.push(dep) // 让 watcher 关联到 dep

      if (!this.depIds.has(id)) {
        dep.addSub(this) // 让 dep 关联到 watcher
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) { // 在 二次提交中 归档就是 让 旧的 deps 和 新 的 newDeps 一致
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps // 同步
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {            // 主要针对计算属性, 一般用于求值计算
      this.dirty = true
    } else if (this.sync) {     // 同步, 主要用于 SSR, 同步就表示立即计算 
      this.run()
    } else {
      queueWatcher(this)        // 一般浏览器中的异步运行, 本质上就是异步执行 run
                                // 类比: setTimeout( () => this.run(), 0 )
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   * 
   * 调用 get 求值或渲染, 如果求值, 新旧值不同, 触发 cb
   */
  run () {
    if (this.active) {
      const value = this.get() // 要么渲染, 要么求值
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

组件更新
组件更新过程核心是新旧vnode diff
新旧节点不同:创建新节点 --> 更新父占位符节点 --> 删除旧节点
新旧节点相同:获取它们的children,根据不同情况做不同的更新逻辑。如果它们都存在子节点,会执行updateChildren逻辑

响应式原理图
剖析vue内部运行机制_第12张图片

你可能感兴趣的:(vue)