You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Wait at most two seconds before processing events.requestIdleCallback(processPendingAnalyticsEvents,{timeout: 2000});
如果你的回调函数是因为设置的这个 timeout 而触发的,你会注意到:
timeRemaining() 会返回 0
deadline 对象的 didTimeout 属性值是 true
如果你发现 didTimeout 是 true,你的代码可能会是这样子的:
functionmyNonEssentialWork(deadline){// Use any remaining time, or, if timed out, just run through the tasks.while((deadline.timeRemaining()>0||deadline.didTimeout)&&tasks.length>0)doWorkIfNeeded();if(tasks.length>0)requestIdleCallback(myNonEssentialWork);}
让我们试试用 requestIdleCallback 去上报数据。在这种情况下,我们可能希望去跟踪一个事件,如“点击导航栏菜单”。然而,因为通常他们是通过动画展现在屏幕上的,我们希望避免立即发送事件到 Google Analytics,因此我们将创建一个事件的数组来延迟上报,且在未来的某个时间点会发送出去。
vareventsToSend=[];functiononNavOpenClick(){// Animate the menu.menu.classList.add('open');// Store the event for later.eventsToSend.push({category: 'button',action: 'click',label: 'nav',value: 'open'});schedulePendingEvents();}
现在我们使用 requestIdleCallback 来处理那些被挂起的事件。
functionschedulePendingEvents(){// Only schedule the rIC if one has not already been set.if(isRequestIdleCallbackScheduled)return;isRequestIdleCallbackScheduled=true;if('requestIdleCallback'inwindow){// Wait at most two seconds before processing events.requestIdleCallback(processPendingAnalyticsEvents,{timeout: 2000});}else{processPendingAnalyticsEvents();}}
functionprocessPendingAnalyticsEvents(deadline){// Reset the boolean so future rICs can be set.isRequestIdleCallbackScheduled=false;// If there is no deadline, just run as long as necessary.// This will be the case if requestIdleCallback doesn’t exist.if(typeofdeadline==='undefined')deadline={timeRemaining: function(){returnNumber.MAX_VALUE}};// Go for as long as there is time remaining and work to do.while(deadline.timeRemaining()>0&&eventsToSend.length>0){varevt=eventsToSend.pop();ga('send','event',evt.category,evt.action,evt.label,evt.value);}// Check if there are more events still to send.if(eventsToSend.length>0)schedulePendingEvents();}
另一个不要在回调中触发 Dom 改动的原因是,Dom 改动是不可预期的,正因为如此,我们可以很容易地超过浏览器给出的时间限期。
最佳的实践就是只在 requestAnimationFrame 的回调中去进行 dom 的改动,因为浏览器会优化同类型的改动。这表明我们的代码要在 requestIdleCallback 时使用文档片段,这样就能在下一个 requestAnimationFrame 回调中把所有改动的 dom 追加上去。如果你正在使用 Virtual DOM 这个库,你可以使用 requestIdleCallback 进行 Dom 变动,但真正的 Dom 改动还是在下一个 requestAnimationFrame 的回调中,而不是 requestIdleCallback 的回调中。
所以谨记上面说的,下面来看下代码吧:
functionprocessPendingElements(deadline){// If there is no deadline, just run as long as necessary.if(typeofdeadline==='undefined')deadline={timeRemaining: function(){returnNumber.MAX_VALUE}};if(!documentFragment)documentFragment=document.createDocumentFragment();// Go for as long as there is time remaining and work to do.while(deadline.timeRemaining()>0&&elementsToAdd.length>0){// Create the element.varelToAdd=elementsToAdd.pop();varel=document.createElement(elToAdd.tag);el.textContent=elToAdd.content;// Add it to the fragment.documentFragment.appendChild(el);// Don't append to the document immediately, wait for the next// requestAnimationFrame callback.scheduleVisualUpdateIfNeeded();}// Check if there are more events still to send.if(elementsToAdd.length>0)scheduleElementCreation();}
在上面,我创建了一个元素,而且使用添加上了 textContent 这个属性。但这时候还不应该把元素追加到文档流中去。创建完元素添加到文档片段后,scheduleVisualUpdateIfNeeded 则被调用,它会创建一个 requestAnimationFrame 的回调,这时候,我们就应该把文档片段追加到 body 中去了:
functionscheduleVisualUpdateIfNeeded(){if(isVisualUpdateScheduled)return;isVisualUpdateScheduled=true;requestAnimationFrame(appendDocumentFragment);}functionappendDocumentFragment(){// Append the fragment and reset.document.body.appendChild(documentFragment);documentFragment=null;}
一切顺利的话,我们则会看到追加 dom 到文档中时,并没有什么性能的损耗。真 tm 的棒!
The text was updated successfully, but these errors were encountered:
原文: Using requestIdleCallback
译文: 使用requestIdleCallback
略有改动
如今,大多数的站点和 app 都需要执行很多的 JavaScript 脚本。你的 JavaScript 通常需要尽可能快地执行,而且,你又不希望通过获取用户行为的方式来达成目的。如果当用户滚动页面的时候,你的 JavaScript 开始上报数据,或者当用户点击按钮的时候,你往 DOM 中添加元素,你的 web 应用其实就已经变得迟钝,导致很差的用户体验。
现在有个好消息,一个新的 API 能够帮助你:
requestIdleCallback
。跟requestAnimationFrame
一样,requestAnimationFrame
允许我们正确地安排动画,同时最大限度地去提升到 60fps。而requestIdleCallback
则会在某一帧结束后的空闲时间或者用户处于不活跃状态时,处理我们的工作。这表明在不获取用户行为条件下,你能执行相关的工作。目前这个新的 API 在 Chrome Canary(M46+) 下可用(需要打开 chrome://flags/#enable-experimental-web-platform-features 去开启该功能),这样你从今天开始先尝试玩玩。但要记着,这个 API 是一个实验性的功能,该规范仍在不断变化,所以任何东西都可能随时改变。为什么我要使用 requestIdleCallback?
靠自己人工的安排不必要的工作是很困难的。比如,要弄清楚一帧剩余的时间,这显然是不可能的,因为当
requestAnimationFrame
的回调完成后,还要进行样式的计算,布局,渲染以及浏览器内部的工作等等。上面的话貌似还不能说明什么。为了确保用户不以某种方式进行交互,你需要为各种交互行为添加监听事件(scroll
、touch
、click
),即使你并不需要这些功能,只有这样才能绝对确保用户没有进行交互。另一方面,浏览器能够确切地知道在一帧的结束时有多少的可用时间,如果用户正在交互,通过使用requestIdleCallback
这个 API,允许我们尽可能高效地利用任何的空闲时间。接下来让我们看看它的更多细节,且让我们知道如果使用它。
检查 requestIdleCallback
目前
requestIdleCallback
这个 API 仍处于初期,所以在使用它之前,你应该检查它是否可用。现在,我们假设已经支持该 API
使用 requestIdleCallback
调用
requestIdleCallback
跟调用requestAnimationFrame
十分相似,它需要把回调函数作为第一个参数:当
myNonEssentialWork
被调用,会返回一个deadline
对象,这个对象包含一个方法,该方法会返回一个数字表示你的工作还能执行多长时间:调用
timeRemaining
这个方法能获得最后的剩余时间,当timeRemaining()
返回0
,如果你仍有其他任务需要执行,你便可以执行另外的requestIdleCallback
:确保你的方法已被调用
当事件很多的时候,你会怎么做?你可能会担心你的回调函数永远不被执行。很好,尽管
requestIdleCallback
跟requestAnimationFrame
很像,但它们也有不同,在于requestIdleCallback
有一个可选的第二个参数:含有timeout
属性的对象。如果设置了timeout
这个值,回调函数还没被调用的话,则浏览器必须在设置的这个毫秒数时,去强制调用对应的回调函数。如果你的回调函数是因为设置的这个
timeout
而触发的,你会注意到:timeRemaining()
会返回0
deadline
对象的didTimeout
属性值是true
如果你发现
didTimeout
是true
,你的代码可能会是这样子的:因为设置
timeout
对你用户导致的潜在破坏(这个操作会使你的 app 变得迟钝且低质量),请小心地设置这个参数。所以,在这,就让浏览器自己去决定什么时候触发回调吧。使用 requestIdleCallback 去上报数据
让我们试试用
requestIdleCallback
去上报数据。在这种情况下,我们可能希望去跟踪一个事件,如“点击导航栏菜单”。然而,因为通常他们是通过动画展现在屏幕上的,我们希望避免立即发送事件到 Google Analytics,因此我们将创建一个事件的数组来延迟上报,且在未来的某个时间点会发送出去。现在我们使用
requestIdleCallback
来处理那些被挂起的事件。上面代码中,你可以看到我设置了2秒的超时,但取决于你的应用。因为对于上报的这些分析数据,设置一个
timeout
来确保数据在一个合理的时间范围内被上报,而不是延迟到某个未知的时间点。这样做才是合理且有意义的。最后我们来写下
requestIdleCallback
执行的回调方法:这个例子中,我假设如果不支持
requestIdleCallback
,则立即上报数据。然而,对于一个在生产环境的应用,最好是用timeout
延迟上报来确保不跟任何相互冲突。使用 requestIdleCallback 改变 dom
requestIdleCallback
可以帮助提高性能的另一个场景是,当你需要做一些非必要的 dom 改动,比如懒加载,滚动页面时候不断在尾部添加元素。让我们看看requestIdleCallback
事实上是如何插入一帧里的。对于浏览器,在给定的一帧内因为太忙而没有去执行任何回调这是有可能的,所以你不应该期望在一帧的末尾有空闲的时间去做任何事。这一点就使得
requestIdleCallback
跟setImmediate
不太像,setImmediate
是在每一帧里都会执行。如果在某一帧的末尾,回调函数被触发,它将被安排在当前帧被 commit 之后,这表示相应的样式已经改动,同时更最重要的,布局已经重新计算。如果我们在这个回调中进行样式的改动,涉及到的布局计算则会被判无效。如果在下一帧中有任何的读取布局相关的操作,例如
getBoundingClientRect
,clientWidth
等等,浏览器会不得不执行一次强制同步布局(Forced Synchronous Layout),这将是一个潜在的性能瓶颈。另一个不要在回调中触发 Dom 改动的原因是,Dom 改动是不可预期的,正因为如此,我们可以很容易地超过浏览器给出的时间限期。
最佳的实践就是只在
requestAnimationFrame
的回调中去进行 dom 的改动,因为浏览器会优化同类型的改动。这表明我们的代码要在requestIdleCallback
时使用文档片段,这样就能在下一个requestAnimationFrame
回调中把所有改动的 dom 追加上去。如果你正在使用 Virtual DOM 这个库,你可以使用requestIdleCallback
进行 Dom 变动,但真正的 Dom 改动还是在下一个requestAnimationFrame
的回调中,而不是requestIdleCallback
的回调中。所以谨记上面说的,下面来看下代码吧:
在上面,我创建了一个元素,而且使用添加上了
textContent
这个属性。但这时候还不应该把元素追加到文档流中去。创建完元素添加到文档片段后,scheduleVisualUpdateIfNeeded
则被调用,它会创建一个requestAnimationFrame
的回调,这时候,我们就应该把文档片段追加到body
中去了:一切顺利的话,我们则会看到追加 dom 到文档中时,并没有什么性能的损耗。真 tm 的棒!
The text was updated successfully, but these errors were encountered: