每日一题之Vue的异步更新实现原理是怎样的?

news2024/11/18 6:44:00

最近面试总是会被问到这么一个问题:在使用vue的时候,将for循环中声明的变量i从1增加到100,然后将i展示到页面上,页面上的i是从1跳到100,还是会怎样?答案当然是只会显示100,并不会有跳转的过程。

怎么可以让页面上有从1到100显示的过程呢,就是用setTimeout或者Promise.then等方法去模拟。

讲道理,如果不在vue里,单独运行这段程序的话,输出一定是从1到100,但是为什么在vue中就不一样了呢?

for(let i=1; i<=100; i++){
    console.log(i);
}

这就涉及到Vue底层的异步更新原理,也要说一说nextTick的实现。不过在说nextTick之前,有必要先介绍一下JS的事件运行机制。

JS运行机制

众所周知,JS是基于事件循环的单线程的语言。
执行的步骤大致是:

  1. 当代码执行时,所有同步的任务都在主线程上执行,形成一个执行栈
  2. 在主线程之外还有一个任务队列(task queue),只要异步任务有了运行结果就在任务队列中放置一个事件;
  3. 一旦执行栈中所有同步任务执行完毕(主线程代码执行完毕),此时主线程不会空闲而是去读取任务队列。此时,异步的任务就结束等待的状态被执行。
  4. 主线程不断重复以上的步骤。 我们把主线程执行一次的过程叫一个tick,所以nextTick就是下一个tick的意思,也就是说用nextTick的场景就是我们想在下一个tick做一些事的时候。

所有的异步任务结果都是通过任务队列来调度的。而任务分为两类:宏任务(macro task)和微任务(micro task)。它们之间的执行规则就是每个宏任务结束后都要将所有微任务清空。
常见的宏任务有setTimeout/MessageChannel/postMessage/setImmediate,微任务有MutationObsever/Promise.then

nextTick原理

派发更新

大家都知道vue的响应式的靠依赖收集和派发更新来实现的。在修改数据之后的派发更新过程,会触发setter的逻辑,执行dep.notify()

// src/core/observer/watcher.js
class Dep {
    notify() {
        //subs是Watcher的实例数组
        const subs = this.subs.slice()
        for(let i=0, l=subs.length; i<l; i++){
            subs[i].update()
        }
    }
}

遍历subs里每一个Watcher实例,然后调用实例的update方法,下面我们来看看update是怎么去更新的:

class Watcher {
    update() {
        ...
        //各种情况判断之后
        else{
            queueWatcher(this)
        }
    }
}

参考 前端进阶面试题详细解答

update执行后又走到了queueWatcher,那就继续去看看queueWatcher干啥了(希望不要继续套娃了:

//queueWatcher 定义在 src/core/observer/scheduler.js
const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let waiting = false
let flushing = false
let index = 0

export function queueWatcher(watcher: Watcher) {
    const id = watcher.id
    //根据id是否重复做优化
    if(has[id] == null){
        has[id] = true
        if(!flushing){
            queue.push(watcher)
        }else{
            let i=queue.length - 1
            while(i > index && queue[i].id > watcher.id){
                i--
            }
            queue.splice(i + 1, 0, watcher)
        }

        if(!waiting){
            waiting = true
            //flushSchedulerQueue函数: Flush both queues and run the watchers
            nextTick(flushSchedulerQueue)
        }
    }
}

这里queue在pushwatcher时是根据idflushing做了一些优化的,并不会每次数据改变都触发watcher的回调,而是把这些watcher先添加到⼀个队列⾥,然后在nextTick后执⾏flushSchedulerQueue

flushSchedulerQueue函数是保存更新事件的queue的一些加工,让更新可以满足Vue更新的生命周期。

这里也解释了为什么for循环不能导致页面更新,因为for是主线程的代码,在一开始执行数据改变就会将它push到queue里,等到for里的代码执行完毕后i的值已经变化为100时,这时vue才走到nextTick(flushSchedulerQueue)这一步。

nextTick源码

接着打开vue2.x的源码,目录core/util/next-tick.js,代码量很小,加上注释才110行,是比较好理解的。

const callbacks = []
let pending = false

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 = true
    timerFunc()
  }

首先将传入的回调函数cb(上节的flushSchedulerQueue)压入callbacks数组,最后通过timerFunc函数一次性解决。

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
    }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

timerFunc下面一大片if else是在判断不同的设备和不同情况下选用哪种特性去实现异步任务:优先检测是否原生⽀持Promise,不⽀持的话再去检测是否⽀持MutationObserver,如果都不行就只能尝试宏任务实现,首先是setImmediate,这是⼀个⾼版本 IE 和 Edge 才⽀持的特性,如果都不⽀持的话最后就会降级为 setTimeout 0。

这⾥使⽤callbacks⽽不是直接在nextTick中执⾏回调函数的原因是保证在同⼀个 tick 内多次执⾏nextTick,不会开启多个异步任务,⽽把这些异步任务都压成⼀个同步任务,在下⼀个 tick 执⾏完毕。

nextTick使用

nextTick不仅是vue的源码文件,更是vue的一个全局API。下面来看看怎么使用吧。

当设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环tick中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用数据驱动的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。

官网用例:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据

vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

并且因为$nextTick() 返回一个 Promise 对象,所以也可以使用async/await 语法去处理事件,非常方便。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149486.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机基础——计算机应用领域以及未来发展趋势

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.计算机应用领域 1.信息管理 2.过程控制 3.计算机辅助技术 1&#xff09…

一天天过去,每天该如何过?

刚跨了年&#xff0c;又快到春节&#xff0c;日子就这样一天天过去。如何过好这一生是个大命题&#xff0c;不如简化点考虑下如何过好一天&#xff1f;人的时间大体分为两类&#xff1a;主动的&#xff0c;可以自由支配&#xff1b;被动的&#xff0c;等着被安排。过去我在互联…

MCU-51:LCD1602详解

目录一、LCD1602简介1.1 显示原理1.2 引脚及应用电路1.3 技术参数1.4 引脚功能1.5 指令集1.6 连接方式二、时序图2.1 写时序2.2 读时序三、代码演示3.1 LCD1602.c3.2 示例注意&#xff1a;一定要看一、LCD1602简介 LCD1602&#xff08;Liquid Crystal Display&#xff09;液晶…

能量加油站Java上

1、final 在 Java 中有什么作用&#xff1f; 1、final 修饰的类叫最终类&#xff0c;该类不能被继承。2、final 修饰的方法不能被重写3、final 修饰的变量叫常量&#xff0c;常量必须初始化&#xff0c;初始化之后值就不能被修改 2、Math.round() 指向上取整 补充 Double.do…

01背包问题再探

原题&#xff1a; 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi&#xff0c;价值是 wi。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。 输出最大价值。 输入格式 第一行两个整…

盖子的c++小课堂——第五讲:for 循环

前言 hi&#xff0c;大家好&#xff0c;我是盖子的盖&#xff0c;最近大家都放假了吗&#xff0c;反正我还没有&#xff0c;我们期末考才刚开始考呜呜呜&#xff0c;真羡慕那些放假了的童鞋们~~(╥╯^╰╥)~~ 好啦&#xff0c;废话不多说&#xff0c;开始今天的小课堂吧~~ 上…

厚积薄发打卡Day113:Debug设计模式:设计原则(一)<开闭原则、依赖倒置、单一职责>

厚积薄发打卡Day113&#xff1a;Debug设计模式&#xff1a;设计原则&#xff08;一&#xff09;<开闭原则、依赖倒置、单一职责> 开闭原则 定义 一个软件实体如类、模块和函数应该对扩展开放&#xff0c;对修改关闭。其优点&#xff1a;提高软件系统的可复用性及可维护…

JavaEE多线程-创建线程(Thread)

目录一、线程(Thread)1.1 Thread类中的构造方法1.2 启用线程的方法二、创建第一个多线程三、多线程并发执行简单演示四、多线程并发执行的优势五、Thread的常见构造方法和属性5.1 属性5.2 方法六、中断线程七、线程等待一、线程(Thread) 线程是操作系统中的概念. 操作系统内核…

LeetCode二叉树经典题目(六):特殊位置构造二叉树

目录 21. LeetCode404. 左叶子之和 22.LeetCode513. 找树左下角的值 23. LeetCode112. 路径总和 24. LeetCode113. 路径总和 II 25. LeetCode106. 从中序与后序遍历序列构造二叉树 26. LeetCode105. 从前序与中序遍历序列构造二叉树​编辑 27. LeetCode654. 最大二叉树 …

LED、Mini LED、Micro LED、LCD、OLED技术

1、传统led、miniled、microled的异同 2、OLED OLED&#xff08;Organic Light-Emitting Diode&#xff09;&#xff0c;又称为有机电激光显示、有机发光半导体&#xff08;Organic Electroluminescence Display&#xff0c;OLED&#xff09;。OLED属于一种电流型的有机发光器…

S32K144-hello_word点灯

官方提供了很多的参考例程&#xff0c;每个历程分别配置了不同的外设&#xff0c;这里挨个尝试解读一下。 示例效果 RGB红灯绿灯交替闪烁。 导入示例 示例文件所在目录&#xff1a; 该示例使用PCC和PORT模块交替切换两个LED。 硬件连接 配置引脚功能 生成代码 S32DS自带引…

C#上位机基础学习_登录窗体的创建方法和步骤

C#上位机基础学习_登录窗体的创建方法和步骤 本次和大家分享如何制作一个简单的登录窗体。具体的方法和步骤可以参考以下内容: 如下图所示,打开Visual Studio 2019,新建一个Windows 窗体应用(.NET Framework), 如下图所示,在窗体中添加Label标签、Text文本框、Button按…

Java中常用API总结(5)—— Object类中的深克隆和浅克隆

对象克隆一、前言二、浅克隆1.概述2.实例1️⃣思路2️⃣继承cloneable接口底层原理3️⃣重写clone方法底层原理3.代码实现三、深克隆1.概述2.实例3.代码实现四、结语一、前言 本文将详细讲述Object类中的对象克隆方法&#xff0c;其中包含深克隆和浅克隆&#xff0c;两者有一定…

算法竞赛100天第2天——STL IN C++(算法竞赛必备知识总结汇总)

本文已收录于专栏 &#x1f332;《百日算法竞赛》&#x1f332; 目录 前言&#xff1a; 序列容器 序列的要求&#xff1a; 1.vector vector常用方法 vector遍历 2、deque 头文件 方法 3、list 头文件 方法 4、queue 方法&#xff1a; 关联容器 1、map 2、se…

SWPUCTF 2022新生赛部分wp

&#x1f60b;大家好&#xff0c;我是YAy_17&#xff0c;是一枚爱好网安的小白。 本人水平有限&#xff0c;欢迎各位大佬指点&#xff0c;一起学习&#x1f497;&#xff0c;一起进步⭐️。⭐️此后如竟没有炬火&#xff0c;我便是唯一的光。⭐️[SWPUCTF 2022 新生赛]ez_ez_ph…

【算法】链表

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录链表数组转链表链表转数组往链表的前面增加一个节点获得指定位置的值在链表的末尾增加一个节点在…

我最近发现的一些问题。

‍‍ 大家好&#xff0c;我是小z&#xff0c;也可以叫我阿粥~这段时间看了不少分析报告&#xff0c;启发颇多。也发现有一些普遍存在且很容易误导分析师的问题。在聊问题之前&#xff0c;先给大家分享一个&#xff08;我刚写的&#xff09;小故事&#xff1a;有一个学生…

Pytorch 多项式拟合

目录 1、训练误差和泛化误差 2、独立同分布假设 3、欠拟合和过拟合 4、多项式回归 1、训练误差和泛化误差 训练误差&#xff08;training error&#xff09;是指&#xff0c; 模型在训练数据集上计算得到的误差。 泛化误差&#xff08;generalization error&#xff09;是指…

【OpenCV 例程 300篇】255.OpenCV 实现图像拼接

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】255.OpenCV 实现图像拼接 6.2 OpenCV 实现图像拼接 OpenCV中图像的数据结构是Numpy数组&#xff0c;使用切片方法可以实现图像的裁剪&#xff0c;使用数组堆叠方法可以实现图像的拼接。 Numpy 函数…

K8S Replication Controller 示例

K8S Replication Controller Replication Controller可确保在任何时间运行指定数量的pod副本。换句话说&#xff0c;ReplicationController确保一个pod或一组同类pod始终处于可用状态。 可以理解为Replication Controller &#xff08;RC&#xff09;是基于 K8S Pod对象之上的…