前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一位摸金校尉决定转行前端

一位摸金校尉决定转行前端

作者头像
公众号@魔术师卡颂
发布2020-09-21 15:36:54
4690
发布2020-09-21 15:36:54
举报
文章被收录于专栏:魔术师卡颂

我是一名摸金校尉。

我们这行起源于东汉末年三国时期。曹操为了弥补军饷的不足,设立发丘中郎将,摸金校尉等军衔,专司盗墓取财,贴补军饷。

曹操之后,盗墓者皆各自为政,同行之间并无师徒之分,凡以摸金之法盗墓,均为摸金校尉。

拜近几年“盗墓”题材小说所赐,越来越多的人了解我们这行。但这些小说以讹传讹,为了吸引眼球往往故作神秘、夸大其词。

摸金险象环生,稍不留意便万劫不复。事实上,不像小说里靠“主角光环”每每死里逃生,我们有严谨的工作流程。

高风险,收益不确定。随着时间推移,从业者越来越少。最近我也决定转行当前端了。

为了防止这老祖宗的手艺失传,这里我就和你唠唠我们这行怎么工作的。

你问为啥转行前端?嘿,别说,我们这行的工作原理和浏览器工作原理还真像,学起来毫无压力。

安全第一

万事安全第一。

我们这行容错率太低,稍有差次,那就是个狗带。所以下墓后的每一步,都得慎之又慎,按章办事。

古墓暗无天日,机关暗道错综复杂。最重要的,就是及时绘制地图

每过一炷香的时间,都需要将这段时间路过的坑道,遇到的机关悉数绘制下来,此谓绘图

绘图前这段时间用来做事

做事

那我们具体都做什么事呢?比如探路寻宝测机关...

测机关就得停止探路,要寻宝就不能测机关。总之,一次只能做一件事。

活着出去固然是最重要的,但是又不能空手而归。

老祖宗早已为“要做的事”划分了轻重缓急,既要保证“重要的事”先做,又要保证其他事不至于不做。

做事本身也很有讲究,每次做完事后可能会有一些琐碎的后续工作。这些工作需要在下次做事前完成。

测机关来说,当测完机关后还需要检查一遍装备,以免下次使用出什么差次。

比如检查绳索检查手电...

如果事情做的麻利,那一炷香的时间其实可以做很多事。

比如这一炷香的时间依次做了:

  • 测机关
  • 测机关后的一些琐碎工作
  • 探路
  • 绘图

所以,下墓后的工作流程是:

一炷香为周期完成一或多件事,最后完成绘图

接着开始下一炷香的周期。

按照这个流程操作,不说万无一失,那也是很有保障的。

坏就坏在,有些同行太过贪心,比如这样:

如果在一炷香时间,一件事做的时间太长,那就没有时间绘图了!!

地图缺失一块,哪里有机关,哪里有暗道被少标记了,各种风险不言而喻!

终究这行还是太过搏命,好在我及时转行前端,接下来让我从浏览器角度再来解读下吧。

浏览器的一帧

一般浏览器的刷新率为60HZ,即1秒钟刷新60次。

代码语言:javascript
复制
1000ms / 60hz = 16.6

大概每过16.6ms浏览器会渲染一帧画面,也就是说浏览器一炷香的时间是16.6ms。

在这段时间内,大体会做两件事:taskrender

其中task被称为宏任务,就像下墓后我们要做的事一样。

包括setTimeoutsetIntervalsetImmediatepostMessagerequestAnimationFrameI/ODOM 事件等。

render指渲染页面。

eventLoop

task按优先级被划分到不同的task queue中。就像老祖宗定的“轻重缓急”。

比如:为了及时响应用户交互,浏览器会为鼠标键盘(Mouse、Key)事件所在task queue分配3/4优先权。

这样可以及时响应用户交互,又不至于不执行其他task queue中的task

虚线框部分要做的工作是:

  1. 将新产生的task插入不同task queue中。
  2. 按优先级从某个task queue中选择一个task作为本次要执行的task

这就是事件循环eventLoop)。

task执行过程中如果调用PromiseMutationObserverprocess.nextTick会将其作为microTask(微任务)保存在microTask queue中。

就像做事后的琐碎工作。

每当执行完task,在执行下一个task前,都需要检查microTask queue,执行并清空里面的microTask

比如如下代码

代码语言:javascript
复制
setTimeout(() => console.log('timeout'));
Promise.resolve().then(() => {
    console.log('promise1');
    Promise.resolve().then(() => console.log('Promise2'));
});
console.log('global');

执行过程为:

  1. “全局作用域的代码执行”是第一个task
  2. 执行过程中调用setTimeout计时器线程会去处理计时,在计时结束后会将计时器回调加入task queue中。
  3. 调用Promise.resolve,产生microTask,插入microTask queue
  4. 打印global
  5. “全局作用域的代码执行”的task执行完毕,开始遍历清理microTask queue
  6. 打印promise1
  7. 调用Promise.resolve,产生microTask,插入当前microTask queue
  8. 继续遍历microTask queue,执行microTask打印promise2
  9. 开始第二个task,打印timeout

一帧执行多个task

就像一炷香时间可以做多件事,在一帧时间可以执行多个task

执行如下代码后,屏幕会先显示红色再显示黑色,还是直接显示黑色?

代码语言:javascript
复制
document.body.style.background = 'red';
setTimeout(function () {
    document.body.style.background = 'black';
})

答案是:不一定。

全局代码执行setTimeout为不同的2个task

如果这2个task在同一帧中执行,则页面渲染一次,直接显示黑色(如下图情况一)。

如果这2个task被分在不同帧中执行,则每一帧页面会渲染一次,屏幕会先显示红色再显示黑色(如下图情况二)。

如果我们将setTimeout的延迟时间增大到17ms,那么基本可以确定这2个task会在不同帧执行,则“屏幕会先显示红色再显示黑色”的概率会大很多。

requestAnimationFrame

可以发现,task没有办法精准的控制执行时机。那么有什么办法可以保证代码在每一帧都执行呢?

答案是:使用requestAnimationFrame(简称rAF)。

rAF会在每一帧render前被调用。

一般被用来绘制动画,因为当动画代码执行完后接下来就进入render。动画效果可以最快被呈现。

如下代码执行结果是什么呢:

代码语言:javascript
复制
setTimeout(() => {
  console.log("setTimeout1");
  requestAnimationFrame(() => console.log("rAF1"));
})
setTimeout(() => {
  console.log("setTimeout2");
  requestAnimationFrame(() => console.log("rAF2"));
})

Promise.resolve().then(() => console.log('promise1'));
console.log('global');
代码语言:javascript
复制
向右翻动展示答案?                               大概率是:     1. global 2. promise1 3. setTimeout1 4. setTimeout2 5. rAF1 6. rAF2                                 

setTimeout1setTimeout2作为2个task,使用默认延迟时间(不传延迟时间参数时,大概会有4ms延迟),那么大概率会在同一帧调用。

rAF1rAF2则一定会在不同帧的render前调用。

所以,大概率我们会在同一帧先后调用setTimeout1setTimeout2rAF1,再在另一帧调用rAF2

requestIdleCallback

如果render完后这一帧还有剩余时间呢?

如图中绿色部分:

此时你可以使用requestIdleCallbackAPI,如果渲染完成后还有空闲时间,则这个API会被调用。

掉帧与时间切片

如果task执行时间过长会怎么样呢?

如图taskA执行时间超过了16.6ms(比如taskA中有个很耗时的while循环)。

那么这一帧就没有时间render,页面直到下一帧render后才会更新。

表现为页面卡顿一帧,或者说掉帧。就像下墓后我们没有时间绘图

有什么好的解决办法么?

刚才提到的requestIdleCallback是一个解决办法。我们可以将一部分工作放到空闲时间中执行。

但是遇到长时间task还是会掉帧。

更好的办法是:时间切片。即把长时间task分割为几个短时间task

如图我们将taskA拆分为2个task。则每一帧都有机会render。这样就能减少掉帧的可能。

React15中,采用递归的方式构建虚拟DOM树

如果树层级很深,对应task的执行时间很长,就可能出现掉帧的情况。

为了解决掉帧造成的卡顿,React16递归的构建方式改为可中断的遍历

5ms的执行时间划分task,每遍历完一个节点,就检查当前task是否已经执行了5ms

如果超过5ms,则中断本次task

通过将task执行时间切分为一个个小段,减少长时间task造成无法render的情况。这就是时间切片

摸了摸手边的摸金符,我欣慰的想到:虽然996,但好歹身边都是活人。

这行,是转对了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 魔术师卡颂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 安全第一
  • 做事
  • 浏览器的一帧
  • eventLoop
  • 一帧执行多个task
  • requestAnimationFrame
  • requestIdleCallback
  • 掉帧与时间切片
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档