Vue.js 源码剖析-响应式原理 part1

本文详细解析Vue.js实例的初始化过程,包括静态成员与实例成员的初始化,以及首次渲染的具体步骤。通过调试技巧深入探讨Vue如何从创建实例到完成页面渲染的全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目标

在这里插入图片描述

  1. vue.use set,el. d a t a , data, data,el.mount等
  2. 创建vue实例,初始化好数据后,vue如何渲染到页面的
  3. 响应式

在这里插入图片描述
目录
根据不同功能把代码拆分不同文件夹(提高可读性可维护性)
compiler转换render函数
core核心
globalApi Vue的静态方法
instance 创建vue实例
observer 响应式实现(重点讲)
vueDom 重写了snabdom
server 服务端渲染
sfc 单文件组件 把单文件组件转成js对象
在这里插入图片描述
了解flow即可 vue3已经全面TS开发
在这里插入图片描述
flow可以使js有java搬的开发体验,运行前检查。

准备工作-调试

在这里插入图片描述

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web- full-dev"

在这里插入图片描述

调试

examples 的示例中引入的 vue.min.js 改为 vue.js
打开 Chrome 的调试工具中的 source
在这里插入图片描述
一路f11调试。
(开启sourmap可以根据src不同模块不同功能调试,不开启会直接用dist/vue.js 里面一万多行代码,易读性差)

Vue的不同构建版本

在这里插入图片描述
full-完整版, runtime-only-运行时
前俩行完整版代码
后俩行压缩版本

编译器的作用是把template转换成render渲染函数(转Vnode)。

编译器代码三千多行。

在这里插入图片描述

  • 尝试完整版
    在这里插入图片描述

// 注意因为有template这里的#app没了
在这里插入图片描述

  • 尝试运行时版本
    在这里插入图片描述

报错,无法解析模板
在这里插入图片描述
把template修改render函数
在这里插入图片描述
在这里插入图片描述

  • 尝试基于vue-cli创建的项目(运行时版本)
    查看webpack的配置(vue对webpack做了深度封装一般看不了)
    在这里插入图片描述
    vue inspect 是把webpack配置打印出来,看起来不方便,使用这个输出到文件
vue inspect > output.js
// Compiler
 // 需要编译器,把 template 转换成 render 函数
  // const vm = new Vue({ // el: '#app', // template: '<h1>{{ msg }}</h1>', // data: { // msg: 'Hello Vue' // } // }) // Runtime // 不需要编译器 const vm = new Vue({ el: '#app', render (h) { return h('h1', this.msg) },data: { msg: 'Hello Vue' } }) 123

在这里插入图片描述
注意: *.vue 文件中的模板是在构建时预编译的,最终打包后的结果不需要编译器,只需要运行时版本即可
在这里插入图片描述

寻找入口文件

查看 dist/vue.js 的构建过程
npm run dev

script/config.js 的执行过程

  • 作用:生成 rollup 构建的配置文件
  • 使用环境变量 TARGET = web-full-dev
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    结果
  • 把 src/platforms/web/entry-runtime-with-compiler.js 构建成 dist/vue.js,如果设置 –
    sourcemap 会生成 vue.js.map
  • src/platform 文件夹下是 Vue 可以构建成不同平台下使用的库,目前有 weex 和 web,还有服务
    器端渲染的库
    在这里插入图片描述

从入口开始

src/platform/web/entry-runtime-with-compiler.js

在这里插入图片描述
在这里插入图片描述
如何找到$mount方法何时调用?
在这里插入图片描述
方法:如下
在这里插入图片描述
先打包出带sourcemap的vue文件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
向下依次点击会顺序执行
在这里插入图片描述

render和template先执行那个?

el 不能是 body 或者 html 标签
如果没有 render,把 template 转换成 render 函数
如果有 render 方法,直接调用 mount 挂载 DOM
在这里插入图片描述

调试的方法

在这里插入图片描述

Vue初始化的过程

运行时版
直接导出vue
在这里插入图片描述

完整版比较复杂
在这里插入图片描述
这里的核心就是把template转为render

从runtime开始

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开始探索vue构造函数

core中的代码和平台无关
在这里插入图片描述
在这里插入图片描述

观察导入的vue

在这里插入图片描述
终于找到vue构造函数
在这里插入图片描述
这个文件主要做了:
1创建vue构造函数
2设置vue实例的成员

注意:关于vue构造函数
此处不用 class 的原因是因为方便后续给 Vue 实例混入实例成员(通过原型挂载,用类的话就在语法上很不搭配)

总结

  • 四个导出 Vue 的模块
  1. src/platforms/web/entry-runtime-with-compiler.js (核心就是增加了编译功能)
    web 平台相关的入口
    重写了平台相关的 $mount() 方法
    注册了 Vue.compile() 方法,传递一个 HTML 字符串返回 render 函数
  2. src/platforms/web/runtime/index.js
    web 平台相关
    注册和平台相关的全局指令:v-model、v-show
    注册和平台相关的全局组件: v-transition、v-transition-group
    全局方法:
    patch:把虚拟 DOM 转换成真实 DOM
    $mount:挂载方法
  3. src/core/index.js
    与平台无关
    设置了 Vue 的静态方法,initGlobalAPI(Vue)
  4. src/core/instance/index.js
    与平台无关
    定义了构造函数,调用了 this._init(options) 方法
    给 Vue 中混入了常用的实例成员

Vue初始化-两个问题

解决报红问题
在这里插入图片描述

在这里插入图片描述
问题2
解决前
在这里插入图片描述
下载
在这里插入图片描述
解决
在这里插入图片描述
虽然安装了上面的插件 但没有点击跳转的功能(无大碍跳过即可)

Vue初始化-静态成员

globalApi中
在这里插入图片描述
runtime/index中
在这里插入图片描述
继续看 globalApi
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其实extend就是一个浅拷贝
回到globalApi中

 extend(Vue.options.components, builtInComponents)//在注册全局组件    这个builtInComponents是全局组件

builtInComponents ↓
在这里插入图片描述
回到globalApi中,下面的函数都在global文件夹中
在这里插入图片描述
initUse的实现

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
                          // 这里的this  谁调用用指向谁
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    // 把arguments转换数组,去掉数组中的第一个元素(plugin)去除
    const args = toArray(arguments, 1)
    // 把this(Vue)插入第一个元素的位置
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      // 这里的apply为了展开参数
      // 调用插件方法 传递参数
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

initMixin

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
                    //拷贝
    this.options = mergeOptions(this.options, mixin)// this 是vue
    return this
  }
}

initExtend
核心就是返回组件的构造函数

太长了,后面放源码自己看

initAssetRegisters(Vue)
// 注册 Vue.directive()、 Vue.component()、Vue.filter() ,因为参数一样所以可以一起注册
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  // 遍历 ASSET_TYPES 数组,为 Vue 定义相应方法
  // ASSET_TYPES 包括了directive、 component、filter
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        // 如果未定义找到之前定义好的
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        // Vue.component('comp', { template: '' })
        // 判断是否组件                      isPlainObject 是否原始对象   
                                          // export function isPlainObject (obj: any): boolean {
                                          //   return _toString.call(obj) === '[object Object]'
                                          // }  
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          // 把组件配置转换为组件的构造函数
          definition = this.options._base.extend(definition) //this.options._base = vue  extend把对象转换成组件构造函数
        }
        // 如果是指令,俩种情况,第一种如果是对象直接执行最后一句,第二种函数进入if分支
        if (type === 'directive' && typeof definition === 'function') {
            // 这里的bind和update 是方法
          definition = { bind: definition, update: definition }
        }
        // 全局注册,存储资源并赋值
        // this.options['components']['comp'] = definition
        this.options[type + 's'][id] = definition //如果直接传组件构造函数会直接执行这句话
        return definition
      }
    }
  })
}

总结 定义vue.config,set,delete,observable等,初始化options,继续注册keepalive,use,mixin,extent等,以上都是静态成员

Vue初始化-实例成员

这段代表都是给vue实例混入相关成员

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
// 此处不用 class 的原因是因为方便后续给 Vue 实例混入实例成员
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)  //判断是否生成环境和是否new出来的
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 调用 _init() 方法  这里的是下面的initMixin注册的
  this._init(options)
}
// 注册 vm 的 _init() 方法,初始化 vm
initMixin(Vue)
// 注册 vm 的 $data/$props/$set/$delete/$watch
stateMixin(Vue)
// 初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入 render
// $nextTick/_render
renderMixin(Vue)

export default Vue

这里开始就不一一贴代码了,自己一个一个点进去看会笔记快,源码我都写了注释

  • 先看initMixin
    这里的init方法相当于整个vue的入口所有事情都从这里开始
    在这里插入图片描述
    看源码快捷键 按ctrl和鼠标进入后 alt+← 可以快速回去

  • stateMixin(Vue)
    // 注册 vm 的 d a t a / data/ data/props/ s e t / set/ set/delete/$watch

  • eventsMixin(Vue)
    // 初始化事件相关方法
    // o n / on/ on/once/ o f f / off/ off/emit
    在这里插入图片描述

注意这里的初始化的方法 很不错

  • lifecycleMixin(Vue)
    在这里插入图片描述

// 初始化生命周期相关的混入方法
// _update/ f o r c e U p d a t e / forceUpdate/ forceUpdate/destroy

  • renderMixin(Vue)
    // 混入 render
    // $nextTick/_render
    render方法中
    在这里插入图片描述

Vue初始化-实例成员-init

静态成员和实例成员初始化完后会调用vue的构造函数,然后调用init,init是在initMixin中初始的
在这里插入图片描述
下面的都直接看源码 都写注释了
在这里插入图片描述
以下方法都一个个点进去看源码都写注释了
在这里插入图片描述
依赖注入原理
在这里插入图片描述

Vue初始化-实例成员-initState

点进去一个一个看
在这里插入图片描述

调试Vue初始化过程

主要调试vue导出的四个文件
在这里插入图片描述
在这里插入图片描述
f5
继续找到俩个跟平台相关的
在这里插入图片描述
patch把vdom转成dom,然后mount挂载到页面
继续找第四个断点,打包文件后的入口,此文件中重写了mount方法,增加了把模板变成render的功能。

在这里插入图片描述


开始调试 f5
进入instance/index
在这里插入图片描述
最开始只有默认成员和默认构造函数
在这里插入图片描述
观察initMixin执行完后 prototype的变化。
按f10跳过此函数,发现prototype中增加了 init方法
在这里插入图片描述
继续f10跳过stateMinx,发先多了 d a t a / data/ data/props/ s e t / set/ set/delete/$watch但都是undefined,后面通过选项赋值
在这里插入图片描述
继续f10跳过eventsMixin,发现多了事件相关的 o n / on/ on/once/ o f f / off/ off/emit
在这里插入图片描述
继续f10跳过lifecycleMixin,发现多了_update/ f o r c e U p d a t e / forceUpdate/ forceUpdate/destroy,其中update调用了patch,把vdom转成dom
在这里插入图片描述
继续f10跳过renderMixin,发现多了_开头的方法,等把模板转换成render的时候调用这些方法
在这里插入图片描述
在这里插入图片描述
还多了nexttick和render(调用用户的render或者把模板转成render)。

这里不看keepalive的执行过程 直接f8到下一个断点


core/index中
f11进入globalApi
按f10执行到
在这里插入图片描述
接下来又初始化了util然后是set和delte等
在这里插入图片描述
继续一步一步到
初始化option 此时的空对象(啥都没有连proto都没有),此option存储全局指令和组件
在这里插入图片描述
foreach结束后
在这里插入图片描述
到base这里,base主要存储vue的构造函数
在这里插入图片描述
初始化第一个组件keepalive组件
在这里插入图片描述
开始初始静态方法
在这里插入图片描述
开始注册全局组件和过滤器
在这里插入图片描述
初始完成静态成员的过程结束。f8跳到下一个断点


web/runtime/index 此时的代码都是和平台相关的
开始注册平台相关方法
点击f10 执行到
在这里插入图片描述
在这里插入图片描述
继续下一步,初始化patch和mount,这里在vue的proto上挂载了,但未执行,会在vue的init中调用
f10 到在这里插入图片描述
发现已经添加到vue的proto中
在这里插入图片描述
观察结束,f8进入下一个


到了打包文件的入口

在这里插入图片描述
执行完后 又给vue挂载了compile方法(作用是手工转换成render函数)
在这里插入图片描述
调式完毕四个文件,可以发现vue构造函数的变化,以及初始化静态和实例成员的过程。
下面开始调试init方法,也就是首次渲染的过程。

首次渲染过程

在这里插入图片描述
重点调试init,观察首次渲染的过程
在这里插入图片描述
f8进入下一个文件
core/index
f8进入下一个文件
runtime/index
f8进入下一个文件
在这里插入图片描述
f8再f11进入init
f10一步一步往下走
在这里插入图片描述
此时不是组件,开始合并option

合并前在这里插入图片描述
合并后
在这里插入图片描述
在这里插入图片描述
f11进入initProxy,大概先判断当前环境是否支持proxy,然后通过proxy代理
在这里插入图片描述
跳出initproxy,调试结束,下面的init等方法先不看,现在重点看如何渲染的。
新增断点,然后f8,然后f11进入mount
在这里插入图片描述
f10执行到
在这里插入图片描述
一直f10如果有方法就f11跳进去看实现
最后到mount方法,返回runtime/index中重写的mount
在这里插入图片描述
按f11进入mount
在这里插入图片描述
这里重新获取el是因为,如果是运行时版本的话需要获取el,但现在用的是完整版的,已经在之前的方法获取了
进入mountComponent(vue核心代码)
在这里插入图片描述
一直下一步
在这里插入图片描述
这里定义了但还没执行_update,是在

继续调试到 创建watch,在这里才执行updateCompenent(准确的说是在watcher中的get方法调用了)
在这里插入图片描述
f8 f11进入watch
在这里插入图片描述
observe文件中的代码都是和响应式相关的
补充 vue中一般有三种watvh 1渲染2计算属性的3侦听器的
在这里插入图片描述
在这里插入图片描述
lazy的作用是延迟执行,因为当前是首次渲染需要立即更新。
如果是计算属性会true,数据更新后才更新视图。
继续往下走
在这里插入图片描述
继续走,这里会判断是否lazy 如果不是就立即执行 get方法
在这里插入图片描述
观察get
存入当前watch入栈(每个组件对应每个watch》watch去渲染视图》如果组件嵌套则先渲染内部的》所以需要保存父组件的watch)
在这里插入图片描述
get中最关键的一句话,调用刚刚存的getter(这里的getter就是刚刚穿过来的updateComponent)
在这里插入图片描述
继续设置断点,f8,f11进入get
在这里插入图片描述
一直执行到
在这里插入图片描述
f11进入后,到了updateComponent,updateComponent中的update执行完后就会渲染到页面上。f10跳过回到get方法,发现页面已经渲染完完毕
在这里插入图片描述
在这里插入图片描述
执行结束会继续执行watchr,watchr执行完后 回到lifecircle里
继续f10,把lifecircle执行完毕回到
在这里插入图片描述
继续f10 又会会到入口文件》又回到init 》 构造函数,首次渲染结束。

首次渲染过程-总结

在这里插入图片描述
最后触发mounted页面挂载完毕。
在这里插入图片描述
init相当vue的入口
第一个mount(entry-runtime-with-compiler)是把模板编译成render函数
第二个mount(runtime/index)重新获取el,然后调用mountComponent。
mountComponent
在这里插入图片描述

get
在这里插入图片描述
首次渲染结束。
在这里插入图片描述

数据响应式原理-响应式处理入口

添加链接描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值