2023 我的前端面试小结
margin 和 padding 的使用场景
- 需要在 border 外侧添加空白,且空白处不需要背景(色)时,使用 margin; 
- 需要在 border 内测添加空白,且空白处需要背景(色)时,使用 padding。 
节流与防抖
- 函数防抖 是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。 
- 函数节流 是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。 
HTTP 和 HTTPS 协议的区别
HTTP 和 HTTPS 协议的主要区别如下:
- HTTPS 协议需要 CA 证书,费用较高;而 HTTP 协议不需要; 
- HTTP 协议是超文本传输协议,信息是明文传输的,HTTPS 则是具有安全性的 SSL 加密传输协议; 
- 使用不同的连接方式,端口也不同,HTTP 协议端口是 80,HTTPS 协议端口是 443; 
- HTTP 协议连接很简单,是无状态的;HTTPS 协议是有 SSL 和 HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 更加安全。 
Promise 的基本用法
(1)创建 Promise 对象
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
一般情况下都会使用new Promise()来创建 promise 对象,但是也可以使用promise.resolve和promise.reject这两个方法:
- Promise.resolve 
Promise.resolve(value)的返回值也是一个 promise 对象,可以对返回值进行.then 调用,代码如下:
resolve(11)代码中,会让 promise 对象进入确定(resolve状态),并将参数11传递给后面的then所指定的onFulfilled 函数;
创建 promise 对象可以使用new Promise的形式创建对象,也可以使用Promise.resolve(value)的形式创建 promise 对象;
- Promise.reject 
Promise.reject 也是new Promise的快捷形式,也创建一个 promise 对象。代码如下:
就是下面的代码 new Promise 的简单形式:
下面是使用 resolve 方法和 reject 方法:
上面的代码的含义是给testPromise方法传递一个参数,返回一个 promise 对象,如果为true的话,那么调用 promise 对象中的resolve()方法,并且把其中的参数传递给后面的then第一个函数内,因此打印出 “hello world”, 如果为false的话,会调用 promise 对象中的reject()方法,则会进入then的第二个函数内,会打印No thanks;
(2)Promise 方法
Promise 有五个常用的方法:then()、catch()、all()、race()、finally。下面就来看一下这些方法。
- then() 
当 Promise 执行的内容符合成功条件时,调用resolve函数,失败就调用reject函数。Promise 创建完了,那该如何调用呢?
then方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为resolved时调用,第二个回调函数是 Promise 对象的状态变为rejected时调用。其中第二个参数可以省略。 then方法返回的是一个新的 Promise 实例(不是原来那个 Promise 实例)。因此可以采用链式写法,即then方法后面再调用另一个 then 方法。
当要写有顺序的异步事件时,需要串行时,可以这样写:
那当要写的事件没有顺序或者关系时,还如何写呢?可以使用all 方法来解决。
2. catch()
Promise 对象除了有 then 方法,还有一个 catch 方法,该方法相当于then方法的第二个参数,指向reject的回调函数。不过catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。
3. all()
all方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected。
调用all方法时的结果成功的时候是回调函数的参数也是一个数组,这个数组按顺序保存着每一个 promise 对象resolve执行时的值。
(4)race()
race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected。
那么race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决:
5. finally()
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。finally本质上是then方法的特例:
上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。
什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?
以下代码就是一个回调函数的例子:
回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)。假设多个请求存在依赖性,可能会有如下代码:
以上代码看起来不利于阅读和维护,当然,也可以把函数分开来写:
以上的代码虽然看上去利于阅读了,但是还是没有解决根本问题。回调地狱的根本问题就是:
- 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身 
- 嵌套函数一多,就很难处理错误 
当然,回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误,不能直接 return。
HTTP2 的头部压缩算法是怎样的?
HTTP2 的头部压缩是 HPACK 算法。在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,采用哈夫曼编码来压缩整数和字符串,可以达到 50%~90%的高压缩率。
具体来说:
- 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送; 
- 首部表在 HTTP/2 的连接存续期内始终存在,由客户端和服务器共同渐进地更新; 
- 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值。 
例如下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销。
参考 前端进阶面试题详细解答
水平垂直居中的实现
- 利用绝对定位,先将元素的左上角通过 top:50%和 left:50%定位到页面的中心,然后再通过 translate 来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题。 
- 利用绝对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况: 
- 利用绝对定位,先将元素的左上角通过 top:50%和 left:50%定位到页面的中心,然后再通过 margin 负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况 
- 使用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。该方法要考虑兼容的问题,该方法在移动端用的较多: 
代码输出结果
输出结果:3 6
解析:
- 我们知道,匿名函数的 this 是指向全局对象的,所以 this 指向 window,会打印出 3; 
- getY 是由 obj 调用的,所以其 this 指向的是 obj 对象,会打印出 6。 
title 与 h1 的区别、b 与 strong 的区别、i 与 em 的区别?
- strong 标签有语义,是起到加重语气的效果,而 b 标签是没有的,b 标签只是一个简单加粗标签。b 标签之间的字符都设为粗体,strong 标签加强字符的语气都是通过粗体来实现的,而搜索引擎更侧重 strong 标签。 
- title 属性没有明确意义只表示是个标题,H1 则表示层次明确的标题,对页面信息的抓取有很大的影响 
- i 内容展示为斜体,em 表示强调的文本 
iframe 有那些优点和缺点?
iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。
优点:
- 用来加载速度较慢的内容(如广告) 
- 可以使脚本可以并行下载 
- 可以实现跨子域通信 
缺点:
- iframe 会阻塞主页面的 onload 事件 
- 无法被一些搜索引擎索识别 
- 会产生很多页面,不容易管理 
并发与并行的区别?
- 并发是宏观概念,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务,这种情况就可以称之为并发。 
- 并行是微观概念,假设 CPU 中存在两个核心,那么我就可以同时完成任务 A、B。同时完成多个任务的情况就可以称之为并行。 
进程和线程的区别
- 进程可以看做独立应用,线程不能 
- 资源:进程是 cpu 资源分配的最小单位(是能拥有资源和独立运行的最小单位);线程是 cpu 调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)。 
- 通信方面:线程间可以通过直接共享同一进程中的资源,而进程通信需要借助 进程间通信。 
- 调度:进程切换比线程切换的开销要大。线程是 CPU 调度的基本单位,线程的切换不会引起进程切换,但某个进程中的线程切换到另一个进程中的线程时,会引起进程切换。 
- 系统开销:由于创建或撤销进程时,系统都要为之分配或回收资源,如内存、I/O 等,其开销远大于创建或撤销线程时的开销。同理,在进行进程切换时,涉及当前执行进程 CPU 环境还有各种各样状态的保存及新调度进程状态的设置,而线程切换时只需保存和设置少量寄存器内容,开销较小。 
回流与重绘的概念及触发条件
(1)回流
当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流。
下面这些操作会导致回流:
- 页面的首次渲染 
- 浏览器的窗口大小发生变化 
- 元素的内容发生变化 
- 元素的尺寸或者位置发生变化 
- 元素的字体大小发生变化 
- 激活 CSS 伪类 
- 查询某些属性或者调用某些方法 
- 添加或者删除可见的 DOM 元素 
在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的 DOM 元素重新排列,它的影响范围有两种:
- 全局范围:从根节点开始,对整个渲染树进行重新布局 
- 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局 
(2)重绘
当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。
下面这些操作会导致回流:
- color、background 相关属性:background-color、background-image 等 
- outline 相关属性:outline-color、outline-width 、text-decoration 
- border-radius、visibility、box-shadow 
注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。
为什么需要浏览器缓存?
对于浏览器的缓存,主要针对的是前端的静态资源,最好的效果就是,在发起请求之后,拉取相应的静态资源,并保存在本地。如果服务器的静态资源没有更新,那么在下次请求的时候,就直接从本地读取即可,如果服务器的静态资源已经更新,那么我们再次请求的时候,就到服务器拉取新的资源,并保存在本地。这样就大大的减少了请求的次数,提高了网站的性能。这就要用到浏览器的缓存策略了。
所谓的浏览器缓存指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载,不需要再去服务端请求了。
使用浏览器缓存,有以下优点:
- 减少了服务器的负担,提高了网站的性能 
- 加快了客户端网页的加载速度 
- 减少了多余网络数据传输 
两栏布局的实现
一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现:
- 利用浮动,将左边元素宽度设置为 200px,并且设置向左浮动。将右边元素的 margin-left 设置为 200px,宽度设置为 auto(默认为 auto,撑满整个父元素)。 
- 利用浮动,左侧元素设置固定大小,并左浮动,右侧元素设置 overflow: hidden; 这样右边就触发了 BFC,BFC 的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠。 
- 利用 flex 布局,将左边元素设置为固定宽度 200px,将右边的元素设置为 flex:1。 
- 利用绝对定位,将父级元素设置为相对定位。左边元素设置为 absolute 定位,并且宽度设置为 200px。将右边元素的 margin-left 的值设置为 200px。 
- 利用绝对定位,将父级元素设置为相对定位。左边元素宽度设置为 200px,右边元素设置为绝对定位,左边定位为 200px,其余方向定位为 0。 
POST 和 PUT 请求的区别
- PUT 请求是向服务器端发送数据,从而修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次 PUT 操作,其结果并没有不同。(可以理解为时更新数据) 
- POST 请求是向服务器端发送数据,该请求会改变数据的种类等资源,它会创建新的内容。(可以理解为是创建数据) 
协商缓存和强缓存的区别
(1)强缓存
使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。
强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性。
(1)服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。
(2)Expires 是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,
Cache-Control可设置的字段:
- public:设置了该字段值的资源表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是使用 max-age=来精确控制;
- private:设置了该字段值的资源只能被用户浏览器缓存,不允许任何代理服务器缓存。在实际开发当中,对于一些含有用户信息的 HTML,通常都要设置这个字段值,避免代理服务器(CDN)缓存;
- no-cache:设置了该字段需要先和服务端确认返回的资源是否发生了变化,如果资源未发生变化,则直接使用缓存好的资源;
- no-store:设置了该字段表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源;
- max-age=:设置缓存的最大有效期,单位为秒;
- s-maxage=:优先级高于 max-age=,仅适用于共享缓存(CDN),优先级高于 max-age 或者 Expires 头;
- max-stale[=]:设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。
一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires。
no-cache 和 no-store 很容易混淆:
- no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存; 
- no-store 是指不使用任何缓存,每次请求都直接从服务器获取资源。 
(2)协商缓存
如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。
上面已经说到了,命中协商缓存的条件有两个:
- max-age=xxx过期了
- 值为 - no-store
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。
协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 Etag 和 Last-Modified 属性。
(1)服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。
(2)因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。
当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
总结:
强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。
documentFragment 是什么?用它跟直接操作 DOM 的区别是什么?
MDN 中对documentFragment的解释:
DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document 使用,就像标准的 document 一样,存储由节点(nodes)组成的文档结构。与 document 相比,最大的区别是 DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。
当我们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。在频繁的 DOM 操作时,我们就可以将 DOM 元素插入 DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和直接操作 DOM 相比,将 DocumentFragment 节点插入 DOM 树时,不会触发页面的重绘,这样就大大提高了页面的性能。
display 的 block、inline 和 inline-block 的区别
(1)block: 会独占一行,多个元素会另起一行,可以设置 width、height、margin 和 padding 属性;
(2)inline: 元素不会独占一行,设置 width、height 属性无效。但可以设置水平方向的 margin 和 padding 属性,不能设置垂直方向的 padding 和 margin;
(3)inline-block: 将对象设置为 inline 对象,但对象的内容作为 block 对象呈现,之后的内联对象会被排列在同一行内。
对于行内元素和块级元素,其特点如下:
(1)行内元素
- 设置宽高无效; 
- 可以设置水平方向的 margin 和 padding 属性,不能设置垂直方向的 padding 和 margin; 
- 不会自动换行; 
(2)块级元素
- 可以设置宽高; 
- 设置 margin 和 padding 都有效; 
- 可以自动换行; 
- 多个块状,默认排列从上到下。 
CDN 的原理
CDN 和 DNS 有着密不可分的联系,先来看一下 DNS 的解析域名过程,在浏览器输入的解析过程如下:(1) 检查浏览器缓存(2)检查操作系统缓存,常见的如 hosts 文件(3)检查路由器缓存(4)如果前几步都没没找到,会向 ISP(网络服务提供商)的 LDNS 服务器查询(5)如果 LDNS 服务器没找到,会向根域名服务器(Root Server)请求解析,分为以下几步:
- 根服务器返回顶级域名(TLD)服务器如 - .com,- .cn,- .org等的地址,该例子中会返回- .com的地址
- 接着向顶级域名服务器发送请求,然后会返回次级域名(SLD)服务器的地址,本例子会返回 - .test的地址
- 接着向次级域名服务器发送请求,然后会返回通过域名查询到的目标 IP,本例子会返回 - www.test.com的地址
- Local DNS Server 会缓存结果,并返回给用户,缓存在系统中 
CDN 的工作原理: (1)用户未使用 CDN 缓存资源的过程:
- 浏览器通过 DNS 对域名进行解析(就是上面的 DNS 解析过程),依次得到此域名对应的 IP 地址 
- 浏览器根据得到的 IP 地址,向域名的服务主机发送数据请求 
- 服务器向浏览器返回响应数据 
(2)用户使用 CDN 缓存资源的过程:
- 对于点击的数据的 URL,经过本地 DNS 系统的解析,发现该 URL 对应的是一个 CDN 专用的 DNS 服务器,DNS 系统就会将域名解析权交给 CNAME 指向的 CDN 专用的 DNS 服务器。 
- CND 专用 DNS 服务器将 CND 的全局负载均衡设备 IP 地址返回给用户 
- 用户向 CDN 的全局负载均衡设备发起数据请求 
- CDN 的全局负载均衡设备根据用户的 IP 地址,以及用户请求的内容 URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求 
- 区域负载均衡设备选择一台合适的缓存服务器来提供服务,将该缓存服务器的 IP 地址返回给全局负载均衡设备 
- 全局负载均衡设备把服务器的 IP 地址返回给用户 
- 用户向该缓存服务器发起请求,缓存服务器响应用户的请求,将用户所需内容发送至用户终端。 
如果缓存服务器没有用户想要的内容,那么缓存服务器就会向它的上一级缓存服务器请求内容,以此类推,直到获取到需要的资源。最后如果还是没有,就会回到自己的服务器去获取资源。
CNAME(意为:别名):在域名解析中,实际上解析出来的指定域名对应的 IP 地址,或者该域名的一个 CNAME,然后再根据这个 CNAME 来查找对应的 IP 地址。











 
    
评论