剖析vue内部运行机制

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

Vue的实质

Vue实际上是一个方法类,在原型上扩展了很多方法
源码目录:src/core/instance/index.js
在这里插入图片描述

Vue生成Dom的流程

源码目录:src/core/instance/init.js
在这里插入图片描述在这里插入图片描述初始化数据,将属性变为响应式数据
源码目录:src/core/instance/state.js
在这里插入图片描述
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
图解:
在这里插入图片描述

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

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

在这里插入图片描述
vue初始化流程思维导图
在这里插入图片描述

响应式原理

核心
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,用于依赖收集和派发更新
    在这里插入图片描述在这里插入图片描述

依赖收集
源码目录: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,最后执行它们的回调函数。
整个派发更新过程:
在这里插入图片描述
在这里插入图片描述
nextTick
异步更新机制和nextTick原理
在这里插入图片描述

把要执行的任务推入到一个队列中,在下一个tick执行
数据改变后触发watcher的update,但是watchers的flush是在nextTick后,所以重新渲染是异步的

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

计算属性和侦听属性
计算属性的本质是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逻辑

响应式原理图
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue.js 是一款轻量级的前端框架,采用 MVVM 架构,本质上是一个响应式的系统,通过观察者模式的方式,实现了数据与视图之间的双向绑定。Vue.js 的实现原理主要包括以下几个方面: 1. 数据响应式 Vue.js 基于 ES5 的 Object.defineProperty() 方法,实现了双向绑定的核心机制。当一个 Vue 实例被创建后,Vue 会遍历这个实例的 data 对象属性,将这些属性通过 Object.defineProperty() 方法转化为 getter 和 setter,并在 getter 方法中建立一个依赖收集的机制,记录所有依赖这个属性的 Watcher,当属性值发生变化时,依赖收集器会通知相关的 Watcher, Watcher 会更新视图。 2. 模板解析与生成虚拟 DOM Vue.js 的模板采用了类 HTML 的语法结构,Vue 的编译器会将其转换成虚拟 DOM,并将其与 Vue 实例进行绑定,当 Vue 实例数据发生变化时,Vue 会重新生成虚拟 DOM,并与之前的虚拟 DOM 进行比较,得出需要更新的部分,最终通过真实 DOM 更新视图。这个过程需要用到 diff 算法,用来尽可能地复用页面已有的元素,减少重绘重排的开销。 3. 组件化开发 Vue.js 将页面抽象成一个一个的组件,每个组件都有自己的作用域、模板、数据等属性。当组件数据发生变化时,Vue.js 会重新生成组件对应的虚拟 DOM,并对比之前的虚拟 DOM,最终只更新变化的部分,通过这种方式提供了组件级别的性能优化。 总之,Vue.js 实现了一种基于数据的响应式机制,通过虚拟DOM进行DOM操作,最终生成所需要显示的页面。Vue.js组件化开发的方式可以提高代码的可维护性和可扩展性,框架底层的实现优化可以提高页面的性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值