nextTick
在 Vue 中是一个很出名的工具函数,我们在实际运用的时候也经常会用到,那么它实际上到底有什么样的作用,Vue 中又是如何设计的,我们在日常中有什么场景是可以借鉴的。
我们以 Vue 最新的 v2.6.14 版本来分析,链接 https://github.com/vuejs/vue/blob/v2.6.14/src/core/util/next-tick.js
nextTick
是个什么东西,参考 Vue 2 的官方 API 文档:https://cn.vuejs.org/v2/api/#Vue-nextTick
可以看出是执行一个回调函数,我们这里可以成为一个任务,那在 Vue 中文档已经讲明白了,在下次 DOM 更新循环结束后执行这个任务(回调),这样你就可以取到更新后的 DOM 了。
先来看下 nextTick 全部的代码,把flow相关去掉,加上我们自己的注释:
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// 是否使用的是 MicroTask,如 Promise MutationObserver
// 如果浏览器不支持 则会使用 MacroTask setImmediate setTimeout
// 相关进一步知识可以参考 浏览器 eventloop 相关文章
export let isUsingMicroTask = false
// 储存所有的 callback 队列,可以认为是一个个任务
const callbacks = []
// 是否在等待执行
let pending = false
// 依次执行任务队列,循环 & 执行
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// 实现异步的函数,从名字上看下一个 tick,即一个 timer
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
// 原生 Promise 异步
const p = Promise.resolve()
timerFunc = () => {
// 利用 promise.then 实现,一个 micro task 之后执行 flushCallbacks
p.then(flushCallbacks)
// In problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 降级使用 MutationObserver
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
// 触发textNode的改变,进而触发MutationObserver的回调执行 flushCallbacks
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
// 直接利用 setImmediate
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
// 经典的 setTimeout
setTimeout(flushCallbacks, 0)
}
}
// 主实现
export function nextTick (cb, ctx) {
let _resolve
// 往 callbacks 队列中添加一个一个任务
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// 如果不是在等待中,即上一轮的callbacks任务队列已经执行完毕
// 那么就进入等待状态,重新进入新一轮的等待下一个timer然后执行新一轮存下来的callbacks任务队列
if (!pending) {
pending = true
timerFunc()
}
// nextTick的另一种用法,nextTick().then()
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
我们可以看出来,代码虽然不多,但是处理的情况还是很多的,也有很多兼容性的处理。如果我们来翻译下,nextTick 最核心的实现就是:拿一个队列存储所有要执行的任务,在下一个tick(异步)执行这些任务。
那根据这个核心实现,在不考虑兼容性和异常的情况下,我们可以实现一个极简版本的 nextTick:
let pending = false
const tasks = []
const flushCallbacks = () => {
pending = false
tasks.forEach(task => task())
tasks.length = 0
}
const p = Promise.resolve()
const timerFunc = () => {
p.then(flushCallbacks)
}
function nextTick(task) {
tasks.push(task)
if (!pending) {
pending = true
timerFunc()
}
}
短短20行,但是功能很核心也很强大,我们可以像这样使用:
const task1 = () => console.log('1')
const task2 = () => console.log('2')
console.log('before')
nextTick(task1)
nextTick(task2)
console.log('after')
// 运行的结果:before after 1 2
这个时候,相信你已经更进一步理解了 nextTick
:将需要异步执行的任务收集起来在下一个 tick 依次执行他们。
那为什么需要 nextTick
呢,我们不能直接执行这些任务吗?在 Vue 中的话,官网也给到了大家答案,详情 https://cn.vuejs.org/v2/guide/reactivity.html#%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0%E9%98%9F%E5%88%97。
如果简化来理解的话就是:为了更好的性能,将更新 DOM 操作存放在异步更新队列中,在下一个 tick 统一进行更新 DOM 操作。
试想下,如果我们每更新一次数据,Vue 就需要去更新一次 DOM 操作的话,得有多卡顿,因为日常我们处理逻辑一定是这样的:
const data = {
title: 'hello',
desc: 'world'
}
this.msg = data.title
this.context = data.desc
这个还是一个局部场景,更别想说,我们的整个 Vue 应用的数据更新,DOM 更新了。
所以 Vue 中就采用了异步更新队列这种方式来进行优化,也就是依赖上边我们分析的 nextTick
所做的最核心的事情。
在 nextTick
之中,我们可以从其中学到什么或者我们可以进一步了解什么呢?
看出这里边对于队列的操作(当然,用数组模拟的,本质是一样的):队列里添加任务,执行队列里的任务,清空队列。
队列是一个我们十分常用的数据结构,上边所提到的 eventloop,你会发现和 nextTick 本质是一样的,只是变得更复杂了,存在多个队列的情况,需要处理。
我们知道了部分 timerFunc
的实现,相对应的也就是我们需要知道,哪些 API 的操作是异步的,以及是哪种异步处理(MacroTask、MicroTask),他们之间有什么差异和使用的影响,我们遇到异步场景的时候应该如何去选择。
还有一个点,这里用到了降级的方案 setTimeout
,传的第二个参数是 0,那么这个时候的效果是啥样的;setTimeout 还可以有其他的什么用法,到底可以有几个参数,返回值是啥类型的,什么时候需要我们手工去 clearTimeout。
相对应的延伸,就是大名鼎鼎的 eventloop 相关知识,也需要去区分浏览器环境和 Node.js 环境。
异步和队列碰撞在一起,可以有很多火花。
我们有很多时候时候都需要处理异步任务,而对于这些任务的处理,最合适的数据结构就是队列了,例如大名鼎鼎的 async 库 https://github.com/caolan/async 简直就是把异步玩到了极致,里边有很多很好的实现思路以及技巧,感兴趣的也是可以深入了解的。
我们的现实需求也一样,例如,在小程序场景中,不能超出10个的并发请求,超出的请求会被取消掉,所以我们需要对请求进行封装一层,在mpx中是封装为了mpx-fetch,而且我们还要求了高低优先级两种请求,这种情况,就需要我们借助于队列来实现我们的需求。
在 flushCallbacks
中,我们看到了一个技巧,正常我们自己的简单实现中,是直接便利 callbacks 然后执行的,而 Vue 中则不是,他是复制了一份新的,然后循环执行的。
这么做的原因,其实是考虑了一种特殊情况,如果某一个 callback 执行的时候,又一次调用了 nextTick,进而更新了 callbacks,那这个时候的执行就不是我们所期望的了。所以需要先拷贝一份原有的,即使在 cb 中更新了 callbacks 也不影响我们的循环和执行,符合预期。
这是一个很严谨的地方,我们在实际场景中,也要有这种思考和意识。
同时这个问题还可以有很多的延伸,针对于数组循环,正向循环和逆向循环有啥区别吗,是不是都一样;以及我们用 for 循环和用数组本身的 forEach 会有啥不一样吗;还有 for 循环的终止条件,我们写 i < array.length
和 const len = array.length; i < len
有啥区别没有?
Promise 是一个很好的东西,相当有用,我们需要深入理解并使用它。这里有一个比较有意思的一个点是 nextTick
的返回值处理,应用到了一个技巧:外部如何更新 Promise 的状态,即你所看到的 _resolve
这个变量的作用。
Promise,一个各大厂基本都在考察的,Promise有哪些规范,包含哪些定义,哪些API,如何实现一个 Promise。
希望你去深入学习和理解它,做到精通 Promise!
isNative
的处理,他是如何判断的本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/vXPfiOM3QevqMVqGlKevQA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。