八股文-001
HTTP 和 HTTPS
1. 概念
- http: 是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。
- https: 是以安全为目标的 HTTP 通道,即 HTTP 下 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输和真实性。
2. 区别及优缺点
HTTP
是超文本传输协议,信息是明文传输,HTTPS
协议要比 HTTP
协议安全,HTTPS
是具有安全性的 SSL
加密传输协议,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
HTTP 主要特点:简单快速、灵活、无连接、无状态
HTTP
协议的默认端口为 80,HTTPS
的默认端口为 443HTTP
的连接很简单,是无状态的。HTTPS
握手阶段比较费时,会使页面加载时间延长 50%,增加 10%~20%的耗电。HTTPS
缓存不如HTTP
高效,会增加数据开销。HTTPS
协议需要 ca 证书,费用较高,功能越强大的证书费用越高。- SSL 证书需要绑定
IP
,不能再同一个 IP 上绑定多个域名,IPV4 资源支持不了这种消耗。
3. HTTPS 工作原理
客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤:
- 建立 SSL 链接:客户端使用 https url 访问服务器,则要求 web 服务器
建立 ssl 链接
。 - 传输证书:web 服务器接收到客户端的请求之后,会
将网站的证书(证书中包含了公钥),传输给客户端
。 - 协商安全等级:客户端和 web 服务器端开始
协商 SSL 链接的安全等级
,也就是加密等级。 - 建立会话密钥:客户端浏览器通过双方协商一致的安全等级,
建立会话密钥
,然后通过网站的公钥来加密会话密钥,并传送给网站。 - 解密密钥:web 服务器
通过自己的私钥解密出会话密钥
。 - 进行通信:web 服务器
通过会话密钥加密与客户端之间的通信
。
传送门 ☞ # 解读 HTTP1/HTTP2/HTTP3(opens new window)
TCP
彻底搞懂 TCP 协议:从 TCP 三次握手四次挥手说起 - 知乎 (zhihu.com)
1. TCP 三次握手
- 第一次握手:服务器(S)只可以确认自己可以接受客户端(C)发送的报文段;
- 第二次握手:客户端(C)可以确认服务器(S)收到了自己发送的报文段,并且可以确认自己可以接受服务器(S)发送的报文段;
- 第三次握手:服务器(S)可以确认客户端(C)收到了自己发送的报文段;
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
2. TCP 四次挥手
- Client 发送一个 FIN 包来告诉 Server 我已经没数据需要发给 Server 了;
- Server 收到后回复一个 ACK 确认包说我知道了;
- 然后 server 在自己也没数据发送给 client 后,Server 也发送一个 FIN 包给 Client 告诉 Client 我也已经没数据发给 client 了;
- Client 收到后,就会回复一个 ACK 确认包说我知道了。
3. TCP/IP 如何保证数据包传输的有序可靠
对字节流分段并进行编号然后通过 ACK 回复
和超时重发
这两个机制来保证。
- 为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区;
- 并为每个已发送的数据包启动一个超时定时器;
- 如在定时器超时之前收到了对方发来的应答信息(可能是对本包的应答,也可以是对本包后续包的应答),则释放该数据包占用的缓冲区;
- 否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止。
- 接收方收到数据包后,先进行 CRC 校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去。
4. TCP 和 UDP 的区别
- TCP 是面向
链接
的,而 UDP 是面向无连接的。 - TCP 仅支持
单播传输
,UDP 提供了单播,多播,广播的功能。 - TCP 的三次握手保证了连接的
可靠性
; UDP 是无连接的、不可靠的一种数据传输协议,首先不可靠性体现在无连接上,通信都不需要建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收。 - UDP 的
头部开销
比 TCP 的更小,数据传输速率更高
,实时性更好
。
传送门 ☞ # 深度剖析 TCP 与 UDP 的区别
5. TCP 粘包问题分析与对策
TCP 粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
粘包出现原因
简单得说,在流传输中出现,UDP 不会出现粘包,因为它有消息边界
粘包情况有两种,一种是粘在一起的包都是完整的数据包
,另一种情况是粘在一起的包有不完整的包
。
为了避免粘包现象,可采取以下几种措施:
(1)对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push
,TCP 软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;
(2)对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施
,使其及时接收数据,从而尽量避免出现粘包现象;
(3)由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。分包多发
。
以上提到的三种措施,都有其不足之处。
(1)第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。
(2)第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。
(3)第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。
一种比较周全的对策是:接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开。实验证明这种方法是高效可行的。
HTTP 请求跨域问题
1. 跨域的原理
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略
造成的。 同源策略,是浏览器对 JavaScript 实施的安全限制,只要协议、域名、端口
有任何一个不同,都被当作是不同的域。 跨域原理,即是通过各种方式,避开浏览器的安全限制
。
2. 解决方案
最初做项目的时候,使用的是 jsonp,但存在一些问题,使用 get 请求不安全,携带数据较小,后来也用过 iframe,但只有主域相同才行,也是存在些问题,后来通过了解和学习发现使用代理和 proxy 代理配合起来使用比较方便,就引导后台按这种方式做下服务器配置,在开发中使用 proxy,在服务器上使用 nginx 代理,这样开发过程中彼此都方便,效率也高;现在 h5 新特性还有 windows.postMessage()
JSONP: ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是 返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。
步骤:
- 去创建一个 script 标签
- script 的 src 属性设置接口地址
- 接口参数,必须要带一个自定义函数名,要不然后台无法返回数据
- 通过定义函数名去接受返回的数据
//动态创建 script var script = document.createElement('script'); // 设置回调函数 function getData(data) { console.log(data); } //设置 script 的 src 属性,并设置请求地址 script.src = 'http://localhost:3000/?callback=getData'; // 让 script 生效 document.body.appendChild(script);
JSONP 的缺点: JSON 只支持 get,因为 script 标签只能使用 get 请求; JSONP 需要后端配合返回指定格式的数据。
document.domain 基础域名相同,子域名不同
window.name 利用在一个浏览器窗口内,载入所有的域名都是共享一个 window.name
CORS CORS(Cross-origin resource sharing)跨域资源共享,服务器设置对 CORS 的支持原理:服务器设置 Access-Control-Allow-Origin HTTP 响应头之后,浏览器将会允许跨域请求
proxy 代理 目前常用方式,本地使用
http-proxy-middleware
,通过服务器(nginx/apache
)设置代理window.postMessage() 利用 h5 新特性 window.postMessage()
本地存储
有 cookie、sessionStorage、localStorage
相同点:
- 存储在客户端
不同点:
- cookie 数据大小不能超过 4k;sessionStorage 和 localStorage 的存储比 cookie 大得多,可以达到 5M+
- cookie 设置的过期时间之前一直有效;localStorage 永久存储,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage 数据在当前浏览器窗口关闭后自动删除
- cookie 的数据会自动的传递到服务器;sessionStorage 和 localStorage 数据保存在本地
介绍下 304 过程
- a. 浏览器请求资源时首先命中资源的 Expires 和 Cache-Control,Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效,可以通过 Cache-control: max-age 指定最大生命周期,状态仍然返回 200,但不会请求数据,在浏览器中能明显看到 from cache 字样。
- b. 强缓存失效,进入协商缓存阶段,首先验证 ETag,ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据客户端上送的 If-None-Match 值来判断是否命中缓存。
- c. 协商缓存 Last-Modify/If-Modify-Since 阶段,客户端第一次请求资源时,服务服返回的 header 中会加上 Last-Modify,Last-modify 是一个时间标识该资源的最后修改时间。再次请求该资源时,request 的请求头中会包含 If-Modify-Since,该值为缓存之前返回的 Last-Modify。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。
浏览器缓存机制 强制缓存 && 协商缓存
浏览器与服务器通信的方式为应答模式,即是:浏览器发起 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 和请求结果结果
传送门 ☞ # 彻底理解浏览器的缓存机制
进程、线程和协程
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位
,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元
,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程 ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
协程,英文 Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在
,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。
进程和线程的区别与联系
【区别】:
调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行;
拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。但是进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个进程死掉就等于所有的线程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
【联系】:
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;
资源分配给进程,同一进程的所有线程共享该进程的所有资源;
处理机分给线程,即真正在处理机上运行的是线程;
线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
HTML5 新特性、语义化
概念:
HTML5 的语义化指的是
合理正确的使用语义化的标签来创建页面结构
。【正确的标签做正确的事】语义化标签:
header nav main article section aside footer
语义化的优点:
- 在
没CSS样式的情况下,页面整体也会呈现很好的结构效果
代码结构清晰
,易于阅读,利于开发和维护
方便其他设备解析(如屏幕阅读器)根据语义渲染网页。有利于搜索引擎优化(SEO)
,搜索引擎爬虫会根据不同的标签来赋予不同的权重
- 在
CSS 选择器及优先级
选择器:
- id 选择器(#myid)
- 类选择器(.myclass)
- 属性选择器(a[rel="external"])
- 伪类选择器(a:hover, li:nth-child)
- 标签选择器(div, h1,p)
- 相邻选择器(h1 + p)
- 子选择器(ul > li)
- 后代选择器(li a)
- 通配符选择器(*)
优先级:
!important
- 内联样式(1000)
- ID 选择器(100)
- 类选择器/属性选择器/伪类选择器(10)
- 元素选择器/伪元素选择器(1)
- 关系选择器/通配符选择器(0)
带!important 标记的样式属性优先级最高; 样式表的来源相同时:!important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性
position 属性的值有哪些及其区别
固定定位 fixed: 元素的位置相对于浏览器窗口是固定位置,即使窗口是滚动的它也不会移动。Fixed 定 位使元素的位置与文档流无关,因此不占据空间。 Fixed 定位的元素和其他元素重叠。
相对定位 relative: 如果对一个元素进行相对定位,它将出现在它所在的位置上。然后,可以通过设置垂直 或水平位置,让这个元素“相对于”它的起点进行移动。 在使用相对定位时,无论是 否进行移动,元素仍然占据原来的空间。因此,移动元素会导致它覆盖其它框。
绝对定位 absolute: 绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那 么它的位置相对于。absolute 定位使元素的位置与文档流无关,因此不占据空间。 absolute 定位的元素和其他元素重叠。
粘性定位 sticky: 元素先按照普通文档流定位,然后相对于该元素在流中的 flow root(BFC)和 containing block(最近的块级祖先元素)定位。而后,元素定位表现为在跨越特定阈值前为相对定 位,之后为固定定位。
默认定位 static: 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。 inherit: 规定应该从父元素继承 position 属性的值。
box-sizing 属性
box-sizing 规定两个并排的带边框的框,语法为 box-sizing:content-box/border-box/inherit
content-box:宽度和高度分别应用到元素的内容框,在宽度和高度之外绘制元素的内边距和边框。【标准盒子模型】
border-box:为元素设定的宽度和高度决定了元素的边框盒。【IE 盒子模型】
inherit:继承父元素的 box-sizing 值。
CSS 盒子模型
CSS 盒模型本质上是一个盒子,它包括:边距,边框,填充和实际内容。CSS 中的盒子模型包括 IE 盒子模型和标准的 W3C 盒子模型。 在标准的盒子模型中,width 指 content 部分的宽度
。 在 IE 盒子模型中,width 表示 content+padding+border 这三个部分的宽度
。
故在计算盒子的宽度时存在差异:
标准盒模型: 一个块的总宽度 = width+margin(左右)+padding(左右)+border(左右)
怪异盒模型: 一个块的总宽度 = width+margin(左右)(既 width 已经包含了 padding 和 border 值)
BFC(块级格式上下文)
BFC 的概念:
BFC
是 Block Formatting Context
的缩写,即块级格式化上下文。BFC
是 CSS 布局的一个概念,是一个独立的渲染区域,规定了内部 box 如何布局, 并且这个区域的子元素不会影响到外面的元素,其中比较重要的布局规则有内部 box 垂直放置,计算 BFC 的高度的时候,浮动元素也参与计算。
BFC 的原理布局规则:
- 内部的 Box 会在
垂直方向
,一个接一个地放置 - Box
垂直方向的距离由margin决定
。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠 - 每个元素的 margin box 的左边, 与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反
- BFC 的区域
不会与float box重叠
- BFC 是一个独立容器,容器里面的
子元素不会影响到外面的元素
- 计算 BFC 的高度时,
浮动元素也参与计算高度
- 元素的类型和
display属性,决定了这个Box的类型
。不同类型的 Box 会参与不同的Formatting Context
。
如何创建 BFC?
- 根元素,即 HTML 元素
- float 的值不为 none
- position 为 absolute 或 fixed
- display 的值为 inline-block、table-cell、table-caption
- overflow 的值不为 visible
BFC 的使用场景:
- 去除边距重叠现象
- 清除浮动(让父元素的高度包含子浮动元素)
- 避免某元素被浮动元素覆盖
- 避免多列布局由于宽度计算四舍五入而自动换行
让一个元素水平/垂直居中
水平居中
对于 行内元素 :
text-align: center
;对于确定宽度的块级元素:
(1)width 和 margin 实现。
margin: 0 auto
;(2)绝对定位和 margin-left: -width/2, 前提是父元素 position: relative
对于宽度未知的块级元素
(1)
table标签配合margin左右auto实现水平居中
。使用 table 标签(或直接将块级元素设值为 display:table),再通过给该标签添加左右 margin 为 auto。(2)inline-block 实现水平居中方法。display:inline-block 和 text-align:center 实现水平居中。【易忽略】
(3)
绝对定位+transform
,translateX 可以移动本身元素的 50%。【常用 absolute + margin】(4)flex 布局使用
justify-content:center
垂直居中
- 利用
line-height
实现居中,这种方法适合纯文字类 - 通过设置父容器 相对定位 ,子级设置
绝对定位
,标签通过 margin 实现自适应居中 - 弹性布局 flex :父级设置 display: flex; 子级设置 margin 为 auto 实现自适应居中
- 父级设置相对定位,子级设置绝对定位,并且通过位移 transform 实现
table 布局
,父级通过转换成表格形式,然后子级设置 vertical-align 实现
。(需要注意的是:vertical-align: middle 使用的前提条件是内联元素以及 display 值为 table-cell 的元素)。
- 利用
传送门 ☞ # 图解 CSS 水平垂直居中常见面试方法
隐藏页面中某个元素的方法
1.opacity:0
,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定 一些事件,如 click 事件,那么点击该区域,也能触发点击事件的
2.visibility:hidden
,该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已经绑定的事件 ,隐藏对应元素,在文档布局中仍保留原来的空间(重绘)
3.display:none
,把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素。 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)
该问题会引出 回流和重绘
用 CSS 实现三角符号
/*记忆口诀:盒子宽高均为零,三面边框皆透明。*/
/*尖朝向边的反方向*/
div:after {
position: absolute;
width: 0px;
height: 0px;
content: ' ';
border-right: 100px solid transparent;
border-top: 100px solid #ff0;
border-left: 100px solid transparent;
border-bottom: 100px solid transparent;
}
页面布局
1. Flex 布局
布局的传统解决方案,基于盒状模型,依赖 display 属性 + position 属性 + float 属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。
Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。指定容器 display: flex 即可。 简单的分为容器属性和元素属性。
容器的属性:
- flex-direction:决定主轴的方向(即子 item 的排列方法)flex-direction: row | row-reverse | column | column-reverse;
- flex-wrap:决定换行规则 flex-wrap: nowrap | wrap | wrap-reverse;
- flex-flow: .box { flex-flow: || ; }
- justify-content:对其方式,水平主轴对齐方式
- align-items:对齐方式,竖直轴线方向
- align-content
项目的属性(元素的属性):
- order 属性:定义项目的排列顺序,顺序越小,排列越靠前,默认为 0
- flex-grow 属性:定义项目的放大比例,即使存在空间,也不会放大
- flex-shrink 属性:定义了项目的缩小比例,当空间不足的情况下会等比例的缩小,如果 定义个 item 的 flow-shrink 为 0,则为不缩小
- flex-basis 属性:定义了在分配多余的空间,项目占据的空间。
- flex:是 flex-grow 和 flex-shrink、flex-basis 的简写,默认值为 0 1 auto。
- align-self:允许单个项目与其他项目不一样的对齐方式,可以覆盖
- align-items,默认属 性为 auto,表示继承父元素的 align-items 比如说,用 flex 实现圣杯布局
2. Rem 布局
首先 Rem 相对于根(html)的 font-size 大小来计算。简单的说它就是一个相对单例 如:font-size:10px,那么(1rem = 10px)了解计算原理后首先解决怎么在不同设备上设置 html 的 font-size 大小。其实 rem 布局的本质是等比缩放,一般是基于宽度。
优点:可以快速适用移动端布局,字体,图片高度
缺点:
① 目前 ie 不支持,对 pc 页面来讲使用次数不多; ② 数据量大:所有的图片,盒子都需要我们去给一个准确的值;才能保证不同机型的适配; ③ 在响应式布局中,必须通过 js 来动态控制根元素 font-size 的大小。也就是说 css 样式和 js 代码有一定的耦合性。且必须将改变 font-size 的代码放在 css 样式之前。
3. 百分比布局
通过百分比单位 " % " 来实现响应式的效果。通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。 直观的理解,我们可能会认为子元素的百分比完全相对于直接父元素,height 百分比相 对于 height,width 百分比相对于 width。 padding、border、margin 等等不论是垂直方向还是水平方向,都相对于直接父元素的 width。 除了 border-radius 外,还有比如 translate、background-size 等都是相对于自身的。
缺点:
(1)计算困难 (2)各个属性中如果使用百分比,相对父元素的属性并不是唯一的。造成我们使用百分比单位容易使布局问题变得复杂。
4. 浮动布局
浮动布局:当元素浮动以后可以向左或向右移动,直到它的外边缘碰到包含它的框或者另外一个浮动元素的边框为止。元素浮动以后会脱离正常的文档流,所以文档的普通流中的框就变的好像浮动元素不存在一样。
优点:
这样做的优点就是在图文混排的时候可以很好的使文字环绕在图片周围。另外当元素浮动了起来之后,它有着块级元素的一些性质例如可以设置宽高等,但它与 inline-block 还是有一些区别的,第一个就是关于横向排序的时候,float 可以设置方向而 inline-block 方向是固定的;还有一个就是 inline-block 在使用时有时会有空白间隙的问题
缺点:
最明显的缺点就是浮动元素一旦脱离了文档流,就无法撑起父元素,会造成父级元素高度塌陷
。
如何使用 rem 或 viewport 进行移动端适配
rem 适配原理:
改变了一个元素在不同设备上占据的 css 像素的个数
rem 适配的优缺点
- 优点:没有破坏完美视口
- 缺点:px 值转换 rem 太过于复杂(下面我们使用 less 来解决这个问题)
viewport 适配的原理:
viewport 适配方案中,每一个元素在不同设备上占据的 css 像素的个数是一样的。但是 css 像素和物理像素的比例是不一样的,等比的
viewport 适配的优缺点
- 在我们设计图上所量取的大小即为我们可以设置的像素大小,即所量即所设
- 缺点破坏完美视口
清除浮动的方式
- 添加额外标签
<div class="parent">
<!-- 添加额外标签并且添加clear属性 -->
<div style="clear:both"></div>
<!-- 也可以加一个br标签 -->
</div>
- 父级添加 overflow 属性(BFC),或者设置高度
- 建立伪类选择器清除浮动
/* 在css中添加:after伪元素 */
.parent:after {
/* 设置添加子元素的内容是空 */
content: '';
/* 设置添加子元素为块级元素 */
display: block;
/* 设置添加的子元素的高度0 */
height: 0;
/* 设置添加子元素看不见 */
visibility: hidden;
/* 设置clear:both */
clear: both;
}
JS 中的 8 种数据类型及区别
包括值类型(基本对象类型)和引用类型(复杂对象类型)
基本类型(值类型): Number(数字),String(字符串),Boolean(布尔),Symbol(符号),null(空),undefined(未定义)在内存中占据固定大小,保存在栈内存中--【背诵:数字、字符串、布尔三种,加上两个空 null、undefined,最后加上新增,Symbol】
引用类型(复杂数据类型): Object(对象)、Function(函数)。其他还有 Array(数组)、Date(日期)、RegExp(正则表达式)、特殊的基本包装类型(String、Number、Boolean) 以及单体内置对象(Global、Math)等 引用类型的值是对象 保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址。
JS 中的数据类型检测方案
1.typeof
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof 'mc'); // string
console.log(typeof function () {}); // function
console.log(typeof console.log()); // function
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
console.log(typeof undefined); // undefined
优点:能够快速区分基本数据类型
缺点:不能将 Object、Array 和 Null 区分,都返回 object
2.instanceof
console.log(1 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function () {} instanceof Function); // true
console.log({} instanceof Object); // true
优点:能够区分 Array、Object 和 Function,适合用于判断自定义的类实例对象
缺点:Number,Boolean,String 基本数据类型不能判断
3.Object.prototype.toString.call()
var toString = Object.prototype.toString;
console.log(toString.call(1)); //[object Number]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call('mc')); //[object String]
console.log(toString.call([])); //[object Array]
console.log(toString.call({})); //[object Object]
console.log(toString.call(function () {})); //[object Function]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]
// 这里涉及到 .call用法
优点:精准判断数据类型
缺点:写法繁琐不容易记,推荐进行封装后使用
var && let && const
ES6 之前创建变量用的是 var,之后创建变量用的是 let/const
三者区别:
- var 定义的变量,
没有块的概念,可以跨块访问
, 不能跨函数访问。 let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。 const 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。 - var 可以
先使用,后声明
,因为存在变量提升;let 必须先声明后使用。 - var 是允许在相同作用域内
重复声明同一个变量
的,而 let 与 const 不允许这一现象。 - 在全局上下文中,基于 let 声明的全局变量和全局对象 GO(window)没有任何关系 ; var 声明的变量会和 GO 有映射关系;
会产生暂时性死区
:
暂时性死区是浏览器的 bug:检测一个未被声明的变量类型时,不会报错,会返回 undefined 如:console.log(typeof a) //undefined 而:console.log(typeof a)//未声明之前不能使用 let a
- let /const/function 会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于 let 的块作用特征即可解决
感悟:看很多人写的代码,为了用上 es6,代码全是 let,但一个 const 没有。其实定义空数组 const result = [], const obj = {}, 都是可以用 const
JS 垃圾回收机制
项目中,如果存在大量不被释放的内存(堆/栈/上下文),页面性能会变得很慢。当某些代码操作不能被合理释放,就会造成内存泄漏。我们尽可能减少使用闭包,因为它会消耗内存。
浏览器垃圾回收机制/内存回收机制:
浏览器的
Javascript
具有自动垃圾回收机制(GC:Garbage Collecation
),垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。标记清除:在
js
中,最常用的垃圾回收机制是标记清除:当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,会被标记为“离开环境”。垃圾回收器会销毁那些带标记的值并回收它们所占用的内存空间。 谷歌浏览器:“查找引用”,浏览器不定时去查找当前内存的引用,如果没有被占用了,浏览器会回收它;如果被占用,就不能回收。 IE 浏览器:“引用计数法”,当前内存被占用一次,计数累加 1 次,移除占用就减 1,减到 0 时,浏览器就回收它。优化手段:内存优化 ; 手动释放:取消内存的占用即可。
(1)堆内存:fn = null 【null:空指针对象】
(2)栈内存:把上下文中,被外部占用的堆的占用取消即可。
内存泄漏
在 JS 中,常见的内存泄露主要有 4 种,全局变量、闭包、DOM 元素的引用、定时器
作用域和作用域链
创建函数的时候,已经声明了当前函数的作用域==>当前创建函数所处的上下文
。如果是在全局下创建的函数就是[[scope]]:EC(G)
,函数执行的时候,形成一个全新的私有上下文EC(FN)
,供字符串代码执行(进栈执行)
定义:简单来说作用域就是变量与函数的可访问范围,由当前环境与上层环境的一系列变量对象组成
1.全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。 2.函数作用域:在固定的代码片段才能被访问
作用:作用域最大的用处就是隔离变量
,不同作用域下同名变量不会有冲突。
作用域链参考链接一般情况下,变量到创建该变量的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
闭包的两大作用:保存/保护
- 闭包的概念
函数执行时形成的私有上下文 EC(FN),正常情况下,代码执行完会出栈后释放;但是特殊情况下,如果当前私有上下文中的某个东西被上下文以外的事物占用了,则上下文不会出栈释放,从而形成不销毁的上下文。函数执行过程中,会形成一个全新的私有上下文,可能会被释放,可能不会被释放,不论释放与否,他的作用是:
(1)保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,保护自己的私有变量不受外界干扰(操作自己的私有变量和外界没有关系);
(2)保存:如果当前上下文不被释放【只要上下文中的某个东西被外部占用即可】,则存储的这些私有变量也不会被释放,可以供其下级上下文中调取使用,相当于把一些值保存起来了;
我们把函数执行形成私有上下文,来保护和保存私有变量机制称为闭包
。
闭包是指有权访问另一个函数作用域中的变量的函数--《JavaScript 高级程序设计》
稍全面的回答: 在 js 中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数,由于其可访问上级作用域,即使上级函数执行完,作用域也不会随之销毁,这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
闭包的特性:
1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。
1.1.闭包是密闭的容器,,类似于 set、map 容器,存储数据的
1.2.闭包是一个对象,存放数据的格式为 key-value 形式
2、函数嵌套函数
3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除
闭包形成的条件:
- 函数的嵌套
- 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
闭包的用途:
- 模仿块级作用域
- 保护外部函数的变量,能够访问函数定义时所在的词法作用域(阻止其被回收)
- 封装私有化变量
- 创建模块
闭包应用场景
闭包的两个场景,闭包的两大作用:
保存/保护
。 在开发中,其实我们随处可见闭包的身影,大部分前端 JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法;发送 ajax 请求成功|失败的回调;setTimeout 的延时回调;或者一个函数内部返回另一个匿名函数(高阶函数),这些都是闭包的应用。闭包的优点:延长局部变量的生命周期
闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
JS 中 this 的五种情况
- 作为普通函数执行时,
this
指向window
。 - 当函数作为对象的方法被调用时,
this
就会指向该对象
。 - 构造器调用,
this
指向返回的这个对象
。 - 箭头函数 箭头函数的
this
绑定看的是this所在函数定义在哪个对象下
,就绑定哪个对象。如果有嵌套的情况,则 this 绑定到最近的一层对象上。【小程序里面 wx.xxx({ success: (res) => {this.xx} }),this 还是 Page】 - 基于 Function.prototype 上的
apply 、 call 和 bind
调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply
接收参数的是数组,call
接受参数列表,bind
方法通过传入一个对象,返回一个this
绑定了传入对象的新函数。这个函数的this
指向除了使用new
时会被改变,其他情况下都不会改变。若为空默认是指向全局对象 window。
原型 && 原型链
原型关系:
- 每个 class 都有显示原型 prototype
- 每个实例都有隐式原型 proto
- 实例的proto指向对应 class 的 prototype
原型: 在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象
都有一个prototype
属性,这个属性指向函数的原型对象
。
原型链:函数的原型链对象 constructor 默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针proto,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用proto一直指向 Object 的原型对象上,而 Object 原型对象用 Object.prototype.proto = null 表示原型链顶端。如此形成了 js 的原型链继承。同时所有的 js 对象都有 Object 的基本防范
特点: JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
new 运算符的实现机制
- 首先创建了一个新的
空对象
设置原型
,将对象的原型设置为函数的prototype
对象。- 让函数的
this
指向这个对象,执行构造函数的代码(为这个新对象添加属性) - 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
// 代码实现-待补充
EventLoop 事件循环
该题比较简单,并且都是口述,所以面试官肯定会问 node 中的 EventLoop,宿主--容易听成数组
JS
是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.then
,MutationObserver
,宏任务的话就是`setImmediate setTimeout setInterval--【关键字:单线程,执行栈,宏任务队列,微任务队列】
JS 运行的环境。一般为浏览器或者 Node。 在浏览器环境中,有 JS 引擎线程和渲染线程,且两个线程互斥。 Node 环境中,只有 JS 线程。 不同环境执行机制有差异,不同任务进入不同 Event Queue 队列。 当主程结束,先执行准备好微任务,然后再执行准备好的宏任务,一个轮询结束。
浏览器中的事件环(Event Loop)
事件环的运行机制是,先会执行栈中的内容,栈中的内容执行后执行微任务,微任务清空后再执行宏任务,先取出一个宏任务,再去执行微任务,然后在取宏任务清微任务这样不停的循环。
eventLoop 是由 JS 的宿主环境(浏览器)来实现的;
事件循环可以简单的描述为以下四个步骤:
- 函数入栈,当 Stack 中执行到异步任务的时候,就将他丢给 WebAPIs,接着执行同步任务,直到 Stack 为空;
- 此期间 WebAPIs 完成这个事件,把回调函数放入队列中等待执行(微任务放到微任务队列,宏任务放到宏任务队列)
- 执行栈为空时,Event Loop 把微任务队列执行清空;
- 微任务队列清空后,进入宏任务队列,取队列的第一项任务放入 Stack(栈)中执行,执行完成后,查看微任务队列是否有任务,有的话,清空微任务队列。重复 4,继续从宏任务中取任务执行,执行完成之后,继续清空微任务,如此反复循环,直至清空所有的任务。
浏览器中的任务源(task):
宏任务(macrotask)
: 宿主环境提供的,比如浏览器 ajax、setTimeout、setInterval、setTmmediate(只兼容 ie)、script、requestAnimationFrame、messageChannel、UI 渲染、一些浏览器 api微任务(microtask)
: 语言本身提供的,比如 promise.then then、queueMicrotask(基于 then)、mutationObserver(浏览器提供)、messageChannel 、mutationObersve
Node 环境中的事件环(Event Loop)
Node
是基于 V8 引擎的运行在服务端的JavaScript
运行环境,在处理高并发、I/O 密集(文件操作、网络操作、数据库操作等)场景有明显的优势。虽然用到也是 V8 引擎,但由于服务目的和环境不同,导致了它的 API 与原生 JS 有些区别,其 Event Loop 还要处理一些 I/O,比如新的网络连接等,所以 Node 的 Event Loop(事件环机制)与浏览器的是不太一样的。
执行顺序如下:
timers
: 计时器,执行 setTimeout 和 setInterval 的回调pending callbacks
: 执行延迟到下一个循环迭代的 I/O 回调idle, prepare
: 队列的移动,仅系统内部使用poll轮询
: 检索新的 I/O 事件;执行与 I/O 相关的回调。事实上除了其他几个阶段处理的事情,其他几乎所有的异步都在这个阶段处理。check
: 执行setImmediate
回调,setImmediate 在这里执行close callbacks
: 执行close
事件的callback
,一些关闭的回调函数,如:socket.on('close', ...)
setTimeout、Promise、Async/Await 的区别
setTimeout
settimeout 的回调函数放到宏任务队列里,等到执行栈清空以后执行。
Promise
Promise 本身是同步的立即执行函数, 当在 executor 中执行 resolve 或者 reject 的时候, 此时是异步操作, 会先执行 then/catch 等,当主栈完成后,才会去调用 resolve/reject 中存放的方法执行。
console.log(1); let promise1 = new Promise(function (resolve) { console.log(2); resolve(); console.log(3); }).then(function () { console.log(5); }); setTimeout(function () { console.log(6); }); console.log(4);
async/await
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
async function async1() { console.log(2); await async2(); console.log(5); } async function async2() { console.log(3); } console.log(1); async1(); console.log(4);
Async/Await 如何通过同步的方式实现异步
Async/Await 就是一个自执行的 generate 函数。利用 generate 函数的特性把异步的代码写成“同步”的形式,第一个请求的返回值作为后面一个请求的参数,其中每一个参数都是一个 promise 对象。
介绍节流防抖原理、区别以及应用
节流
:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。--【类似王者荣耀的释放大招,有冷却时间】
防抖
:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!--【类似大学写毕业论文,最后一版生效】
使用场景
: 节流:滚动加载更多、表单重复提交…… 防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。
/**
* 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。
* @param fn要被节流的函数
* @param delay规定的时间
*/
function throttle(fn, delay) {
//记录上一次函数触发的时间
var lastTime = 0;
return function () {
//记录当前函数触发的时间
var nowTime = Date.now();
if (nowTime - lastTime > delay) {
//修正this指向问题
fn.call(this);
//同步执行结束时间
lastTime = nowTime;
}
};
}
document.onscroll = throttle(function () {
console.log('scllor事件被触发了' + Date.now());
}, 200);
/**
* 防抖函数 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
* @param fn要被节流的函数
* @param delay规定的时间
*/
function debounce(fn, delay) {
//记录上一次的延时器
var timer = null;
return function () {
//清除上一次的演示器
clearTimeout(timer);
//重新设置新的延时器
timer = setTimeout(function () {
//修正this指向问题
fn.apply(this);
}, delay);
};
}
document.getElementById('btn').onclick = debounce(function () {
console.log('按钮被点击了' + Date.now());
}, 1000);
简述 MVVM
什么是 MVVM?
视图模型双向绑定
,是Model-View-ViewModel
的缩写,也就是把MVC
中的Controller
演变成ViewModel。Model
层代表数据模型,View
代表 UI 组件,ViewModel
是View
和Model
层的桥梁,数据会绑定到viewModel
层并自动将数据渲染到页面中,视图变化的时候会通知viewModel
层更新数据。以前是操作 DOM 结构更新视图,现在是数据驱动视图
。
MVVM 的优点:
1.低耦合
。视图(View)可以独立于 Model 变化和修改,一个 Model 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变化,当 Model 变化的时候 View 也可以不变; 2.可重用性
。你可以把一些视图逻辑放在一个 Model 里面,让很多 View 重用这段视图逻辑。 3.独立开发
。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。 4.可测试
。
Vue 底层实现原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter 和 getter,在数据变动时发布消息给订阅者,触发相应的监听回调 Vue 是一个典型的 MVVM 框架,模型(Model)只是普通的 javascript 对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观
Observer(数据监听器) : Observer 的核心是通过 Object.defineProprtty()来监听数据的变动,这个函数内部可以定义 setter 和 getter,每当数据发生变化,就会触发 setter。这时候 Observer 就要通知订阅者,订阅者就是 Watcher
Watcher(订阅者) : Watcher 订阅者作为 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里面添加自己
- 自身必须有一个 update()方法
- 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调
Compile(指令解析器) : Compile 主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图
谈谈对 vue 生命周期的理解?
每个Vue
实例在创建时都会经过一系列的初始化过程,vue
的生命周期钩子,就是说在达到某一阶段或条件时去触发的函数,目的就是为了完成一些动作或者事件
1. 页面生命周期
create阶段
:vue 实例被创建beforeCreate
: 创建前,此时 data 和 methods 中的数据都还没有初始化created
: 创建完毕,data 中有值,未挂载mount阶段
: vue 实例被挂载到真实 DOM 节点beforeMount
:可以发起服务端请求,去数据mounted
: 此时可以操作 DOMupdate阶段
:当 vue 实例里面的 data 数据变化时,触发组件的重新渲染beforeUpdate
:更新前updated
:更新后destroy阶段
:vue 实例被销毁beforeDestroy
:实例被销毁前,此时可以手动销毁一些方法destroyed
:销毁后
2. 组件生命周期
生命周期(父子组件) 父组件 beforeCreate --> 父组件 created --> 父组件 beforeMount --> 子组件 beforeCreate --> 子组件 created --> 子组件 beforeMount --> 子组件 mounted --> 父组件 mounted -->父组件 beforeUpdate -->子组件 beforeDestroy--> 子组件 destroyed --> 父组件 updated
加载渲染过程 父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted
挂载阶段 父 created->子 created->子 mounted->父 mounted
父组件更新阶段 父 beforeUpdate->父 updated
子组件更新阶段 父 beforeUpdate->子 beforeUpdate->子 updated->父 updated
销毁阶段 父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed
Vue 中 computed 与 watch 区别
通俗来讲,既能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。
watch 属性监听 是一个对象,键是需要观察的属性,值是对应回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,监听属性的变化,需要在数据变化时执行异步或开销较大的操作时使用
computed 计算属性 属性的结果会被缓存
,当computed
中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取。除非依赖的响应式属性变化时才会重新计算,主要当做属性来使用 computed
中的函数必须用return
返回最终的结果 computed
更高效,优先使用。data 不改变,computed 不更新。
使用场景 computed
:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能 watch
:当一条数据影响多条数据的时候使用,例:搜索数据
Vue 组件中的 data 为什么是一个函数?
- 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。
- 如果 data 是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间 data 不冲突,data 必须是一个函数。
Vue 中为什么 v-for 和 v-if 不建议用在一起
- 当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费
- 这种场景建议使用 computed,先对数据进行过滤
React/Vue 项目中 key 的作用
key 的作用是为了在 diff 算法执行时更快的找到对应的节点,
提高diff速度,更高效的更新虚拟DOM
;vue 和 react 都是采用 diff 算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数中,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点。如果没找到就认为是一个新增节点。而如果没有 key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个 map 映射,另一种是遍历查找。相比而言。map 映射的速度更快。
为了在数据变化时强制更新组件,以避免
“就地复用”
带来的副作用。当 Vue.js 用
v-for
更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。重复的 key 会造成渲染错误。
Vue 组件的通信方式
props
/$emit
父子组件通信父->子
props
,子->父$on、$emit
获取父子组件实例parent、children
Ref
获取实例的方式调用组件的属性或者方法 父->子孙Provide、inject
官方不推荐使用,但是写组件库时很常用$emit
/$on
自定义事件 兄弟组件通信Event Bus
实现跨组件通信Vue.prototype.$bus = new Vue()
自定义事件vuex 跨级组件通信
Vuex、
$attrs、$listeners
Provide、inject
nextTick 的实现
nextTick
是Vue
提供的一个全局API
,是在下次DOM
更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick
,则可以在回调中获取更新后的DOM
;- Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,
Vue
将开启 1 个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher
被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM
操作是非常重要的。nextTick
方法会在队列中加入一个回调函数,确保该函数在前面的 dom 操作完成后才调用; - 比如,我在干什么的时候就会使用 nextTick,传一个回调函数进去,在里面执行 dom 操作即可;
- 我也有简单了解
nextTick
实现,它会在callbacks
里面加入我们传入的函数,然后用timerFunc
异步方式调用它们,首选的异步方式会是Promise
。这让我明白了为什么可以在nextTick
中看到dom
操作结果。
nextTick 的实现原理是什么?
在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick 主要使用了宏任务和微任务。 根据执行环境分别尝试采用 Promise、MutationObserver、setImmediate,如果以上都不行则采用 setTimeout 定义了一个异步方法,多次调用 nextTick 会将方法存入队列中,通过这个异步方法清空当前队列。
插槽
具名插槽、匿名插槽、作用域插槽
vue 中的插槽是一个非常好用的东西 slot 说白了就是一个占位的 在 vue 当中插槽包含三种一种是默认插槽(匿名)一种是具名插槽还有一种就是作用域插槽 匿名插槽就是没有名字的只要默认的都填到这里具名插槽指的是具有名字的
keep-alive 的实现
作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染--【两个重要的钩子,actived(激活)、deactived(未激活)】
场景:tabs 标签页 后台导航,vue 性能优化
原理:Vue.js
内部将DOM
节点抽象成了一个个的VNode
节点,keep-alive
组件的缓存也是基于VNode
节点的而不是直接存储DOM
结构。它将满足条件(pruneCache与pruneCache)
的组件在cache
对象中缓存起来,在需要重新渲染的时候再将vnode
节点从cache
对象中取出并渲染。
mixin
mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到 mixin 多个组件有相同的逻辑,抽离出来 mixin 并不是完美的解决方案,会有一些问题 vue3 提出的 Composition API 旨在解决这些问题【追求完美是要消耗一定的成本的,如开发成本】 场景:PC 端新闻列表和详情页一样的右侧栏目,可以使用 mixin 进行混合 劣势:1.变量来源不明确,不利于阅读 2.多 mixin 可能会造成命名冲突 3.mixin 和组件可能出现多对多的关系,使得项目复杂度变高
Vuex 的理解及使用场景
Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。
- Vuex 的状态存储是响应式的;当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新
- 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation, 这样使得我们可以方便地跟踪每一个状态的变化 Vuex 主要包括以下几个核心模块:
- State:定义了应用的状态数据
- Getter:在 store 中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算
- Mutation:是唯一更改 store 中状态的方法,且必须是同步函数
- Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作 5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中
项目优化
移除生产环境的控制台打印
。方案很多,esling+pre-commit、使用插件自动去除,插件包括 babel-plugin-transform-remove-console、uglifyjs-webpack-plugin、terser-webpack-plugin。最后选择了 terser-webpack-plugin,脚手架 vue-cli 用这个插件来开启缓存和多线程打包,无需安装额外的插件,仅需在 configureWebpack 中设置 terser 插件的 drop_console 为 true 即可。最好还是养成良好的代码习惯,在开发基本完成后去掉无用的 console,vscode 中的 turbo console 就蛮好的。
第三方库的按需加载
。echarts,官方文档里是使用配置文件指定使用的模块,另一种使用 babel-plugin-equire 实现按需加载。element-ui 使用 babel-plugin-component 实现按需引入。
公有样式,比如对 element-ui 部分组件(如弹框、表格、下拉选框等)样式的统一调整
。公共组件,比如 date-picker、upload-file 等在 element-ui 提供的组件基本上做进一步的封装。自定义组件包括 preview-file、搜索框等。
前后端数据交换方面,推动项目组使用蓝湖、接口文档,与后端同学协商,规范后台数据返回。
雅虎军规提到的,避免css表达式、滤镜,较少DOM操作,优化图片、精灵图,避免图片空链接等
。
性能问题:页面加载性能、动画性能、操作性能
。Performance API,记录性能数据。
winter 重学前端 优化技术方案:
缓存:客户端控制的强缓存策略
。
降低请求成本
:DNS 由客户端控制,隔一段时间主动请求获取域名 IP,不走系统 DNS(完全看不懂)。TCP/TLS 连接复用,服务器升级到 HTTP2,尽量合并域名。
减少请求数
:JS、CSS 打包到 HTML。JS 控制图片异步加载、懒加载。小型图片使用 data-uri。
较少传输体积
:尽量使用 SVG\gradient 代替图片。根据机型和网络状况控制图片清晰度。对低清晰度图片使用锐化来提升体验。设计上避免大型背景图。
使用CDN加速
,内容分发网络,是建立再承载网基础上的虚拟分布式网络,能够将源站内容缓存到全国或全球的节点服务器上。用户就近获取内容,提高了资源的访问速度,分担源站压力。