前端面试八股文

  • 前端面试八股文已关闭评论
  • 8 次浏览
  • A+
所属分类:Web前端
摘要

http:是一个客户端和服务端请求和应答的标准(TCP),用于从www服务器传输超文本到本地浏览器的超文本传输协议。


1.HTTP和HTTPS的基本概念

http:是一个客户端和服务端请求和应答的标准(TCP),用于从www服务器传输超文本到本地浏览器的超文本传输协议。

https:是以安全为目标的HTTP通道,即HTTP下加入SSL层进行加密。其作用是:建立一个信息安全通道,确保数据的传输,确保网站的真实性。

补充:SSL是洋文“Secure Sockets Layer”的缩写,中文叫做“安全套接层”。

2.HTTP和HTTPS的区别及优缺点?

  1. HTTPS 协议需要 CA 证书,费用较高;而HTTP 协议不需要;
  2. HTTP是超文本传输协议,信息是明文传输;HTTPS协议要比HTTP协议安全,HTTPS是具有安全性的SSL加密传输协议,可防止数据在传输过程中被窃取、改变,确保数据的完整性(当然这种安全性并非绝对的,低于更深的Web安全问题,次数暂且不表);
  3. HTTP协议的默认端口为80;HTTPS默认端口为443;
  4. HTTP 协议连接很简单,是无状态的;HTTPS 协议是有SSL 和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP 更加安全;

3.HTTPS协议工作原理

如何保证信息的安全性:对称加密、非对称加密、数字证书与数字签名

  1. 客户端申请HTTPS通信,建立SSL链接
  2. 服务器响应并向客户端传递证书(证书中包含了密钥)
  3. 客户端验证证书,获取公钥生成对称加密密钥,用公钥加密后传给服务器
  4. 服务器收到消息,用私钥解密,拿出对称密钥,并通知客户端,SSL通道建立完成,HTTPS通道也建立完成。
  5. 共享密钥交换成功,HTTPS通信建立后,客户端和服务端利用共享密钥加密通信
  6. 客户端断开链接
前端面试八股文

一定要用https吗?

https那么的安全,是不是我们在什么场景下都要去使用https进行通信呢?答案是否定的。

1)https虽然提供了消息安全传输的通道,但是每次消息的加解密十分耗时,消息系统资源。所以,除非在一些对安全性比较高的场景下,比如银行系统,购物系统中我们必须要使用https进行通信,其他一些对安全性要求不高的场景,我们其实没必要使用https。

2)使用https需要使用到数字证书,但是一般权威机构颁发的数字证书都是收费的,而且价格也是不菲的,所以对于一些个人网站特别是学生来讲,如果对安全性要求不高,也没必要使用https。

参考:https的工作原理

4.TCP三次握手

第一次握手:建立连接,客户端发送SYN包(syn=j)(是TCP/IP建立连接时使用的握手信号。),随后客户端进入SYN-SENT阶段,等待服务端确认。

第二次握手:服务端收到syn包并确认客户的SYN,同时也发送一个自己的SYN包,随后服务器端进入SYN-RCVD阶段。

第三次握手:客户端接收到来自服务器端的确认收到数据的SYN+ACK包,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段,并向服务器发送确认包ACK。随后客户端进入ESTABLISHED阶段。服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。完成三次握手。

为什么要进行第三次握手?

为了防止服务器端开启一些无用的连接增加服务器开销以及防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

由于网络传输是有延时的(要通过网络光纤和各种中间代理服务器),在传输的过程中,比如客户端发起了SYN=1创建连接的请求(第一次握手)。如果服务器端就直接创建了这个连接并返回包含SYN、ACK和Seq等内容的数据包给客户端,这个数据包因为网络传输的原因丢失了,丢失之后客户端就一直没有接收到服务器返回的数据包。客户端可能设置了一个超时时间,时间到了就关闭了连接创建的请求。再重新发出创建连接的请求,而服务器端是不知道的,如果没有第三次握手告诉服务器端客户端收的到服务器端传输的数据的话,服务器端是不知道客户端有没有接收到服务器端返回的信息的。

5.TCP四次挥手

  • 首先客户端想要释放连接,向服务器端发送一段TCP报文,随后客户端进入FIN-WAIT-1阶段,即半关闭阶段,并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。
  • 服务器端接收到从客户端发出的连接释放报文之后,确认了客户端想要释放连接,发出确认报文,表示“接收到客户端发送的释放连接的请求”。

前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了

  • 服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,向客户端发出释放报文,等待客户端的确认,随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。
  • 客户端收到服务器的连接释放报文后,向服务器端发送确认报文,服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。

简单来说

第一次挥手:客户端告诉服务端我要断开连接了

第二次挥手:服务端告诉客户端我知道你要断开连接了,并告诉客户端你可以断开了

第三次挥手:服务端做好了断开准备,并想客户端发送断开请求,等到客户端确认

第四次挥手:客户端接收到服务端的可以释放的信号,向服务端发送确认断开的信号,服务端收到后就关闭连接。

参考:详解 TCP 连接的“ 三次握手 ”与“ 四次挥手

6.cookie、sessionStorage、localStorage的区别

相同点:都是在开发中用到的临时存储客户端会话信息或者数据的方法

不同点:

1.存储的时间有效期不同

  • cookie的有效期是可以设置的,默认的情况下是关闭浏览器后失效
  • sessionStorage的有效期是仅保持在当前页面,关闭当前会话页或者浏览器后就会失效
  • localStorage的有效期是在不进行手动删除的情况下是一直有效的

2.存储的大小不同

  • cookie的存储是4kb左右,存储量较小,一般页面最多存储20条左右信息
  • localStorage和sessionStorage的存储容量是5Mb(官方介绍,可能和浏览器有部分差异性)

3.与服务端的通信

  • cookie会参与到与服务端的通信中,一般会携带在http请求的头部中,例如一些关键密匙验证等。
  • localStorage和sessionStorage是单纯的前端存储,不参与与服务端的通信

参考:cookie、localStorage和sessionStorage三者的区别

7.Vuex 和 localStorage 的区别

  1. 最重要的区别:vuex 存储在内存中localstorage 则以文件的方式存储在本地,只能存储字符串类型的数据,存储对象需要 JSON 的 stringify 和parse 方法进行处理。读取内存比读取硬盘速度要快。
  2. 应用场景 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex 用于组件之间的传值。localstorage 是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。 Vuex 能做到数据的响应式,localstorage 不能。
  3. 永久性 刷新页面时 vuex 存储的值会丢失,localstorage 不会。注意:对于不变的数据确实可以用 localstorage 可以代替vuex,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage无法做到,原因就是区别 1。

8.浏览器如何渲染网页

  1. 解析HTML生成DOM树 - 渲染引擎首先解析HTML文档,生成DOM树
  2. 构建Render树 - 接下来不管是内联式,外联式还是嵌入式引入的CSS样式会被解析生成CSSOM树,根据DOM树与CSSOM树生成另外一棵用于渲染的树-渲染树(Render tree),
  3. 布局Render树 - 然后对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置
  4. 绘制Render树 - 最后遍历渲染树并用UI后端层将每一个节点绘制出来

解析HTML文件,创建DOM树,自上而下,如果遇到样式(link、style)与脚本(script)都会阻塞

补充1:输入url后发生了什么?

  1. DNS域名解析
  2. 建立TCP连接(三次握手)
  3. 发送HTTP请求
  4. 服务器处理请求,返回响应结果
  5. 关闭TCP连接(四次挥手)
  6. 浏览器解析HTML渲染页面,构建DOM树

详解:从输入url到页面加载完成发生了什么?

补充2:避免JavaScript 阻塞JavaScript 脚本延迟加载的方式有哪些?

  1. defer 属性:给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了defer属性 14 的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
  2. async 属性:给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个async属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
  3. 动态创建 DOM 方式:动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建script 标签来引入js 脚本。
  4. 使用 setTimeout 延迟方法:设置一个定时器来延迟加载js 脚本文件
  5. 让 JS 最后加载:将 js 脚本放在文档的底部,来使js 脚本尽可能的在最后来加载执行。

9.重绘与重排相关

重绘与重排的区别

重排:当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为重排/回流。

重绘:当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。

如何触发重绘与重排

下面这些操作会导致重排/回流:

页面的首次渲染 、浏览器的窗口大小发生变化 、元素的内容发生变化 、元素的尺寸或者位置发生变化、 元素的字体大小发生变化、 激活 CSS 伪类、 查询某些属性或者调用某些方法 、添加或者删除可见的 DOM 元素

在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的 DOM 元素重新排列,它的影响范围有两种: 全局范围:从根节点开始,对整个渲染树进行重新布局局部范围:对渲染树的某部分或者一个渲染对象进行重新布局

下面这些操作会导致重绘:

color、background 相关属性:background-color、background-image等 outline 相 关 属 性 : outline-color 、outline-width、text-decoration border-radius、visibility、box-shadow 注意: 当触发重排/回流时,一定会触发重绘,但是重绘不一定会引发重排/回流。

如何避免重绘或重排

  1. 用transform做形变和位移可以减少reflow
  2. 避免逐个修改节点样式,尽量一次性修改
  3. 使用DocumentFragment将需要多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
  4. 可以将需要多次修改的DOM元素设置display:none,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)
  5. 避免多次读取某些属性
  6. 通过绝对位移将复杂的节点元素脱离文档流,形成新的Render Layer,降低回流成本
  7. 提升为合成层     将元素提升为合成层有以下优点:
    • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
    • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
    • 对于 transform 和 opacity 效果,不会触发 layout 和 paint

提升合成层的最好方式是使用 CSS 的 will-change 属性:

#target{   will-change: transform; }

参考:什么是重排(回流)和重绘?如何避免他们?

注意:

  • 直接使用opacity即触发重绘,又触发重排(GPU底层设计如此!)。
  • opacity配合图层使用,即不触发重绘也不触发重排。(原因:透明度的改变时,GPU在绘画时只是简单的降低之前已经画好的纹理的alpha值来达到效果,并不需要整体的重绘。不过这个前提是这个被修改opacity本身必须是一个图层。)

参考:图层与重排重绘

 10.浏览器的缓存机制 强缓存、协商缓存

浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求-服务器响应请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求和缓存标识存入浏览器缓存中,简单的过程如下图:

前端面试八股文

由上图我们可以知道:

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

以上两点结论就是浏览器缓存机制的关键,他确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存 。

强制缓存

强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:

  • 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)
  • 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存
  • 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果

那么强制缓存的缓存规则是什么?

当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Control优先级比Expires高。

协商缓存

 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。其主要有以下两种情况:

  • 协商缓存生效,返回304
  • 协商缓存失效,返回200和请求结果结果

参考:彻底理解浏览器的缓存机制

11.防抖和节流

防抖:是指在事件被触发 n 秒后再执行回调,如果在这n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

节流:是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

防抖和节流的应用场景

防抖
1.登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖。
2.调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多。
3.文本编辑器实时保存,当无任何更改操作一秒后进行保存。
4.DOM 元素的拖拽功能实现。
5.计算鼠标移动的距离。
6.Canvas 模拟画板功能。
7.搜索联想。
节流
1.scroll 事件,每隔一秒计算一次位置信息等。
2.浏览器播放事件,每个一秒计算一次进度信息等。
3.input框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖)。

实现节流函数和防抖函数

函数防抖的实现:

        function debounce(fn, wait) {             var timer = null;             return function () {                 var context = this,                     args = [...arguments];                 // arguments是function里特定的对象之一,指的是function的参数对象,按题意里的,当然对应的就是它所在的那一层function。即return function(){};                 //如果此时存在定时器的话,则取消之前的定时器重新计时                 if (timer) {                     clearTimeout(timer)                     timer = null                 }                 //设计定时器,使事件间隔指定时间后执行                 timer = setTimeout(() => {                     fn.apply(context, args);                 }, wait)             }         }          function sayHi() {             console.log("防抖成功");         }         var inp = document.getElementById("inp");         inp.addEventListener("input", debounce(sayHi, 2000)); //防抖

函数节流的实现:

        //时间戳版         function throttle(fn, delay) {             var preTime = Date.now();             return function () {                 var context = this, args = [...arguments], nowTime = Date.now();                 //如果两次时间间隔超过了指定时间,则执行函数。                 if (nowTime - preTime >= delay) {                     preTime = Date.now();                     return fn.apply(context, args);                 }             }         }          //定时器版         function throttle2(fun, awit) {             let timeOut = null;             return function () {                 let context = this, args = [...arguments];                 if (!timeOut) {                     timeOut = setTimeout(() => {                         fun.apply(context, args);                         timeOut = null                     }, awit)                 }             }         }          function sayHi() {             console.log(e.target.innerWidth,e.target.innerHeight);         }          window.addEventListener('resize',throttle2(sayHi,1000))