十道大厂面试题(含答案)总结

8,787 阅读8分钟

又到了跳槽季啦,该刷题走起了。这里总结了一些被问到可能会懵逼的面试真题,有需要的可以看下~

1.不借助变量,交换两个数

1. 算术交换

针对的是Number,或者类型可以转换成数字的变量类型

function swap(a, b) {
   a = a + b;
   b = a - b;
   a = a - b;
}

通过算术运算过程中的技巧,可以巧妙地将两个值进行互换。但是,有个缺点就是变量数据溢出。因为JavaScript能存储数字的精度范围是 -2^53 到 2^53。所以,加法运算,会存在溢出的问题。

2. 异或运算

^ 按位异或 若参加运算的两个二进制位值相同则为0,否则为1 此算法能够实现是由异或运算的特点决定的,通过异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变.

a = a ^ b;
b = a ^ b; 
a = a ^ b;

3. ES6的解构

[a, b] = [b, a];

更多参考:

2.实现sum(1,2,3)==sum(1)(2)(3)

function sum(...args){
  function currySum(...rest){
    args.push(...rest)
    return currySum
  }
  currySum.toString= function(){ 
    return args.reduce((result,cur)=>{
      return result + cur
    })
  }
  currySum.toNumber= function(){ 
    return args.reduce((result,cur)=>{
      return result + cur
    })
  }
  return currySum
}

更多参考:github.com/Advanced-Fr…

3.观察者模式 vs 发布-订阅模式,说说区别

观察者模式的概念

观察者模式模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主体对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

发布订阅者模式的概念

发布-订阅模式,消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者,叫做订阅者。意思就是发布者和订阅者不知道对方的存在。需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息。换句话说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在。

区别

我们把这些差异快速总结一下:

在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。

观察者 模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

更多:juejin.cn/post/684490… PS:最好把对应的模式的模型代码也写一下

4.实现LRU算法

实现一个LRU过期算法的KV cache, 所有KV过期间隔相同, 满足如下性质:

  1. 最多存储n对KV;
  2. 如果大于n个, 则随意剔除一个已经过期的KV;
  3. 如果没有过期的KV, 则按照LRU的规则剔除一个KV;
  4. 查询时如果已经过期, 则返回空;
class LRUCache {
    constructor(capacity,intervalTime){
        this.cache = new Map();
        this.capacity = capacity;
        this.intervalTime = intervalTime;
    }
    get(key){
        if(!this.cache.has(key)){
            return null
        }
        const tempValue = this.cache.get(key)
        this.cache.delete(key);
        if(Date.now() - tempValue.time > this.intervalTime){
            return null
        }
        this.cache.set(key, {value: tempValue.value, time: Date.now()})
        return tempValue.value
    }
    put(key, value){
        if(this.cache.has(key)){
            this.cache.delete(key)
        }
        if(this.cache.size >= capacity){ //满了
            const keys = this.cache.keys()
            this.cache.delete(keys.next().value)
        }
        this.cache.set(key, {value,time:Date.now()})
    }
}

巧妙地利用了Map结构的key是有序的这个特点。普通Object的key是无序的。

5.如何监控网页崩溃?

基于 Service Worker 的崩溃统计方案

随着 PWA 概念的流行,大家对 Service Worker 也逐渐熟悉起来。基于以下原因,我们可以使用 Service Worker 来实现网页崩溃的监控:

  1. Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
  2. Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;
  3. 网页可以通过navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息。

基于以上几点,我们可以实现一种基于心跳检测的监控方案:

  • p1:网页加载后,通过postMessageAPI 每5s给 sw 发送一个心跳,表示自己的在线,sw 将在线的网页登记下来,更新登记时间;
  • p2:网页在beforeunload时,通过postMessageAPI 告知自己已经正常关闭,sw 将登记的网页清除;
  • p3:如果网页在运行的过程中 crash 了,sw 中的running状态将不会被清除,更新时间停留在奔溃前的最后一次心跳;
  • sw:Service Worker 每10s查看一遍登记中的网页,发现登记时间已经超出了一定时间(比如 15s)即可判定该网页 crash 了。

更多:zhuanlan.zhihu.com/p/40273861

6.求代码输出,并说出为什么

var obj = {
    '2':3,
    '3':4,
    'length':2,
    'splice':Array.prototype.splice,
    'push':Array.prototype.push
}
obj.push(1)
obj.push(2)
obj.push(3)
console.log(obj)

答案:

{
    '2':1
    '3':2,
    '4':3,
    'length':5,
    'splice':Array.prototype.splice,
    'push':Array.prototype.push
}

obj有长度,相当于类数组,调用数组的push,会在数组的最后加一项,第一次调用,相当于长度变为3,那么下标为2的那一项被赋值为1,下标是2,当其作为对象的key值的时候,会隐式调用toString方法转为字符串2,则和obj本来有的key '2'相同,原来的key为2的value就被覆盖了。以此类推后面的2个push。

详情:github.com/LuckyWinty/…

7.node中的 setTimeout 和 setImmediate 有什么区别

setImmediate() 和 setTimeout() 很类似,但是基于被调用的时机,他们也有不同表现。

  • setImmediate 设计在poll阶段完成时执行,即check阶段;
  • setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行,但它在timer阶段执行

执行计时器的顺序将根据调用它们的上下文而异。如果二者都从主模块内调用,则计时器将受进程性能的约束。举个例子,有如下代码:

setTimeout(() => console.log(1));
setImmediate(() => console.log(2));

上面代码应该先输出1,再输出2,但是实际执行的时候,结果却是不确定,有时还会先输出2,再输出1。

这是因为setTimeout的第二个参数默认为0。但是实际上,Node 做不到0毫秒,最少也需要1毫秒,根据官方文档,第二个参数的取值范围在1毫秒到2147483647毫秒之间。也就是说,setTimeout(f, 0)等同于setTimeout(f, 1)。

实际执行的时候,进入事件循环以后,有可能到了1毫秒,也可能还没到1毫秒,取决于系统当时的状况。如果没到1毫秒,那么 timers 阶段就会跳过,进入 check 阶段,先执行setImmediate的回调函数。

但是,如果是这样的情况,输出顺序就固定了,例:

const fs = require('fs');
fs.readFile('test.js', () => {
 setTimeout(() => console.log(1));
 setImmediate(() => console.log(2));
});

在上述代码中,一定是先输出2,再输出1。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行完毕后队列为空,发现存在 setImmediate 回调,所以就直接跳转到 check 阶段去执行回调了,执行完成后再去到 timers 阶段,然后执行setTimeout。

8.写一个正则,根据name取cookie中的值。

function get(name){
  var reg = new RegExp(name+'=([^;]*)?(;|$)');
  var res = reg.exec(document.cookie);
  if(!res || !res[1])return '';
    try{
      if(/(%[0-9A-F]{2}){2,}/.test(res)){//utf8编码
      return decodeURIComponent(res);
    }else{//unicode编码
      return unescape(res);
    }
  }catch(e){
    return unescape(res);
  }
}

正则表达式中重点看这几句代码: '([^;])', 意思是匹配str=后面的不为; ([^;]表示非集, 也就是所有不为;的字符都能被匹配)的字符串, 该字符串出现0或更多次(), 之后将匹配到的字符串放入第一个捕获组.

详情:github.com/LuckyWinty/…

9.在不改变html结构的情况下,写出至少7种方法实现以下布局。

要求:左边2列,右边1列,中间自适应

    <div class="parent" style="width: 200px">
        <div class="child child1" style="width: 20px"></div>
        <div class="child child2" style="width: 20px">2</div>
        <div class="child child3" style="width: 20px">3</div>
    </div>

答案:

/* 1 */
    .parent{
        background-color: burlywood;
        display: flex;
    }
    .child{
        background-color: black;
        font-size: 20px;
        color: white;
    }
    .child3{
        margin-left: auto;
    }

/* 2 */
    .parent{
        background-color: burlywood;
        position: relative;
    }
    .child{
        font-size: 20px;
        color: white;
    }
    .child1{
        background-color: black;
        position: absolute;
        left: 0;
    }
    .child2{
        background-color: black;
        position: absolute;
        left: 20px;
    }
    .child3{
        background-color: black;
        position: absolute;
        right: 0;
    }

/* 3 */
    .parent{
        background-color: burlywood;
    }
    .child1{
        background-color: black;
        float: left;
    }
    .child2{
        background-color: red;
        float: left;
    }
    .child3{
        float: right;
        background-color: blue
    }

/* 4 */
    .parent{
        background-color: burlywood;
        display: table;
    }
    .child{
        background-color: black;
        display: table-cell;
        height: 20px;
    }
    .child3{
        display: block;
        margin-left: auto;
    }

/* 5 */
    .parent{
        background-color: burlywood;
        position: relative;
    }
    .child{
        background-color: black;
        position: absolute;
        top:0;
        left:0;
    }
    .child2{
        transform: translate(20px, 0);
    }
    .child3{
        transform: translate(180px, 0);
    }

/* 6 */
  .parent{
        background-color: burlywood;
        display: grid;
        grid-template-columns: repeat(10, 1fr);
    }
    .child{
        background-color: black;
        font-size: 20px;
        color:white;
    }
    .child3{
        grid-column: 10 / 11;
    }

/* 7 */
    .parent{
        background-color: burlywood;
        font-size: 0;
    }
    .child{
        background-color: black;
        display: inline-block;
        font-size: 20px;
        color: white;
    }
    .child3{
        margin-left: 140px;
    }

10.用css画一个扇形?

width: 0;
height: 0;
border: solid 100px red;
border-color: red transparent transparent transparent;
border-radius: 100px;

说明

本文部分题目及答案来源于网络,如有错误,欢迎纠正,如有侵权问题,麻烦微信联系 winty230 交涉处理。

最后

  • 欢迎加我微信(winty230),拉你进技术群,长期交流学习...
  • 欢迎关注「前端Q」,认真学前端,做个有专业的技术人...

GitHub