We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
最近在学习React Fiber架构,把相关内容整理了下,方便理解并且加深自己的印象。本篇文章主要分为三个内容:基础知识储备,架构的设计,框架运行流程。
单线程:js 是单线程的,这意味着在任何给定时间,只能执行一段代码。所有的代码执行都在一个主线程上完成。这种设计简化了编程模型,因为开发者不需要处理多线程编程中常见的数据竞争、死锁等问题 非阻塞:尽管 js 是单线程的,但它使用了事件循环和回调机制来支持非阻塞的 I/O 操作(例如网络请求、文件操作等)。这意味着当一个 I/O js 不会停下来等待操作完成,而是继续执行后续的代码。当 I/O 操作完成后,对应的回调函数会被放入事件队列中,等待主线程执行。
因为JS 脚本执行和浏览器布局、绘制不能同时执行,所以 gui渲染线程 与 js线程 是互斥的。主流浏览器刷新频率为 60Hz,即每(1000ms / 60Hz)16.6ms 浏览器刷新一次。在 16.6ms 内,浏览器要处理js脚本执行、 样式布局、 样式绘制,如果任一环节的执行时间超过了16.6ms就会导致页面掉帧、卡顿。 人眼的可视帧数是24Hz,如果低于这个帧数,就能明显感受到卡顿,越高于这个帧数显示就越流畅。
当我一次了解React Fiber架构时第一时间想到的是单核cpu时代,计算机是如何保证“多任务”运行的。
单核 cpu 和js 单线程的模型很像,都是一次只能处理一个任务,但是单核cpu借助 时间片调度、中断机制、多级反馈队列模拟了运行多任务目的。
react主要是用下面的浏览器api ,在浏览器模拟“多任务”的执行。
这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
为了提高性能和电池寿命,在大多数浏览器里,当 requestAnimationFrame() 运行在后台标签页或者隐藏的 <iframe> 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。
requestAnimationFrame()
使用MessageChannel()构造函数来创建通讯信道。一旦创建,信道的两个端口即可通过 MessageChannel.port1 和 MessageChannel.port2 属性进行访问(都会返回 MessagePort 对象)。创建信道的应用程序使用 port1,在另一端的程序使用 port2——你向 port2 发送信息,然后携带 2 个参数(需要传递的消息,要传递所有权的对象,在这里是 port 自身)调用 window.postMessage 方法将端口信息传递到另一个浏览器上下文。
MessageChannel()
MessageChannel.port1
MessageChannel.port2
main(); function main() { let channel = new MessageChannel(); let port1 = channel.port1; let port2 = channel.port2; port1.onmessage = function(e){ console.log("port1接收到数据:",e.data); } setTimeout(() => { console.log('我是setTimeout:0') }, 0) port2.postMessage("port2数据:main"); new Promise((resolve, reject) => { console.log('我是微任务Promise') port2.postMessage("port2数据:Promise"); resolve() }).then((data) => { console.log('我是微任务Promise.then') }); console.log('主线程任务!!!') }
在浏览器环境中调整上述代码中的顺序,看下面的执行结果:
对比上图代码在浏览器环境中执行结果得出以下结论:
new Promise()函数,是在主线程立即执行;
MessageChannel和setTimeout(fn, 0)类似,主线程执行结束后,如果有postMessage,立即执行。并且两者之前没有优先级关系,谁在前面就先执行谁
微任务then的执行优先级比MessageChannel和setTimeout(fn, 0)都要高;
这里的Promise是浏览器的原生API,如果使用某些库可能执行顺序会有差异。因为库会考虑兼容性问题,使用settimeout(fn, 0)模拟微任务。
因为浏览器兼容性问题,react主要使用requestAnimationFrame和MessageChannel 来模拟实现requestIdleCallback的功能,并且是用settimeout来实现降级策略。
根据前置知识,**requestAnimationFrame(RAF)**可以在浏览器重绘前执行指定的回调函数。可以将优先级较高(显示、用户操作等)相关的任务用RAF回调执行,当一帧执行完之后如果还有剩余的空闲时间,就通过postmessage告知任务队列当前帧有空余时间可以执行低优先级的任务。
// 模拟暂停任务 function sleep(duration){ let now = Date.now(); while(duration+now>Date.now()){ } } // 一帧的时间 16.6ms let activeTimeFrame = 1000/60; // 一帧结束的时间戳 let deadFrameTime; let pendingCallback; let channel = new MessageChannel(); // 当前帧剩余的时间 let timeRemaining = ()=>deadFrameTime - performance.now(); channel.port2.onmessage = function(){ console.log("接收到port1 返回的消息"); let currentTime = performance.now(); let didTimeOut = deadFrameTime <= currentTime; if(didTimeOut||timeRemaining()>0){ if(pendingCallback){ pendingCallback({didTimeOut,timeRemaining}) } } } // 模拟requestIdlCallback window.requestIdleCallback = function(callback,option){ window.requestAnimationFrame((rafTime)=>{ console.log(rafTime); // 这里是计算,当前帧结束的时间 deadFrameTime = rafTime + activeTimeFrame; pendingCallback = callback; channel.port1.postMessage("hello"); }) } let works = [ ()=>{ console.log("任务1") sleep(20); }, ()=>{ console.log("任务2") sleep(20); },()=>{ sleep(20); console.log("任务3") },()=>{ sleep(20); console.log("任务4") },()=>{ sleep(20); console.log("任务5") }, ] function progress(dealine){ console.log("剩下多少空闲的时间:",dealine.timeRemaining()); if(dealine.timeRemaining()>0&&works.length>0){ performUnitOfWork(); } if(works.length>0){ window.requestIdleCallback(progress); } } function performUnitOfWork(){ let work = works.shift(); work(); } window.requestIdleCallback(progress);
进程是操作系统分配资源和调度的基本单位。一个进程包含了运行程序所需的代码和数据资源,以及执行的上下文。进程间相互独立,拥有各自独立的地址空间,一个进程无法直接访问另一个进程的资源和数据。
线程是进程内的一个执行流程,是 CPU 调度和执行的最小单位。一个进程可以包含多个线程,它们共享该进程的资源(如内存空间)。线程之间的通信和数据共享比进程间更简单、效率更高,但这也意味着线程之间的同步非常重要,以避免数据竞争和不一致。协程
协程是一种用户态的轻量级"线程",它的调度完全由用户控制,不需要操作系统介入。协程提供了非常高效的异步编程模型,能在等待操作(如 I/O)完成时挂起当前协程,并恢复其他协程的执行,从而提高程序的整体效率和响应性。
纤程是一种比线程更轻量级的协作单元,它与协程类似,通常用于特定的程序库中实现。纤程的调度也是用户级的,允许开发者控制何时进行任务切换,适用于需要高度控制并发操作的场景。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
最近在学习React Fiber架构,把相关内容整理了下,方便理解并且加深自己的印象。本篇文章主要分为三个内容:基础知识储备,架构的设计,框架运行流程。
1.单线程和“非阻塞”
单线程:js 是单线程的,这意味着在任何给定时间,只能执行一段代码。所有的代码执行都在一个主线程上完成。这种设计简化了编程模型,因为开发者不需要处理多线程编程中常见的数据竞争、死锁等问题
非阻塞:尽管 js 是单线程的,但它使用了事件循环和回调机制来支持非阻塞的 I/O 操作(例如网络请求、文件操作等)。这意味着当一个 I/O js 不会停下来等待操作完成,而是继续执行后续的代码。当 I/O 操作完成后,对应的回调函数会被放入事件队列中,等待主线程执行。
2.浏览器的刷新频率
因为JS 脚本执行和浏览器布局、绘制不能同时执行,所以 gui渲染线程 与 js线程 是互斥的。主流浏览器刷新频率为 60Hz,即每(1000ms / 60Hz)16.6ms 浏览器刷新一次。在 16.6ms 内,浏览器要处理js脚本执行、 样式布局、 样式绘制,如果任一环节的执行时间超过了16.6ms就会导致页面掉帧、卡顿。
人眼的可视帧数是24Hz,如果低于这个帧数,就能明显感受到卡顿,越高于这个帧数显示就越流畅。
3.单核cpu运行多任务
当我一次了解React Fiber架构时第一时间想到的是单核cpu时代,计算机是如何保证“多任务”运行的。
3.1 .时间片轮转调度
3.2 中断机制
3.3 多级反馈队列
单核 cpu 和js 单线程的模型很像,都是一次只能处理一个任务,但是单核cpu借助 时间片调度、中断机制、多级反馈队列模拟了运行多任务目的。
4.基础API依赖
react主要是用下面的浏览器api ,在浏览器模拟“多任务”的执行。
4.1 requestIdleCallback
这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序
4.2 requestAnimationFrame
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
为了提高性能和电池寿命,在大多数浏览器里,当
requestAnimationFrame()
运行在后台标签页或者隐藏的 <iframe> 里时,requestAnimationFrame()
会被暂停调用以提升性能和电池寿命。4.3 MessageChannel
使用
MessageChannel()
构造函数来创建通讯信道。一旦创建,信道的两个端口即可通过MessageChannel.port1
和MessageChannel.port2
属性进行访问(都会返回 MessagePort 对象)。创建信道的应用程序使用 port1,在另一端的程序使用 port2——你向 port2 发送信息,然后携带 2 个参数(需要传递的消息,要传递所有权的对象,在这里是 port 自身)调用 window.postMessage 方法将端口信息传递到另一个浏览器上下文。4.4 MessageChannel的执行时机
在浏览器环境中调整上述代码中的顺序,看下面的执行结果:
对比上图代码在浏览器环境中执行结果得出以下结论:
new Promise()函数,是在主线程立即执行;
MessageChannel和setTimeout(fn, 0)类似,主线程执行结束后,如果有postMessage,立即执行。并且两者之前没有优先级关系,谁在前面就先执行谁
微任务then的执行优先级比MessageChannel和setTimeout(fn, 0)都要高;
浏览器兼容性
因为浏览器兼容性问题,react主要使用requestAnimationFrame和MessageChannel 来模拟实现requestIdleCallback的功能,并且是用settimeout来实现降级策略。
5.如何模拟“闲时”调用
根据前置知识,**requestAnimationFrame(RAF)**可以在浏览器重绘前执行指定的回调函数。可以将优先级较高(显示、用户操作等)相关的任务用RAF回调执行,当一帧执行完之后如果还有剩余的空闲时间,就通过postmessage告知任务队列当前帧有空余时间可以执行低优先级的任务。
6.扩展知识
进程
进程是操作系统分配资源和调度的基本单位。一个进程包含了运行程序所需的代码和数据资源,以及执行的上下文。进程间相互独立,拥有各自独立的地址空间,一个进程无法直接访问另一个进程的资源和数据。
线程
线程是进程内的一个执行流程,是 CPU 调度和执行的最小单位。一个进程可以包含多个线程,它们共享该进程的资源(如内存空间)。线程之间的通信和数据共享比进程间更简单、效率更高,但这也意味着线程之间的同步非常重要,以避免数据竞争和不一致。协程
协程
协程是一种用户态的轻量级"线程",它的调度完全由用户控制,不需要操作系统介入。协程提供了非常高效的异步编程模型,能在等待操作(如 I/O)完成时挂起当前协程,并恢复其他协程的执行,从而提高程序的整体效率和响应性。
纤程
纤程是一种比线程更轻量级的协作单元,它与协程类似,通常用于特定的程序库中实现。纤程的调度也是用户级的,允许开发者控制何时进行任务切换,适用于需要高度控制并发操作的场景。
The text was updated successfully, but these errors were encountered: