商汤科技面试题目

2,084 阅读15分钟

tcp的流量控制

流量控制:防止发送方发的太快,耗尽接收方的资源,从而使接收方来不及处理

流量控制的一些知识点:
    1)接收端抑制发送端的依据:接收端缓冲区的大小
    2)流量控制的目标是接收端,是怕接收端来不及处理
    3)流量控制的机制是丢包
怎么实现流量控制?
    窗口指的是一次批量的发送多少数据
    
    使用滑动窗口,滑动窗口就是类似于一个窗口一样的东西,
    是用来告诉发送端可以发送数据的大小或者说窗口标记了接收端缓冲区的大小,
    这样就可以实现
   

tcp拥塞控制

TCP/IP的拥塞控制就是为了防止发的太快,使得网络来不及处理,从而导致网络拥塞

TCP/IP协议的拥塞控制:
    TCP进行拥塞控制的四种算法:慢开始,拥塞避免,快速重传,快速恢复

慢开始:
    当主机来时发送数据时,由于并不清楚网络的负荷情况,如果立即把大量的数据字节注入到网络中,
    那么就有可能引起网络发生拥塞,所以由小到大逐渐增大发送窗口,
    也就是由小到大逐渐增大拥塞窗口数值,试探一下网络的拥塞情况(此时cwnd的值是以指数形式向上增加)
拥塞避免:
    不再以指数形式增长拥塞窗口,而是每经过一个往返事件就将发送方的拥塞窗口+1,使其增长缓慢
快速重传:
    快速重传要求接收方不再等待自己发送数据时才进行确认,此时要求立即发送确认,快速重传规定,发送方只要一连接到3个重复确认,就知道接收方没有收到包,此时就应该立即重传
快速恢复:
    将慢启动门限值设置为原来的一半,不再执行慢启动而是直接进入拥塞避免阶段

拥塞控制使用的机制就是:AIMD
    加法增加:
        是指执行拥塞避免算法后,在收到对所有报文段的确认之后,就把拥塞窗口增加一个mss的大小,使用色窗口缓慢增大,以防止网络过早
    乘法减少:
        出现一次超时,就把慢开始门阀限值为当前的拥塞窗口值乘以0.5
发送端如何知道已经丢包?
    定时器超时
    收到三个重复的ack

流量控制和拥塞控制的区别

相同点:
    现象都是出现丢包
    实现机制都是让发送方发的慢一点,发的少一点
 不同点:
    1)丢包的位置不相同
        流量控制丢包位置是在接受端上
        拥塞控制丢包位置是在路由器上
    2)作用的对象不同
        流量控制的对象是接受方,怕发送方发的太快,使得接受方来不及处理
        拥塞控制的对象是网络,怕发送方发的太夸,造成网络的阻塞,使得网络来不及处理
    3)联系:
        拥塞控制通常表示的是一个全局的过程,他会涉及到网络中所有的主机,所有的路由器和降低网络传输性能的所有因素
        流量控制发生在发送端和接收端之间,只是点到点之间的控制
    

promise.all实现原理

promise.all方法用于将多个promise实例,包装成一个新的promise实例,
promise.all方法接收一个数组作为一个参数,他的每一项都是一个promise对象

const p = Promise.all([p1, p2, p3]);

p的状态由p1,p2,p3决定,分为两种情况
(1) 只有p1,p2,p3的状态都变成了fulfilled,p的状态才会变成fulfilled,此时p1,p2,p3的返回值组成一个数组,传递给p的回调函数
(2)只要p1,p2,p3之中有一个被rejected,p的状态就变成了reject,此时第一个被reject的实例的返回值,会传递给p的回调函数

Promise.all = arr => {
    let aResult = [];    //用于存放每次执行后返回结果
    return new _Promise(function (resolve, reject) {
      let i = 0;
      next();    //开始逐次执行数组中的函数
      function next() {
        arr[i].then(function (res) {
          aResult.push(res);    //执行后返回的结果放入数组中
          i++;
          if (i == arr.length) {    //如果函数数组中的函数都执行完,便把结果数组传给then
            resolve(aResult);
          } else {
            next();
          }
        })
      }
    })
  };

里面的function next()看起来像是一个循环,但实际上是一个递归调用,只有数组前一个执行完了,
才能执行下一个,如果用循环的话,无法控制下一个的执行。

在这里有一个要点,也是Promise.all本身有的,传进Promise.all的数组元素,
必须都是一个Promise对象,否则是无法实现调用的

webpack热更新原理,有没有配置过webpack,自己实现一些插件之类

webpack 热更新原理

webpack的热更新,无需完全刷新整个页面的同时,更新所有类型的模块,是webpack提供的最有用的功能之一,热更新作为webpack内置的功能,可以通过--hot或者是hotModuleReplacementPlugin开启,

刷新分为两种,一种是页面刷新,不保留页面状态,就是简单粗暴,另外一种是基于WDS的模块热替换,只需要局部刷新页面上发生变化的模块,同时可以保留当前的页面状态,比如复选框的选中状态,输入框的输入等

模块热替换:
    模块热替换功能在应用程序运行过程中,替换,添加或删除模块,而无需重新加载整个页面,主要是通过以下几种方式,来显著加快开发速度:
        保留在完全重新加载页面期间丢失的应用程序状态
        只更新变更内容,以节省宝贵的开发时间
        在源代码中对css/js进行修改,会立刻在浏览器中进行更新,这几乎相当于在浏览器devtools直接更改样式
        

Vue双向数据绑定实现,用Object.defineProperty()实现的缺点,有什么场景是不能用它实现的,那么其他场景如何实现,不用object.defineProperty()如何实现?

vue的双向绑定数据是通过v-model属性实现的,具体的原理是采用的订阅者发布者模式,
通过object.defineProperty实现数据的双向绑定,
主要是劫持setter和getter属性,通过劫持setter和getter属性检测数据的变化,
并且通过observer对数据进行枚举操作,获取属性的setter和getter值,
Object.defineProperty的缺陷:
    1)无法检测到对象属性的新增或删除
        由于js的动态性,可以为对象追加新的属性或者删除其中某个属性,
        这点对经过Object.defineProperty方法建立的响应式对象来说,
        只能追踪对象已有数据是否被修改,无法追踪新增属性和删除属性,
        这就需要另外处理。
    2)不能监听数组的变化
       vue在实现数组的响应式时,它使用了一些hack,
       把无法监听数组的情况通过重写数组的部分方法来实现响应式,
       这也只限制在数组的push/pop/shift/unshift/splice/sort/reverse七个方法,
       其他数组方法及数组的使用则无法检测到, 
解决方法主要是使用proxy属性,这个proxy属性是ES6中新增的一个属性,
    proxy属性也是一个构造函数,他也可以通过new的方式创建这个函数,
    表示修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程
    proxy可以理解为在目标对象之前架设一层拦截,外界对该对象的访问,都必须经过这层拦截,
    因此提出了一种机制,可以对外界的网文进行过滤和改写,proxy这个词是代理,
    用来表示由他代理某些操作,可以译为代理器
    
    Proxy,字面意思是代理,是ES6提供的一个新的API,用于修改某些操作的默认行为,
    可以理解为在目标对象之前做一层拦截,外部所有的访问都必须通过这层拦截,
    通过这层拦截可以做很多事情,比如对数据进行过滤、修改或者收集信息之类。
    借用proxy的巧用的一幅图,它很形象的表达了Proxy的作用。
proxy代理的特点:
    proxy直接代理的是整个对象而非对象属性,
    proxy的代理针对的是整个对象而不是像object.defineProperty针对某个属性,
    只需要做一层代理就可以监听同级结构下的所有属性变化,
    包括新增的属性和删除的属性
proxy代理身上定义的方法共有13种,其中我们最常用的就是set和get,但是他本身还有其他的13种方法

proxy的劣势:
    兼容性问题,虽然proxy相对越object.defineProperty有很有优势,但是并不是说proxy,就是完全的没有劣势,主要表现在以下的两个方面:
        1)proxy有兼容性问题,无完全的polyfill:
            proxy为ES6新出的API,浏览器对其的支持情况可在w3c规范中查到,通过查找我们可以知道,
            虽然大部分浏览器支持proxy特性,但是一些浏览器或者低版本不支持proxy,
            因此proxy有兼容性问题,那能否像ES6其他特性有polyfill解决方案呢?,
            这时我们通过查询babel文档,发现在使用babel对代码进行降级处理的时候,并没有合适的polyfill
        2)第二个问题就是性能问题,proxy的性能其实比promise还差,
        这就需要在性能和简单实用上进行权衡,例如vue3使用proxy后,
        其对对象及数组的拦截很容易实现数据的响应式,尤其是数组
        
        虽然proxy有性能和兼容性处理,但是proxy作为新标准将受到浏览器厂商重点持续的性能优化,
        性能这块会逐步得到改善
    

数组去重,数组里面有重复的函数对象

var arr = [{
    "name": "ZYTX",
    "age": "Y13xG_4wQnOWK1QwJLgg11d0pS4hewePU95UHtpMl3eE81uS74NC-6zu-Rtnw4Ix",
    "gender": "AAAAAA.doc"
}, {
    "name": "ZYTA",
    "age": "Y13xG_4wQnOWK1QwJLgg11d0pS4hewePU95UHtpMl3eE81uS74NC-6zu-Rtnw4Ix",
    "gender": "BBBBBB.doc"
}, {
    "name": "ZDTX",
    "age": "Y13xG_4wQnOWK1QwJLgg11d0pS4hewePU95UHtpMl3eE81uS74NC-6zu-Rtnw4Ix",
    "gender": "CCCCCC.doc"
}, {
    "name": "ZYTX",
    "age": "Y13xG_4wQnOWK1QwJLgg11d0pS4hewePU95UHtpMl3eE81uS74NC-6zu-Rtnw4Ix",
    "gender": "AAAAAA.doc"
}];
var hash = {};
arr = arr.reduce(function(item, next) {
    hash[next.name] ? '' : hash[next.name] = true && item.push(next);
    return item
}, [])

数组的扁平化概念是指将一个多维数组变成是一个一维数组

[1, [2, 3, [4, 5]]]  ------>    [1, 2, 3, 4, 5]

具体实现

方法一:最常用的方法递归法:

function flatten(arr) {
    var res = [];
    arr.map(item => {
        if(Array.isArray(item)) {
            res =[] res.concat(flatten(item));
        } else {
            res.push(item);
        }
    });
    return res;
}

方法二:使用es6新特性扩展运算符:

[].concat(...[1, 2, 3, [4, 5]]);
// [1, 2, 3, 4, 5]

方法三:使用reduce方法:

数组的reduce方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行,将其结果汇总称为单个返回值)

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

callback:
    执行数组中的每个值(如果没有提供则第一个值除外)的函数,包括四个参数
    accumulator:
        累计器累计回调的返回值,她是上一次调用回调时返回的累积值
    currenValue:
        数组中正在处理的元素
        
function flatten(arr) {  
    return arr.reduce((result, item)=> {
        return result.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}

方法四:toString & split

也就是说调用数组的toString方法,将数组变成字符串,然后再用split分割还原为数组

function flatten(arr) {
    return arr.toString().split(',').map(function(item) {
        return Number(item);
    })
} 

方法五:使用join&split

和上面的toString方法一样,join也可以将数组转换未字符串

function flatten(arr) {
    return arr.join(',').split(',').map(function(item) {
        return parseInt(item);
    })
}

虽然数组arr,若arr[i]为数组则递归遍历,直到arr[i]不为数组然后与之前的结果concat

数组遍历,对象遍历

数组遍历的方法有很多,比如说我们常用的pop,push,shift,unshift数组还有split和slice对数组进行增删改操作

for in 语句用于对数组或者是对象的属性进行循环操作

for of 循环可以使用的范围包括数组,set和map结构,某些类似数组的对象(argument对象,Dom节点对象)

for in 可以循环遍历对象的属性,原型链上的所有属性都将被访问

同时可以遍历对象的还有Object.keys(),只遍历自身属性

memory cache 和disk cache 的区别,除此之外还有别的浏览器缓存吗?

浏览器缓存分为强制缓存和浏览器缓存,
其中强制缓存主要是
    expires:
        指定了服务器端到达指定的具体时间点
        修改了本地时间,可能会造成缓存失效
    cache-control两种形式的缓存,
        可以组合使用多种指令
    两者对比:
        expires是http1.0的产物
        cache-control是http1.1的产物
协商缓存:
    last-modified/if-modified-since(只能以秒计时,有局限性)
    Etag/if-none-match:
        响应请求时,返回一个唯一标识Etag
    Etag服务器会生成资源有变化会重新生成
缓存机制:
    强制缓存优先于协商缓存
    协商缓存失效,返回200,重新返回资源和缓存标识
    协商缓存生效则返回304,继续使用缓存
缓存位置主要有四个:
    service worker 只有缓存控制哪些文件,如何匹配缓存,如何读取缓存,并且缓存是持续的
    memory cache
        读取高效
        持续时间很短
    disk cache
        读取速度慢
        比memory cache胜在容量和存储失效
    push cache
        只在会话(session)中生效
        回话结束就被释放,并且缓存时间也很短暂
用户的影响:
    地址栏输入地址:;
        查询disk cache是否匹配
        没有匹配则发送网络请求
    普通刷新:
        优先使用memory cache
        其次才是disk cache
    强制刷新:
        浏览器不使用缓存

class实现继承如何使用es5实现

判断数组的多种方式

1)instanceof 操作符判断

arr instance of Array,instanceof 用于判断某个实例是否属于某个对象

2)对象构造函数的constructor 判断
    arr.constructor===Array
    object的每个实例都有构造函数从事通融,用于保存着用于创建当前对象的函数
3)Array原型链上的isPrototypeOf()
    Array.prototype.isPrototype(arr)
    用于测试一个对象是否存在于另一个对象的原型链上
4)object.getPrototypeOf(arr)
    返回指定对象的原型
    Object.getPrototypeOf(arr) === Array.prototype两者比较即可
5) Object.prototype.toString.call(arr)==='[object Array]'

6)Array.isArray()
    ES5中新增了Array.isArray方法,返回值是布尔值类型的数据

Vuex中为什么分为mutations和actions来执行同步和异步

主要是为了devtools能够追踪状态变化

事实上,vuex里面actions只是一个架构性概念,并不是必须的,说到底只是一个函数,
但是对于mutation而言,mutation必须是同步的,同步的意义就在于这样每一个mutation执行完成后都可以对应到一个新的状态,这样devtools就可以打个快照存储下来

如果在mutation中做了异步操作,在dev-tools中会立即打印一个快照,但是此时的异步执行还没有执行完,此时的快照信息就是错误的

而在actions中异步操作dev-tools会等等异步操作执行完才去打印mutation的一个快照,这样方便我们查看mutation里的变化

轮播图如何实现

Vue怎么监听深度嵌套对象的属性辩护

父组件中给comp传入一个props,子组件怎么监听user.age发生变化

const user = {"name":"zzzz","age":11}
<comp props = "user"/>
 
// 子组件中
watch() {
    <!--方法一-->
    'user.age':()=>{
         
    }
    <!--方法二-->
    user:()=>{
        handler:()=>{},
        deep:true
    }
}

把setTimeout函数Promise化实现

async fn(){
    console.log("A");
    await sleep(1000);
    console.log("B");
}
// 实现 sleep 函数,使得打印的结果为 A-> sleep一秒钟->B
 
function sleep(time){
    return new Promise(function(resolve,reject){
            console.log("sleep一秒钟");
            setTimeOut(resolve,time);
    })
}

css水平垂直居中

因为引用计数,在ES6中的解决办法是什么?

ES6中考虑到了这一点,推出了两种新的数据结构:weakSet和weakMap,
他们对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个weak,表示这是弱引用

transform,使用transform会有什么问题(当存在scale的时候,会发生原点origin的变化,所有需要重置)

前端缓存机制,如果去掉etags\last-modefied\cache-control这些控制缓存的字段,浏览器会怎么处理缓存

浏览器默认会采用一个启发式的算法,通常会取响应头的Date_value - Last-Modified_value值的10%作为缓存时间。

flex布局

margin

line-height