1.引言

    最近又来了几个面试机会,心情复杂,本来都开始准备二战了,加上老妈给我说了句你放心二战,不希望自己边工作边二战,二战的钱还是有的,搞得我确实心情复杂。既然有厂给机会了还是得准备准备,毕竟每次都是多个厂一起来,也不亏的。这次本来以为是小公司但是没想到确实腾讯的来面试,整体感觉有点友好。

2.没答好的题目

    这里直接也不怕丢人,没答好的题目直接放下来:
(1)ISO7层网络模型(当时考研选的是除了计网的408):
物理层:实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异(可以理解成很多厂家的接口不一样,可能会导致兼容问题);
数据链路层:通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路,即提供可靠的通过物理介质传输数据的方法;
网络层:包含有常用的网络层协议,数据链路层的数据在这一层被转换为数据包,然后通过路径选择、分段组合、顺序、进/出路由等控制,将信息从一个网络设备传送到另一个网络设备;
传输层:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输;
会话层:组织和协调两个会话进程之间的通信,并对数据交换进行管理。用户可以按照半双工、单工和全双工的方式建立会话,单工指的是数据的传输是单向的,双工是指数据的传输可以双向同时进行,半双工指的是数据的传输可以双向不同时进行;
表示层:处理用户信息的表示问题,如编码、数据格式转换和加密解密;
应用层:计算机用户、各种应用程序和网络之间的接口,其功能是直接向用户提供服务,完成用户希望在网络上完成的各种工作。
(2)3次握手和四次挥手:
    三次握手:第一次客户端向服务端发送一个带有SYN标志的数据包,具体内容为SYN=1,seq=x,x为随机生成数值;第二次握手服务端接受一个带有SYN/ACK标志的数据包确认信息,具体内容为SYN=1,ACK=x+1,seq=y,y为随机生成数值;第三次客户端回传一个ACK标志的数据包,具体内容为SYN=1,ACK=y+1,seq=x+1
    四次挥手:第一次挥手客户端发送一个带FIN标志位的数据包(内容为FIN=1,seq=x),用来关闭从客户端到服务端的数据传送,并进入FIN_WAIT_1状态;第二次挥手服务端接受到客户端的FIN数据包之后给客户端发送一个带ACK标志位的数据包(内容为FIN=1,ack=x+1,seq=y),同时服务端进入CLOSE_WAIT状态;第三次挥手服务端发送一个FIN用来关闭服务端到客户端的数据传送(内容为FIN=1,ack=x+1,seq=z),服务端进入LAST_ACK状态;第四次挥手客户端接受到FIN数据包之后,进入TIME_WAIT状态,接着向服务端发送一个ACK数据包(内容为FIN=1,ack=z+1,seq=h),服务端接受之后进入CLOSED状态,完成断开连接。
(3)web缓存:
    web缓存主要可以分成三大类:数据库缓存、服务器端缓存以及浏览器缓存,数据库缓存比如redis,服务器端缓存包括代理服务器缓存、CDN缓存等,浏览器缓存算是常考了:cookie、session、localstorage、sessionStorage、indexedDB,浏览器缓存还有http缓存(这里没有答上来),http缓存只针对于get请求,当客户端向服务器请求资源时,会先获取该资源缓存的header信息,结合expires有效期,如果命中强缓存返回200,如果命中协商缓存返回304,Expires是一个GMT格式的时间字符串,用来标识资源的失效时间,Cache-Control字段包含的值有:public(客户端和服务端都可以用来缓存数据)、private(只让客户端缓存数据)、immutable(即使用户刷新数据,在有效期内也不会刷新)、no-cache(跳过设置强缓存,但是依然可以设置协商缓存)、no-store(不缓存)。
    协商缓存命中将会返回最新header同时提醒浏览器去调用缓存里面的内容,last-modified用于和请求头里面的last-modified-since对比,如果相等则表示协商缓存命中,但是last-modified只要编辑了就会标记为修改了,只能精确到秒级,因此出现了etag,基于内容生成一个标识字符串,请求头带有一个if-no-match(其值是上一次的etag),如果两次一致说明协商缓存命中。
(4)如何实现两个对象的实例相等:

 //让两个对象的实例相等
 function Single() {}
 const obj1 = new Single();
 const obj2 = new Single();
 console.log("第一次比较两个对象:", obj1 === obj2);//false
 //借鉴单例模式实现
 let _instance = null;
 function singleTon() {
     if (!_instance) _instance = new Single();
     return _instance;
 }
 const obj3 = new singleTon();
 const obj4 = new singleTon();
 console.log("第二次比较两个对象:", obj3 === obj4);//true

(5)事件循环机制
    感觉讲得不够清晰,参考大佬的术语:当有异步任务被压入异步任务队列时候,javascript会将这些异步任务分为宏任务微任务两个新的队列。然后,在所有同步任务执行完毕之后,异步任务会优先执行所有已经存在任务队列中的微任务。在所有的微任务执行完毕之后,再去宏任务队列中执行一个(注意是一个)宏任务,执行完一个宏任务之后会再去微任务队列中检查是否有新的微任务,有则全部执行,再回到宏任务队列执行一个宏任务,以此循环。这一套流程,就是事件循环。
(6)排序方式:
腾讯面经及准备
腾讯面经及准备
快速排序的思想:分治+递归(太经典了)

public static void quick_sort(int[] arr,int l,int r){
        if(l>=r)return;
        int x = arr[l],i = l-1,j = r+1;
        while (i<j){
            do i++; while (arr[i]<x);
            do j--; while (arr[j]>x);
            if (i<j){
                int t;
                t=arr[i];
                arr[i]=arr[j];
                arr[j]=t;
            }
        }
        quick_sort(arr,l,j);
        quick_sort(arr,j+1,r);
    }

(7)同源组策略:协议号+域名+端口号

4.整体感觉

    准备的基本能够答上来,感觉还是知识面不够,面试官倒是挺好的,十分友好,自我感觉良好其实菜的抠脚。

5.准备

    知识无止境,得再主动储备点,较少“额…不知道”的情况。本着谁家面试找谁家面经的思路,于是储备了如下的问题:
(1)当前时间是5点35分,求时钟与分钟之间的夹角(逻辑题)?
    有点意思,那首先得开始心算了,脑中一个钟面,35分钟也就是指向7,5与7之间的度数也就是30度,然后需要减去分针偏移,也就是30-35/60*15=30-35/4=30-8.75=21.25。
(2)js中数组有哪些方法?讲讲reduce?
    js数组的方法:push、pop、shift、unshift、replace、indexof、foreach、slice(start,end(不含))、splice(删除点,删除数量,替换的元素)、reverse、concat、sort(官方的底层原理里面,数据量小使用插入排序,数据量大使用快速排序)、join、map、includes、filter、every(全部符合标准返回true)、some(有符合标准的返回true)、reduce。这里对几个相对陌生点的函数补充说明:map与foreach的比较,forEach 比 map 的执行得慢,forEach()返回值是undefined,不可以链式调用,foreach可以直接操作原数组,map()返回一个新数组,原数组不会改变,都不能够直接跳出循环,除非抛出异常。注意foreach的修改:

let arr = [1, 2, 3, 4];
arr.forEach((value, index) => {
    value = 5;
})//不能改
let obj2 = [{
    name: 'zhangsan',
    old: 23
}, {
    name: 'lisi',
    old: 20
}];
obj2.forEach((value, index) => {
    value.name += "hello";//可以改
})

reduce的使用场景:数组求和、数组对象求和、数组的扁平化、计算元素出现的次数,使用方法:
腾讯面经及准备
举个例子:

//map也可以做到
let names = ['小明', '张三', '李四', '王五', '小明']
let countedNames = names.reduce(function (allNames, name) {
  // 判断当前数组元素是否出现过
  if (name in allNames) {
    allNames[name]++
  } else {
    allNames[name] = 1
  }
  return allNames
}, {})
console.log(countedNames); // {小明: 2, 张三: 1, 李四: 1, 王五: 1}

(3)xss跨站脚本攻击,主要是利用了html的解析规则,当动态页面中插入的内容含有这些特殊字符(如<)时,用户浏览器会将其误认为是插入了HTML标签,当这些HTML标签引入了一段JavaScript脚本时,这些脚本程序就将会在用户浏览器中执行。解决方法:手动过滤掉<>换成html语义标签,cookie设置 - HttpOnly 防止获取cookie信息。
(4)https实现原理:首先需要明白https=http+ssl协议,http使用的是明文传输,https内部使用混合加密模式(综合考虑了安全和数据传输时间,非对称加密比较慢)对传输的数据进行了加密处理,具体的过程为:客户端请求建立https安全链接,服务端向客户端发送带公钥的证书,客户端验证证书的有效性,获取里面公钥,生成对称加密密匙,用公匙加密数据发送给服务器,服务器利用私钥解密获取对称密匙,通知客户端ssl链接建立完成,然后客户端和服务端就可以使用https进行安全连接了。
(5)获取当前域名的方法:

let url1 = window.location.href;
let url2 = self.location.href;
let url3 = document.URL;
//前三种可以从URL中解析出域名,第四种可以直接拿到域名
let url4 = document.location.host;

(5)git版本回退:两种方式git reset +版本号,该命令会强行覆盖当前版本和要回退的版本之间的其他版本,不是很建议使用,git revert 能够在现在的版本的基础上新建立一个关于需要回退版本的新版本,不会覆盖回退版本和当前最新版本之间的版本,比较友好。
(5)逻辑题(如果逻辑题没有准备的话,遇见了很难答出来):班上百分之七十的同学喜欢足球,百分之八十的同学喜欢篮球,百分之九十的同学喜欢羽毛球,请问同时喜欢足球和篮球的有多少人?70%+80%-100%=50%;
(6)更改this的指向bind、call、apply的区别:

 obj1.say.call(obj2, 'call学校', '1年级');
 obj1.say.apply(obj2, ['apply学校', '2年级']);
 obj1.say.bind(obj2, 'bind学校', '3年级')();

(7)浏览器的渲染流程:这个问题就和用户输入url按下回车之后会发生什么一样,首先是url解析,解析出通信协议、域名或者ip、端口号 、文件资源路径、请求参数、锚片段(方便长页面中快速定位网站内容的位置);接下来是域名解析:搜索浏览器DNS缓存,浏览器缓存会维护一张域名跟ip地址的映射关系表,如果没有命中则搜索操作系统的DNS缓存,如果还是没有命中,则操作系统将域名发送到本地域名服务器,本地域名服务器通过递归的方式查询自己的DNS缓存,查找到后将结果返回若本地域名服务器的DNS缓存也没有命中,则本地域名服务器向上级域名服务器进行迭代查询。本地域名服务器先向根域名服务器发起请求,根域名服务器将顶级域名服务的地址返回给本地域名服务器,本地域名服务器根据顶级域名服务器地址向顶级域名服务器发起请求,然后得到权限域名服务器地址,本地域名服务器通过权限域名服务器地址向权限域名服务器发起请求,然后得到该域名的ip地址,本地域名将IP地址返给操作系统,并且本地域名将该IP缓存起来,操作系统将IP返回给浏览器,并将ip缓存起来,浏览器得到ip地址,并将ip缓存起来。接下来是TCP连接、响应请求,然后浏览器拿到结果进行页面渲染:根据HTML文件解析出DOM树,根据CSS解析出CSS规则树,将DOM树和 CSS规则树合并,构建渲染树,然后进行重排,根据渲染树进行节点位置大小计算,然后是重绘,根据计算好的信息绘制整个页面。
(8)重绘和回流:重绘指的是当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。重绘触发的属性:color 、background 、outline-color 、border-style、 background-image 、outline 、border-radius、 background-position、 outline-style 、 visibility、 background-repeat、 outline-width、 text-decoration、 background-size。触发回流的属性: 宽高相关、position上下左右 、 padding、 margin 、text-align、font-weight、 overflow 、display 、 font-family、border-width、 float、 line-border、 clear、 vertival-align、 min-height 、 white-space。减少重回和回流的一些处理方案:transform、opacity(需要配合图层 z-index 来使用)、filters等CSS3属性来开启硬件加速,避免回流;适当选择CSS动画的速度、CSS选择器尽量不要层级过大、将经常发生重回和回流的节点设置成图层;不要使用table布局、编写动画的时候尽可能使用请求动画帧requestAnimationFrame(CSS动画算是我个人比较薄弱的地方了)、合并dom的样式修改,最好预先定义好一套class、离线dom操作,首先将元素设置成display:none直接脱离dom树,修改完成之后再展示元素也就是减少为2次回流、利用好vue的DocumentFragment,当需要添加多个dom元素时,如果先将这些元素添加到DocumentFragment中,再统一将DocumentFragment添加到页面,会减少页面渲染dom的次数。也就是下面这个意思(是用第二段代码来优化):

var ul = document.getElementById("ul");
for (var i = 0; i < 20; i++) {
    var li = document.createElement("li");
    li.innerHTML = "index: " + i;
    ul.appendChild(li);
}
var ul = document.getElementById("ul");
var fragment = document.createDocumentFragment();
for (var i = 0; i < 20; i++) {
    var li = document.createElement("li");
    li.innerHTML = "index: " + i;
    fragment.appendChild(li);
}
ul.appendChild(fragment);

(9)闭包的优缺点:闭包是指有权访问另外一个函数作用域中的变量的函数,闭包的优点是访问到函数内部的局部变量,可以避免全局变量的污染,这些变量的值始终保持在内存中,不会在外层函数调用后被自动清除,闭包的缺点是容易产生内存泄漏。接着需要明确内存泄漏的几大原因:意外的全局变量(对于es5中没有声明的变量会挂载到window对象中)、闭包函数的使用、手动删除了dom元素,但是dom元素的引用还没有被删除,被遗忘的定时器,对象之间的循环引用。JS的垃圾回收机制:引用计数法和标记清除法(更常用),其中引用计数法指的是当某一个对象的引用关系发生改变时,引用计数器就会自动的修改这个对象所对应的引用数值(比如我们代码中有一个对象空间,一个变量引用了它,那么这个对象空间的引用数值加1) 引用数值为0的时候GC就开始工作,将当前的对象空间回收,如果发现引用次数为0立即回收,但是引用计数法不能够消除循环引用的对象空间,引用计数需要有一个程序来动态监视程序的引用情况,会消耗一定的性能。标记清除法指的是从根节点遍历每一个可以访问到的对象并打上一个标记,表示可达,当没有可用分块时,会对堆区内存遍历,对没有标记可达的内存进行回收,缺点是由于需要进行大量的遍历,内存分配比较慢,当需要分配大内存块的时候可能会由于内存过于碎片化无法进行分配。在V8引擎中还有一种分代式垃圾回收机制,V8中内存分为新生代区和老生代区,新生代区远小于老生代区内存(一般为1-8M),新生代中使用Scavenge 算法 进行垃圾回收效果,Scavenge算法底层又使用复制式内存分配,新生代区会将内存分为使用区和空闲区,对于新加入的对象会加入到使用区,使用区快满的时候进行一次清除,具体的操作为先将使用区中存活的对象复制到空闲区进行排序(相当于进行一次整理),将原来使用区的空间全部释放,使用区和空闲区的身份互换,这样无限循环。对于老生代空间的管理,使用标记整理法来进行内存的回收,相当于标记清除法的增量操作,将被使用的空间整理到一起,方便接下来分配大的内存空间,类似于操作系统里面的动态分区分配的“紧凑处理”。
(10)GET和POST的区别:你可能分分钟能够搜到,但是未必就能够短时间讲清楚,具体区别为:GET在浏览器回退时是无害的,而POST会再次提交请求(比如浏览器在tab页面的前进后退会触发post的表单的第二次提交),GET请求会被浏览器主动cache,而POST不会,除非手动设置。GET请求只能进行url编码,而POST支持多种编码方式,GET只接受ASCII字符,而POST没有限制,GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留,GET请求在URL中传送的参数是有长度限制的,而POST没有,GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息,GET参数通过URL传递,POST放在Request body中。
(11)tree-shaking的原理:我在简历里面写了webpack,所以感觉有必要把这个整理一下,tree-shaking最早是由Rollup实现,是一种采用删除不需要的额外代码的方式优化代码体积的技术,在打包过程中静态分析模块之间的导入导出,确定哪些模块导出值没有被使用并打上标记,并将其删除,从而实现了打包产物的优化。优点是能够减少应用的体积,提高性能,使代码的整体结构更加清晰,但是tree-shaking可能会剔除不该使用的变量或者函数,影响程序的正确性。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。