📕
余烬的小册
数据结构与算法GitHub
  • 总述
  • 经验记录
    • 经验总结
      • web component
      • 前端性能优化总结与分析
      • 我的长列表优化方案
      • 双向通讯解决方案
      • 🔧基于istanbul实现代码测试覆盖率工具
      • 表单系统(低代码表单)
      • 跨端小程序
      • 设计一个即时聊天功能
      • 跨页面通讯 3658699fe4cb4d0bbe22b0881390bacd
    • 踩坑记录
      • HTML踩坑记录
      • Flutter踩坑记录
      • CSS踩坑记录
  • 源码解析
    • Vue源码解析
      • Vue2源码解析系列-响应式原理
      • Vue2源码解析系列-模板编译
      • Vue2源码解析系列-渲染系统(待更新)
        • Patch
      • Vue2源码解析系列-调度系统(todo)
      • Vue2组件更新流程(todo)
      • 如何学习Vue源码
      • Vue3源码解析系列-响应系统
      • Vue3源码解析系列-渲染系统
      • Vue3源码解析系列-组件化和渲染优化(todo)
      • Vue router源码解析(todo)
    • React源码解析(todo)
    • 微前端
      • qiankun源码解析(todo)
    • Vite源码解析
      • Vite Client源码
      • Vite Server源码(todo)
  • 前端技术
    • javaScript
      • ES6
        • 变量声明
        • 模块化
        • 箭头函数
        • 你不知道的for...of
        • 新的数据结构Set和Map
        • JavaScript异步编程终极解决方案
        • ES6 Class 3a0c0a225a534984aabe9a943c5df975
      • JavaScript Error
      • JavaScript浅拷贝和深拷贝
      • JavaScript闭包
      • JavaScript最佳实践
      • JavaScript设计模式
      • async函数的polyfill
    • 深入理解JavaScript系列
      • JavaScript中的继承
      • JavaScript原始类型和引用类型
      • JavaScript浅拷贝和深拷贝
      • JavaScript手写系列
      • JavaScript之this
      • 词法环境和环境记录
      • JavaScript内存泄漏
      • 执行上下文
      • 从ECMAScript规范中学习this
    • TypeScript
      • TypeScript基础教程
      • Typescript高级操作
      • TypeScript工具类型
      • Typescript手写实现工具类型
      • Typescript总结(思维导图)
    • 浏览器原理
      • 页面渲染原理
      • 浏览器存储
      • JavaScript事件循环
      • 事件循环
      • 跨域
      • DOM事件流
      • 从输入url到页面渲染
      • 判断节点之间的关系及根据节点关系查找节点
      • history API
    • 跨端技术
      • Flutter
        • Flutter布局组件
    • 前端工程化
      • Babel插件开发指南
      • 循环依赖
      • pm2
    • React
      • React 状态管理
      • React组件通讯
      • Redux入门
      • Flux
      • React Hook(todo)
      • Effect
  • 服务器端
    • 计算机网络
      • 应用层
      • 运输层
      • 物理层
      • 数据链路层
      • HTTP缓存
      • HTTPS
      • 网络层
    • NodeJs
      • Node.js
      • nodejs最佳实践
      • 《深入浅出Nodejs》小结
      • mongoose填充(populate)
      • node事件循环
      • Node子进程
      • nestjs从零开始
      • nodejs流
      • Nodejs调试
      • Koa源码解析
    • 服务器
      • 操作系统
      • Linux
      • nginx常用指令
      • nginx常用配置
    • 数据库
      • Mysql常见语法
      • MongoDB Indexes索引
  • 前端安全与性能优化
    • 前端安全
      • 跨站脚本攻击(XSS)
      • 跨站点请求伪造(CSRF)
      • 点击劫持
      • 中间人攻击
      • 越权攻击与JWT
    • 前端性能优化
      • 前端监控系统
      • 前端性能优化总结与分析 7348bba0918645b1899006dc842a64c1
      • 衡量性能的核心指标 0dc15ef127cf4f4a9f1137c377420292
      • 图片懒加载
  • 杂项
    • 其他
      • Git
      • web component框架
      • 实现滚动框的懒加载
      • Stencil指南
    • CSS
      • 定位和层叠上下文
      • BFC
      • 盒模型
      • css选择器
      • css变量
由 GitBook 提供支持
在本页
在GitHub上编辑
  1. 源码解析
  2. Vue源码解析

Vue2组件更新流程(todo)

status: Idea Created time: March 15, 2024 6:52 PM

Vue2中有三个非常重要的类:Watcher、Observer和Dep。

  • Watcher:Vue2响应式中非常重要的一个类,管理依赖和状态的管理,watcher.update负责依赖的更新。

  • Observer:响应式对象,vm.$options.data会被其转换成响应式。本质上是通过Object.defineProperty(对于对象)和更改原型方法(对于数组)实现。

  • Dep:用于存储Watcher的一个结构,每个状态都有一个对应的dep。

整体流程:

  1. 在初始化时,vue2会对Component、watch和computed等创建Watcher实例(watcher),并通过new Observer将vm.$options.data转换响应式对象(初始化data在initData函数中完成操作),同时每个状态都有一个对应的dep对象,用于存放该状态的所有依赖(watcher)。

  2. 以组件为例,这里需要了解一个前提知识,vue无论是模板语法还是渲染函数,最后的目的都是为了生成vnode,而模板语法其实会被vue通过模板编译操作转换成渲染函数(render函数),render函数执行的返回值就是vnode,然后通过patch新旧vnode来更新DOM,从而达到更新视图的结果。

  3. 在初始渲染组件时,vue会为组件创建一个watcher对象(执行new Watcher),在Watcher实例化的过程中,会将Dep.target指向watcher自身,之后执行一次组件render函数,而在render函数内可能会使用某些响应式状态,因此在执行render函数中就自动会触发这些状态的getter函数,在getter函数中就会将Dep.target(即当前组件watcher对象)添加到observer对应的dep中

  4. 当更新状态时,自然会触发状态的setter函数,此时就会通知该状态dep中所有的watcher,执行watcher.update()进行更新,在这个例子中,会重新执行render函数和patch函数进行DOM更新。

具体代码:

  1. 首先,在组件初始化时,为组件创建一个watcher,每一个vue组件都有一个对应的watcher对象。

function mountComponent (
    vm,
    el,
    hydrating
  ) {
    vm.$el = el;
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode;
    }
    callHook(vm, 'beforeMount');

    var updateComponent;
    if (config.performance && mark) {
      updateComponent = function () {
        var vnode = vm._render(); // 执行render函数获得vnode,如果是模板语法,vue内部也会通过模板编译来将其转换成render函数
        vm._update(vnode, hydrating); // vm._update函数内部其实也是通过patch来进行比对新旧vnode,从而进行更新
      };
    } else {
      updateComponent = function () {
        vm._update(vm._render(), hydrating);
      };
    }
    // 每个vue组件都有对应的一个watcher对象
    // 重点在这个实例化Watcher语句,参数传递了vm和updateComponent
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);
		// ...
    return vm
  }

之后主要看Watcher实例化的过程

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    this.cb = cb
    this.id = ++uid // uid是一个初始值为0的变量
    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') {
      this.getter = expOrFn
    } else {
	    // parsePath处理expOrFn是'obj.a'这种情况,例如在watch中就有这种写法
      this.getter = parsePath(expOrFn)
    }
    // 最主要在于这条
    this.value = this.lazy
      ? undefined
      : this.get()
	  }
	  
	  get () {
	    pushTarget(this) // 内部会执行 Dep.target = target
	    let value
	    const vm = this.vm
	    try {
		    // this,getter就是watcher初始化时传入的expOrFn参数,在这个例子中,它是updateComponent函数
		    // 也就是说,此时会调用一次render函数
	      value = this.getter.call(vm, vm)
	    } catch (e) {
		    // ...
	    } finally {
	      // ...
	    }
	    return value
	  }
  }

也就是说,在初始化vue组件的watcher时,首先会将Dep.target指向watcher自身,然后执行一次vm.render进行渲染,同时也是让组件render引用的状态来收集watcher自身。

接下来看看vm.$options.data是如何变成

上一页Vue2源码解析系列-调度系统(todo)下一页如何学习Vue源码