IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 从理解路由到实现一套Router(路由) -> 正文阅读

[JavaScript知识库]从理解路由到实现一套Router(路由)

前言

  • 学完本篇文章你将对React不同优先级任务调度有一个初步的认识。

前置知识

学习本片文章前需要知道浏览器一帧中会做哪些事情

  • 三部分:执行js、渲染页面、空闲时间。* 在我们调用setState的时候会创建一个更新任务,当然react内部也会有其他等级的任务。为了避免任务执行时间超过当前帧空闲时间,造成页面卡顿的现象。React利用调度器实现了了在空闲时间内按优先级执行任务。几个概念

  • work:一个任务,其count属性代表该任务包含count个子组件需要更新。* workList:任务队列,包含着React需要执行的所有任务。* perform:执行更新流程,当任务执行完毕后,执行schedule,开始下一轮调度。* schedule:调度器,从workList中获取一个高优先级任务,交给perform执行。* perform与schedule循环往复,实现整个任务调度。初识任务调度


下面例子中,创建了三个不同等级的任务,每个任务里面都有100个组件需要更新。当然,更新组件的等级是相同的,这里只是作为区分。

html

<style> #app {word-break: break-all;} </style>

<body><div id="app"></div><script src="./react优先级调度算法-基础版.js"></script>
</body> 

javascript

const workList = [];
const contetnBox = document.querySelector("#app");

let list = [{value: "低",},{value: "中",},{value: "高",},
]

list.forEach(item => {const btn = document.createElement("button");btn.innerText = item.value;contetnBox?.appendChild(btn);btn.onclick = () => {// 添加任务workList.unshift({ ...item, count: 100 })schedule()}
})

// 执行任务
const renderComponent = (content) => {// 更好的观察任务调度,做的延迟效果let i = 10000000;while (i) {i--;}const ele = document.createElement("span");ele.innerText = `${content}`;contetnBox?.appendChild(ele)
}

// 调度器
function schedule() {// 获取一个任务,并弹出任务队列const curWork = workList.pop();if (curWork) {// 执行更新流程perform(curWork)}
}

// 更新流程
function perform(work) {while (work.count) {// count代表这个任务中有count个组件需要更新work.count -= 1;renderComponent(work.value)}// 当前任务中的组件全部更新完后,继续执行调度schedule()
} 

效果图

在点击按钮后,会向任务队列workList中添加work。调度器开始工作,调度器从workList中获取一个任务,并弹出任务队列中。任务执行完后,调度器继续调度。schedule与perform循环调用,直至任务队列清空。

按优先级顺序执行任务

引入React调度器

引入该包的目的是使用一些方法辅助完成调度工作

项目结构

learn-schedule
├─ package-lock.json
├─ package.json
├─ public
│└─ index.html
└─ src └─ index.js 

导包

import {//空闲优先级unstable_IdlePriority as IdlePriority,//低优先级unstable_LowPriority as LowPriority,//用户阻塞优先级unstable_UserBlockingPriority as UserBlockingPriority,//普通优先级unstable_NormalPriority as NormalPriority,//立刻执行的优先级unstable_ImmediatePriority as ImmediatePrity,// 当某一个preform正在被调度,但是还没被执行时,可以使用该函数进行取消unstable_cancelCallback as cancelCallback,// 用于调度preform方法unstable_scheduleCallback as scheduleCallback,// 当前帧是否用尽了, 用尽了为true,此时需要中断任务unstable_shouldYield as shouldYield,// 返回当前正在调度的任务unstable_getFirstCallbackNode as getFirstCallbackNode,// unstable_scheduleCallback的返回值CallbackNode
} from "scheduler" 

添加变量

// 本次schedule进行时,正在调度的任务的优先级
// 设置初始值为undefined
let prevPriority = undefined; 

修改schedule方法

function schedule() {// 当前正在执行的调度任务const cbNode = getFirstCallbackNode();// 获取优先级最高的任务const curWork = workList.sort((node1, node2) => {return node1.priority - node2.priority;})[0]// 如果任务不存在,即任务队列为空if (!curWork) {return;}const { priority } = curWork;// 只有本次任务优先级 > 已经正在在执行的任务的优先级,才会中断正在执行的任务if (priority === prevPriority) {return;}// 此时本次的任务优先级 > 正在执行的任务优先级// 需要中断正在执行的任务if (cbNode) {cancelCallback(cbNode);}// 执行任务,以某个优先级来调度某个任务// 为什么要使用bind,因为scheduleCallback第二个参数是一个回调函数scheduleCallback(priority, perform.bind(null, curWork))
} 

修改work

// 本次schedule进行时,正在调度的任务的优先级
let prevPriority = undefined;

const workList = [];
const contetnBox = document.querySelector("#app");

let list = [{priority: IdlePriority,value: "低",},{priority: LowPriority,value: "中",},{priority: NormalPriority,value: "高",},
]

list.forEach(item => {const btn = document.createElement("button");btn.innerText = item.value;contetnBox?.appendChild(btn);btn.onclick = () => {// 添加任务workList.unshift({ ...item, count: 100 })schedule()}
}) 

修改perform方法

任务执行完的时候,将prevPriority制空

// 更新流程
function perform(work) {if (work.count === 0) {const workIndex = workList.indexOf(work)workList.splice(workIndex, 1)// 任务执行完的时候,将prevPriority制空prevPriority = undefined;}while (work.count) {work.count -= 1;renderComponent(work.value)}schedule()
} 

效果图

这个时候已经创建了三个不同优先级的任务,当点击多次低优先级任务后,再点击中优先级任务,会发现中优先级任务先被执行。此时已经完成了不同优先级的任务调度。

超出空闲时间时,任务可中断

上一个版本我们可以发现一个问题,当低优先级任务执行过程中,点击中优先级任务的时候,并没有马上中断低优先级的任务,而是等当前正在执行的低优先级任务执行完毕后,才执行中优先级任务。解决该问题的方法就是使perform可中断

修改perform

function perform(work) {// 当前任务是否是同步执行// ImmediatePrity是立即执行优先级,所以需要同步执行const isSync = work.priority === ImmediatePrity;// shouldYield判断浏览器当前帧是否剩余空闲时间while ((isSync || !shouldYield()) && work.count) {work.count -= 1;renderComponent(work.value)}if (work.count === 0) {const workIndex = workList.indexOf(work)workList.splice(workIndex, 1)// 任务执行完的时候,将prevPriority制空prevPriority = undefined;} else {prevPriority = work.priority;}//继续调度schedule()
} 

效果图

当多次点击低优先级任务的时候,再点击中优先级任务,会发现调度器立刻切换到中优先级任务执行,当中优先级任务执行完毕后,会接着执行低优先级任务,并且页面流畅渲染。

优化调度

到这里的时候,其实已经实现了不同优先级的任务调度,但是还有可优化的余地。

观察发现,当任务正在执行时碰到没有空闲时间用完,需要中断执行,这时候会再次执行schedule方法重新进行一系列的调度工作。这个时候会浪费一些性能,其实当没有更高优先级的任务时,我们可以不进行调度,直接再下一个空闲时间内继续执行当前的work。

添加全局变量

// 当前被调度的回调函数
let curCallback = null; 

修改schedule

在函数最后一行为curCallback赋值。curCallback是一个包裹了当前work的数据结构,后面会讲到。

// 调度器
function schedule() {// 当前正在执行的调度任务const cbNode = getFirstCallbackNode();// 获取优先级最高的任务const curWork = workList.sort((node1, node2) => {return node1.priority - node2.priority;})[0]// 如果任务不存在,即任务队列为空if (!curWork) {curCallback = null;return;}const { priority } = curWork;// 只有本次任务优先级 > 已经正在在执行的任务的优先级,才会中断正在执行的任务if (priority <= prevPriority) {return;}// 此时本次的任务优先级 > 正在执行的任务优先级// 需要中断正在执行的任务if (cbNode) {cancelCallback(cbNode);}// 执行任务,以某个优先级来调度某个任务// 为什么要使用bind,因为scheduleCallback第二个参数是一个回调函数curCallback = scheduleCallback(priority, perform.bind(null, curWork))
} 

修改perform

在perform执行的最后,需要重新获取一遍curCallback,此时会触发schedule的priority === prevPriority判断,当没有更高优先级时,前后两个的curCallback值相等时,此时就可以直接循环perform函数即可,由因为perform是当作回调函数传递给scheduleCallback的,而且当perform返回一个函数时,scheduleCallback会直接执行这个函数,而不会去执行其他调度相关的工作,减少了部分性能开支。

// 更新流程
function perform(work) {// 当前任务是否是同步执行// ImmediatePrity是立即执行优先级,所以需要同步执行const isSync = work.priority === ImmediatePrity;// shouldYield判断浏览器当前帧是否剩余空闲时间while ((isSync || !shouldYield()) && work.count) {work.count -= 1;renderComponent(work.value)}if (work.count === 0) {const workIndex = workList.indexOf(work)workList.splice(workIndex, 1)prevPriority = undefined;} else {prevPriority = work.priority;}//存储当前回调const prevCallback = curCallback//继续调度schedule()//获取新的回调const newCallback = curCallback// 当没有更高优先级的时候,直接走performif (prevCallback === newCallback) {return perform.bind(null, work)}
} 

补充:curCallback是什么

其实就是对当前work信息的一个封装

在React源码源码中长这样

type Task = {id: number,callback: Callback | null,priorityLevel: PriorityLevel,startTime: number,expirationTime: number,sortIndex: number,isQueued?: boolean,
}; 

在项目中打印

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:19:49  更:2022-11-05 00:20:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 19:04:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码