美文网首页WEB前端学习交流干货
互联网大厂面试题总结和解析,帮你更快了解大厂需要什么样的WEB前

互联网大厂面试题总结和解析,帮你更快了解大厂需要什么样的WEB前

作者: 無言尽显 | 来源:发表于2020-08-27 21:26 被阅读0次

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

    防抖 (debounce)

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

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

    1. 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
    2. 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
    3. 文本编辑器实时保存,当无任何更改操作一秒后进行保存

    代码如下,可以看出来防抖重在清零 clearTimeout(timer)

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

    节流 (throttle)

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

    1. scroll 事件,每隔一秒计算一次位置信息等
    2. 浏览器播放事件,每个一秒计算一次进度信息等
    3. input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖)

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

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

    总结 (简要答案)

    • 防抖:防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。代码实现重在清零 clearTimeout。防抖可以比作等电梯,只要有一个人进来,就需要再等一会儿。业务场景有避免登录按钮多次点击的重复提交。
    • 节流:控制流量,单位时间内事件只能触发一次,与服务器端的限流 (Rate Limit) 类似。代码实现重在开锁关锁 timer=timeout; timer=null。节流可以比作过红绿灯,每等一个红灯时间就可以过一批。

    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 摘要计算,得到指纹信息

    但是对于常见的需求就有成熟的解决方案,若在生产环境使用,可以使用以下库

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

    1. canvas
    2. webgl
    3. UserAgent
    4. AudioContext
    5. 对新式 API 的支持程度等
    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

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

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

    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

    如果需要全量替换字符串,可以使用 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

    $ htop -p 31796
    

    总结

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

    1. pidstat -r
    2. htop/top -p
    3. 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 的逻辑如下

    1. 如果存在跨域,则带上 Origin,值为当前域名
    2. 如果不存在跨域,则不带 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,响应头中返回 Access-Control-Allow-Origin: foo.shanyue.tech,被 CDN 缓存
    2. bar.shanyue.tech,因由缓存,响应头中返回 Access-Control-Allow-Origin: foo.shanyue.tech,跨域出现问题

    此时,Vary: Origin 就上场了,代表为不同的 Origin 缓存不同的资源

    总结 (简要答案)

    CORS 如何指定多个域名?

    根据请求头中的 Origin 来设置响应头 Access-Control-Allow-Origin,思路如下

    1. 总是设置 Vary: Origin,避免 CDN 缓存破坏 CORS 配置
    2. 如果请求头不带有 Origin,证明未跨域,则不作任何处理
    3. 如果请求头带有 Origin,证明浏览器访问跨域,根据 Origin 设置相应的 Access-Control-Allow-Origin: <Origin>

    使用伪代码实现如下

    // 获取 Origin 请求头
    const requestOrigin = ctx.get('Origin');
    
    ctx.set('Vary', 'Origin')
    
    // 如果没有,则跳过
    if (!requestOrigin) {
      return await next();
    }
    
    // 设置响应头
    ctx.set('Access-Control-Allow-Origin', requestOrigin)
    

    07 既然 cors 配置可以做跨域控制,那可以防止 CSRF 攻击吗

    对 CORS 一点用也没有

    1. form 提交不通过 CORS 检测,你可以在本地进行测试
    2. 即使通过 xhrfetch 进行提交被 CORS 拦住,但是对于简单请求而言,请求仍被发送,已造成了攻击

    08 如何避免 CDN 为 PC 端缓存移动端页面

    如果 PC 端和移动端是一套代码则不会出现这个问题。这个问题出现在 PC 端和移动端是两套代码,却共用一个域名。

    使用 nginx 配置如下,根据 UA 判断是否移动端,而走不同的逻辑 (判断UA是否移动端容易出问题)

    location / {
        // 默认 PC 端
        root /usr/local/website/web;
    
        # 判断 UA,访问移动端
        if ( $http_user_agent ~* "(Android|webOS|iPhone|iPad|BlackBerry)" ){ 
            root /usr/local/website/mobile;
        }
    
        index index.html index.htm;
    }
    

    解决方案通常使用 Vary 响应头,来控制 CDN 对不同请求头的缓存。

    此处可以使用 Vary: User-Agent ,代表如果 User-Agent 不一样,则重新发起请求,而非从缓存中读取页面

    Vary: User-Agent
    

    当然,User-Agent 实在过多,此时缓存失效就会过多。

    简答

    使用 Vary: User-Agent,根据 UA 进行缓存。

    Vary: User-Agent
    

    但最好不要出现这种情况,PC 端和移动端如果是两套代码,建议用两个域名,理由如下

    1. nginx 判断是否移动端容易出错
    2. 对缓存不友好

    09 如何实现表格单双行条纹样式

    通过 css3 中伪类 :nth-child 来实现。其中 :nth-child(an+b) 匹配下标 { an + b; n = 0, 1, 2, ...} 且结果为整数的子元素

    • nth-child(2n)/nth-child(even): 双行样式
    • nth-child(2n+1)/nth-child(odd): 单行样式

    其中 tr 在表格中代表行,实现表格中单双行样式就很简单了:

    tr:nth-child(2n) {
      background-color: red;
    }
    
    tr:nth-child(2n+1) {
      background-color: blue;
    }
    

    同理:

    1. 如何匹配最前三个子元素: :nth-child(-n+3)
    2. 如何匹配最后三个子元素: :nth-last-child(-n+3)

    10 简述下 css specificity

    css specificity 即 css 中关于选择器的权重,以下三种类型的选择器依次下降

    1. id 选择器,如 #app
    2. classattributepseudo-classes 选择器,如 .header[type="radio"]:hover
    3. type 标签选择器和伪元素选择器,如 h1p::before

    其中通配符选择器 *,组合选择器 + ~ >,否定伪类选择器 :not() 对优先级无影响

    另有内联样式 <div class="foo" style="color: red;"></div>!important(最高) 具有更高的权重

    [:not 的优先级影响 - codepen]可以看出 :not 对选择器的优先级无任何影响

    好了,今天的分享就到这里,如果你是正在学习前端或准备学习前端,可以去我的前端学习交流裙(109029339)免费下载一些前端学习视频,而且不定时还有大咖直播分享,希望能帮助大家共同成长。

    相关文章

      网友评论

        本文标题:互联网大厂面试题总结和解析,帮你更快了解大厂需要什么样的WEB前

        本文链接:https://www.haomeiwen.com/subject/fdvhsktx.html