vue源码分析之nextTick源码分析-逐行逐析-个人困惑

nextTick的使用背景

  • 在vue项目中,经常会使用到nextTick这个api,一直在猜想其是怎么实现的,今天有幸研读了下,虽然源码又些许问题,但仍值得借鉴

核心源码解析

判断当前环境使用最合适的API并保存函数

promise

  • 判断是否支持promise,如果支持就使用Promise对象的then方法包裹要执行的 flushCallbacks函数

MutationObserver

  • 判断是否支持MutationObserver,如果支持就创建一个MutationObserver 用于监听dom改动之后执行 flushCallbacks 函数 并赋值给 observer

setImmediate

  • 判断是否支持setImmediate,如果支持就使用setImmediate包裹 flushCallbacks函数

setTimeout

  • 如果以上三种都不支持使用setTimeout包裹 flushCallbacks函数
export let isUsingMicroTask = false//是否使用微任务标志

const callbacks = []//任务对列 调用nextTick时传入的回调函数组成的数组
let pending = false//初始化 是否在进行中状态  默认是false  

function flushCallbacks() {
  //循环执行 callbacks  任务队列中的任务
  pending = false
  const copies = callbacks.slice(0)//
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    // 依次执行callbacks数组中的函数
    copies[i]()
  }
}

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // 向下兼容操作 如果支持Promise 则使用Promise
  const p = Promise.resolve()//直接返回一个resolved状态的Promise对象
  timerFunc = () => {
    p.then(flushCallbacks)
    // 在Promise的then方法中执行 flushCallbacks 函数
    if (isIOS) setTimeout(noop)//ios中在一些异常的webview中,promise结束后任务队列并没有刷新,所以强制执行setTimeout(noop)来刷新任务队列
  }
  isUsingMicroTask = true//重置使用微任务标示为true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||

  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// MutationObserver 属性支持,则使用MutationObserver 
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  // 创建一个MutationObserver 用于监听dom改动之后执行 flushCallbacks 函数 并赋值给 observer
  const textNode = document.createTextNode(String(counter))
  // 创建一个文本节点
  observer.observe(textNode, {
    characterData: true
  })
  // 每次执行timeFunc都会让文本节点的内容在0/1之间切换
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  // 切换之后将新值赋值到那个我们MutationObserver观测的文本节点上去
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// setImmediate 属性支持,则使用setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
// 以上都不支持,则使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

调用异步函数执行回调对列

入参分析

nextTick(cb?: Function, ctx?: Object) {

}
  • cb是 传入的回调函数
  • ctx是函数执行的上下文
  • 而两者又都是可选参数, 有所困惑 下文有解析

函数执行逻辑

  • 将调用nextTick是传入的执行函数添加到 callbacks中
  • 可使用call和传入的ctx修改传入的执行函数的this指向
export function nextTick(cb?: Function, ctx?: Object) {
  /*  nexttick的参数中cb不能为可选参数,如果cb参数不传将没有回调函数,
    nextTick将没有意义,并且ctx将成为第一个参数,由于是形参,ctx将顶替cb,此时ctx相当于没有了,
    resolve出去的将是一个undefined */
  // cb 是 nextTick 包裹的执行函数
  // ctx 是函数执行的上下文 
  // console.log("nextTick000",cb,ctx)
  let _resolve//伪代码
  // 向 callbacks 数组中添加一个函数
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)//修改回调函数的this指向 
      } catch (e) {
        //  异常捕获 如果cb不是函数 捕获异常
        handleError(e, ctx, 'nextTick')
      }
      console.log("00000")
    } else if (_resolve) {
      // 伪代码
      console.log('ctx')
      _resolve(ctx)
    }
  })
  console.log("9999")
  // console.log("pendingcallbacks",callbacks,pending)
  if (!pending) {
    // console.log("pendingcallback0999",callbacks,pending)
    pending = true
    timerFunc()
  }
    if (!cb && typeof Promise !== 'undefined') {//判断浏览器是否支持 Promise
//  cb不可能没有,由于传递的是形参,第一个回调函数如果没有传递,则第二个参数ctx顶上,所以cb不可能没有
// 并且如果cb没有了,则表示没有传递任何参数
    console.log("111")
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
  • 这个代码有删减,因为其余代码执行 有所困惑 下文有解析

代码分析

在这里插入图片描述

  • nexttick的参数中cb不能为可选参数,如果cb参数不传将没有回调函数,nextTick将没有意义,并且ctx将成为第一个参数,由于是形参,ctx将顶替cb,此时ctx相当于没有了,resolve出去的将是一个undefined

代码实测

this.$nextTick()

在这里插入图片描述

  • 结果很意外,得到的是当前组件的实例
  • 实在没看明白,这个ctx 组件的实例 是怎么出现在这个代码中的

依次执行nextTick

  • 循环执行 callbacks 任务队列中的任务
function flushCallbacks() {
  //循环执行 callbacks  任务队列中的任务
  pending = false
  const copies = callbacks.slice(0)//
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    // 依次执行callbacks数组中的函数
    copies[i]()
  }
}

源码

/* @flow */
/* globals MutationObserver */

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// isIE 判段运行环境是否在ie浏览器
// isIOS 判断是否是ios
//isNative  判断函数是否是由JavaScript引擎原生实现的
export let isUsingMicroTask = false//是否使用微任务标志

const callbacks = []//任务对列 调用nextTick时传入的回调函数组成的数组
let pending = false//初始化 是否在进行中状态  默认是false  

function flushCallbacks() {
  //循环执行 callbacks  任务队列中的任务
  pending = false
  const copies = callbacks.slice(0)//
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    // 依次执行callbacks数组中的函数
    copies[i]()
  }
}

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // 向下兼容操作 如果支持Promise 则使用Promise
  const p = Promise.resolve()//直接返回一个resolved状态的Promise对象
  timerFunc = () => {
    p.then(flushCallbacks)
    // 在Promise的then方法中执行 flushCallbacks 函数
    if (isIOS) setTimeout(noop)//ios中在一些异常的webview中,promise结束后任务队列并没有刷新,所以强制执行setTimeout(noop)来刷新任务队列
  }
  isUsingMicroTask = true//重置使用微任务标示为true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||

  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// MutationObserver 属性支持,则使用MutationObserver 
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  // 创建一个MutationObserver 用于监听dom改动之后执行 flushCallbacks 函数 并赋值给 observer
  const textNode = document.createTextNode(String(counter))
  // 创建一个文本节点
  observer.observe(textNode, {
    characterData: true
  })
  // 每次执行timeFunc都会让文本节点的内容在0/1之间切换
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  // 切换之后将新值赋值到那个我们MutationObserver观测的文本节点上去
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// setImmediate 属性支持,则使用setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
// 以上都不支持,则使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

export function nextTick(cb?: Function, ctx?: Object) {
   /*  nexttick的参数中cb不能为可选参数,如果cb参数不传将没有回调函数,
    nextTick将没有意义,并且ctx将成为第一个参数,由于是形参,ctx将顶替cb,此时ctx相当于没有了,
    resolve出去的将是一个undefined */
  // cb 是 nextTick 包裹的执行函数
  // ctx 是函数执行的上下文 
  // console.log("nextTick000",cb,ctx)
  let _resolve//伪代码
  // 向 callbacks 数组中添加一个函数
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)//修改回调函数的this指向 
      } catch (e) {
        //  异常捕获 如果cb不是函数 捕获异常
        handleError(e, ctx, 'nextTick')
      }
      console.log("00000")
    } else if (_resolve) {
      console.log('ctx')
      _resolve(ctx)
    }
  })
  console.log("9999")
  // console.log("pendingcallbacks",callbacks,pending)
  if (!pending) {
    // console.log("pendingcallback0999",callbacks,pending)
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {//判断浏览器是否支持 Promise
//  cb不可能没有,由于传递的是形参,第一个回调函数如果没有传递,则第二个参数ctx顶上,所以cb不可能没有
// 并且如果cb没有了,则表示没有传递任何参数
    console.log("111")
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

个人困惑

  • 本人实在是不太理解在没有传入参数时,ctx是怎么拿到的,并且返回resolve有何作用
  _resolve(ctx)

致谢

  • 感谢您百忙之中抽时间阅读我写的博客,谢谢你的肯定,也希望对您能有所帮助
  • 如果您有更好的见解请在评论区留言或者私聊我,期待与您的交流

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云原生超融合八大核心能力|ZStack Edge云原生超融合产品技术解读

企业数字化转型的焦点正在发生变化&#xff0c;云基础设施由资源到应用&#xff0c;数据中心从核心到边缘。面向云原生趋势&#xff0c;围绕应用升级&#xff0c;新一代超融合产品——云原生超融合应运而生。 ZStackEdge云原生超融合是基于云原生架构研发的新一代IT基础设施 …

EI论文联合复现:含分布式发电的微网/综合能源系统储能容量多时间尺度线性配置方法程序代码!

适用平台&#xff1a;Matlab/Gurobi 程序提出了基于线性规划方法的多时间尺度储能容量配置方法&#xff0c;以满足微电网的接入要求为前提&#xff0c;以最小储能配置容量为目标&#xff0c;对混合储能装置进行容量配置。程序较为基础&#xff0c;算例丰富、注释清晰、干货满满…

【设计模式】策略模式及函数式编程的替代

本文介绍策略模式以及使用函数式编程替代简单的策略模式。 策略模式 在策略模式&#xff08;Strategy Pattern&#xff09;中一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。 在策略模式定义了一系列算法或策略&#xff0c;并将每个算法封装在独立…

PyPDF2:项目实战源码分享(PDF裁剪)

目录&#x1f4d1; 1. 背景&#x1f4d1;2. 源码模块解析&#x1f4d1;2.1 读取PDF页数2.2 获取指定页的宽高尺寸2.3 裁剪单页PDF2.4 批量裁剪PDF 总结&#x1f4d1; 1. 背景&#x1f4d1; 接PyPDF2模块推荐博文中提到的实际需求&#xff08;将银行网站下载来的多页且单页多张…

【大数据】Flink 内存管理(二):JobManager 内存分配(含实际计算案例)

Flink 内存管理&#xff08;二&#xff09;&#xff1a;JobManager 内存分配 1.分配 Total Process Size2.分配 Total Flink Size3.单独分配 Heap Size4.分配 Total Process Size 和 Heap Size5.分配 Total Flink Size 和 Heap Size JobManager 是 Flink 集群的控制元素。它由三…

亿道丨三防平板丨加固平板丨为零售业提供四大优势

随着全球经济的快速发展&#xff0c;作为传统行业的零售业也迎来了绝佳的发展机遇&#xff0c;在互联网智能化的大环境下&#xff0c;越来越多的零售企业选择三防平板电脑作为工作中的电子设备。作为一种耐用的移动选项&#xff0c;三防平板带来的不仅仅是坚固的外壳。坚固耐用…

【Python笔记-设计模式】前端控制器模式

一、说明 常作为MVC&#xff08;Model-View-Controller&#xff09;模式的一部分&#xff0c;用来处理用户请求并将其分发给相应的处理程序&#xff08;即路由匹配&#xff09;。 (一) 解决问题 将请求的处理流程集中管理&#xff0c;统一处理所有的请求 (二) 使用场景 需…

向量数据库的特性、索引和分析权衡

向量数据库概述 向量数据库的特征 数据库多样性&#xff1a;向量数据库在实现、性能、可扩展性和易用性方面存在差异&#xff0c;支持语义搜索应用。融资与地理位置&#xff1a;多数向量数据库初创公司集中在加州湾区&#xff0c;但资金并不直接反映数据库能力。编程语言&…

【前端素材】推荐优质后台管理系统Dashmin平台模板(附源码)

一、需求分析 后台管理系统在多个层次上提供了丰富的功能和细致的管理手段&#xff0c;帮助管理员轻松管理和控制系统的各个方面。其灵活性和可扩展性使得后台管理系统成为各种网站、应用程序和系统不可或缺的管理工具。 后台管理系统是一种具有多层次结构的软件系统&#xf…

【DDD】学习笔记-领域模型与数据模型

领域模型与数据模型 领域驱动的设计模型最重要的概念就是聚合&#xff0c;同时&#xff0c;聚合还要受到限界上下文边界的控制。Eric Evans 之所以要引入限界上下文&#xff0c;其中一个重要原因就是因为我们“无法维护一个涵盖整个企业的统一模型”&#xff0c;于是需要限界上…

我花了5天时间,开发了一个在线学习的小网站

大三寒假赋闲在家&#xff0c;闲来无事&#xff0c;用了5天时间做了一个在线学习的小网站&#xff0c;一鼓作气部署上线&#xff0c;制作的过程比较坎坷。内心经历过奔溃&#xff0c;也经历过狂喜。 按照惯例先放出网址&#xff0c;欢迎大家来访问学习&#xff1a;www.pbjlove…

滑动窗口刷题(二)

目录 1.最大连续1的个数 III 1.题目解析 2.算法原理 2.1暴力枚举&#xff08;不过多介绍&#xff09; 2.2双指针优化 3.代码编写 2. 将 x 减到 0 的最小操作数 1.题目解析 2.算法原理 2.1滑动窗口 3.代码编写 3. 水果成篮 1.题目解析 2.算法思路 2.1滑动窗口哈希…

关于电脑功耗与电费消耗的问题,你了解多少?

一台电脑24小时运行需要多少电量&#xff1f; 大家好&#xff0c;我是一名拥有多年维修经验的上门维修师傅。 今天我就来回答大家关于电脑24小时运行需要多少电量的问题。 电脑功耗及用电量 首先我们来看看电脑的功耗情况。 普通台式电脑的功耗通常在300瓦左右&#xff0c;即…

《The Art of InnoDB》第二部分|第4章:深入结构-磁盘结构-redo log

4.3 redo log 目录 4.3 redo log 4.3.1 redo log 介绍 4.3.2 redo log 的作用 4.3.3 redo log file 结构 4.3.4 redo log 提交逻辑 4.3.5 redo log 持久化逻辑 4.3.6 redo log 检查点 4.3.7 小结 未完待续.... 上文我们学习了表空间&#xff0c;下面我们来介绍日志系统…

vue从flask获取数据并显示

记录一个前后端分离遇到的问题&#xff0c;即vue前端从flask后端获取数据。具体描述如下&#xff1a;flask只负责连接数据库并获取数据库的数据&#xff0c;并返回给前端vue&#xff1b;vue则需要获取后端返回的数据并显示。 方法如下&#xff0c;分别用一个vue组件和一个flas…

torch.nn.embedding的介绍和用法

nn.Embedding 是 PyTorch 中的一个神经网络层&#xff0c;它主要用于将离散的、高维的数据&#xff08;如词索引&#xff09;转换为连续的、低维的空间中的稠密向量表示。在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;这个层通常用于实现词嵌入&#xff08;Word Em…

ES6内置对象 - Map

Map&#xff08;Map对象保存键值对&#xff0c;键值均不限制类型&#xff09; 特点&#xff1a; 有序&#xff08;Set集合是无序的&#xff09;&#xff1b;键值对&#xff08;键可以是任意类型&#xff09;&#xff1b;键名不能重复&#xff08;如果重复&#xff0c;则覆盖&…

自考《计算机网络原理》考前冲刺

常考选择填空 1、计算机网络的定义&#xff1a;计算机网络是互连的、自治的计算机的集合。 2、协议的定义&#xff1a;协议是网络通信实体之间在数据交换过程中需要遵循的规则或约定 3、协议的3个要素 (1) 语法&#xff1a;定义实体之间交换信息的格式与结构&#xff0c;或…

经典Go知识点总结

开篇推荐 来来来,老铁们,男人女人都需要的技术活 拿去不谢:远程调试,发布网站到公网演示,远程访问内网服务,游戏联机 推荐链接 1.无论sync.Mutex还是其衍生品都会提示不能复制,但是能够编译运行 加锁后复制变量&#xff0c;会将锁的状态也复制&#xff0c;所以 mu1 其实是已…

Docker Container(容器)

"在哪里走散&#xff0c;你都会找到我~" Docker 容器 什么是容器&#xff1f; 通俗来讲&#xff0c;容器是镜像运行的实体。我们对于镜像的认知是&#xff0c;“存储在磁盘上的只读文件”。当我们启动一个容器的本质&#xff0c;就是启动一个进程&#xff0c;即容器…