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

目标

Vue.js 源码剖析-响应式原理_第1张图片

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

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

准备工作-调试

Vue.js 源码剖析-响应式原理_第5张图片

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

Vue.js 源码剖析-响应式原理_第6张图片

调试

examples 的示例中引入的 vue.min.js 改为 vue.js
打开 Chrome 的调试工具中的 source
Vue.js 源码剖析-响应式原理_第7张图片
一路f11调试。
(开启sourmap可以根据src不同模块不同功能调试,不开启会直接用dist/vue.js 里面一万多行代码,易读性差)

Vue的不同构建版本

Vue.js 源码剖析-响应式原理_第8张图片
full-完整版, runtime-only-运行时
前俩行完整版代码
后俩行压缩版本

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

编译器代码三千多行。

Vue.js 源码剖析-响应式原理_第9张图片

  • 尝试完整版
    Vue.js 源码剖析-响应式原理_第10张图片

// 注意因为有template这里的#app没了
Vue.js 源码剖析-响应式原理_第11张图片

  • 尝试运行时版本
    Vue.js 源码剖析-响应式原理_第12张图片

报错,无法解析模板
在这里插入图片描述
把template修改render函数
在这里插入图片描述
Vue.js 源码剖析-响应式原理_第13张图片

  • 尝试基于vue-cli创建的项目(运行时版本)
    查看webpack的配置(vue对webpack做了深度封装一般看不了)
    Vue.js 源码剖析-响应式原理_第14张图片
    vue inspect 是把webpack配置打印出来,看起来不方便,使用这个输出到文件
vue inspect > output.js
// Compiler
 // 需要编译器,把 template 转换成 render 函数
  // const vm = new Vue({ // el: '#app', // template: '

{ { msg }}

', // data: { // msg: 'Hello Vue' // } // }) // Runtime // 不需要编译器 const vm = new Vue({ el: '#app', render (h) { return h('h1', this.msg) },data: { msg: 'Hello Vue' } }) 123

Vue.js 源码剖析-响应式原理_第15张图片
注意: *.vue 文件中的模板是在构建时预编译的,最终打包后的结果不需要编译器,只需要运行时版本即可
Vue.js 源码剖析-响应式原理_第16张图片

寻找入口文件

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

script/config.js 的执行过程

  • 作用:生成 rollup 构建的配置文件
  • 使用环境变量 TARGET = web-full-dev
    Vue.js 源码剖析-响应式原理_第17张图片
    Vue.js 源码剖析-响应式原理_第18张图片
    Vue.js 源码剖析-响应式原理_第19张图片
    结果
  • 把 src/platforms/web/entry-runtime-with-compiler.js 构建成 dist/vue.js,如果设置 –
    sourcemap 会生成 vue.js.map
  • src/platform 文件夹下是 Vue 可以构建成不同平台下使用的库,目前有 weex 和 web,还有服务
    器端渲染的库
    Vue.js 源码剖析-响应式原理_第20张图片

从入口开始

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

Vue.js 源码剖析-响应式原理_第21张图片
Vue.js 源码剖析-响应式原理_第22张图片
如何找到$mount方法何时调用?
在这里插入图片描述
方法:如下
Vue.js 源码剖析-响应式原理_第23张图片
先打包出带sourcemap的vue文件
Vue.js 源码剖析-响应式原理_第24张图片
Vue.js 源码剖析-响应式原理_第25张图片
Vue.js 源码剖析-响应式原理_第26张图片
Vue.js 源码剖析-响应式原理_第27张图片
向下依次点击会顺序执行
Vue.js 源码剖析-响应式原理_第28张图片

render和template先执行那个?

el 不能是 body 或者 html 标签
如果没有 render,把 template 转换成 render 函数
如果有 render 方法,直接调用 mount 挂载 DOM
Vue.js 源码剖析-响应式原理_第29张图片

调试的方法

Vue.js 源码剖析-响应式原理_第30张图片

Vue初始化的过程

运行时版
直接导出vue
Vue.js 源码剖析-响应式原理_第31张图片

完整版比较复杂
Vue.js 源码剖析-响应式原理_第32张图片
这里的核心就是把template转为render

从runtime开始

Vue.js 源码剖析-响应式原理_第33张图片
Vue.js 源码剖析-响应式原理_第34张图片

Vue.js 源码剖析-响应式原理_第35张图片
Vue.js 源码剖析-响应式原理_第36张图片
Vue.js 源码剖析-响应式原理_第37张图片
Vue.js 源码剖析-响应式原理_第38张图片
Vue.js 源码剖析-响应式原理_第39张图片

开始探索vue构造函数

core中的代码和平台无关
Vue.js 源码剖析-响应式原理_第40张图片
Vue.js 源码剖析-响应式原理_第41张图片

观察导入的vue

在这里插入图片描述
终于找到vue构造函数
Vue.js 源码剖析-响应式原理_第42张图片
这个文件主要做了:
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初始化-两个问题

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

Vue.js 源码剖析-响应式原理_第43张图片
问题2
解决前
Vue.js 源码剖析-响应式原理_第44张图片
下载
Vue.js 源码剖析-响应式原理_第45张图片
解决
Vue.js 源码剖析-响应式原理_第46张图片
虽然安装了上面的插件 但没有点击跳转的功能(无大碍跳过即可)

Vue初始化-静态成员

globalApi中
Vue.js 源码剖析-响应式原理_第47张图片
runtime/index中
Vue.js 源码剖析-响应式原理_第48张图片
继续看 globalApi
Vue.js 源码剖析-响应式原理_第49张图片

Vue.js 源码剖析-响应式原理_第50张图片
Vue.js 源码剖析-响应式原理_第51张图片
Vue.js 源码剖析-响应式原理_第52张图片
其实extend就是一个浅拷贝
回到globalApi中

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

builtInComponents ↓
Vue.js 源码剖析-响应式原理_第53张图片
回到globalApi中,下面的函数都在global文件夹中
Vue.js 源码剖析-响应式原理_第54张图片
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的入口所有事情都从这里开始
    Vue.js 源码剖析-响应式原理_第55张图片
    看源码快捷键 按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
    Vue.js 源码剖析-响应式原理_第56张图片

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

  • lifecycleMixin(Vue)
    Vue.js 源码剖析-响应式原理_第57张图片

// 初始化生命周期相关的混入方法
// _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.js 源码剖析-响应式原理_第58张图片
下面的都直接看源码 都写注释了
Vue.js 源码剖析-响应式原理_第59张图片
以下方法都一个个点进去看源码都写注释了
Vue.js 源码剖析-响应式原理_第60张图片
依赖注入原理
Vue.js 源码剖析-响应式原理_第61张图片

Vue初始化-实例成员-initState

点进去一个一个看
Vue.js 源码剖析-响应式原理_第62张图片

调试Vue初始化过程

主要调试vue导出的四个文件
Vue.js 源码剖析-响应式原理_第63张图片
Vue.js 源码剖析-响应式原理_第64张图片
Vue.js 源码剖析-响应式原理_第65张图片
继续找到俩个跟平台相关的
Vue.js 源码剖析-响应式原理_第66张图片
patch把vdom转成dom,然后mount挂载到页面
继续找第四个断点,打包文件后的入口,此文件中重写了mount方法,增加了把模板变成render的功能。

Vue.js 源码剖析-响应式原理_第67张图片


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

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


core/index中
f11进入globalApi
按f10执行到
Vue.js 源码剖析-响应式原理_第76张图片
接下来又初始化了util然后是set和delte等
Vue.js 源码剖析-响应式原理_第77张图片
继续一步一步到
初始化option 此时的空对象(啥都没有连proto都没有),此option存储全局指令和组件
Vue.js 源码剖析-响应式原理_第78张图片
foreach结束后
Vue.js 源码剖析-响应式原理_第79张图片
到base这里,base主要存储vue的构造函数
Vue.js 源码剖析-响应式原理_第80张图片
初始化第一个组件keepalive组件
Vue.js 源码剖析-响应式原理_第81张图片
开始初始静态方法
Vue.js 源码剖析-响应式原理_第82张图片
开始注册全局组件和过滤器
Vue.js 源码剖析-响应式原理_第83张图片
初始完成静态成员的过程结束。f8跳到下一个断点


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


到了打包文件的入口

Vue.js 源码剖析-响应式原理_第86张图片
执行完后 又给vue挂载了compile方法(作用是手工转换成render函数)
Vue.js 源码剖析-响应式原理_第87张图片
调式完毕四个文件,可以发现vue构造函数的变化,以及初始化静态和实例成员的过程。
下面开始调试init方法,也就是首次渲染的过程。

首次渲染过程

Vue.js 源码剖析-响应式原理_第88张图片
重点调试init,观察首次渲染的过程
Vue.js 源码剖析-响应式原理_第89张图片
f8进入下一个文件
core/index
f8进入下一个文件
runtime/index
f8进入下一个文件
在这里插入图片描述
f8再f11进入init
f10一步一步往下走
Vue.js 源码剖析-响应式原理_第90张图片
此时不是组件,开始合并option

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

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

首次渲染过程-总结

Vue.js 源码剖析-响应式原理_第112张图片
最后触发mounted页面挂载完毕。
Vue.js 源码剖析-响应式原理_第113张图片
init相当vue的入口
第一个mount(entry-runtime-with-compiler)是把模板编译成render函数
第二个mount(runtime/index)重新获取el,然后调用mountComponent。
mountComponent
Vue.js 源码剖析-响应式原理_第114张图片

get
Vue.js 源码剖析-响应式原理_第115张图片
首次渲染结束。
Vue.js 源码剖析-响应式原理_第116张图片

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

添加链接描述

你可能感兴趣的:(vue原理)