解读Vue源码之路

Vue2.x源码解读之路

解读源码之前的准备

需要下载Vue的源码,直接从github上clone即可。
需要了解Flow:JavaScript的静态类型检查器相关知识。
需要了解打包工具Rollup

Vue打包的版本

– vue.js/vue.common.js 完整版
– runtime.js是运行时版
– min.js是压缩版
完整版:同时包含编译器和运行时的版本。
编译器:用来将模板字符串编译成JavaScript渲染函数的代码,体积大、效率低。把template模板内容转换为JavaScript的渲染函数render函数,render函数的作用是生成虚拟DOM。

各个版本的对比
  • 运行时(runtime):用来创建Vue实例、渲染并处理虚拟DOM等的代码,体积小、效率高。基本上就就是除去编译器的代码。
  • UMD:UMD版本是通用的模板板本,支持多种模块方式(支持CommonJS规范、ES Module规范)。vue.js默认文件就是运行时+编译器的UMD版本。
  • CommonJS:CommonJS版本用来配合老的打包工具比如Browerily或者webpack。
  • ES Module: 从2.6开始的Vue会提供两个ESModules(ESM)构建文件,为现代打包工具提供的版本。
    注:单文件组件会在打包的时候转换为js对象,在转换对象的时候还会把模板转换为render函数,所以单文件组件在运行的时候是不需要编译器。
四个导出Vue的模板

1、src/platforms/web/entry-runtime-with-compiler.js

  • web平台相关的入口
  • 重写了平台相关的$mount()方法;这个方法内部去编译模板;可以把template/el转换为render函数
  • 注册了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) initGlobalAPI里面增加了很多静态方法:例如:静态方法 set/delete/nextTick
    4、src/core/instance/index.js
  • 与平台无关
  • 定义了构造函数,调用了this.init(options)方法
  • 给Vue中混入了常用的实例成员(data/$props/$set/$delete/$watch等)
Vue的首次渲染过程

在这里插入图片描述

Vue的数据响应式原理

响应式处理的入口:

  • 整个响应式处理的过程是比较复杂的(可以把分析的过程进行拆分,首先来找到响应式处理的入口,然后依次去分析observer对象,依赖收集,数组的响应式处理,派发更新,以及watcher观察者对象),下面我们先从
    1、 src/core/instance/init.js

    • initState(vm) vm 状态的初始化
    • 初始化了 _data 、 _props、 methods等

    2、 src/core/instance/state.js

在Vue 的构造函数中调用了 1 文件的_init(),在_init()方法中调用了initState()方法,这个方法的作用是初始化Vue实例的状态,初始化了 _data 、 _props、 methods等,initState在 文件2(src/core/instance/state.js)中定义,在initState方法中调用了initData(), initData作用:把data中的数据注入到Vue实例,并转换为响应式的对象,在initData最后调用了observe(),observe就是响应式的入口。

数据响应式原理-Watcher

watcher分为三种:Computed Watcher(计算属性)、用户Watcher(侦听器) 、 渲染 Watcher;前两种watcher都是在initState中初始化的。
创建顺序:计算属性Watcher、用户Watcher(侦听器) 、 渲染Watcher
渲染Watcher的两种方式:首次渲染、数据更新

  • 首次渲染:
    src\core\instance\lifecycle.js 文件中调用了new Watcher,传入了vm,和updateComponent函数(此函数中调用了_update能够生成虚拟DOM),在watcher初始化中,首先判断isRenderWatcher是否是渲染watcher,如果是渲染Watcher,把当前的渲染watcher记录到Vue实例的_watcher中;然后把所有的watcher都记录到Vue实例的_watchers中包括计算属性、监听器,然后调用this.get()方法,在get方法中首先调用pushTarget,pushTarget作用:把当前watcher对象保存到栈中,并且把当前的watcher赋值给Dep.target; this.getter 是一个函数,如果是渲染watcher,就是updateComponent方法,当updateComponent 执行完毕后,就把虚拟DOM生成真实DOM,并且渲染到页面中。调用popTarget(),把当前的watcher从栈中弹出;调用this.cleanupDeps() 把当前watcher对象从dep的subs数组中移除,并且把watcher中记录的dep也移除

  • 数据更新:当数据更新的时候,首先调用src\core\observer\dep.js 文件中的dep.notify方法去通知watcher,先把watcher放到一个queue队列中,然后去遍历这个队列,去调用所有watcher的run方法,在run方法中调用了this.get方法,get方法中调用了渲染watcher的updateCompent方法

关于数组修改是否是响应式的:Vue会重新修补那些会改变原数组元素的方法,当这些方法被调用的时候就会调用dep.notify方法,来更新视图。Vue重新修补数组的方法:‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’, ‘sort’, ‘reverse’

响应式处理过程:
在这里插入图片描述

Vue的虚拟DOM

在小编的前几次博客中已经写过虚拟DOM的相关概念了,再来复习一下:

  • 什么是虚拟DOM:虚拟DOM是使用JavaScript对象描述真实DOM
  • Vue.js中的虚拟DOM借鉴Snabbdom,并添加了Vue.js的特性
    • 例如:指令和组件机制。
  • 为什么要使用虚拟DOM:避免直接操作DOM,提高开发效率;作为一个中间层可以跨平台;虚拟DOM不一定可以提高性能,首次渲染的时候会增加开销,复杂视图情况下提升渲染性能。
h函数:就是源码中的vm.$createElement(tag,data,children,normalizeChildren)
          -- tag:标签名或者组件对象
          -- data:描述tag,可以设置DOM的属性或者标签的属性,可以绑定事件
          -- children:tag中的文本内容或者子节点
          -- normalizeChildren: 把文本内容转换成数组包裹的vnode的节点

        h函数的返回结果:VNode(虚拟DOM) 的核心属性:
          -- tag  调用h函数传入的tag
          -- data  调用h函数传入的
          -- children   调用h函数传入的
          -- text
          -- elm  记录的真实DOM
          -- key  复用当前这个元素
VNode的创建过程:h函数(就是createElement函数)

当用户传入render函数的时候,调用$createElement函数,当模板编译生成的render的时候,调用_c函数。区别是最后一个参数是在_c里面是false,在$createElement里面是true

createElm 函数:把vnode转换为DOM元素,并且插入到DOM树上,而且还会触发一些相应的钩子(create钩子函数)

虚拟DOM创建过程的整体分析:

在这里插入图片描述

v-for中key的作用以及好处:

key的作用:以便它能够跟踪每个节点的身份,在进行比较的时候,会基于 key 的变化重新排列元素顺序。从而重用和重新排序现有元素,并且会移除 key 不存在的元素。方便让 vnode 在 diff 的过程中找到对应的节点,然后成功复用。     好处:减少了DOM操作,提升渲染性能 
Vue的模板编译过程

模板编译过程:

  • compileToFunctions:模板编译的入口函数 。定义位置:src\compiler\create-compiler.js 核心:先去找缓存中编译的结果,如果有的话直接返回,没有的话,开始编译,并且把编译的字符串形式的js代码转换为函数形式,最后缓存并且进行返回。

  • 模板入口函数中最终调用了compile函数来进行编译。compile函数在文件src\compiler\create-compiler.js中定义。 compile核心:合并选项,调用baseCompile进行编译,记录错误和信息,返回编译好的compile对象

  • baseCompile:定义位置:src\compiler\index.js。 baseCompile函数核心: 通过parse函数把模板转换成 ast 抽象语法树,通过optimize函数优化抽象语法树AST;通过generate 把抽象语法树生成字符串形式的 js 代码;最后返回ast抽象语法树和渲染函数render,和静态渲染函数staticRenderFns(生成静态 VNode 树)。

  • parse函数:作用:把模板字符串转换成AST对象。位置:src\compiler\parser\index.js。 处理过程:parse函数在处理过程中,会依次遍历HTML模板字符串,把HTML模板字符串转换成AST对象,HTML中的属性和指令都会记录到AST对象的属性上。parse处理完毕之后生成了AST对象

  • optimize函数: 内部标记AST对象及其子对象的静态节点和静态根节点。

  • generate函数:把抽象语法树生成字符串形式的js代码

// 什么事抽象语法树:抽象语法树简称AST;使用对象的形式描述树形的代码结构;此处的抽象语法树是用来描述树形结构的HTML字符串
// 为什么要使用抽象语法树:模板字符串转换成AST后,可以通过AST对模板做优化处理;标记模板中的静态内容(标签只用的纯文本内容),在patch的时候直接跳过静态内容;在patch的过程中静态内容不需要对比和重新渲染,从而优化性能。
// 在线转换AST网站:http://astexplorer.net

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值