随笔(三)

这两天刚结束完美团三面与腾讯三面,明天要开始阿里的面试,面试过程好煎熬,等待结果的过程很痛苦,写点东西平缓一下心情吧。

这个月,我寻求了现在工作的本科舍友的帮助,进行模拟问答。目的是想知道目前一线大厂对某些领域或者某些功能问题,是如何解决的(包括想法、技术、成本等)。这篇文章就是对某些问题的记录吧,可能今天写不完,后面会进行补充更新。

一、耗资源的任务处理

我们在实际开发中,一定会遇到非常耗资源的任务,会阻塞页面渲染,面对这种情况,如何进行处理。有两种解决方案,第一种是webworker,第二种是webassembly。

web worker

这里可能有人会疑问,JS虽然是单线程的,但不是有事件循环嘛。我们要知道,这是将耗时任务挂起,等到任务队列空闲下来,再去执行该任务,如果遇到非常耗时的任务,可以使用时间切片的方法进行优化,但这种优化,杯水车薪罢了。

web worker本质就是运行在浏览器背后独立的线程,将任务在此上进行执行,再返回给主线程。

具体实现过程如下,注意的是执行的任务也就是js文件,不能使用本地文件,必须是网络上的同源文件。实际开发可以放在public下面,当然到时候项目真正上线,可以让后端或者运维直接将文件丢在静态资源下面就行。

1
2
3
4
5
6
<script setup>
let worker = new Worker("https://xxxx/xxx.js");
worker.onMessage("message", () => {
//收到结果,执行相关操作
})
</script>
1
2
... //相关任务计算
self.postMessage(res);

这里还会遇到一个情况,就是模块导入问题,如果执行的任务文件需要引入第三方库或者其他资源文件,就需要此概念。

1
importScripts("xxx.js") //这个网络地址可以跨域

webassembly

这个我还没具体用过,只能说一下思路。将任务执行代码用C、C++编写,然后转换成webassembly,导入到项目js文件中执行,webassembly是个二级制的格式,所以能被直接执行处理,又因为使用比较高效的语言,所以也能极大缩短执行时间。

二、requestAnimationFrame

我们在做倒计时秒杀活动的时候,会遇到两个问题,一个是当前时间获取不准确问题,另一个是实现如果用setTimeout/setInterval是不精确的。

这里可能有人会有第一个疑问,不是直接Data.now()或者getTime()不就可以了,但是这个获取的时间是用户设备的时间,而用户设备的时间会存在不精准的情况,我们应该要获取服务端的本地时间。那我们如何获取服务端的时间呢,在发送请求得到响应报文的时候,可以发现header中存在一个字段Date,这个就是服务端时间。接着又会存在一个疑问,我在倒计时的时候,如果不断发起请求获取时间,再完成差值计算,一方面过多的请求次数会耗费服务端性能,另一个方面请求也耗时。面对这种情况,其实我们只需完成一次请求,然后与本地设备时间进行插值比较即可,相当于去了解本地设备时间的误差。

针对第二个问题,因为定时器会受到事件循环宏微任务的影响,计算不精准,所以可以使用requestAnimationFrame去解决。它本质就是告诉浏览器在下一次页面重绘前,需要执行它里面的回调函数。因为页面刷新频率是固定的,所以不存在上述问题,其次requestAnimationFrame还有CPU节能、减少DOM操作的优点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function countDown(serverTime){
let stop = false;
//本地与服务端时间的差值
const diffTime = serverTime - Data.now();
let start = Data.now();
function animate(){
if(stop){
return;
}
requestAnimationFrame(animate);
if(Date.now() - start >= 100){
start += 100;
const currentTime = Data.now() + diffTime;
const duration = targetTime - currentTime;
//秒杀时间到
if(duration <= 0){
stop = true;
duration = 0;
}
render(duration);
}
}
animate();
}

function render(duration){
let sec = duration / 1000;
let min = sec / 60;
let hour = min / 60;
sec = format(sec % 60);
min = format(min % 60);
hour = format(hour % 60);

const html = `${hour}:${min}:${sec}`;
document.getElementById("xxx").innerHTML = html;

function format(time){
const str = Math.floor(time) + "";
return str.padStart(2, "0");
}
}

三、正则表达式

3.1 限定符

符号 意义
? 0个或1个
* 0个或多个
+ 1个或多个
{2} 出现2次
{2, 6} 出现2~6次
{2, } 出现次数不少于2次
{ab}+ ab出现次数>=1

3.2 运算符

符号 意义
(a|b) a或者b
[abc] [a-z]
[^0-9] 非数字

3.3 元字符

符号 意义
\d 数字字符
\w 单词(英文、数字、下划线)
\s 空白符(空格、换行符)
\D 非数字字符
\W 非单词字符
\S 非空白符
. 任意字符,但不包括换行符
^ 匹配行首
$ 匹配行尾
样例 作用
/#[a-fA-F0-9]{6}/g RGB匹配
/\b(25[0-5]|2[0-4\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g ip

3.4 方法

方法 返回值 作用
正则.test(字符串) boolean 检测字符串是否符合正则的标准
正则.exec(字符串) [字符串中符合正则的第一项, 一些其他信息] 捕获字符串中符合条件的内容
字符串.search(正则) 有的话返回开始索引,没有返回 -1 查找字符串中是否有满足条件的内容
字符串.match(正则) 有g => [匹配到的每一项] 返回字符串中符合正则条件的内容
字符串.replace(正则,要替换的字符串) 替换后的字符串 将字符串中满足正则条件的字符串替换掉
eval(正则字符串) 正则表达式 将字符串形式转为标准正则形式