2020-09-14 Vue源码剖析

获取Vue

项目地址:https://github.com.vuejs/vue
迁出项目:git clone https://github.com.vuejs/vue.git
当前版本号:2.6.11

文件结构:

目录

源码目录
源码目录
调试环境搭建

  • 安装依赖:npm i 或者yarn

    安装到phantom.js即可终止

  • 安装rollup:npm i -g rollup

  • 修改dev脚本,添加–sourcemap,在package.js文件里

  • 运行开发命令:npm run dev 或 yarn dev会在dist目录下生成vue.js文件和vue.js.map(其主要作用就是调试过程中文件映射更好的查找源文件,即加上–sourcemap的作用)

  • 引入前面创建的vue.js,在samples/commits/index.html

    <script src="../../dist/vue.js"></script>
    

术语解释:

  • runtime:仅包含运行时,不包含编译器
  • common:cjs规范,用于webpack1
  • esm:ES模块,用于webpack2+
  • umd: universal module definition,兼容cjs和amd,用于浏览器

注:平时项目开发用webpack编译使用的版本是vue-runtime-esm.js

文件解析

src\platforms\web\entry-runtime-with-compiler.js

  • 入口文件
  • 解析模板相关选项

$mount()函数里面,先判断render函数=>再判断template=>最后el选项
$mount

模板

el
mount的作用最终都是把el和template编译为render函数,所以我们在项目后面没有el和template选项时,要在app后加上$mount(’#app’)函数

src\platforms\web\runtime\index.js

  • 安装平台patch,实现跨平台操作
  • 实现$mount(’#app’)=>mountComponent:render()=>vdom=>patch()=>dom

注:mountComponent()方法在lifecylce.js文件里

$mount

src\core\index.js

  • 初始化全局API

api

src\core\instance\index.js

  • Vue的构造函数
  • 声明实例属性和方法

vue

src\core\instance\init.js

  • 初始化

merge

创建组件实例、初始化其数据、属性、事件等
初始化
el

src\core\instance\lifecycle.js

  • mountComponent()=>updateComponent=>new Watcher()=>updateComponent()=>render()=>update()=>patch()主要作用是把虚拟dom编译成真实dom

几个生命周期钩子函数都在这里除了beforeCreate和created

$mount

  • mountComponent()-执行挂载,获取dom并转换为dom
  • new Watcher()-创建组件渲染watcher
  • updateComponent()-执行初始化或更新
  • update()-初始化或更新,将传入的vdom转换为dom,初始化时执行的是dom创建操作
  • render()- src/core/instance/render.js,渲染组件,获取vdom

整体流程捋一捋
new Vue()=>_init()=>$mount()=>mountComponent()=>new Watcher()=>updateComponent()=>render()=>_update()

一道相关面试题:谈谈vue生命周期

  • 概念:组件创建、更新和销毁过程
  • 用途:生命周期钩子使我们可以在合适的时间做合适的事情
  • 分类列举:
    - 初始化阶段:beforeCreate、created、beforeMount、mounted
    - 更新阶段:beforeUpdate、updated
    - 销毁阶段:beforeDestroy、destroyed
  • 应用:
    - created时,所有数据准备就绪,适合做数据获取、赋值等数据操作
    - mounted时,$el已生成,可以获取dom;子组件也已挂载,可以访 问它们
    - updated时,数值变化已作用于dom,可以获取dom最新状态
    - destroyed时,组件实例已销毁,适合取消定时器等操作

数据响应式
数据响应式是MVVM框架的一大特点,通过某种策略可以感知数据的变化。Vue中利用了JS语言特性Object.defineProperty(),通过定义对象属性getter/setter拦截对属性的访问
具体实现是在Vue初始化时,会调用initState,它会初始化data,props等

整体流程:
initState(vm:Component) src/core/instance/state.js
初始化数据,包括props,methods,data,computed和watch

initData核心代码是将data数据响应化

function initData (vm: Component) { 
// 执行数据响应化 observe(data, true /* asRootData */) 
}

core/observer/index.js
observe方法返回一个Observer实例
Observer对象根据数据类型执行相应的响应化操作
defineReactive定义对象属性的getter/setter,getter负责添加依赖,setter负责通知更新

core/observer/dep.js
Dep负责管理一组Watcher,包括watcher实例的增删及通知更新
大管家和小管家

Watcher解析一个表达式并收集依赖,当数值变化时触发回调函数,常用于$watch API和指令中
每个组件也会有对象的Watcher,数值变化会触发其update函数导致重新渲染

export default class Watcher {
	constructor(){}
	get(){}
	addDep(){}
	update(){}
}

dep

数组响应化
数组数据变化的侦测跟对象不同,我们操作数组通常使用push、pop、splice等方法,此时没有办法得知数据变化。所以vue中采取的策略是拦截这些方法并通知dep

src/core/observer/array.js
为数组原型中的7个可以改变内容的方法定义拦截器

Observer中覆盖数组原型

if (Array.isArray(value)) { 
	// 替换数组原型 
	protoAugment(value, arrayMethods) // value.__proto__ = arrayMethods 
	this.observeArray(value) 
}

Vue异步更新策略

Vue高效的秘诀是一套批量异步的更新策略
事件循环
事件循环Event Loop:浏览器为了协调事件处理、脚本执行、网络请求和渲染等任务而制定的工作机制

宏任务Task:代表一个个离散的、独立的工作单元。浏览器完成一个宏任务,在下一个宏任务执行开始前,会对页面进行重新渲染。主要包括创建文档对象、解析HTML、执行主线JS代码以及各种页面加载、输入、网络事件和定时器等

微任务:微任务是更小的任务,是在当前宏任务执行结束后立即执行的任务。如果存在微任务,浏览器会清空微任务之后在重新渲染。微任务的例子有Promise回调函数、DOM变化等

浏览器在一次事件循环中,只做一个宏任务,执行所有同步代码,异步代码放到异步队列中(宏任务如setTimeout定时器等放到宏任务队列中,微任务如promise.then等放到微任务队列中),在下一次事件循环前,执行微任务队列并清空。

Vue中的具体实现
nextTick(flushSchedulerQueue)
nextTick

  • 异步:只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更
  • 批量:如果同一个watcher被触发多次,只会被推入到队列中一次。去重对于避免不必要的计算和DOM操作是非常重要的。然后,在下一个事件循环"tick"中,Vue刷新队列执行实际工作。
  • 异步策略:Vue在内部对异步队列尝试使用原生的Promise.thenMutationObserversetImmediate,如果执行环境都不支持,则会采用setTimeout代替

首先当前data属性值发生改变时,会触发set赋值操作,在src\core\observer\index.js里的set中会调用dep.notify()方法,通知更新,执行入队操作

src\core\observer\dep.js中的subs是watcher数组,会调用update()方法src\core\observer\watcher.js,执行watcher入队操作

queueWatcher(watcher) src\core\observer\scheduler.js执行watcher操作

nextTick(flushSchedulerQueue) src\core\util\next-tick.js
nextTick按照特定异步策略执行队列操作

注意:
flushSchedulerQueue:刷新watchers队列
flushCallbacks:刷新callbacks数组中的回调
callbacks:[flushSchedulerQueue]
nextTick:其实做的事情就是往callbacks里放函数

虚拟DOM

虚拟DOM(Virtual DOM)是对DOM的JS抽象表示,它们是JS对象,能够描述DOM结构和关系。应用的各种状态变化会作用于虚拟DOM,最终映射到DOM上
虚拟DOM
优点:

  • 虚拟DOM轻量、快速:当它们发生变化时通过新旧虚拟DOM比对可以得到最小的DOM操作量,配合异步更新策略减少刷新频率,从而提升性能
  • 跨平台:将虚拟DOM更新转换为不同运行时特殊操作实现跨平台
  • 兼容性:还可以加入兼容性代码增强操作的兼容性

patch实现
patch src\core\vdom\patch.js
首先进行树级别的比较,可能有三种情况:增删改

  • new VNode不存在就删
  • old VNode不存在就增
  • 都存在就执行diff执行更新

diff
diff是先同层在深度递归

patchVNode
比较两个VNode,包括三种类型操作:属性更新、文本更新、子节点更新
具体规则如下:

  1. 新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren
  2. 如果新节点有子节点而老节点没有子节点,先清空老节点的文本内容,然后为其新增子节点。
  3. 当新节点没有子节点而老节点有子节点的时候,则移除该节点的所有子节点。
  4. 当新老节点都无子节点的时候,只是文本的替换

updateChildren
updateChildren主要作用是用一种较高效的方式比对新旧两个VNode的children得出最小操作补丁。执行一个双循环是传统方式,vue中针对web场景特点做了特别的算法优化
update

key的作用:

  • 判断两个VNode是否是相同节点,会进行节点的复用操作
  • 不添加会更新额外的操作,会始终认为两个节点相同

组件化机制

组件声明:Vue.component()
initAssetRegisters(Vue) src\core\global-api\assets.js

Vue.component(name, options)=>默认选项中加入组件配置=>mergeOptions=>局部组件内部就同时包含了已有的组件声明和全局组件声明

组件注册使用extend方法将配置转换为构造函数并添加到components选项,然后获取组件实例=>$mount()=>render()=>update()=>patch()

  • 全局声明:Vue.component()
  • 局部声明-:components

注:如果有父子组件
parent create
child create
parent mount
child mount
创建的过程是自上而下,挂载的过程是自下而上

组件实例创建及挂载
整体流程:
new Vue()=>$mount()=>vm._render()=>createElement()=>createComponent()=>vm._update()=>patch()=>createElm=>createComponent()

首先自定义组件
<comp foo="abc" @myClick="..."></comp>
经过编译_c('comp')
生成虚拟DOMvnode {tag:'vue-component-1-comp', data:{...}, children:{...}, componentConstructor}
得到componentInstance组件实例
在执行instance.$mount挂载
生成DOM节点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值