首页 > 其他分享 >nextTick原理学习

nextTick原理学习

时间:2022-09-21 00:45:36浏览次数:74  
标签:nextTick 异步 回调 学习 callbacks 任务 原理 执行

一、为什么用nextTick

(1)js执行原理Eventloop

首先js是单线程的,所谓单线程,就是同一时间只能处理一件事情。JS中的任务分为同步任务和异步任务,其中异步任务分为宏任务和微任务。

所有同步任务都在主线程上执行,形成一个执行栈。而异步任务则会形成任务队列,宏任务进入宏队列,微任务进入微队列。

执行顺序:

①执行同步代码;

②等待所有的同步任务执行完毕,执行栈清空

③开始读取任务队列的任务,先从微队列取队首任务放入执行栈中执行

④继续取直到微队列任务完毕,如果执行过程中又产生了微任务则加入队列末尾,这个任务也会在这个周期执行

⑤微队列和执行栈都为空,则取宏队列队首任务放入栈中执行

⑥执行完毕,执行栈为空,重复③-⑤

(2)Vue数据驱动视图更新

vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM,而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更;这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数从而减少性能的消耗。

nextTick 的本质是为了利用 JavaScript 的异步回调任务队列来实现 Vue 框架中自己的异步回调队列。

二、nextTick作用

nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行;
使用场景:想要操作基于最新数据的生成DOM 时,就将这个操作放在 nextTick 的回调中。

比如,动态生成文本框,实现自动聚焦功能;引入swiper库,需要等挂载的DOM生成后再生成swiper对象。

三、nextTick实现原理

将传入的回调函数包装成异步任务,nextTick 提供了四种异步方法 ,因为微任务优先于宏任务执行,所以优先级为

Promise.then > MutationObserver > setImmediate > setTimeOut(fn,0)

源码:

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
//  noop 表示一个无操作空函数,用作函数默认值,防止传入 undefined 导致报错
//  handleError 错误处理函数
//  isIE, isIOS, isNative 环境判断函数,
//  isNative 判断是否原生支持,如果通过第三方实现支持也会返回 false

export let isUsingMicroTask = false     // nextTick 最终是否以微任务执行

const callbacks = []     // 存放调用 nextTick 时传入的回调函数
let pending = false     // 标识当前是否有 nextTick 在执行,同一时间只能有一个执行

// 声明 nextTick 函数,接收一个回调函数和一个执行上下文作为参数
export function nextTick(cb?: Function, ctx?: Object) {
    let _resolve
    // 将传入的回调函数存放到数组中,后面会遍历执行其中的回调
    callbacks.push(() => {
        if (cb) {   // 对传入的回调进行 try catch 错误捕获
            try {
                cb.call(ctx)
            } catch (e) {
                handleError(e, ctx, 'nextTick')
            }
        } else if (_resolve) {
            _resolve(ctx)
        }
    })
    
    // 如果当前没有在 pending 的回调,就执行 timeFunc 函数选择当前环境优先支持的异步方法
    if (!pending) {
        pending = true
        timerFunc()
    }
    
    // 如果没有传入回调,并且当前环境支持 promise,就返回一个 promise
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve
        })
    }
}

// 判断当前环境优先支持的异步方法,优先选择微任务
// 优先级:Promise---> MutationObserver---> setImmediate---> setTimeout
// setTimeOut 最小延迟也要4ms,而 setImmediate 会在主线程执行完后立刻执行
// setImmediate 在 IE10 和 node 中支持

// 多次调用 nextTick 时 ,timerFunc 只会执行一次

let timerFunc   
// 判断当前环境是否支持 promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {  // 支持 promise
    const p = Promise.resolve()
    timerFunc = () => {
    // 用 promise.then 把 flushCallbacks 函数包裹成一个异步微任务
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
    }
    // 标记当前 nextTick 使用的微任务
    isUsingMicroTask = true
    
    
    // 如果不支持 promise,就判断是否支持 MutationObserver
    // 不是IE环境,并且原生支持 MutationObserver,那也是一个微任务
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
    let counter = 1
    // new 一个 MutationObserver 类
    const observer = new MutationObserver(flushCallbacks) 
    // 创建一个文本节点
    const textNode = document.createTextNode(String(counter))   
    // 监听这个文本节点,当数据发生变化就执行 flushCallbacks 
    observer.observe(textNode, { characterData: true })
    timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter)  // 数据更新
    }
    isUsingMicroTask = true    // 标记当前 nextTick 使用的微任务
    
    
    // 判断当前环境是否原生支持 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    timerFunc = () => { setImmediate(flushCallbacks)  }
} else {

    // 以上三种都不支持就选择 setTimeout
    timerFunc = () => { setTimeout(flushCallbacks, 0) }
}

// 如果多次调用 nextTick,会依次执行上面的方法,将 nextTick 的回调放在 callbacks 数组中
// 最后通过 flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调
function flushCallbacks() {
    pending = false
    const copies = callbacks.slice(0)    // 拷贝一份
    callbacks.length = 0    // 清空 callbacks
    for (let i = 0; i < copies.length; i++) {    // 遍历执行传入的回调
        copies[i]()
    }
}
// callbacks.slice(0) 将 callbacks 拷贝出来一份,
// 是因为考虑到 nextTick 回调中可能还会调用 nextTick 的情况,
// 如果 nextTick 回调中又调用了一次 nextTick,则又会向 callbacks 中添加回调,
// nextTick 回调中的 nextTick 应该放在下一轮执行,
// 如果不将 callbacks 复制一份就可能一直循环 

参考:https://blog.csdn.net/web220507/article/details/125141403

标签:nextTick,异步,回调,学习,callbacks,任务,原理,执行
From: https://www.cnblogs.com/cxuep/p/16714217.html

相关文章

  • 200-CH32V307(RISC-V)学习开发-以太网例程-网络指示灯GPIO选择, 检测网线连接状态
    <p><iframename="ifd"src="https://mnifdv.cn/resource/cnblogs/LearnCH32V307VCT6"frameborder="0"scrolling="auto"width="100%"height="1500"></iframe></p> ......
  • python学习随笔
    python本周学习随笔一、打开Pycharm,新建项目1.创建名为hello.py文件(Python文件以.py后缀结尾)2.在hello.py中输入以下内容print("helloworld")3.在代码区域右键,选......
  • MarkDown学习
    MarkDown学习二级标题三级标题四级标题 字体hallo,world!hallo,world!hallo,world!hallo,world!引用学习MarkDown,努力学习IT。分割线图片![截图][https://i03pi......
  • hadoop学习
    大数据技术要解决的难题——海量数据要如何存储,海量数据要如何处理?海量数据的存储问题不是现在才有的,在很早之前就用NFS(网络文件系统)将数据分开存储来解决海量数据的存......
  • 计算机组成原理
    第一章计算机系统概述第二章数据的表示和运算第三章存储系统第四章指令系统第五章中央处理器第六章总线第七章I/O系统......
  • Java学习笔记---JDK8新特性(Lambda表达式)
    1.Lambda表达式基础格式:()->{};//()为lambda表达式的参数//->为箭头操作符//{}为lambda方法体lambda表达式结果为一个实例对象,用于直接实例化......
  • vector 初步学习记录
    cat/etc/vector/vector.toml[sources.elasticsearch_search_slowlog]type="file"include=["/var/log/elasticsearch/picapica_es_index_search_slowl......
  • 2022-9-20 Spring学习笔记
    目录1.Spring1.1JavaBean1.2Spring的优势1.3将对象放入IOC容器配置类赋值的方法根据不同类型的赋值作用域自动装配注解1.4类型转换1.SpringSpring框架是Java应用最广......
  • 【Coel.学习笔记】随机化算法:模拟退火与爬山法
    简介模拟退火(\(\text{SimulateAnneal}\))和爬山法是随机化算法,二者的原理都在于通过随机生成答案并检查,把答案逐步缩小在一个可行的区间,尽可能地靠近正确答案。在考场......
  • 2022-08-30 第二小组 张鑫 学习笔记
    实训五十二天Servlet学习内容HttpServletRequest//请求  所有和请求相关的操作  当请求来的时候,request就被实例化HttpServletResponse//响应  所有和......