Vue源码系列讲解——实例方法篇【三】(生命周期相关方法)
目录
0. 前言
1. vm.$mount
1.1 用法回顾
1.2 内部原理
2. vm.$forceUpdate
2.1 用法回顾
2.2 内部原理
3. vm.$nextTick
3.1 用法回顾
3.2 JS的运行机制
3.3 内部原理
能力检测
执行回调队列
4. vm.$destory
4.1 用法回顾
4.2 内部原理
0. 前言
与生命周期相关的实例方法有4个,分别是vm.$mount
、vm.$forceUpdate
、vm.$nextTick
和vm.$destory
。其中,$forceUpdate
和$destroy
方法是在lifecycleMixin
函数中挂载到Vue
原型上的,$nextTick
方法是在renderMixin
函数中挂载到Vue
原型上的,而$mount
方法是在跨平台的代码中挂载到Vue
原型上的。代码如下:
export function lifecycleMixin (Vue) {Vue.prototype.$forceUpdate = function () {}Vue.prototype.$destroy = function (fn) {}
}export function renderMixin (Vue) {Vue.prototype.$nextTick = function (fn) {}
}
当执行lifecycleMixin
和renderMixin
函数后,会向Vue
原型上挂载相应的实例方法。
接下来,我们就来分析这4个与生命周期相关的实例方法其内部的原理都是怎样的。
1. vm.$mount
1.1 用法回顾
在介绍方法的内部原理之前,我们先根据官方文档示例回顾一下它的用法。
vm.$mount( [elementOrSelector] )
-
参数:
{Element | string} [elementOrSelector]
{boolean} [hydrating]
-
返回值:
vm
- 实例自身 -
作用:
如果
Vue
实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用vm.$mount()
手动地挂载一个未挂载的实例。如果没有提供
elementOrSelector
参数,模板将被渲染为文档之外的的元素,并且你必须使用原生DOM API
把它插入文档中。这个方法返回实例自身,因而可以链式调用其它实例方法。
1.2 内部原理
关于该方法的内部原理在介绍生命周期篇的模板编译阶段中已经详细分析过,此处不再重复。
2. vm.$forceUpdate
2.1 用法回顾
在介绍方法的内部原理之前,我们先根据官方文档示例回顾一下它的用法。
vm.$forceUpdate()
-
作用:
迫使
Vue
实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
2.2 内部原理
通过用法回顾我们知道, 该方法是用来迫使Vue
实例重新渲染的。也就是说,当调用了该方法,当前实例会立即重新渲染。
在分析原理之前,我们先思考这样一个问题:什么情况下实例会重新渲染?那就是当实例依赖的数据发生变化时,变化的数据会通知其收集的依赖列表中的依赖进行更新,在之前的文章中我们说过,收集依赖就是收集watcher
,依赖更新就是watcher
调用update
方法更新,所以实例依赖的数据发生变化时,就会通知实例watcher
去执行update
方法进行更新。
那么我们就知道了,实例的重新渲染其实就是实例watcher
执行了update
方法。
OK,有了这个概念之后,接下来我们来分析$forceUpdate
源码实现,代码如下:
Vue.prototype.$forceUpdate = function () {const vm: Component = thisif (vm._watcher) {vm._watcher.update()}
}
可以看到,源码实现的逻辑跟我们上面分析的是一致的。在之前的文章中我们说过,当前实例的_watcher
属性就是该实例的watcher
,所以要想让实例重新渲染,我们只需手动的去执行一下实例watcher
的update
方法即可。
3. vm.$nextTick
vm.$nextTick
是全局 Vue.nextTick
的别名,其用法相同。
3.1 用法回顾
在介绍方法的内部原理之前,我们先根据官方文档示例回顾一下它的用法。
vm.$nextTick( [callback] )
-
参数:
{Function} [callback]
-
用法:
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法
Vue.nextTick
一样,不同的是回调的this
自动绑定到调用它的实例上。2.1.0 起新增:如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。请注意 Vue 不自带 Promise 的 polyfill,所以如果你的目标浏览器不是原生支持 Promise (IE:你们都看我干嘛),你得自行 polyfill。
从上面的官方文档对$nextTick
方法的介绍中我们似乎还是不能理解该方法的作用,那么我们举个例子看一下,如下:
<template><div id="example">{{message}}</div>
</template>
<script>var vm = new Vue({el: '##example',data: {message: '123'}})vm.message = 'new message' // 更改数据console.log(vm.$el.innerHTML) // '123'Vue.nextTick(function () {console.log(vm.$el.innerHTML) // 'new message'})
</script>
在上面例子中,当我们更新了message
的数据后,立即获取vm.$el.innerHTML
,发现此时获取到的还是更新之前的数据:123。但是当我们使用nextTick
来获取vm.$el.innerHTML
时,此时就可以获取到更新后的数据了。这是为什么呢?
这里就涉及到Vue
中对DOM
的更新策略了,Vue
在更新 DOM
时是异步执行的。只要侦听到数据变化,Vue
将开启一个事件队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher
被多次触发,只会被推入到事件队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM
操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue
刷新事件队列并执行实际 (已去重的) 工作。
在上面这个例子中,当我们通过 vm.message = 'new message'
更新数据时,此时该组件不会立即重新渲染。当刷新事件队列时,组件会在下一个事件循环“tick”中重新渲染。所以当我们更新完数据后,此时又想基于更新后的 DOM
状态来做点什么,此时我们就需要使用Vue.nextTick(callback)
,把基于更新后的DOM
状态所需要的操作放入回调函数callback
中,这样回调函数将在 DOM
更新完成后被调用。
OK,现在大家应该对nextTick
是什么、为什么要有nextTick
以及怎么使用nextTick
有个大概的了解了。那么问题又来了,Vue
为什么要这么设计?为什么要异步更新DOM
?这就涉及到另外一个知识:JS
的运行机制。
3.2 JS的运行机制
我们知道 JS
执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:
- 所有同步任务都在主线程上执行,形成一个执行栈(
execution context stack
)。 - 主线程之外,还存在一个"任务队列"(
task queue
)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。 - 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
主线程的执行过程就是一个 tick
,而所有的异步结果都是通过 “任务队列” 来调度。 任务队列中存放的是一个个的任务(task
)。 规范中规定 task
分为两大类,分别是宏任务(macro task
) 和微任务(micro task
),并且每执行完一个个宏任务(macro task
)后,都要去清空该宏任务所对应的微任务队列中所有的微任务(micro task
),他们的执行顺序如下所示:
for (macroTask of macroTaskQueue) {// 1. 处理当前的宏任务handleMacroTask();// 2. 处理对应的所有微任务for (microTask of microTaskQueue) {handleMicroTask(microTask);}
}
在浏览器环境中,常见的
- 宏任务(
macro task
) 有setTimeout
、MessageChannel
、postMessage
、setImmediate
; - 微任务(
micro task
)有MutationObsever
和Promise.then
。
OK,有了这个概念之后,接下来我们就进入正菜:从Vue
源码角度来分析nextTick
的实现原理。
3.3 内部原理
nextTick
的定义位于源码的src/core/util/next-tick.js
中,其大概可分为两大部分:
- 能力检测
- 根据能力检测以不同方式执行回调队列
能力检测
Vue
在内部对异步队列尝试使用原生的 Promise.then
、MutationObserver
和 setImmediate
,如果执行环境不支持,则会采用 setTimeout(fn, 0)
代替。
宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务。
这一部分的源码如下:
let microTimerFunc
let macroTimerFunc
let useMacroTask = false/* 对于宏任务(macro task) */
// 检测是否支持原生 setImmediate(高版本 IE 和 Edge 支持)
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {macroTimerFunc = () => {setImmediate(flushCallbacks)}
}
// 检测是否支持原生的 MessageChannel
else if (typeof MessageChannel !== 'undefined' && (isNative(MessageChannel) ||// PhantomJSMessageChannel.toString() === '[object MessageChannelConstructor]'
)) {const channel = new MessageChannel()const port = channel.port2channel.port1.onmessage = flushCallbacksmacroTimerFunc = () => {port.postMessage(1)}
}
// 都不支持的情况下,使用setTimeout
else {macroTimerFunc = () => {setTimeout(flushCallbacks, 0)}
}/* 对于微任务(micro task) */
// 检测浏览器是否原生支持 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {const p = Promise.resolve()microTimerFunc = () => {p.then(flushCallbacks)}
}
// 不支持的话直接指向 macro task 的实现。
else {// fallback to macromicroTimerFunc = macroTimerFunc
}
首先声明了两个变量: microTimerFunc
和 macroTimerFunc
,它们分别对应的是 micro task
的函数和 macro task
的函数。对于 macro task
的实现,优先检测是否支持原生 setImmediate
,这是一个高版本 IE
和Edge
才支持的特性,不支持的话再去检测是否支持原生的 MessageChannel
,如果也不支持的话就会降级为 setTimeout 0
;而对于 micro task
的实现,则检测浏览器是否原生支持 Promise
,不支持的话直接指向 macro task
的实现。
执行回调队列
接下来就进入了核心函数nextTick
中,如下:
const callbacks = [] // 回调队列
let pending = false // 异步锁// 执行队列中的每一个回调
function flushCallbacks () {pending = false // 重置异步锁// 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份并清空回调函数队列const copies = callbacks.slice(0)callbacks.length = 0// 执行回调函数队列for (let i = 0; i < copies.length; i++) {copies[i]()}
}export function nextTick (cb?: Function, ctx?: Object) {let _resolve// 将回调函数推入回调队列callbacks.push(() => {if (cb) {try {cb.call(ctx)} catch (e) {handleError(e, ctx, 'nextTick')}} else if (_resolve) {_resolve(ctx)}})// 如果异步锁未锁上,锁上异步锁,调用异步函数,准备等同步函数执行完后,就开始执行回调函数队列if (!pending) {pending = trueif (useMacroTask) {macroTimerFunc()} else {microTimerFunc()}}// 如果没有提供回调,并且支持Promise,返回一个Promiseif (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})}
}
首先,先来看 nextTick
函数,该函数的主要逻辑是:先把传入的回调函数 cb
推入 回调队列callbacks
数组,同时在接收第一个回调函数时,执行能力检测中对应的异步方法(异步方法中调用了回调函数队列)。最后一次性地根据 useMacroTask
条件执行 macroTimerFunc
或者是 microTimerFunc
,而它们都会在下一个 tick 执行 flushCallbacks
,flushCallbacks
的逻辑非常简单,对 callbacks
遍历,然后执行相应的回调函数。
nextTick
函数最后还有一段逻辑:
if (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})
}
这是当 nextTick
不传 cb
参数的时候,提供一个 Promise 化的调用,比如:
nextTick().then(() => {})
当 _resolve
函数执行,就会跳到 then
的逻辑中。
这里有两个问题需要注意:
-
如何保证只在接收第一个回调函数时执行异步方法?
nextTick
源码中使用了一个异步锁的概念,即接收第一个回调函数时,先关上锁,执行异步方法。此时,浏览器处于等待执行完同步代码就执行异步代码的情况。 -
执行
flushCallbacks
函数时为什么需要备份回调函数队列?执行的也是备份的回调函数队列?因为,会出现这么一种情况:
nextTick
的回调函数中还使用nextTick
。如果flushCallbacks
不做特殊处理,直接循环执行回调函数,会导致里面nextTick
中的回调函数会进入回调队列。
以上就是对 nextTick
的源码分析,我们了解到数据的变化到 DOM
的重新渲染是一个异步过程,发生在下一个 tick。当我们在实际开发中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick
后执行。
4. vm.$destory
4.1 用法回顾
在介绍方法的内部原理之前,我们先根据官方文档示例回顾一下它的用法。
vm.$destroy()
-
用法:
完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
触发
beforeDestroy
和destroyed
的钩子。
4.2 内部原理
关于该方法的内部原理在介绍生命周期篇的销毁阶段中已经详细分析过,此处不再重复。
相关文章:
Vue源码系列讲解——实例方法篇【三】(生命周期相关方法)
目录 0. 前言 1. vm.$mount 1.1 用法回顾 1.2 内部原理 2. vm.$forceUpdate 2.1 用法回顾 2.2 内部原理 3. vm.$nextTick 3.1 用法回顾 3.2 JS的运行机制 3.3 内部原理 能力检测 执行回调队列 4. vm.$destory 4.1 用法回顾 4.2 内部原理 0. 前言 与生命周期相关…...

百度SEO工具,自动更新网站的工具
在网站SEO的过程中,不断更新网站内容是提升排名和吸引流量的关键之一。而对于大多数网站管理员来说,频繁手动更新文章并进行SEO优化可能会是一项繁琐且耗时的任务。针对这一问题,百度自动更新文章SEO工具应运而生,它能够帮助网站管…...

供应链|NUS覃含章MS论文解读:数据驱动下联合定价和库存控制的近似方法 (二)
编者按 本次解读的文章发表于 Management Science,原文信息:Hanzhang Qin, David Simchi-Levi, Li Wang (2022) Data-Driven Approximation Schemes for Joint Pricing and Inventory Control Models. https://doi.org/10.1287/mnsc.2021.4212 文章在数…...

删除有序数组中的重复项Ⅱ
问题 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 说明…...
Java底层自学大纲_数据结构和算法篇
数据结构和算法专题_自学大纲所属类别学习主题建议课时(h) A 数据结构和算法001 数据结构和算法基础,时间复杂度Ot和空间复杂度Os2.5 A 数据结构和算法002 数学知识回顾-指数、对数、级数、模运算、复杂度2.5 A 数据结构和算法003 高数知识…...

群晖NAS配置WebDav结合内网穿透实现公网访问本地影视资源
文章目录 本教程解决的问题是:按照本教程方法操作后,达到的效果是:1 使用环境要求:2 配置webdav3 测试局域网使用potplayer访问webdav3 内网穿透,映射至公网4 使用固定地址在potplayer访问webdav 国内流媒体平台的内…...
Vue3报错Promise executor functions should not be async.
解决方法 加注释。。。// eslint-disable-next-line no-async-promise-executor // eslint-disable-next-line no-async-promise-executor new Promise<boolean>(async (resolve, reject) > {... }),...

(正规api接口代发布权限)短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态
短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态源头开发(本篇禁止抄袭复刻) 一、短视频矩阵系统开发者架构 云罗短视频矩阵系统saas化系统,开发层将在CAP原则基础上使用分布式架构,对此网站的整体架构采用了基于B/S三层架构模式…...
【Redis】redis通用命令
redis连接命令(客户端) 要在 redis 服务上执行命令需要一个 redis 客户端。Redis 客户端在我们之前安装redis 的src目录下,具体为/usr/local/redis/src。注意此redis实例没有设置密码,如果设置了密码需要先使用命令AUTH执行验证或…...

mysql服务治理
一、性能监控指标和解决方案 1.QPS 一台 MySQL 数据库,大致处理能力的极限是,每秒一万条左右的简单 SQL,这里的“简单 SQL”,指的是类似于主键查询这种不需要遍历很多条记录的 SQL。 根据服务器的配置高低,可能低端…...

opencv--使用直方图找谷底进行确定分割阈值
直方图原理就不说了,大家自行百度 直方图可以帮助分析图像中的灰度变化,进而帮助确定最优二值化的灰度阈值(threshold level)。如果物体与背景的灰度值对比明显,此时灰度直方图就会包含双峰(bimodal histo…...

dolphinscheduler海豚调度(四)钉钉告警
在之前的博文中,我们已经介绍了DolphinScheduler海豚调度的基本概念和工作流程,以及Shell任务和SQL任务的实践。今天,让我们来学习DolphinScheduler中的另一个重要功能:钉钉告警。 钉钉群添加机器人 在钉钉群添加机器人…...
Java-Safe Point(安全点)
虽然安全点的概念和垃圾回收相关,但是概念还是比较独立的,所以本文是从这篇文章Java-虚拟机-垃圾收集器/垃圾收集算法/GCROOT根中抽出来的 安全点safe point 当执行垃圾回收(GC)的时候,不是立马就执行的,…...

大数据旅游数据分析:基于Python旅游数据采集可视化分析推荐系统
文章目录 基于Python旅游数据采集可视化分析推荐系统一、项目概述二、项目说明三、开发环境四、功能实现五、系统页面实现用户登录注册系统首页数据操作管理价格与销量分析旅游城市和景点等级分析旅游数据评分情况分析旅游数据评论情况分析旅游景点推荐Django系统后台管理 六、…...

打造智能汽车微服务系统平台:架构的设计与实现
随着智能汽车技术的飞速发展,微服务架构在汽车行业中的应用越来越广泛。采用微服务架构可以使汽车系统更加灵活、可扩展,并且有利于快速推出新功能和服务。本文将从设计原则、关键技术、数据安全等方面,介绍如何搭建智能汽车微服务系统平台架…...

机试指南:Ch5:线性数据结构 Ch6:递归与分治
文章目录 第5章 线性数据结构1.向量 vector2.队列 queue(1)队列的特点、应用(2)基本操作(3)例题例题1:约瑟夫问题2 (难度:中等) (4)习题习题1:排队打饭 (难度:中等) 3.栈 stack(1)栈…...

展厅设计的理念是什么
1、立足当地文化 升华本地精神 ,因地制宜,深入挖掘本土文化特色,撷取其精华,灵活运用、巧妙融入,做到掌控宏观全局。 重点突出,努力打造本土拳头品牌,挖掘其内涵,拓展延伸、着重展示…...
springboot 定时任务备份mysql数据库
记录在Linux 系统上定时备份MySQL数据库 1、在代码中添加备份 package org.jeecg.modules.xczxhhr.job;import lombok.extern.slf4j.Slf4j; import org.quartz.Job; import org.quartz.JobExecutionContext;import java.io.BufferedReader; import java.io.File; import java…...
PMP考试之20240301
1、在回顾项目团队最新的绩效报告时,项目经理注意到他们的产出出现了重大下降。项目经理决定: A.增加每个团队成员在截止日期前完成任务的压力 B.增加状态报告和团队评审会议的频率 C.为表现最好的团队成员提供特别奖励 D.改善和促进团队成员之间的信任和凝聚力…...

什么是MAC地址? win10电脑查看MAC地址的多种方法
您是否知道连接到家庭网络的每件硬件都有自己的身份?正如每个设备都分配有自己的 IP 地址一样,每个硬件都有一个唯一的网络标识符。 该标识符称为MAC 地址。MAC 代表媒体访问控制。您可能需要 MAC 地址来解决网络问题或配置新设备。在 Windows 中查找您…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...