资深前端工程师大厂面试总结

145 阅读8分钟

01 什么是防抖和节流,他们的应用场景有哪些

防抖 (debounce) 防抖,顾名思义,防止抖动,以免把一次事件误认为多次,敲键盘就是一个每天都会接触到的防抖操作。

想要了解一个概念,必先了解概念所应用的场景。在 JS 这个世界中,有哪些防抖的场景呢

登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖 文本编辑器实时保存,当无任何更改操作一秒后进行保存 代码如下,可以看出来 防抖重在清零 clearTimeout(timer)

function debounce (f, wait) {
  let timer
  return (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      f(...args)
    }, wait)
  }
}

节流 (throttle)

节流,顾名思义,控制水的流量。控制事件发生的频率,如控制为1s发生一次,甚至1分钟发生一次。与服务端(server)及网关(gateway)控制的限流 (Rate Limit) 类似。

scroll

代码如下,可以看出来节流重在加锁 timer=timeout

function throttle (f, wait) {
  let timer
  return (...args) => {
    if (timer) { return }
    timer = setTimeout(() => {
      f(...args)
      timer = null
    }, wait)
  }
}
(简要答案)

>防抖:防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。防抖可以比作等电梯,只要有一个人进来,就需要再等一会儿。业务场景有避免登录按钮多次点击的重复提交。

>节流:控制流量,单位时间内事件只能触发一次,与服务器端的限流 (Rate Limit) 类似。 >「代码实现重在开锁关锁」 。节流可以比作过红绿灯,每等一个红灯时间就可以过一批。

02 在前端开发中,如何获取浏览器的唯一标识

在 Issue 中交流与讨论: 02 在前端开发中,如何获取浏览器的唯一标识

由于不同的系统显卡绘制 canvas 时渲染参数、抗锯齿等算法不同,因此绘制成图片数据的 CRC 校验也不一样。

function getCanvasFp () {
  const canvas = document.getElementById('canvas')
  const ctx = canvas.getContext('2d')
  ctx.font = '14px Arial'
  ctx.fillStyle = '#ccc'
  ctx.fillText('hello, shanyue', 2, 2)
  return canvas.toDataURL('image/jpeg')
}

因此根据 canvas 可以获取浏览器指纹信息。

1.绘制 canvas ,获取 base64 的 dataurl

2.对 dataurl 这个字符串进行 md5 摘要计算,得到指纹信息 但是对于常见的需求就有成熟的解决方案,若在生产环境使用,可以使用以下库

fingerprintjs2

它依据以下信息,获取到浏览器指纹信息, 而这些信息,则成为 component

canvas
webgl
UserAgent
AudioContext
requestIdleCallback(function () {
  Fingerprint2.get((components) => {
    const values = components.map((component) => component.value)
    const fp = Fingerprint2.x64hash128(values.join(''), 31)
  })
})

fingerprintjs2 中,对于 component 也有分类

browser independent component :有些 component 同一设备跨浏览器也可以得到相同的值,有些独立浏览器,得到不同的值 stable component : 有些 component 刷新后值就会发生变化,称为不稳定组件 在实际业务中,可根据业务选择合适的组件

const options = {
  excludes: {userAgent: true, language: true}
}

简答

">根据 canvas 可以获取浏览器指纹信息

1.绘制 canvas ,获取 base64 的 dataurl

2.对 dataurl 这个字符串进行 md5 摘要计算,得到指纹信息

若在生产环境使用,可以使用 fingerprintjs2 ,根据业务需求,如单设备是否可跨浏览器,以此选择合适的 component

03 在服务端应用中如何获得客户端 IP 在 Issue 中交流与讨论: 03 在服务端应用中如何获得客户端 IP

如果有x-forwarded-for 的请求头,则取其中的第一个 IP,否则取建立连接 socket 的 remoteAddr。

x-forwarded-for 基本已成为了基于 proxy 的标准HTTP头,格式如下,可见第一个 IP 代表其真实的 IP,可以参考 MDN X-Forwarded-For

X-Forwarded-For: 203.0.113.195, 70.41.3.18, 150.172.238.178
X-Forwarded-For: <client><proxy1><proxy2>

以下是 koa 获取 IP 的方法

get ips() {
    const proxy = this.app.proxy;
    const val = this.get(this.app.proxyIpHeader);
    let ips = proxy && val
      ? val.split(/\s*,\s*/)
      : [];
    if (this.app.maxIpsCount > 0) {
      ips = ips.slice(-this.app.maxIpsCount);
    }
    return ips;
  },

  get ip() {
    if (!this[IP]) {
      this[IP] = this.ips[0] || this.socket.remoteAddress || '';
    }
    return this[IP];
  },

04 js 如何全部替代一个子串为另一个子串

更多描述: 假设有一个字符串 hello. hello. hello. 需要替换为 AAA ,即把 hello. 替换为 A

在 Issue 中交流与讨论: 04 js 如何全部替代一个子串为另一个子串

如果需要全量替换字符串,可以使用 String.prototype.replace(re, replacer) ,其中正则表达式需要开启 global flag

const s = 'foo foo foo'
s.replce(/foo/g'bar')

那如题中, 是否可以使用正则表达式来替代子串

答: 不可以,因为使用子串构建正则时,有可能有特殊字符,就有可能出现问题 ,如下

// 期待结果: 'AhelloX hello3 ''hello. helloX hello3 '.replace(new RegExp('hello. ''g'), 'A')
< "AAA"

而在 javascript 中替换子串只能使用一种巧妙的办法: str.split('foo').join('bar')

'hello. hello. hello. '.split('hello. ').join('A')
< "AAA"

真是一个巧(笨)妙(拙)的办法啊!!!** 大概 TC39 也意识到了一个问题,于是出了一个新的 API** ,在 ESNext

String.prototype.replaceAll()

'aabbcc'.replaceAll('b''.'); 
// 'aa..cc'

总结(及直接答案)

两种办法

str.split('foo').join('bar')

str.replaceAll('foo', 'bar') ,在ESNext中,目前支持性不好

05 如何获取一个进程的内存并监控

更多描述: 在编写脚本时,有时会出现内存过大发生 OOM 的事情,那我们如何得知某个进程的内存?另外又如何监控它

通过 ps 可以获知一个进程所占用的内存

$ ps -O rss -p 3506
  PID   RSS S TTY          TIME COMMAND
 3506  6984 S pts/1    00:00:00 vim

如果要监控内存,肯定使用对进程万能的命令 pidstat (PS: 这名字一听就知道是干嘛的)

## -r 显示内存信息
## -p 指定 pid
## 1: 每个一秒打印一次
$ pidstat -r -p 3506 1
Linux 3.10.0-957.21.3.el7.x86_64 (shanyue)      11/04/19        _x86_64_        (2 CPU)

20:47:35      UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
20:47:36        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:37        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:38        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:39        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:40        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:41        0      3506      0.00      0.00  139940   6984   0.18  vim

pidstat 是属于 sysstat 下的 linux 性能工具,但在 mac 中,如何定位内存的变化?此时可以使用万能的 top/htop

总结

简而言之,有以下三个命令

pidstat -r
htop/top -p
ps -O rss -p

06 CORS 如果需要指定多个域名怎么办

CORS 通过控制 Access-Control-Allow-Origin 控制哪些域名可以共享资源,取值如下

Access-Control-Allow-Origin: <origin> | *

其中 * 代表所有域名, origin 代表指定特定域名,那如何设置多个域名了?

此时需要通过代码实现, 根据请求头中的 Origin来设置响应头 Access-Control-Allow-Origin ,那 Origin又是什么东西?

请求头: Origin 并不是所有请求都会自动带上 Origin ,在浏览器中带 Origin的逻辑如下

Origin
Origin

逻辑理清楚后,关于服务器中对于 Access-Control-Allow-Origin 设置多域名的逻辑也很清晰了

1.如果请求头不带有 `Origin ``,证明未跨域,则不作任何处理

2.如果请求头带有 Origin ,证明跨域,根据Origin 设置相应的 Access-Control-Allow-Origin: <Origin>

使用伪代码实现如下:

// 获取 Origin 请求头
const requestOrigin = ctx.get('Origin');

// 如果没有,则跳过
if (!requestOrigin) {
return await next();
}

// 设置响应头
ctx.set('Access-Control-Allow-Origin', requestOrigin)

Vary: Origin

此时可以给多个域名控制 CORS,但此时假设有两个域名访问 static.shanyue.tech 的跨域资源

1.foo.shanyue.tech ,响应头中返回 Access-Control-Allow-Origin: foo.shanyue.tech

2.bar.shanyue.tech,响应头中返回Access-Control-Allow-Origin: bar.shanyue.tech

看起来一切正常,但如果中间有缓存怎么办?

1.foo.shanyue.tech,响应头中返回