当前位置: 首页 > news >正文

管理网站怎么做的展览展示设计公司排名

管理网站怎么做的,展览展示设计公司排名,网络营销成功的案例分析,网站建设哪家质量好文章目录 背景时间切片原理requestIderCallback 方法setImmediateMessageChannelsetTimeout React 18 时间切片源码手撸时间切片问题拆解构建任务队列宏任务包装首次开启任务递归任务执行workLoop 开启工作循环demo 模拟 总结 背景 前文学习了 fiber 架构和双缓存技术&#xff…

文章目录

    • 背景
    • 时间切片原理
      • requestIderCallback 方法
      • setImmediate
      • MessageChannel
      • setTimeout
    • React 18 时间切片源码
    • 手撸时间切片
      • 问题拆解
      • 构建任务队列
      • 宏任务包装
      • 首次开启任务
      • 递归任务执行
      • workLoop 开启工作循环
      • demo 模拟
    • 总结

背景

前文学习了 fiber 架构和双缓存技术,接下来我们深入源码一起学习下时间切片的原理。

React 探秘(一):fiber 架构

React 探秘(二):双缓存技术

React 时间切片是 React 通过将任务分割成小的时间片,然后分批次去处理任务,在 js 线程繁忙的时候把控制权交还给浏览器本身如渲染进程等,以提高应用程序性能的一种技术。本文将介绍 React v18.3.1 时间切片并提供一个简单的 demo,以便开发者学习相关知识。

时间切片的主要优点:
提高应用程序的响应性和流畅度,分批次运行任务可以避免长时间占用 CPU。更好地控制渲染过程,让用户可以快速看到应用程序的变化,避免白屏等问题。

时间切片技术位置 fiber 架构的 Scheduler 调度器层。
Scheduler 分为两大部分:

  • 时间切片: 异步渲染是优先级调度实现的前提
  • 优先级调度:在异步渲染的基础上引入优先级机制控制任务的打断、替换。

本文只介绍时间切片相关内容;

时间切片原理

时间切片的原理就是把我们一次性执行完的任务,切分到不同时间间隔去完成,如果超出这个时间间隔,就会暂时挂起,交给浏览器,等到空闲了继续执行。那么问题就转化为如何实现给任务添加时间间隔?

这里涉及到 js 事件循环机制,同步代码(宏任务)-微任务-宏任务。

  • 执行全局代码:当 JavaScript 代码第一次运行时,首先会执行同步代码(相当于一次宏任务),如果遇到微任务会把微任务方微任务队列,遇到宏任务放入宏任务队列
  • 检查微任务队列:一旦同步代码(宏任务)完成,事件循环会检查并执行微任务队列中的所有任务,直到队列为空。
  • 执行下一个宏任务:如果微任务队列为空,事件循环会从宏任务队列中取出下一个任务并执行。
  • 重复上述步骤:这个过程会不断循环,直到所有任务执行完毕。

宏任务:会在下次事件循环中执行,不会阻塞本次页面渲染更新。

微任务:「微任务是在本次页面更新前会全部执行」,这一点与同步执行无异,不会让出主线程。

常见的宏任务方法有:

  • setTimeout
  • messageChannel
  • setImmediate

此外还有 requestIdleCallback 是在浏览器渲染后有空闲时间时执行。

requestIderCallback 方法

window.requestIdleCallback() 方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间 timeout,则有可能为了在超时前执行函数而打乱执行顺序。

通过这个函数我们其实就可以时间一个简单的时间切片:

function workLoop(deadline) {let shouldYield = false;// 存在fiber并且时间空闲while (nextUnitOfWork && !shouldYield) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);shouldYield = deadline.timeRemaining() < 1; // 剩余时间是否小于1ms 代表任务繁忙}// 没有fiber并且wip存在if (!nextUnitOfWork && workInProgress) {commitRoot();}// 繁忙时继续执行主任务requestIdleCallback(workLoop);
}

我们执行某个 fiberNode 的时候,浏览器主线程被占用,这个时候就可以暂停 fiberNode 的继续执行,等浏览器空闲时,继续 nextUnitOfWork。这就实现了可暂停可继续。但是呢这 api 有限制:

  • requestIdleCallback 的执行时机不是完全可控的,这可能导致在不同环境中表现不一致。
  • requestIdleCallback 是利用帧之间空闲时间来执行 js,它是一个低优先级的处理策略,但实际上 fiber 的处理上,并不算是一个低优先级任务。

setImmediate

setImmediate 这个是最早执行的宏任务,但是也可能会有兼容性问题。

MessageChannel

MessageChannel 的执行时机比 setTimeout 靠前,而且执行实际准确,但是会有兼容性问题。

setTimeout

setTimeout 执行时机在 messageChannel 之后,如下 demo:

function workLoop() {setTimeout(() => {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);workLoop()}, 0)
}

但是 setTimeout 的递归层级过深的话,延迟就不是1ms,而是4ms,这样会造成延迟时间过长,时间浪费。

看了上面这些方法多多少少都有些问题,那么下面我们讲一下 react 怎么实现时间切片的。

React 18 时间切片源码

源码位置: https://github.com/facebook/react/blob/v18.3.1/packages/scheduler/src/forks/Scheduler.js

在这里插入图片描述

可以看到 react 18 中其实就是做了个兼容性判断

  • 优先 setImmediate
  • 其次 messageChannel
  • 最后 setTimeout

直接看源码很容易懵逼,因为源码中包含大量的兼容判断和优先级相关代码,容易混淆我们的视线,因此我们把复杂问题拆解一下,从源码入手,手撸一个 mini 版时间切片。

手撸时间切片

问题拆解

  • 入口构建任务队列
    • 创建时间切片,通过当前时间 + 延迟得到过期时间,塞入任务队列
  • 创建宏任务
    • 通过 setImmediate 等方法创建宏任务。
  • 执行宏任务-循环执行时间切片
    • 递归调用时间切片方法,用于挂起、重启。
  • 开启工作循环
    • 循环执行队列任务,超出时间不执行。

构建任务队列

使用 performance.now() 获取更精确的时间,来创建每个任务过期时间,并塞入任务队列中。

// 入口创建 task 并添加过期时间,执行任务
function scheduleCallback(callbcak) {let unitOfwork = {callbcak,expirationTime: performance.now() + 5,}taskQueue.push(unitOfwork)// 开启宏任务requestHostCallback(workLoop)
}

宏任务包装

通过如下三个方法 localSetImmediate MessageChannel localSetTimeout 包装我们的 callback 为宏任务

// 把 performWorkUntilDeadline 方法放入宏任务当中
if (typeof localSetImmediate === 'function') {schedulePerformWorkUntilDeadline = () => {localSetImmediate(performWorkUntilDeadline);};
} else if (typeof MessageChannel !== 'undefined') {const channel = new MessageChannel();const port = channel.port2;channel.port1.onmessage = performWorkUntilDeadline;schedulePerformWorkUntilDeadline = () => {port.postMessage(null);};
} else {schedulePerformWorkUntilDeadline = () => {localSetTimeout(performWorkUntilDeadline, 0);};
}

首次开启任务

拿到当前正在处理的任务,开启执行包装好的宏任务

function requestHostCallback(callback) {scheduledHostCallback = callback;if (!isMessageLoopRunning) {isMessageLoopRunning = true;schedulePerformWorkUntilDeadline();}
}

递归任务执行

执行宏任务,获取当前时间,判断如果还有未完成的任务则开启递归。

// 宏任务执行的方法(核心方法)
const performWorkUntilDeadline = () => {if (scheduledHostCallback !== null) {const currentTime = getCurrentTime();startTime = currentTime;const hasTimeRemaining = true;let hasMoreWork = true;try {// 执行任务 scheduledHostCallback 就是 workLoophasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);} finally {if (hasMoreWork) {// 如果任务队列中还存在任务则继续递归执行schedulePerformWorkUntilDeadline();} else {isMessageLoopRunning = false;scheduledHostCallback = null;}}} else {isMessageLoopRunning = false;}
};

workLoop 开启工作循环

循环执行队列中的任务,currentTask 为空结束循环,判断时间是否过期,过期则不执行任务,把控制权还给浏览器。


function workLoop(hasTimeRemaining, initialTime) {let currentTime = initialTime;currentTask = peek(taskQueue);while (currentTask) {// 判断是时间是否过期if ((currentTask.expirationTime > currentTime) && (shouldYieldToHost() || !hasTimeRemaining)) {break} else {// 执行具体回调currentTask.callbcak()currentTask = taskQueue.shift()// currentTask = peek(taskQueue); // react 18 写法 包含小顶堆的排序算法}}// 还有剩余任务未执行完成返回 trueif (currentTask !== null) {return true;} else {return false;}
}

demo 模拟

下面使具体案例来模拟一下时间切片带来的改善:

完整版时间切片方法:


let taskQueue = [] // 任务队列
let isMessageLoopRunning = false; // 标记 宏任务 正在运行
let scheduledHostCallback = null; // 要执行的函数 workLoop
let currentTask = null; // 当前执行的任务
let startTime = null;  // 任务开始的时间const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;
const localClearTimeout =typeof clearTimeout === 'function' ? clearTimeout : null;
const localSetImmediate =typeof setImmediate !== 'undefined' ? setImmediate : null;// 获取当前时间
const getCurrentTime = () => performance.now();
// 根据时间判断是否把控制权交给浏览器
function shouldYieldToHost() {const timeElapsed = getCurrentTime() - startTime;if (timeElapsed < 5) {return false;}return true;
}
// 获取数组第一项
function peek(heap) {return heap.length === 0 ? null : heap[0];
}
// 入口创建 task 并添加过期时间,执行任务
function scheduleCallback(callbcak) {let unitOfwork = {callbcak,expirationTime: performance.now() + 5,}taskQueue.push(unitOfwork)// 开启宏任务requestHostCallback(workLoop)
}// 宏任务执行的方法(核心方法)
const performWorkUntilDeadline = () => {if (scheduledHostCallback !== null) {const currentTime = getCurrentTime();startTime = currentTime;const hasTimeRemaining = true;let hasMoreWork = true;try {// 执行任务 scheduledHostCallback 就是 workLoophasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);} finally {if (hasMoreWork) {// 如果任务队列中还存在任务则继续递归执行schedulePerformWorkUntilDeadline();} else {isMessageLoopRunning = false;scheduledHostCallback = null;}}} else {isMessageLoopRunning = false;}
};// 把 performWorkUntilDeadline 方法放入宏任务当中
if (typeof localSetImmediate === 'function') {schedulePerformWorkUntilDeadline = () => {localSetImmediate(performWorkUntilDeadline);};
} else if (typeof MessageChannel !== 'undefined') {const channel = new MessageChannel();const port = channel.port2;channel.port1.onmessage = performWorkUntilDeadline;schedulePerformWorkUntilDeadline = () => {port.postMessage(null);};
} else {schedulePerformWorkUntilDeadline = () => {localSetTimeout(performWorkUntilDeadline, 0);};
}function requestHostCallback(callback) {scheduledHostCallback = callback;if (!isMessageLoopRunning) {isMessageLoopRunning = true;schedulePerformWorkUntilDeadline();}
}function workLoop(hasTimeRemaining, initialTime) {let currentTime = initialTime;currentTask = peek(taskQueue);while (currentTask) {// 判断是时间是否过期if ((currentTask.expirationTime > currentTime) && (shouldYieldToHost() || !hasTimeRemaining)) {break} else {// 执行具体回调currentTask.callbcak()currentTask = taskQueue.shift()// currentTask = peek(taskQueue); // react 18 写法 包含小顶堆的排序算法}}// 还有剩余任务未执行完成返回 trueif (currentTask !== null) {return true;} else {return false;}
}

demo 模拟实现:

let taskIndex = 0;
let taskTotal = 5000; // 任务数量
const start = Date.now();function handleTask() {for (let j = 0; j < 5000; j++) {// 执行一些耗时操作const btn1Attr = document.getElementById('btn1').attributes;const btn2Attr = document.getElementById('btn2').attributes;const btn3Attr = document.getElementById('btn3').attributes;}if(taskIndex >= taskTotal) {console.log(`任务调度完成,用时:`, Date.now() - start, 'ms!');}
}while (taskIndex <= taskTotal) {scheduleCallback(handleTask) // 时间切片执行// handleTask()  // 普通执行taskIndex++
}document.getElementById('btn1').onclick = function () {console.log(11111, 'click')
}// html
<body><div id="root"><button id="btn1">按钮1</button><button id="btn2">按钮2</button><button id="btn3">按钮3</button><button id="btn4">按钮4</button></div><script src="./sh.js"></script>
</body>

上面这一串代码在使用我们封装的 scheduleCallback 执行任务时,dom 渲染几乎秒开,但是如果使用普通的调用页面则会卡顿 3s 左右,才会出现。

总结

react 使用时间切片提升渲染性能,在熟知原理后,同样我们在业务中也有很多优化场景可以使用到。例如:高频埋点批量切片上传,大量 dom 节点操作等等。

http://www.15wanjia.com/news/189340.html

相关文章:

  • 制作网页和网站的区别科技感强的网站
  • 浙江建设继续教育学院网站一条龙平台
  • 长沙建站网站模板建设项目安监备案网站
  • 输变电壹级电力建设公司网站有赞微商城登录
  • 优门设 网站vps怎么上传网站程序
  • 网站建设营销的技巧杭州 商城网站开发
  • 制作一个营销型网站学校网站建设哪家好
  • 家政门户网站源码上海 网站建设google
  • 17网站一起做网店新塘wordpress 去版权
  • 山东建设厅官方网站二建报名企业门户网站建站
  • 廊坊网站推广网站开发的技术选型
  • 支付网站开发费可以做无形资产汽车配件生产企业网站模板
  • 网站建设找朝云科技如何建立公司自己的网站
  • 微信网站怎么做中关村手机之家官网
  • 平面设计常用网站毕业设计做网站应该学什么
  • 站长工具seo综合查询网定制化软件
  • 网站开发开票内容个人app
  • 哪个网站可以做简历建站公司费用情况
  • 做守望同人的网站绵阳观察怎么登录不上
  • 东莞企业网站模板建站微信小程序开发制作
  • 绥化北京网站建设湖南营销型网站建设磐石网络
  • 购物网站技术方案建筑公司网站背景图
  • 手机 网站编辑器环球网最新消息
  • 自建网站与平台建站网站设计 案例
  • python做网站好用吗网站建设动态页面修改删除
  • 高水平大学建设大学网站电子商务网站建设与管理王生春
  • 有哪些企业网站平台免费行情软件app网站大全下载u288
  • 网页设计与网站建设完全学习手册国外好用的网站
  • 官方网站链接如何做app下载安装app
  • 男人和女人做哪个网站wordpress简单的博客主题