1. 从浏览器地址栏输入 url 到请求返回发生了什么?
答:
-
输入 URL 后解析出协议、主机、端口、路径等信息,并构造一个 HTTP 请求。
-
缓存判断: 浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。
- 强制缓存:通过Cache-Control: max-age=3600的字段去设置强制缓存,当浏览器第一次发送请求到服务器时,服务器会在响应头中设置一个缓存有效期,下次再次请求资源时,游览器会先判断缓存有效期是否过期,如果没有过期,则直接使用本地缓存,否则在去浏览器中请求资源。
- 协商缓存:通过If-Modified-Since或If-None-Match字段来实现,当浏览器第一次发送请求给服务器时,服务器会在响应头设置资源的Last-Modified时间或Etag唯一标识。当再次请求资源时,浏览器还是会发送请求到服务器,服务器会根据If-Modified-Since(值为上次响应的Last-Modified时间)或If-None-Match(值为上次响应的ETage)去判断资源是否更新,若未更新,则返回304 Not Modified状态码,此时,游览器就从本地获取资源。若更新,则会返回最新的资源文件。
-
DNS 域名解析。(字节面试被虐后,是时候搞懂 DNS 了)
- 首先会先去浏览器和操作系统缓存中检查,查看最近的DNS查询结果,若未找到。
- 本地域名服务器查询: 请求会被发送到配置的本地DNS服务器(通常是ISP提供)
- 根服务器查询:= 如果本地DNS服务器没有缓存记录,它会向根DNS服务器查询,根服务器不会知道域名完整ip地址,但是他知道顶级域名(TLD)服务器地址。(例如:com、org等)
- 顶级域名服务器查询: 本地DNS服务器接着会向顶级域名服务器查询,顶级域名服务器会返回该域名的权威DNS服务器地址。
- 权威DNS服务器查询: 接着,本地DNS服务器会向权威DNS服务器查询,权威服务器存储了关于该域名的记录,并返回与请求域名关联的IP地址。
-
TCP 连接。
总是要问:为什么需要三次握手,两次不行吗?其实这是由 TCP 的自身特点可靠传输决定的。客户端和服务端要进行可靠传输,那么就需要确认双方的接收和发送能力。第一次握手可以确认客服端的发送能力,第二次握手,确认了服务端的发送能力和接收能力,所以第三次握手才可以确认客户端的接收能力。不然容易出现丢包的现象。
-
http 请求。
-
服务器处理请求并返回 HTTP 报文。
-
浏览器渲染页面。
-
- 解析HTML,构建DOM树
-
- 解析CSS,生成CSS规则树
-
- 合并DOM树和CSS规则,生成render树
-
- 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
-
- 绘制render树(paint),绘制页面像素信息 。
2.前端如何进行性能优化?
1. 网络方面:
a.静态资源使用CDN
当网站所挂服务器离用户越来越远时,访问网站的延迟也会越高,CDN(内容分发网络)在不同的地理位置部署Web服务器,根据用户位置分配最近的资源,缩短请求时间达到优化加载的速度的效果。
b.减少不必要的HTTP请求
一个完整的HTTP请求需要经历连接与释放的过程,需要一定的时间,减少HTTP可以节省一定时间。 比如在开发中,去减少在一些页面中根本没有用到的接口请求。
c.使用HTTP2.0
HTTP1.1版本存在线程阻塞的问题,在同一时间,同一域名的请求有一定的数量限制,超过限制数目的请求会被阻塞。
HTTP2.0特性:
- **支持二进制传输:**http1.1采用的是文本传输的方式,http2.0采用二进制传输,传输更快。
- **支持多路复用:**多个请求可以共用一个TCP连接,提高连接的利用率,降低延迟
- **头部压缩:**将头部信息采用压缩算法进行压缩,数据量更小。
d.采用HTTP缓存
可以通过设置强制缓存或者协商缓存来进行控制,符合缓存条件时直接读取缓存,减少发送请求,提高加载速度。
e.使用Gzip压缩
Gzip 通过 LZ77 算法与 Huffman 编码来压缩文件,重复度越高的文件可压缩的空间就越大,对 JS、CSS、HTML 等文本资源均有效。当 Nginx 返回 js 文件的时候,会判断是否开启 gzip,然后压缩后再返回给浏览器。
该压缩方法需要 Nginx 配置 也开启 Gzip 压缩,单纯前端通过 webpack 插件开启 Gzip 压缩是不能达到优化效果的
2.图片优化方面
a.采用图片懒加载
先不给图片设置src路径,当图片出现在浏览器可视区域时,才去加载图片,这就是延迟/懒加载。
图片设置data-src属性在页面不可见时图片不会加载。
图片懒加载思路:
- 先在img标签设置自定义属性data-src
- 计算当页面可见时,src值替换为data-src,加载图片。
b.采用图片压缩
可以利用webpack的image-webpack-loader对图片进行压缩。
c.采用略缩图
点击或加载到之后再查看清晰大图
如果有一个1920 * 1080大小的图片,用略缩图的方式展示给用户,并且当用户鼠标悬停在上面时才展示全图,如果用户从未真正将鼠标悬停在缩略图上,则不浪费了下载图片的时间。
所以,可以用两张图片来进行优化。一开始,只加载缩略图,当用户悬停在图片上时,才加载大图
d.使用Web Workers
JavaScript语言是单线程,所有与浏览器UI无关的长时间运行脚本任务只能在一个线程完成,一次只能做一件事。如果是复杂的计算,则页面运行可能会被阻塞。
此时可以使用Web Worker进行处理与浏览器UI无关的长时间运行脚本。Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法,线程可以执行任务而不干扰用户界面。
e.服务端渲染SSR
客户端渲染: 客户端获取HTML,下载JS文件运行生成DOM,渲染页面。
服务端渲染: 服务端直接返回HTML文件,客户端只需解析HTML
当前使用 Vue/React 开发的网站,只要不是对 SEO 有要求,大部分都是采用的客户端渲染。而如果网站要追求 SEO 好和首屏速度快的话,那就可采用服务端渲染。因为它只需加载一个渲染完毕的 HTML 文件,比客户端渲染要更快。
3.减少打包体积
a.压缩资源的体积
打包体积减少,可以减少文件下载时间,可以让用户体验更好。
在webpack可以使用如下插件进行压缩:
- HTML: html-webpack-plugin
- CSS: css-minimizer-webpack-plugin
- JavaScript: terser-webpack-plugin
b.使用tree shaking删除未使用的代码
Tree shaking 是一种优化 JavaScript 应用程序的技术,它通过静态代码分析和模块依赖关系来删除未使用的代码,从而减小最终打包后的代码体积。
4.使用事件委托
添加到页面上的事件数量会影响页面的运行性能,如果添加的事件过多,会导致网页的性能下降。采用事件委托的方式,可以大大减少注册事件的个数。
使用事件委托的好处:
- 提高性能
- 节省内存占用,减少事件注册
- 实现当新增子对象时无需再次对其绑定
5.CSS方面优化
a.降低CSS选择器的复杂性
- 保持简单,不要使用嵌套过多过于复杂的选择器
- 通配符和属性选择器效率最低,需要匹配的元素最多,尽量避免使用
b.使用transform和opacity属性更改来实现动画
在CSS中,transforms和opacity这两个属性更改不会触发重排与重绘,他们由合成器单独处理的属性。
6.渲染优化方面
a.减少重绘重排
-
用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式
-
需要对元素进行复杂操作时,可以先隐藏元素(display:none)操作完成后再显示
-
需要创建多个 DOM 节点时,使用 DocumentFragment 创建完最后再一次性加入文档
b.采用防抖节流优化高频事件
当页面有一些事件频繁触发时,为了优化体验,需要对这类事件进行调用次数的限制,于是可以使用防抖与节流来减少调用频率。
- 防抖:一段时间后只执行一次,将多次执行变为最后一次执行
- 节流:在固定的频率执行,将多次执行变为在规定时间内只执行一次
c.优化动画
- 优先使用 CSS 来实现动画效果
- 使用 translateZ/translate3d 开启硬件加速
- 合理使用 requestAnimationFrame 代替 setTimeout
6.预加载(加载顺序)
preload
/prefetch
可控制 HTTP 优先级,从而达到关键请求更快响应的目的。
<link rel="prefetch" href="style.css" as="style" /> <link rel="preload" href="main.js" as="script" />
-
preload 优先级较高,提前加载较晚出现,但对当前页面非常重要的资源
-
prefetch 优先级较低,提前加载后继路由需要的资源。一般用以加载其它路由资源,如当页面出现 Link,可 prefetch 当前 Link 的路由资源
3.对虚拟DOM的理解?为什么要用虚拟DOM?
答: 我们打印出来虚拟DOM以后,实际上可以发现虚拟DOM是一个JS对象,对象内有type、props、children等属性。type是表示标签类型,props是标签内部的属性,children为节点中子节点的内容。
-
1.可以减少DOM操作:
- a.虚拟DOM可以将多次操作合并为一次操作,比如一个页面如果有500次变化,没有虚拟DOM的就会渲染500次,而虚拟DOM只需要渲染一次,从这点上来看,页面越复杂,虚拟DOM的优势越大
- b.虚拟DOM可以借助DOM diff算法,对比新旧虚拟DOM树的差异,只将差异更新到DOM中,以最小化成本更新。
-
2.支持跨平台:虚拟DOM本质上是一个对象,可以在兼容不同平台,抹平平台之间的差异,实现支持跨平台使用。
为什么要用虚拟DOM?
- 保证性能下限,在不进行手动优化的情况下,提供过得去的性能
- 虚拟DOM可以提供跨平台使用。
4.React Diff算法的原理是什么?
答:
原理: diff算法就是通过对比新旧两株虚拟DOM树的变更差异,将更新补丁作用于真实DOM,以最小成本完成视图更新。
具体流程如下: 我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是prop,每次进行diff的时候,react会先比较该节点类型,假如节点类型不一样,那么react会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较prop是否有更新,假如有prop不一样,那么react会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点。
5.虚拟DOM的引入与直接操作原生DOM相比,虚拟DOM一定比原生DOM更快吗?
答:不一定,虚拟DOM渲染是先将真实DOM转换为JS对象,然后通过DOM diff算法处理完毕以后,再去转换为真实DOM,也就是说他始终会创建JS对象。对于首次加载页面的情况,需要先创建所有的虚拟DOM,然后再把虚拟DOM转换为真实DOM,这样会多一层计算,不会比直接渲染真实DOM更快。
6.React Diff算法的优化策略有哪些?
答:
- 1.虚拟DOM树节点比较时,只比较同一层的节点,不跨层进行比较。
- 2.若发现虚拟DOM树当前比较的节点类型不一样,直接判断为脏组件,从而直接删除该节点以及它的所有子节点。
- 3.同一层级的一组子节点,可以通过设置唯一的key来进行区分。
补充: React key是干嘛用的?为什么要加?key主要解决哪一类问题
答:为了方便react内部进行优化,我们必须给每一个react节点添加key,这个key是prop在设计值之初不是给开发者用的,而是给react用的,大概的作用就是给每一个react节点添加一个身份标识,方便react进行识别,在重渲染过程中,如果key一样,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新,如果key不一样,则react先销毁该组件,然后重新创建该组件。
补充: 能使用数组的index作为虚拟DOM的节点的key吗? 答:不能,key是用于渲染对象的排序,所以必须是用唯一标记的值,假如使用数组的index的话,如果删除数组中间任意一个数,后面的数补上来,导致index发生变化,无法找到对应的虚拟DOM节点。可以使用UUID作为唯一的key。
7.对Redux的理解,主要解决了什么问题?
答:Redux提供了一个叫store的统一仓库,组件通过dispatch将state直接传入store,不用通过其他组件,并且组件通过subscribe从store获取state的改变。使用redux,所有的组件都可以从store中获取到所需的state,也能从store获取到state的改变。这比组件之间互相传递数据清晰的多。
解决了什么问题?
由于在react中组件间通信的数据流是单向的,顶层组件通过props属性向下层传递数据,而下层组件不能向上层组件传递数据,兄弟组件之间同样不能,Redex使用store仓库统一管理数据,减少了数据管理难度。
8.Redux的工作流程是怎么样的?
答:
-
首先用户(通过view)发出Action,发出方式就用dispatch方法
-
然后Store自动调用Reducer,并且传入两个参数,当前State和收到的Action,Reducer会返回新的State
-
state一旦有变化,Store就会调用监听函数,来更新View
9.同步和异步的执行顺序是怎么样的?(你能讲一下事件循环机制吗?)
答:
1.JavaScript将任务分为同步任务和异步任务,同步任务进入主线中,异步任务首先到Event Table进行回调函数注册。
2.当异步任务的触发条件满足,将回调函数从Event Table压入到Event Queue中。
3.主线程里面的同步任务执行完毕,系统会去Event Queue中读取异步的回调函数。
4.只要主线程空了,就会去Event Queue读取回调函数,这个过程被称为Event Loop。
10.CSS中实现元素居中的7种方式总结
答:
元素分类:
- 1.块级元素(Block) 这些元素以块的形式显示在页面上,每个块级元素会独占一行(除非通过其他CSS属性进行修改)。块级元素可以设置宽度、高度、内边距和外边距。
常见的块级元素:<div>, <p>, <h1>-<h6>, <ul>, <li>, <section>, <footer>
- 2.行内元素(Inline) 行内元素也称为内联元素,这些元素以行内的形式显示在页面上,它们不会独占一行,而是在同一行上与其他元素并排显示。行内元素的宽度和高度默认由其内容决定,无法设置宽度和高度。
常见的行内元素包括:<span>, <a>, <strong>, <em>, <img>, <input>
- 3.行内块元素(Inline-block) 这些元素以行内块的形式显示在页面上,具有行内元素的特性,但可以设置宽度、高度、内边距和外边距,行内块元素会在同一行显示,但是他们之间会保留空白间隔。
一些常见的行内块元素包括:<button>, <label>, <select>, <textarea>, <img>
。
水平居中的方式:
1.使用text-align:center居中(适用于内联元素,而不适用于块级元素)
使用text-align:center,可以在CSS中实现内联元素的水平居中, 这个技术利用了CSS的text-align属性,通过对元素的文本对齐方式进行调整实现居中的效果。
<div class="container">
<span>检测居中效果</span><br>
<img src="1.jpg" alt=""><br>
<input type="text" value="检测居中效果">
</div>
.container {
text-align: center;
}
在上述示例中,将容器的 text-align 属性设置为 center,使容器内的文本水平居中显示。由于内联元素的默认宽度与内容宽度一致,所以通过调整文本的对齐方式,元素就可以在容器中水平居中。
需要注意的是,这种方法适用于内联元素,而不适用于块级元素。对于块级元素,可以将其包裹在一个容器中,并对容器应用 text-align: center; 实现块级元素的水平居中。
这是一种简单而常用的方法,特别适用于文本、按钮、图标等内联元素的水平居中。然而,它只能实现水平居中,对于垂直居中需要采用其他的布局方法。若元素是单行文本, 则可设置 line-height 等于父元素高度来实现垂直居中。
2.margin:0 auto居中 (对于内联元素和块级元素都可以)
要将块级元素水平居中,可以使用margin属性将左右边距设置为auto
.container {
width: 300px; /* 设置容器的宽度 */
margin: 0 auto; /* 水平居中 */
}
将容器的宽度设置为一个固定值,然后使用 margin: 0 auto; 将左右外边距设置为 "auto",实现元素的水平居中。由于左右外边距都设置为 "auto",浏览器会自动将剩余的空间均匀分配给两侧的外边距,从而使元素居中显示。
3.使用Flex居中元素
Flex 弹性布局,通过将容器的 display 属性设置为 flex,并使用 justify-content 和 align-items 属性分别进行水平和垂直居中设置,元素将在容器中居中显示。
.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
Flexbox 还提供了其他属性,如 flex-direction、flex-wrap、align-content 等,可以根据具体需求进行进一步的布局调整。使用 Flexbox 可以轻松实现各种居中效果,并且具有很好的浏览器兼容性。
4.使用 Grid 居中元素
网格布局 Grid 是另一种强大的布局模型,也可以用于实现元素的居中布局。通过将容器的 display 属性设置为 grid,并使用 place-items 属性设置为 center,元素将在容器中居中显示。
.container {
display: grid;
place-items: center center; /* 水平和垂直居中*/
}
place-items
是一个简写属性,用于同时设置align-items
和justify-items
属性,place-items: center center;
将元素在单元格内垂直和水平方向上都居中对齐。
5.使用absolute和translateX(-50%)的方式(使用绝对定位和负边距可以适用于不同类型的元素,包括块级元素和内联元素)
水平居中: 首先将容器的左边距设置为50%(相对于父容器),然后使用transform: translateX(-50%),将元素向左平移50%的宽度,从而实现水平居中。
.container {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
水平垂直居中: 将要居中的元素的定位属性设置为 absolute。通过将元素的 top 和 left 属性都设置为 50%,元素的左上角将位于容器的中心。最后,通过 transform 属性和 translate 函数将元素向上和向左平移自身宽度和高度的一半,从而实现垂直居中的效果。
.container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
6.使用 calc() 函数居中
calc() 函数通过执行简单的数学运算,并返回计算结果作为CSS属性值。使用 calc() 函数可以根据具体的需求进行灵活的计算和布局,实现元素在水平或垂直方向的居中。
对于水平居中,可以使用 calc() 函数结合百分比和像素值来计算元素的左右外边距。通过将50%(容器的一半宽度)减去150像素(元素宽度的一半)来计算得到。
.container {
width: 300px;
margin-left: calc(50% - 150px);
margin-right: calc(50% - 150px);
/* background-color: blue; */
}
对于垂直居中,可以使用 calc() 函数结合百分比、像素值和视口单位(如vh)来计算元素的上下外边距。通过将50vh(视口高度的一半)减去200像素(元素高度的一半)来计算得到的。
.container {
height: 400px;
margin-top: calc(50vh - 200px);
margin-bottom: calc(50vh - 200px);
}
7.使用table居中(表格布局)
使用表格布局(Table Layout)可以实现元素的居中布局,需要创建一个包含一个单元格的表格,并将元素放置在该单元格中。
.container {
display: table;
width: 100%;
}
.content {
display: table-cell;
text-align: center;
}
<div class="container">
<div class="content">
<span>检测居中span效果</span><br>
<input>
<div>检测居中div效果</div><br>
<p>检测居中p效果</p><br>
</div>
</div>
垂直居中的方式:
1.父元素height高度等于line-height的高度
.parent {
height: 高度;
}
.son {
line-height: 高度;
}
2.使用table的垂直居中
.parent {
display: table;
}
.son {
display: table-cell;
vertical-align: middle;
}
3.使用flex和align-item:center的方式
.parent {
display: flex;
align-items: center;
}
4.使用absolute的方式
.son {
position: absolute;
top: 50%;
transform: translate( 0, -50%);
}
或
.son {
position: absolute;
top: 50%;
height: 高度;
margin-top: -0.5高度;
}
或
.son {
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
}