React18原理: 时间分片技术选择

渲染1w个节点的不同方式

1 )案例1:一次渲染1w个节点

html 复制代码
<div id='root'><div>

<script type="text/javascript">
	function randomHexColor() {
		return "#" + ("0000"+ (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
	}

	setTimeout(function() {
		var k = 0;
		var root = document.getElementById("root");
		for(var i = 0; i < 10000; i++) {
			k += new Date - 0 ;
			var el = document.createElement("div");
			el.innerHTML = k;
			root.appendChild(el);
			el.style.cssText = `background: ${randomHexColor()};height: 40px`;
		}
	},1000);
</script>

1 次完成 1w 个节点的渲染

可以看到,圈中的部分,明显被阻塞,这一块绝对会导致用户体验很差

2 )案例2: 1w 个节点分100次执行,每次执行100个

html 复制代码
<div id='root'><div>

<script type="text/javascript">
	var root = document.getElementById("root");

	function randomHexColor() {
		return "#" + ("0000"+ (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
	}

	function loop(n) {
		var k = 0;
		console.log(n);
		for(var i = 0; i < 100; i++) {
			k += new Date - 0 ;
			var el = document.createElement("div");
			el.innerHTML = k;
			root.appendChild(el);
			el.style.cssText = `background: ${randomHexColor()};height: 40px`;
		}
		if (n) {
			// 内部再调用 n-1次 loop,总计调用 n 次 loop
			setTimeout(function() {
				loop(n - 1);
			}, 40)
		}
	}

	// 1s 执行一次
	setTimeout(function() {
		loop(100)
	}, 1000);
</script>
  • 这里,40ms 执行一次,100次,共4s执行完成
  • 可以看到,没有明显的阻塞

为什么会有如此区别

  • 究其原因是因为浏览器是单线程,它将GUI描绘,时间器处理,事件处理,JS执行远程资源加载统统放在一起,当做某件事,只有将它做完才能做下一件事
  • 如果有足够的时间,浏览器是会对我们的代码进行编译优化(JIT) 及进行热代码优化,一些DOM操作,内部也会对reflow进行处理
  • reflow是一个性能黑洞,很可能让页面的大多数元素进行重新布局
  • 所以,浏览器歇一会儿跑一会儿,比一直跑性能更好

时间分片技术方案选择

1 )注意兼容问题

  • 注意,在 request18 版本并没有使用 requestIdleCalback, 仍旧因为兼容问题

2 ) 方案选择

  • 在浏览器环境中,常见的macro task有setTimeout、MessageChannel、postMessage
  • 而常见的 micro task 有 MutationObsever, Promise.then

2.1 微任务问题

  • 为什么不使用微任务呢?

  • 宏任务是在下一轮事件循环中执行,微任务是在当前时间循环中执行

  • 微任务将在页面更新前全部执行完,也就是是要在当前帧执行完的,所以达不到「将主线程还给浏览器」的目的

  • 使用微任务就会造成一个问题,在极端情况下,一直卡在当前帧,因为微任务是在当前帧执行完的(本轮事件循环内)

  • 所以在 setTimeout, MessageChannel, postMessage 三选一

  • 为什么不使用setTimeout(fn,0)呢?

    ts 复制代码
    function timer() {
      console.ltme('计时器')
      setTimeout(() => {
        console.timeEnd('计时器')
        timer()
      }, 0)
    }
    
    timer()
    • 递归的setTimeout()调用会使调用间隔变为4ms,导致浪费了4ms
    • 这是一个递归的定时器,在递归的时候,看下它的间隔时间
    • 在定时器递归大约4次以后,它的递归时间就变成了4ms~ 5ms 变得不准确
    • 明明我们指定的是 0ms, 这里差距有些大了,所以这个api有些问题,误差太大

2.2 宏任务问题

  • 为什么不使用 rAF() 呢?

    • 如果上次任务调度不是rAF()触发的,将导致在当前帧更新前进行两次任务调度
    • 页面更新的时间不确定,如果浏览器间隔了10ms才更新页面,那么这10ms就浪费了
  • React Scheduler使用MessageChannel的原因为:生成宏任务,实现:

    • 将主线程还给浏览器,以便浏览器更新页面

    • 浏览器更新页面后继续执行未完成的任务

      js 复制代码
      const { port1, port2 } = new MessageChannel();
      
      port1.onmessage = function (event){
        console.log('收到来自port2的消息:', event.data);//收到来自port2的消息: pong
      };
      
      port2.onmessage = function (event) {
        console.log('收到来自port1的消息:', event.data);//收到来自port1的消息: ping
        port2.postMessage('pong');
      };
      port1.postMessage('ping');
相关推荐
Doris89320 小时前
【JS】Web APIs BOM与正则表达式详解
前端·javascript·正则表达式
建南教你种道德之花20 小时前
浏览器缓存完全指南:从原理到实践
前端
南游20 小时前
后台计时器罢工?我改用visibilitychange监听,代码从此‘永不停机’!
javascript
晚霞的不甘20 小时前
实战进阶:构建高性能、高可用的 Flutter + OpenHarmony 车载 HMI 系统
开发语言·javascript·flutter
1024小神20 小时前
swiftui中view分为几种类型?各有什么特点
前端
网络点点滴21 小时前
pinia简介
开发语言·javascript·vue.js
局i21 小时前
v-for 与 v-if 的羁绊:Vue 中列表渲染与条件判断的爱恨情仇
前端·javascript·vue.js
suke21 小时前
紧急高危:Next.js 曝出 CVSS 10.0 级 RCE 漏洞,请立即修复!
前端·程序员·next.js
狮子座的男孩21 小时前
js函数高级:06、详解闭包(引入闭包、理解闭包、常见闭包、闭包作用、闭包生命周期、闭包应用、闭包缺点及解决方案)及相关面试题
前端·javascript·经验分享·闭包理解·常见闭包·闭包作用·闭包生命周期