杭州高端网站开发网站建设公司.
杭州高端网站开发,网站建设公司.,做网站用windows还是linux,网易邮箱163登录作者 | #x1f47d;来源 | 前端Sharing背景在不同的技术框架背景下#xff0c;处理更新的手段各不相同#xff0c;今天我们来探讨一下#xff0c;主流的前端框架批量处理的方式#xff0c;和其内部的实现原理。通过今天的学习#xff0c;你将收获这些内容#xff1a;主… 作者 | 来源 | 前端Sharing背景在不同的技术框架背景下处理更新的手段各不相同今天我们来探讨一下主流的前端框架批量处理的方式和其内部的实现原理。通过今天的学习你将收获这些内容 主流前端框架的批量更新方式。 vue 和 react 批量更新的实现。 宏任务和微任务的特点。一次 vue 案例首先来想一个问题。比如在 vue 中一次更新中。templatediv姓名 {{ name }}年龄 {{ age }}button clickhandleClick 点击/button/div
/templatescript
export default {data(){return {age:0,name:}},methods:{handleClick(){this.name alienthis.age 18}}
}
/script如上是一个非常简单的逻辑代码点击按钮会触发 name 和 age 的更新。那么首先想一个问题就是正常情况下vue 的数据层是通过响应式处理的那么比如 age 和 name 可以理解成做了一层属性代理字符串模版 template 里面的属性 name 和 age 的 get 会和组件的渲染 watcher vue3.0 里面的 effect 建立起关联。一次重新赋值会触发 set 那么根据响应式会触发渲染 watcher 重新执行然后就会重新更新组件渲染视图。那么暴露的问题就是我们在 handleClick 中同时改变了 name 和 age 属性那么按照正常情况下会分别触发 name 和 age 的 set那么如果不做处理那么会让渲染 watcher 执行两次结果就是组件会 update 两次但是结果是这样的吗结果是vue 底层通过批量处理只让组件 update 一次。一次 react 案例上面介绍了在 vue 中更新批处理的案例之后我们来看一下在 react 中的批量更新处理。把上述案例用 react 来实现一下function Index(){const [ age , setAge ] React.useState(0)const [ name, setName ] React.useState()return div姓名 {name}年龄 {age}button onClick{(){setAge(18)setName(alien)}}点击/button/div
}点击按钮触发更新会触发两次 useState 的更新函数。那么 React 的更新流程大致是这样的。首先会找到 fiberRoot 。然后进行调和流程。执行 Index 组件得到新的 element。diff fiber得到 effectList。执行 effect list得到最新的 dom 并进行渲染绘制。那么按常理来说Index 组件会执行两次。可事实是只执行一次 render。批量处理意义通过上面的案例说明在主流框架中对于更新都采用批处理。一次上下文中的 update 会被合并成一次更新。那么为什么要进行更新批处理呢批处理主要是出于对性能方面的考虑这里拿 react 为例子看一下批处理前后的对比情况例子一假设没有批量更新/ ------ js 层面 ------第一步发生点击事件触发一次宏任务。第二步执行 setAge 更新 fiber 状态。第三步进行 render 阶段Index 执行得到新的 element。得到 effectlist.第四步进行 commit 阶段更新 dom。第五步执行 setName 更新 fiber 状态。第六步重复执行第三步第四步。/ ------ 浏览器渲染 ------js 执行完毕渲染真实的 dom 元素。我们可以看到如果没有批量更新处理那么会多走很多步骤包括 render 阶段 commit 阶段dom 的更新等这些都会造成性能的浪费接下来看一下有批量更新的情况。例子二存在批量更新。/ ------ js 层面 ------第一步发生点击事件触发一次宏任务。第二步setAge 和 setName 批量处理 更新 fiber 状态。第三步进行 render 阶段Index 执行得到新的 element。得到 effectlist.第四步进行 commit 阶段更新 dom。/ ------ 浏览器渲染 ------js 执行完毕渲染真实的 dom 元素。从上面可以直观看到更新批处理的作用了本质上在 js 的执行上下文上优化了很多步骤减少性能开销。简述宏任务和微任务在正式讲批量更新之前先来温习一下宏任务和微任务这应该算是前端工程师必须掌握的知识点。所谓宏任务我们可以理解成script 标签中主代码执行一次用户交互比如触发了一次点击事件引起的回调函数定时器 setInterval 延时器 setTimeout 队列 MessageChannel 等。这些宏任务通过 event loop来实现有条不紊的执行。例如在浏览器环境下宏任务的执行并不会影响到浏览器的渲染和响应。我们来做个实验。function Index(){const [ number , setNumber ] useState(0)useEffect((){let timerfunction run(){timer setTimeout(() {console.log(----宏任务执行----)run()}, 0)}run()return () clearTimeout(timer)},[])return divbutton onClick{() setNumber(number 1 )} 点击{number}/button/div
}如上简单的 demo 中通过递归调用 run 函数让 setTimeout 宏任务反复执行。这种情况下 setTimeout 执行并不影响点击事件的执行和页面的正常渲染。什么是微任务呢 那么我们再来分析一下微任务在 js 执行过程中我们希望一些任务不阻塞代码执行又能让该任务在此轮 event loop 执行完毕那么就引入了一个微任务队列的概念了。微任务相比宏任务有如下特点微任务在当前 js 执行完毕后立即执行会阻塞浏览器的渲染和响应。一次宏任务完毕后会清空微任务队列。常见的微任务有 Promise queueMicrotask 浏览器环境下的 MutationObserver node 环境下 process.nextTick 等。我们同样做个实验看一下微任务function Index(){const [ number , setNumber ] useState(0)useEffect((){function run(){Promise.resolve().then((){run()})}run()},[])return divbutton onClick{() setNumber(number 1 )} 点击{number}/button/div
}在这种情况下浏览器直接卡死了没有了响应证实了上述的结论。微任务宏任务实现批量更新讲完了宏任务和微任务继续来看第一种批量更新的实现就是基于宏任务 和 微任务 来实现。先来描述一下这种方式比如每次更新我们先并不去立即执行更新任务而是先把每一个更新任务放入一个待更新队列 updateQueue 里面然后 js 执行完毕用一个微任务统一去批量更新队列里面的任务如果微任务存在兼容性那么降级成一个宏任务。这里优先采用微任务的原因就是微任务的执行时机要早于下一次宏任务的执行。典型的案例就是 vue 更新原理vue.$nextTick原理 还有 v18 中 scheduleMicrotask 的更新原理。以 vue 为例子我们看一下 nextTick 的实现runtime-core/src/scheduler.tsconst p Promise.resolve()
/* nextTick 实现用微任务实现的 */
export function nextTick(fn?: () void): Promisevoid {return fn ? p.then(fn) : p
}可以看到 nextTick 原理本质就是 Promise.resolve() 创建的微任务。再看看 react v18 里面的实现。react-reconciler/src/ReactFiberWorkLoop/ensureRootIsScheduledfunction ensureRootIsScheduled(root, currentTime) {/* 省去没有必要的逻辑 */if (newCallbackPriority SyncLane) {/* 支持微任务 */if (supportsMicrotasks) {/* 通过微任务处理 */scheduleMicrotask(flushSyncCallbacks);}}
}接下里看一下 scheduleMicrotask 是如何实现的。/* 向下兼容 */
var scheduleMicrotask typeof queueMicrotask function ? queueMicrotask : typeof Promise ! undefined ? function (callback) {return Promise.resolve(null).then(callback).catch(handleErrorInNextTick);
} : scheduleTimeout;scheduleMicrotask 也是用的 Promise.resolve 还有一个 setTimeout 向下兼容的情况。接下来模拟一下这个方式的实现。class Scheduler {constructor(){this.callbacks []/* 微任务批量处理 */queueMicrotask((){this.runTask()})}/* 增加任务 */addTask(fn){this.callbacks.push(fn)}runTask(){console.log(------合并更新开始------)while(this.callbacks.length 0){const cur this.callbacks.shift()cur()}console.log(------合并更新结束------)console.log(------开始更新组件------)}
}
function nextTick(cb){const scheduler new Scheduler()cb(scheduler.addTask.bind(scheduler))
}/* 模拟一次更新 */
function mockOnclick(){nextTick((add){add(function(){console.log(第一次更新)})console.log(----宏任务逻辑----)add(function(){console.log(第二次更新)})})
}mockOnclick()我们来模拟一下具体实现细节通过一个 Scheduler 调度器来完成整个流程。通过 addTask 每次向队列中放入任务。用 queueMicrotask 创建一个微任务来统一处理这些任务。mockOnclick 模拟一次更新。我们用 nextTick 来模拟一下更新函数的处理逻辑。可控任务实现批量更新上述介绍了通过微任务的方式实现了批量更新还有一种方式通过拦截把任务变成可控的典型的就是 React v17 之前的 batchEventUpdate 批量更新。这种情况的更新来源于对事件进行拦截比如 React 的事件系统。以 React 的事件批量更新为例子比如我们的 onClick onChange 事件都是被 React 的事件系统处理的。外层用一个统一的处理函数进行拦截。而我们绑定的事件都是在该函数的执行上下文内部被调用的。那么比如在一次点击事件中触发了多次更新。本质上外层在 React 事件系统处理函数的上下文中这样的情况下就可以通过一个开关证明当前更新是可控的可以做批量处理。接下来 React 就用一次就可以了。来看一下 React 的底层实现逻辑react-dom/src/events/ReactDOMUpdateBatching.jsexport function batchedEventUpdates(fn, a) {/* 开启批量更新 */const prevExecutionContext executionContext;executionContext | EventContext;try {/* 这里执行了的事件处理函数 比如在一次点击事件中触发setState,那么它将在这个函数内执行 */return fn(a);} finally {/* try 里面 return 不会影响 finally 执行 *//* 完成一次事件批量更新 */executionContext prevExecutionContext;if (executionContext NoContext) {/* 立即执行更新。 */flushSyncCallbackQueue();}}
}在 React 事件执行之前通过 isBatchingEventUpdatestrue 打开开关开启事件批量更新当该事件结束再通过 isBatchingEventUpdates false; 关闭开关然后在 scheduleUpdateOnFiber 中根据这个开关来确定是否进行批量更新。比如一次点击事件中const [ age , setAge ] React.useState(0)
const [ name, setName ] React.useState()
const handleClick(){setAge(18)setName(alien)
}那么首先 handleClick 是由点击事件产生的那么在 React 系统中先执行事件代理函数然后执行 batchedEventUpdates。这个时候开启了批量更新的状态。接下来 setAge 和 setName 在批量状态下不会立即更新。最后通过 flushSyncCallbackQueue 来立即处理更新任务。接下来我们模拟一下具体的实现body button onclickhandleClick() 点击/button
/body
scriptlet batchEventUpdate false let callbackQueue []function flushSyncCallbackQueue(){console.log(-----执行批量更新-------)while(callbackQueue.length 0 ){const cur callbackQueue.shift()cur()}console.log(-----批量更新结束-------)}function wrapEvent(fn){return function (){/* 开启批量更新状态 */batchEventUpdate truefn()/* 立即执行更新任务 */flushSyncCallbackQueue()/* 关闭批量更新状态 */batchEventUpdate false}}function setState(fn){/* 如果在批量更新状态下那么批量更新 */if(batchEventUpdate){callbackQueue.push(fn)}else{/* 如果没有在批量更新条件下那么直接更新。 */fn()}}function handleClick(){setState((){console.log(---更新1---)})console.log(上下文执行)setState((){console.log(---更新2---)})}/* 让 handleClick 变成可控的 */handleClick wrapEvent(handleClick)/script分析一下核心流程本方式的核心就是让 handleClick 通过 wrapEvent 变成可控的。首先 wrapEvent 类似于事件处理函数在内部通过开关 batchEventUpdate 来判断是否开启批量更新状态最后通过 flushSyncCallbackQueue 来清空待更新队列。在批量更新条件下事件会被放入到更新队列中非批量更新条件下那么立即执行更新任务。往期推荐Redis 缓存击穿失效、缓存穿透、缓存雪崩怎么解决如果被问到分布式锁应该怎样回答三分钟教你用 Scarlet 写一个 WebSocket AppJava 底层知识什么是 “桥接方法” 点分享点收藏点点赞点在看
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/87500.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!