temp
1. 计网八股文
1.1 TCP/IP五层协议
1.1.1 TCP三次握手
- 第一次握手:
建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认
;SYN:同步序列编号(Synchronize Sequence Numbers)。 - 第二次握手:
服务器收到syn包并确认客户的SYN
(ack=j+1),同时也发送一个自己的SYN包
(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; - 第三次握手:
客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)
,此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
1.1.2 四次挥手
- 客户端进程发出连接释放报文
,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,
客户端进入FIN-WAIT-1(终止等待1)状态`。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。 - 服务器收到连接释放报文,发出确认报文
,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,
服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
- 客户端收到服务器的确认请求后,此时,
客户端就进入FIN-WAIT-2(终止等待2)状态
,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最 后的数据)。 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文
,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态
,等待客户端的确认。客户端收到服务器的连接释放报文后,必须发出确认
,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态
。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态
。- 服务器只要收到了客户端发出的确认,
立即进入CLOSED状态
。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些
。
1.1.3 粘包问题分析与对策
CP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
粘包出现原因
简单得说,在流传输中出现,UDP不会出现粘包,因为它有消息边界
粘包情况有两种,一种是粘在一起的包都是完整的数据包
,另一种情况是粘在一起的包有不完整的包
。
为了避免粘包现象,可采取以下几种措施:
对于发送方引起的粘包现象,用户可通过编程设置来避免,
TCP提供了强制数据立即传送的操作指令push
,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、
提高接收进程优先级等措施
,使其及时接收数据,从而尽量避免出现粘包现象;由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。
分包多发
。
以上提到的三种措施,都有其不足之处。
第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。
第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。
第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。
一种比较周全的对策是:接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开。实验证明这种方法是高效可行的。
1.1.4 重传
TCP的拥塞控制机制主要是以下四种机制:
慢启动(慢开始)
拥塞避免
快速重传
快速恢复
超时重传:重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据,也就是我们常说的超时重传。
快速重传:工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。
SACK:这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
1.1.5 OSI七层模型
- 应用层:网络服务与最终用户的一个接口。HTTP,FTP,SMTP
- 表示层:数据的表示,安全,压缩。
- 会话层:建立,管理,终止会话。对应主机进程,指本地主机与远程主机正在进行的会话
- 运输层:定义传输数据的协议端口号,以及流控和差错校验。TCP,UDP
- 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。ICMP IGMP IP
- 数据链路层:建立逻辑连接,进行硬件地址寻址,差错校验
- 物理层:建立,维护,断开物理连接
1.2 http/https
1.2.1 http 和 https 的基本概念
http: 是基于TCP的客户端和服务端请求和应答的标准,用于从服务器传输超文本到本地浏览器的超文本传输协议。无状态、有链接,应用层协议。
https:是以安全为目标的 HTTP 通道,即 HTTP 下加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性。
http 是超文本传输协议,信息是明文传输,HTTPS 协议要比 http 协议
安全
,https 是具有安全性的 ssl/tls 加密传输协议,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。TLS用于两个应用程序之间提供保密性和数据完整性。该协议由两层组成:TLS 记录协议和 TLS 握手协议。http 协议的
默认端口
为 80,https 的默认端口为 443。http 的连接很简单,是无状态的。https 握手阶段比较
费时
,会使页面加载时间延长 50%,增加 10%~20%的耗电。https
缓存
不如 http 高效,会增加数据开销。Https 协议需要 ca 证书,费用较高,功能越强大的
证书费
用越高。SSL 证书需要绑定
IP
,不能再同一个 IP 上绑定多个域名,IPV4 资源支持不了这种消耗。
客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤:
客户端使用 https url 访问服务器,则要求 web 服务器
建立 ssl 链接
。web 服务器接收到客户端的请求之后,会
将网站的证书(证书中包含了公钥),传输给客户端
。客户端和 web 服务器端开始
协商 SSL 链接的安全等级
,也就是加密等级。客户端浏览器通过双方协商一致的安全等级,
建立会话密钥
,然后通过网站的公钥来加密会话密钥,并传送给网站。web 服务器
通过自己的私钥解密出会话密钥
。web 服务器
通过会话密钥加密与客户端之间的通信
。
1.2.2 HTTP 请求跨域问题
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略
造成的。(当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域)
同源策略,是浏览器对 JavaScript 实施的安全限制,只要协议、域名、端口
有任何一个不同,都被当作是不同的域。
解决方法:
JSONP
- JSONP是利用 html 中的script标签没有跨域访问限制的来实现的 工作原理:
客户端通过script标签向服务器发送请求, 同时定义好回调函数接收
服务器接收到请求后,会把数据进行填充,并且放回回调函数中,例如 handleResponse({data:’response’})
客户端在页面中定义相应的回调函数 handleResponse, 服务器返回的脚本会被浏览器执行, 从而触发回调函数,处理服务器返回的数据(把数据作为参数传给客户端定义的函数并执行), 这样就实现了跨域请求和数据获取
局限: JSONP 的限制是需要通过签加载外部脚本, 但是它有一些局限性
JSONP 只支持 GET 请求, 无法通过 POST 或 其他类型请求 JSONP 只能接收 纯文本数据,无法处理 JSON 对象 或 XML 数据
CORS(CrossOrigin Resources Sharing,跨源资源共享)
使用原理:浏览器一旦发现 axios 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。 服务器根据这些附加的值,决定是否同意此次请求。
实现CORS的关键是服务端 因此前端只需要配置**
withCredentials
** 属性。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。
proxy(代理)(个人博客项目使用的方法)
不需要后端处理,前端在开发者模式下,用代理解决跨域
vue.config.js: module.exports = { devServer: { proxy: { // 要进行代理的请求 '/api': { // 真正访问的服务器地址 target: 'http://localhost:3000', // 真正访问的路径 pathRewrite: { // 匹配 /api 替换成 空 '^/api': '' } } } } };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 利用Mock.js模拟接口
- ```js
在 main.js 文件设置 开发模式下引入 mock.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
Vue.config.productionTip = false;
// 开发环境下引入mock.js
if(process.env.NODE_ENV==='development'){
require('@/mock')
}
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');在 src 目录下创建 mock 文件夹,在mock文件夹下创建 index.js 文件,设置mock
import Mock from 'mockjs'; // 配置请求延时 单位:毫秒 Mock.setup({ timeout: 1000 }); // 配置返回数据 第一个参数是要匹配的url(可以使用正则匹配),第二个参数是要返回的数据 Mock.mock('/api/user', { username: 'ayaan', age: 18, gender: '男', type: '帅' }); Mock.mock(/\/api/gis, { msg: '正则匹配', // 随机日期时间 mtime: '@datetime', // 随机数,设置范围1~800 'score|1-800': 800, // 随机中文名 nickname: '@cname' // 还有随机url '@url' 、 随机图片 '@image' 、 随机邮箱 '@email'等等 });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#### 1.2.3 http状态码
1. **(1)2XX 成功**
- 200 OK,表示从客户端发来的请求在服务器端被正确处理
- 204 No content,表示请求成功,但响应报文不含实体的主体部分
- 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于要求请求方重置内容
- 206 Partial Content,进行范围请求
**(2)3XX 重定向**
- 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
- 302 found,临时性重定向,表示资源临时被分配了新的 URL
- 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源
- 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
- 307 temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求
- 302是http1.0的协议状态码,在http1.1版本的时候为了细化302状态码⼜出来了两个303和307。 303明确表示客户端应当采⽤get⽅法获取资源,他会把POST请求变为GET请求进⾏重定向。 307会遵照浏览器标准,不会从post变为get。
**(3)4XX 客户端错误**
- 400 bad request,请求报文存在语法错误
- 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
- 403 forbidden,表示对请求资源的访问被服务器拒绝
- 404 not found,表示在服务器上没有找到请求的资源
**(4)5XX 服务器错误**
- 500 internal sever error,表示服务器端在执行请求时发生了错误
- 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能
- 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求
##### HTTP状态码304是多好还是少好
服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制,当客户端在此对这些页面进行请求,服务器会根据缓存内容判断页面与之前是否相同,若相同便直接返回304,此时客户端调用缓存内容,不必进行二次下载。
状态码304不应该认为是一种错误,而是对客户端**有缓存情况下**服务端的一种响应。
**产生较多304状态码的原因:**
- 页面更新周期长或不更新
- 纯静态页面或强制生成静态html
**304状态码出现过多会造成以下问题:**
- 网站快照停止;
- 收录减少;
- 权重下降。
#### 1.2.4 简述HTTP短连接与长连接区别
短连接: 客户端与服务器进行一次HTTP连接操作,就进行一次TCP连接,连接结束TCP关闭连接。
长连接:如果HTTP头部带有参数keep-alive,即开启长连接网页完成打开后,底层用于传输数据的TCP连接不会直接关闭,会根据服务器设置的保持时间保持连接,保持时间过后连接关闭。
##### keep-alive:
- 客户端向服务器在发送请求报文同时在首部添加发送Connection字段
#### 1.2.5 HTTP协议头/体
**HTTP Request Header 常见的请求头:**
- Accept:浏览器能够处理的内容类型
- Accept-Charset:浏览器能够显示的字符集
- Accept-Encoding:浏览器能够处理的压缩编码
- Accept-Language:浏览器当前设置的语言
- Connection:浏览器与服务器之间连接的类型
- Cookie:当前页面设置的任何Cookie
- Host:发出请求的页面所在的域
- Referer:发出请求的页面的URL
- User-Agent:浏览器的用户代理字符串
**HTTP Responses Header 常见的响应头:**
- Date:表示消息发送的时间,时间的描述格式由rfc822定义
- server:服务器名称
- Connection:浏览器与服务器之间连接的类型
- Cache-Control:控制HTTP缓存
- content-type:表示后面的文档属于什么MIME类型
常见的 Content-Type 属性值有以下四种:
(1)application/x-www-form-urlencoded:浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL转码。
(2)multipart/form-data:该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。
(3)application/json:服务器消息主体是序列化后的 JSON 字符串。
(4)text/xml:该种方式主要用来提交 XML 格式的数据。
服务器向客户端发送数据格式类型:XML、HTML、JSON
- **请求行**(Request Line):
- **方法**:如 GET、POST、PUT、DELETE等,指定要执行的操作。
- **请求 URI**(统一资源标识符):请求的资源路径,通常包括主机名、端口号(如果非默认)、路径和查询字符串。
- **HTTP 版本**:如 HTTP/1.1 或 HTTP/2。
请求行的格式示例:`GET /index.html HTTP/1.1`
- **请求头**(Request Headers):
- 包含了客户端环境信息、请求体的大小(如果有)、客户端支持的压缩类型等。
- 常见的请求头包括`Host`、`User-Agent`、`Accept`、`Accept-Encoding`、`Content-Length`等。
- **空行**:
- 请求头和请求体之间的分隔符,表示请求头的结束。
- **请求体**(可选):
```js
在某些类型的HTTP请求(如 POST 和 PUT)中,请求体包含要发送给服务器的数据。
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Connection: keep-alive状态行(Status Line):
- HTTP 版本:与请求消息中的版本相匹配。
- 状态码:三位数,表示请求的处理结果,如 200 表示成功,404 表示未找到资源。
- 状态信息:状态码的简短描述。
状态行的格式示例:
HTTP/1.1 200 OK
响应头(Response Headers):
- 包含了服务器环境信息、响应体的大小、服务器支持的压缩类型等。
- 常见的响应头包括
Content-Type
、Content-Length
、Server
、Set-Cookie
等。
空行:
- 响应头和响应体之间的分隔符,表示响应头的结束。
响应体(可选):
包含服务器返回的数据,如请求的网页内容、图片、JSON数据等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17HTTP/1.1 200 OK
Date: Wed, 18 Apr 2024 12:00:00 GMT
Server: Apache/2.4.1 (Unix)
Last-Modified: Wed, 18 Apr 2024 11:00:00 GMT
Content-Length: 12345
Content-Type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head>
<title>Example Page</title>
</head>
<body>
<h1>Hello, World!</h1>
<!-- The rest of the HTML content -->
</body>
</html>
1.3 WebSocket
WebSocket是HTML5提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。
WebSocket 的出现就解决了半双工通信的弊端。它最大的特点是:服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。
WebSocket原理:客户端向 WebSocket 服务器通知(notify)一个带有所有接收者ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。
WebSocket 特点的如下:
支持双向通信,实时性更强
可以发送文本,也可以发送二进制数据‘’
建立在TCP协议之上,服务端的实现比较容易
数据格式比较轻量,性能开销小,通信高效
没有同源限制,客户端可以与任意服务器通信
协议标识符是ws(如果加密,则为wss),服务器网址就是 URL
与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
// 在index.html中直接写WebSocket,设置服务端的端口号为 9999 let ws = new WebSocket('ws://localhost:9999'); // 在客户端与服务端建立连接后触发 ws.onopen = function() { console.log("Connection open."); ws.send('hello'); }; // 在服务端给客户端发来消息的时候触发 ws.onmessage = function(res) { console.log(res); // 打印的是MessageEvent对象 console.log(res.data); // 打印的是收到的消息 }; // 在客户端与服务端建立关闭后触发 ws.onclose = function(evt) { console.log("Connection closed."); };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
## 1.4 浏览器
#### 浏览器中输入URL到返回的过程
1. 进行DNS解析操作,根据DNS解析的结果查到服务器IP地址
2. 通过ip寻址和arp,找到服务器,并利用三次握手建立TCP连接
3. 浏览器生成HTTP报文,发送HTTP请求,等待服务器响应
4. 服务器处理请求,并返回给浏览器
5. 根据HTTP是否开启长连接,进行TCP的挥手过程
6. 浏览器根据收到的静态资源进行页面渲染
## 1.5 DNS
域名与IP地址的映射关系
根域名服务器-顶级域名服务器(负责管理该服务器下的所有二级域名)-权威域名服务器-本地域名服务器
#### DNS的工作过程(迭代查询与递归查询)
1. 用户程序(如ping、telnet等)使用域名访问网络时,首先向DNS客户端的地址解析器发出DNS请求。
2. 地址解析器收到DNS请求后,首先查询本地域名缓存。
- 如果本地域名缓存中存在该域名对应的映射表项,地址解析器就直接将域名对应的IP地址返回给用户程序。
- 如果本地域名缓存中不存在所要查找的映射表项,地址解析器就向DNS服务器发送查询请求报文。
3. DNS服务器收到查询报文后,首先判断请求的域名是否处于自己被授权管理的子域里,再根据不同的判断结果,向DNS客户端发送相应的响应报文。
- 如果请求的域名在自己被授权管理的子域范围之内,该DNS服务器首先从自己的数据库中查找域名对应的IP地址。
- 如果请求的域名不在自己被授权管理的子域范围之内,该DNS服务器就将请求交给上一级的DNS服务器处理。DNS服务器完成解析后,将解析的结果返回给DNS客户端。
4. DNS客户端的地址解析器接收并解析DNS服务器发回来的响应报文,将解析结果返回给用户程序。
每次动态解析成功的域名与IP地址的映射均存放在动态域名缓存区中,当下一次查询相同域名的时候,就可以直接从缓存区中读取,不用再向域名服务器进行请求。缓存区中的映射在一段时间后会被老化删除,以保证及时从域名服务器得到最新的内容。
######
## 1.6 COOKIE
## 1.7 SEEION
## 1.8 http协议的请求类型 :GET/POST
#### 1.8.1 Post 和 Get 是 HTTP 请求的两种方法,其区别如下:
- **应用场景:**GET 请求是一个幂等的请求,一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。而 Post 不是一个幂等的请求,一般用于对服务器资源会产生影响的情景,比如注册用户这一类的操作。
- **是否缓存:**因为两者应用场景不同,浏览器一般会对 Get 请求缓存,但很少对 Post 请求缓存。
- **发送的报文格式:**Get 请求的报文中实体部分为空,Post 请求的报文中实体部分一般为向服务器发送的数据。
- **安全性:**Get 请求可以将请求的参数放入 url 中向服务器发送,这样的做法相对于 Post 请求来说是不太安全的,因为请求的 url 会被保留在历史记录中。
- **请求长度:**浏览器由于对 url 长度的限制,所以会影响 get 请求发送数据时的长度。这个限制是浏览器规定的,并不是 RFC 规定的。
- **参数类型:**post 的参数传递支持更多的数据类型。
- PUT请求是向服务器端发送数据,从而修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。(可以理解为是**更新数据**)
- POST请求是向服务器端发送数据,该请求会改变数据的种类等资源,它会创建新的内容。(可以理解为是**创建数据**)
#### 1.8.2 常见的HTTP请求方法
- GET: 向服务器获取数据;
- POST:将实体提交到指定的资源,通常会造成服务器资源的修改;
- PUT:上传文件,更新数据;
- DELETE:删除服务器上的对象;
- HEAD:获取报文首部,与GET相比,不返回报文主体部分;
- OPTIONS:询问支持的请求方法,用来跨域请求;
- CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信;
- TRACE: 回显服务器收到的请求,主要⽤于测试或诊断。
##### OPTIONS:
OPTIONS方法是用于请求获得由 `Request-URI`标识的资源在请求/响应的通信过程中可以使用的功能选项。通过这个方法,客户端可以**在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能**。该请求方法的响应不能缓存。
OPTIONS请求方法的**主要用途**有两个:
- 获取服务器支持的所有HTTP请求方法;
- 用来检查访问权限。例如:在进行 CORS 跨域资源共享时,对于复杂请求,就是使用 OPTIONS 方法发送嗅探请求,以判断是否有对指定资源的访问权限。
# 牛客68题
## 1.说一说HTML语义化?
- 对于开发者而言,语义化标签有着更好的页面结构,利于个人的代码编写。
- 对于用户而言,当网络卡顿时有良好的页面结构,有利于增加用户的体验。
- 对于爬虫来说,有利于搜索引擎的SEO优化,利于网站有更靠前的排名。
- 对于团队来讲,有利于代码的开发和后期的维护。
常见的语义化标签:header、footer、aside、main、h1-h6、input、textarea、video、aduio
## 2.说一说盒模型
CSS盒模型定义了盒的每个部分包含 margin, border, padding, content 。
根据盒子大小的计算方式不同盒模型分成了两种,标准盒模型和怪异盒模型。
- 标准模型,给盒设置 `width` 和 `height`,实际设置的是 content box。`padding` 和 `border `再加上设置的宽高一起决定整个盒子的大小。
- 怪异盒模型,给盒设置 `width` 和 `height`,包含了`padding`和`border `,设置的 `width` 和 `height`就是盒子实际的大小
设置标准盒模型:`box-sizing:content-box` 设置怪异盒模型:`box-sizing:border-box`
## 3.说一下浮动?
浮动的作用
- 设置浮动的图片,可以实现文字环绕图片
- 设置了浮动的块级元素可以排列在同一行,设置了浮动的行内元素可以设置宽高,同时可以按照浮动设置的方向对齐排列盒子。
设置浮动元素的特点:
- -设置了浮动,该元素脱标。元素不占位置
- -浮动可以进行模式转换(行内块元素)
浮动造成的影响
使盒子脱离文档流,如果父级盒子没有设置高度,需要被子盒子撑开,那么这时候父级盒子的高度就塌陷了,同时也会造成父级盒子后面的兄弟盒子布局受到影响。如果浮动元素后面还有其他兄弟元素,其他兄弟元素的布局也会受到影响。
清除浮动的方法:
- 给父元素设置overflow:hidden (`overflow:hidden`:不会新增标签,但是如果父级元素有定位元素超出父级,超出部分会隐藏)
- 在下方创建一个空白div,添加样式clear:both
- 父级添加after伪类
- ```css
father::after{
content:'';
display:table;
clear:both
}
4.说一说样式优先级的规则是什么?
-第一类
!important
,无论引入方式是什么,选择器是什么,它的优先级都是最高的。-第二类引入方式,行内样式的优先级要高于嵌入和外链,嵌入和外链如果使用的选择器相同就看他们在页面中插入的顺序,在后面插入的会覆盖前面的。
-第三类选择器:选择器优先级:id选择器 >(类选择器 | 伪类选择器 | 属性选择器 ) > (后代选择器 | 伪元素选择器 ) > (子选择器 | 相邻选择器) > 通配符选择器
选择器权重值的计算
- A:如果规则是写在标签的style属性中(内联样式),则A=1,否则,A=0. 对于内联样式,由于没有选择器,所以 B、C、D 的值都为 0,即 A=1, B=0, C=0, D=0(简写为 1,0,0,0,下同)。
- B:计算该选择器中ID的数量。(例如,#header 这样的选择器,计算为 0, 1, 0, 0)。
- C:计算该选择器中伪类及其它属性的数量(包括类选择器、属性选择器等,不包括伪元素)。 (例如, .logo[id=’site-logo’] 这样的选择器,计算为 0, 0, 2, 0)。
- D:计算该选择器中伪元素及标签的数量。(例如,p:first-letter 这样的选择器,计算为0, 0, 0, 2)。
-第四类继承样式
-第五类浏览器默认样式优先级最低。
5.说一说CSS尺寸设置的单位
- px:pixel像素的缩写,绝对长度单位,它的大小取决于屏幕的分辨率,是开发网页中常常使用的单位。
- em:相对长度单位,在
font-size
中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体大小,如 width。如当前元素的字体尺寸未设置,由于字体大小可继承的原因,可逐级向上查找,最终找不到则相对于浏览器默认字体大小。 - rem:相对长度单位,相对于根元素的字体大小,根元素字体大小未设置,使用浏览器默认字体大小。
- vw:相对长度单位,相对于视窗宽度的1%。 vh:相对长度单位,相对于视窗高度的1%。
rem应用:在移动端网页开发中,页面要做成响应式的,可使用rem配合媒体查询或者flexible.js实现。原理是通过媒体查询或者flexible.js,能够在屏幕尺寸发生改变时,重置html根元素的字体大小,页面中的元素都是使用rem为单位设置的尺寸,因此只要改变根元素字体大小,页面中的其他元素的尺寸就自动跟着修改
vw应用:由于vw被更多浏览器兼容之后,在做移动端响应式页面时,通常使用vw配合rem。原理是使用vw设置根元素html字体的大小,当窗口大小发生改变,vw代表的尺寸随着修改,无需加入媒体查询和flexible.js,页面中的其他元素仍使用rem为单位,就可实现响应式。
6.说一说BFC
BFC(Block Formatting Context)块级格式化上下文,是Web页面一块独立的渲染区域,内部元素的渲染不会影响边界以外的元素。
BFC元素具有的特性
- 1、在BFC中,盒子从顶部开始垂直地一个接一个排列
- 2、盒子垂直方向的距离由margin决定。同一个BFC的两个相邻盒子margin会重叠
- 3、BFC中,margin-left会触碰到border-left(对于从左至右的方式,反之)
- 4、BFC区域不会与浮动的盒子产生交集,而是紧贴边缘浮动
- 5、计算BFC高度时,自然会检测浮动的盒子高度
主要用途
- 1、清除内部浮动,父元素设置为BFC可以清除子元素的浮动(最常用overflow:hidden,IE6需加上*zoom:1):计算BFC高度时会检测浮动子盒子高度
- 2、解决外边距合并问题
- 3、右侧盒子自适应:BFC区域不会与浮动盒子产生交集,而是紧贴浮动边缘
什么情况下可以让元素产生BFC
- 1、float属性不为none
- 2、position为absolute或fixed
- 3、display为inline-block、flex、grid、table、table-cell、table-caption、flow-root
- 4、overflow不为visible
7.说几个未知宽高元素水平垂直居中方法
-
设置元素相对父级定位position:absolute;left:50%;right:50%
,让自身平移自身高度50% transform: translate(-50%,-50%);
,这种方式兼容性好,被广泛使用的一种方式
- 设置元素的父级为弹性盒子
display:flex
,设置父级和盒子内部子元素水平垂直都居中justify-content:center; align-items:center
,这种方式代码简洁,但是兼容性ie 11以上支持,由于目前ie版本都已经很高,很多网站现在也使用这种方式实现水平垂直居中 - 设置元素的父级为网格元素
display: grid
,设置父级和盒子内部子元素水平垂直都居中justify-content:center; align-items:center
,这种方式代码简介,但是兼容性ie 10以上支持 - 设置元素的父级为表格元素
display: table-cell
,其内部元素水平垂直都居中text-align: center;vertical-align: middle;
,设置子元素为行内块display: inline-block;
,这种方式兼容性较好
8.三栏布局的实现方案
三栏布局,要求左右两边盒子宽度固定,中间盒子宽度自适应,盒子的高度都是随内容撑高的,一般都是中间盒子内容较多,为了保证页面渲染快,在写结构的时候,需要把中间盒子放在左右盒子的前面。
实现三栏布局的方法通常是圣杯布局和双飞翼布局。
圣杯布局的实现方案:三个元素放在同一个父级元素中,代表中间盒子的元素放在最前面,父级盒子设置左右padding
,三个盒子全部浮动,设置中间盒子宽度100%,左右盒子设置固定宽度,设置左边盒子左边距-100%同时相对自身定位,右边平移自身宽度,右边盒子设置右边距-自身宽度,最后设置父级盒子清除浮动,否则父级盒子的高度无法被撑开
1 |
|
双飞翼布局的实现方案:三个盒子对应三个元素,其中中间盒子套了两层,中间盒子内部盒子设置margin
,三个盒子全部浮动,设置中间盒子宽度100%,左右盒子设置固定宽度,设置左边盒子左边距-100%,右边盒子设置右边距-自身宽度,最后设置父级盒子清除浮动,否则父级盒子的高度无法被撑开
1 |
|
圣杯布局: - 优点:不需要添加dom节点 - 缺点:正常情况下是没有问题的,但是特殊情况下就会暴露此方案的弊端,当middle部分的宽小于left部分时就会发生布局混乱。 双飞翼布局: - 优点:不会像圣杯布局那样变形,CSS样式代码更简洁 - 缺点:多加了一层dom节点
9.说一说JS数据类型有哪些,区别是什么?
JS数据类型分为两类:
- 基本数据类型,也叫简单数据类型,包含7种类型,分别是Number 、String、Boolean、BigInt、Symbol、Null、Undefined。
- 引用数据类型也叫复杂数据类型,通常用Object代表,普通对象,数组,正则,日期,Math数学函数都属于Object。
数据分成两大类的本质区别:基本数据类型和引用数据类型它们在内存中的存储方式不同。
- 基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据。
- 引用数据类型是存储在堆内存中,占据空间大。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
Symbol是ES6新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以作为object的key。
数据的创建方法Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。由于Symbol()创建数据具有唯一性,所以 Symbol() !== Symbol(), 同时使用Symbol数据作为key不能使用for获取到这个key,需要使用Object.getOwnPropertySymbols(obj)获得这个obj对象中key类型是Symbol的key值。
let key = Symbol(‘key’);
let obj = { [key]: ‘symbol’};
let keyArray = Object.getOwnPropertySymbols(obj); // 返回一个数组[Symbol(‘key’)]
obj[keyArray[0]] // ‘symbol’
BigInt也是ES6新出的一种数据类型,这种数据类型的特点就是数据涵盖的范围大,能够解决超出普通数据类型范围报错的问题。
使用方法:
-整数末尾直接+n:647326483767797n
-调用BigInt()构造函数:BigInt(“647326483767797”)
注意:BigInt和Number之间不能进行混合操作
10.说一说null 和 undefined 的区别,如何让一个属性变为null
- undefind 是全局对象的一个属性,当一个变量没有被赋值或者一个函数没有返回值或者某个对象不存在某个属性却去访问或者函数定义了形参但没有传递实参,这时候都是undefined。
- undefined通过typeof判断类型是’undefined’。
- undefined == undefined undefined =.== undefined 。
- null代表对象的值未设置,相当于一个对象没有设置指针地址就是null。null通过typeof判断类型是’object’。
- null ==.= null null =.= null null == undefined null !=.= undefined
- undefined 表示一个变量初始状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。在实际使用过程中,不需要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。 让一个变量为null,直接给该变量赋值为null即可。
null typeof判定为‘object’(与底层二进制有关),但是其实属于自己的类型Null,表示该对象定义了并且设置为null,即表示该对象已经被清空,目前无效状态。
11.说一说JavaScript有几种方法判断变量的类型?
- JavaScript有4种方法判断变量的类型,分别是typeof、instanceof、Object.prototype.toString.call()(对象原型链判断方法)、 constructor (用于引用数据类型)
- typeof:常用于判断基本数据类型,对于引用数据类型除了function返回’function‘,其余全部返回’object’。
- instanceof:主要用于区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true,不太适合用于简单数据类型的检测,检测过程繁琐且对于简单数据类型中的undefined, null, symbol检测不出来。
- constructor:用于检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。
- Object.prototype.toString.call():适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。 这四种判断数据类型的方法中,各种数据类型都能检测且检测精准的就是Object.prototype.toString.call()这种方法。
加分回答
instanceof的实现原理:验证当前类的原型prototype是否会出现在实例的原型链__proto__上,只要在它的原型链上,则结果都为true。因此,instanceof
在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype
,找到返回true,未找到返回false。
Object.prototype.toString.call()原理:Object.prototype.toString 表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果
12 说一说数组去重都有哪些方法?
第一种方法:利用对象属性key排除重复项:遍历数组,每次判断对象中是否存在该属性,不存在就存储在新数组中,并且把数组元素作为key,设置一个值,存储在对象中,最后返回新数组。这个方法的优点是效率较高,缺点是占用了较多空间,使用的额外空间有一个查询对象和一个新的数组
第二种方法:利用Set类型数据无重复项:new 一个 Set,参数为需要去重的数组,Set 会自动删除重复的元素,再将 Set 转为数组返回。这个方法的优点是效率更高,代码简单,思路清晰,缺点是可能会有兼容性问
1 |
|
- 第三种方法:filter+indexof 去重:这个方法和第一种方法类似,利用 Array 自带的 filter 方法,返回 arr.indexOf(num) 等于 index 的num。原理就是 indexOf 会返回最先找到的数字的索引,假设数组是 [1, 1],在对第二个1使用 indexOf 方法时,返回的是第一个1的索引0。这个方法的优点是可以在去重的时候插入对元素的操作,可拓展性强。
1 |
|
第四种方法:这个方法比较巧妙,从头遍历数组,如果元素在前面出现过,则将当前元素挪到最后面,继续遍历,直到遍历完所有元素,之后将那些被挪到后面的元素抛弃。这个方法因为是直接操作数组,占用内存较少。
第五种方法:reduce +includes去重:这个方法就是利用reduce遍历和传入一个空数组作为去重后的新数组,然后内部判断新数组中是否存在当前遍历的元素,不存在就插入到新数组中。这种方法时间消耗多,内存空间也有额外占用。
13 说一说伪数组和数组的区别?
伪数组它的类型不是Array,而是Object,而数组类型是Array。
可以使用的length属性查看长度,也可以使用[index]获取某个元素,但是不能使用数组的其他方法,也不能改变长度,遍历使用for in方法。
伪数组的常见场景:
- -函数的参数 arguments
- -原生js获取DOM:document.querySelector(‘div’) document.getElements 等
- -jquery获取DOM:$(“div”)等
加分回答
伪数组转换成真数组方法
-Array.prototype.slice.call(伪数组)
-[].slice.call(伪数组)
-Array.from(伪数组) 转换后的数组长度由 length
属性决定。索引不连续时转换结果是连续的,会自动补位。
14 说一说map 和 forEach 的区别?
- map 和 forEach 的区别:map有返回值,可以开辟新空间,return出来一个length和原数组一致的数组,即便数组元素是undefined或者是null。
- forEach默认无返回值,返回结果为undefined,可以通过在函数体内部使用索引修改数组元素。
加分回答 map的处理速度比forEach快,而且返回一个新的数组,方便链式调用其他数组新方法,比如filter、reduce let arr = [1, 2, 3, 4, 5]; let arr2 = arr.map(value => value * value).filter(value => value > 10); // arr2 = [16, 25]
15 说一说es6中箭头函数?
箭头函数相当于匿名函数,简化了函数定义。
箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return。
另一种是包含多条语句,不可以省略{}和return。 箭头函数最大的特点就是没有this,所以this是从外部获取,就是继承外部的执行上下文中的this,由于没有this关键字所以箭头函数也不能作为构造函数, 同时通过 call()
或 apply()
方法调用一个函数时,只能传递参数(不能绑定this),第一个参数会被忽略。箭头函数也没有原型和super。不能使用yield关键字,因此箭头函数不能用作 Generator 函数。不能返回直接对象字面量。
加分回答
箭头函数的不适用场景:
-定义对象上的方法 当调用 dog.jumps
时,lives
并没有递减。因为 this
没有绑定值,而继承父级作用域。
1 |
|
-不适合做事件处理程序 此时触发点击事件,this不是button,无法进行class切换
1 |
|
箭头函数函数适用场景:
-简单的函数表达式,内部没有this引用,没有递归、事件绑定、解绑定,适用于map、filter等方法中,写法简洁
1 |
|
-内层函数表达式,需要调用this,且this应与外层函数一致时
1 |
|
16 事件扩展符用过吗(…),什么场景下?
展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。
- 1.数组去重[…new Set(arr)]
- 2.数组拷贝[…arr]
- 3.伪数组转真数组 […伪数组]
加分回答 只能用于可迭代对象 在数组或函数参数中使用展开语法时, 该语法只能用于
可迭代对象: var obj = {‘key1’: ‘value1’}; var array = […obj]; // TypeError: obj is not iterable
剩余语法(剩余参数)
剩余语法(Rest syntax) 看起来和展开语法完全相同,不同点在于, 剩余参数用于解构数组和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来并“凝聚”为单个元素。
1 |
|
17 说一说你对闭包的理解?
闭包找到的是同一地址中父级函数中对应变量最终的值
一个函数和词法环境的引用捆绑在一起,这样的组合就是闭包(closure)。闭包是指有权访问另一个函数作用域中变量的函数。
闭包形成的原理:作用域链,当前作用域可以访问上级作用域中的变量 。
闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。
闭包带来的问题:由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。
闭包的应用,能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。
18 说一说JS变量提升?
- 变量提升是指JS的变量和函数声明会在代码编译期,提升到代码的最前面。
- 变量提升成立的前提是使用Var关键字进行声明的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升,同时函数的声明提升会比变量的提升优先。
- 变量提升的结果,可以在变量初始化之前访问该变量,返回的是undefined。在函数声明前可以调用该函数。
加分回答 使用let和const声明的变量是创建提升,形成暂时性死区,在初始化之前访问let和const创建的变量会报错。
19 说一说this指向(普通函数、箭头函数)?
this关键字由来:在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 又搞出来另外一套 this 机制。
this存在的场景有三种全局执行上下文和函数执行上下文和eval执行上下文,eval这种不讨论。
- 在全局执行环境中无论是否在严格模式下,(在任何函数体外部)
this
都指向全局对象。
在函数执行上下文中访问this,函数的调用方式决定了 this
的值。
在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window,通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。
普通函数this指向:
当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;
通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。new 关键字构建好了一个新对象,并且构造函数中的 this 其实就是新对象本身。嵌套函数中的 this 不会继承外层函数的 this 值。
箭头函数this指向:箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数。
加分回答
箭头函数因为没有this,所以也不能作为构造函数,但是需要继承函数外部this的时候,使用箭头函数比较方便
1 |
|
20 说一说call apply bind的作用和区别?
- call、apply、bind的作用都是改变函数运行时的this指向。
- bind和call、apply在使用上有所不同,bind在改变this指向的时候,返回一个改变执行上下文的函数,不会立即执行函数,而是需要调用该函数的时候再调用即可,但是call和apply在改变this指向的同时执行了该函数。
- bind只接收一个参数,就是this指向的执行上文。 call、apply接收多个参数,第一个参数都是this指向的执行上文,后面的参数都是作为改变this指向的函数的参数。但是call和apply参数的格式不同,call是一个参数对应一个原函数的参数,但是apply第二个参数是数组,数组中每个元素代表函数接收的参数,数组有几个元素函数就接收几个元素。
加分回答
- call的应用场景:
- 对象的继承,在子构造函数这种调用父构造函数,但是改变this指向,就可以继承父的属性 function superClass () { this.a = 1; this.print = function () { console.log(this.a); } } function subClass () { superClass.call(this); // 执行superClass,并将superClass方法中的this指向subClass this.print(); } subClass();
- 借用Array原型链上的slice方法,把伪数组转换成真数组 let domNodes = Array.prototype.slice.call(document.getElementsByTagName(“div”));
- apply的应用场景:
- Math.max,获取数组中最大、最小的一项 let max = Math.max.apply(null, array); let min = Math.min.apply(null, array);
- 实现两个数组合并 let arr1 = [1, 2, 3]; let arr2 = [4, 5, 6]; Array.prototype.push.apply(arr1, arr2); console.log(arr1); // [1, 2, 3, 4, 5, 6]
- bind的应用场景 在vue或者react框架中,使用bind将定义的方法中的this指向当前类
21 说一说js继承的方法和优缺点?
js 六种继承方式介绍及优缺点 - 眼里有激光 - 博客园 (cnblogs.com)
- 原型链继承:让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性,原型链继承的。
- 优点:写法方便简洁,容易理解。
- 缺点:在父类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被所有子类型实例所共享。同时在创建子类型的实例时,不能向超类型的构造函数中传递参数。
- 借用构造函数继承:在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。
- 优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。
- 缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
- 组合继承:将原型链和借用构造函数的组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。
- 优点就是解决了原型链继承和借用构造函数继承造成的影响。
- 缺点是无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部
- 原型式继承:在一个函数A内部创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质上,函数A是对传入的对象执行了一次浅复制。ECMAScript 5通过增加Object.create()方法将原型式继承的概念规范化了。这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。在只有一个参数时,Object.create()与这里的函数A方法效果相同。
- 优点是:不需要单独创建构造函数。
- 缺点是:属性中包含的引用值始终会在相关对象间共享
- 寄生式继承:寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
- 优点:写法简单,不需要单独创建构造函数。
- 缺点:通过寄生式继承给对象添加函数会导致函数难以重用。
- 寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
- 优点是:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变;
- 缺点是:代码复杂 加分回答 ES6 Class实现继承。
- 原理:原理ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。 ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。
- 优点:语法简单易懂,操作更方便。
- 缺点:并不是所有的浏览器都支持class关键字
22 说一说new会发生什么?
创建一个空的简单JavaScript对象(即
{}
);为步骤1新创建的对象添加属性
__proto__
,将该属性链接至构造函数的原型对象将步骤1新创建的对象作为
this
的上下文 ;如果该函数没有返回对象,则返回
this
。 加分回答new
关键字后面的构造函数不能是箭头函数。
23 说一说defer和async区别?
html文件都是按顺序执行的,script标签中没有加defer和async时,浏览器在解析文档时遇到script标签就会停止解析,阻塞文档解析,先加载JS文件,加载完之后立即执行,执行完毕后才能继续解析文档。 而在script标签中写入defer或者async时,就会使JS文件异步加载,即html执行到script标签时,JS加载和文档解析同时进行,而async是在JS加载完成后立即执行JS脚本,阻塞文档解析,而defer则是JS加载完成后,当HTML渲染完成,才会执行JS代码。
加分回答
渲染阻塞的原因: 由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JavaScript 引擎为互斥的关系。当浏览器在执行 JavaScript 程序的时候,GUI 渲染线程会被保存在一个队列中,直到 JS 程序执行完成,才会接着执行。如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉
24 说一说promise是什么与使用方法?
Promise的作用:Promise是异步微任务,解决了异步多层嵌套回调(回调地狱)的问题,让代码的可读性更高,更容易维护
Promise使用:Promise是ES6提供的一个构造函数,可以使用Promise构造函数new一个实例,Promise构造函数接收一个函数作为参数,这个函数有两个参数,分别是两个函数 resolve
和reject
,resolve
将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;reject
则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例创建完成后,可以使用then
方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,then和catch最终返回的也是一个Promise,所以可以链式调用。
Promise的特点:
- 对象的状态不受外界影响(Promise对象代表一个异步操作,有三种状态)。 - pending(执行中) - Resolved(成功,又称Fulfilled) - rejected(拒绝) 其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise对象的状态改变,只有两种可能(状态凝固了,就不会再变了,会一直保持这个结果): - 从Pending变为Resolved - 从Pending变为Rejected
- resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数
- then 方法和 catch方法 只要不报错,返回的都是一个fullfilled状态的promise
加分回答
Promise的其他方法:
Promise.resolve() :返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。
Promise.reject():返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法。
Promise.all():返回一个新的promise对象,该promise对象在参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。
Promise.any():接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。
Promise.race():当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
25 说一说JS实现异步的方法?
所有异步任务都是在同步任务执行结束之后,从任务队列中依次取出执行。
- 回调函数是异步操作最基本的方法,比如AJAX回调,
- 回调函数的优点是简单、容易理解和实现,
- 缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return
- Promise包装了一个异步调用并生成一个Promise实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve 和 reject方法,then接收到对应的数据,做出相应的处理。
- Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,
- 缺点是无法取消 Promise,错误需要通过回调函数捕获。
- Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数是一个状态机,封装了多个内部状态,可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。
- 优点是异步语义清晰,
- 缺点是手动迭代
Generator
函数很麻烦,实现逻辑有点绕
- async/awt是基于Promise实现的,async/awt使得异步代码看起来像同步代码,
- 所以优点是,使用方法清晰明了,
- 缺点是awt 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 awt 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。
加分回答
JS 异步编程进化史:callback -> promise -> generator/yield -> async/awt。 async/awt函数对 Generator 函数的改进,体现在以下三点: - 内置执行器。 Generator 函数的执行必须靠执行器,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。 - 更广的适用性。 yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 awt 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。 - 更好的语义。 async 和 awt,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,awt 表示紧跟在后面的表达式需要等待结果。 目前使用很广泛的就是promise和async/awt
26 说一说cookie sessionStorage localStorage 区别?
Cookie、SessionStorage、 LocalStorage都是浏览器的本地存储。
- 它们的共同点:都是存储在浏览器本地的
- 它们的区别:cookie是由服务器端写入的,而SessionStorage、 LocalStorage都是由前端写入的,cookie的生命周期是由服务器端在写入的时候就设置好的,LocalStorage是写入就一直存在,除非手动清除,SessionStorage是页面关闭的时候就会自动清除。cookie的存储空间比较小大概4KB,SessionStorage、 LocalStorage存储空间比较大,大概5M。
- Cookie、SessionStorage、 LocalStorage数据共享都遵循同源原则,SessionStorage还限制必须是同一个页面。在前端给后端发送请求的时候会自动携带Cookie中的数据,但是SessionStorage、 LocalStorage不会
加分回答
由于它们的以上区别,所以它们的应用场景也不同,Cookie一般用于存储登录验证信息SessionID或者token,
LocalStorage常用于存储不易变动的数据,减轻服务器的压力,
SessionStorage可以用来检测用户是否是刷新进入页面,如音乐播放器恢复播放进度条的功能。
27 说一说如何实现可过期的localstorage数据?
localStorage只能用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。所以要实现可过期的localStorage缓存的中重点就是:如何清理过期的缓存。
目前有两种方法,一种是惰性删除,另一种是定时删除。
- 惰性删除是指某个键值过期后,该键值不会被马上删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除。
- 实现方法是,存储的数据类型是个对象,该对象有两个key,一个是要存储的value值,另一个是当前时间。获取数据的时候,拿到存储的时间和当前时间做对比,如果超过过期时间就清除Cookie。
- 定时删除是指,每隔一段时间执行一次删除操作,并通过限制删除操作执行的次数和频率,来减少删除操作对CPU的长期占用。另一方面定时删除也有效的减少了因惰性删除带来的对localStorage空间的浪费。
- 实现过程,获取所有设置过期时间的key判断是否过期,过期就存储到数组中,遍历数组,每隔1S(固定时间)删除5个(固定个数),直到把数组中的key从localstorage中全部删除。
加分回答 LocalStorage清空应用场景:token存储在LocalStorage中,要清空
28 说一下token 能放在cookie中吗?
能。 但是不建议,容易产生csrf问题
token一般是用来判断用户是否登录的,它内部包含的信息有:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
token
可以存放在Cookie
中,token
是否过期,应该由后端来判断,不该前端来判断,所以token
存储在cookie
中只要不设置cookie
的过期时间就ok了,如果 token
失效,就让后端在接口中返回固定的状态表示token
失效,需要重新登录,再重新登录的时候,重新设置 cookie
中的 token
就行。
加分回答
token认证流程
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端签发一个 token ,并把它发送给客户端
- 客户端接收 token 以后会把它存储起来,比如放在 cookie 里或者 localStorage 里
- 客户端每次发送请求时都需要带着服务端签发的 token(把 token 放到 HTTP 的 Header 里)
- 服务端收到请求后,需要验证请求里带有的 token ,如验证成功则返回对应的数据
29 说一说axios的拦截器原理及应用?
axios的拦截器的应用场景: 请求拦截器用于在接口请求之前做的处理,比如为每个请求带上相应的参数(token,时间戳等)。
返回拦截器用于在接口返回之后做的处理,比如对返回的状态进行判断(token是否过期)。
axios为开发者提供了这样一个API:拦截器。拦截器分为 请求(request)拦截器和 响应(response)拦截器。
- 请求拦截器链: 当你发起一个请求时,Axios 会按顺序执行所有已注册的请求拦截器。 每个拦截器都可以修改请求配置 (例如添加 headers) 或中断请求。
- 响应拦截器链: 当服务器返回响应时,Axios 会按顺序执行所有已注册的响应拦截器。 每个拦截器都可以修改响应数据或处理错误。
拦截器原理:创建一个chn数组,数组中保存了拦截器相应方法以及dispatchRequest(dispatchRequest这个函数调用才会真正的开始下发请求),把请求拦截器的方法放到chn数组中dispatchRequest的前面(unshitfe),把响应拦截器的方法放到chn数组中dispatchRequest的后面(push),把请求拦截器和相应拦截器forEach将它们分unshift,push到chn数组中,为了保证它们的执行顺序,需要使用promise,以出队列的方式对chn数组中的方法挨个执行。
加分回答
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。从浏览器中创建 XMLHttpRequests,从 node.js 创建 http 请求,支持 Promise API,可拦截请求和响应,可转换请求数据和响应数据,可取消请求,可自动转换 JSON 数据,客户端支持防御 XSRF
30 说一说创建ajax过程?
创建ajax过程:
- 创建XHR对象:new XMLHttpRequest()
- 设置请求参数:request.open(Method, 服务器接口地址);
- 发送请求: request.send(),如果是get请求不需要参数,post请求需要参数request.send(data)
- 监听请求成功后的状态变化:根据状态码进行相应的处理。 XHR.onreadystatechange = function () { if (XHR.readyState == 4 && XHR.status == 200) { console.log(XHR.responseText); // 主动释放,JS本身也会回收的 XHR = null; } };
加分回答
POST请求需要设置请求头 readyState值说明
0:初始化,XHR对象已经创建,还未执行open
1:载入,已经调用open方法,但是还没发送请求
2:载入完成,请求已经发送完成
3:交互,可以接收到部分数据
4:数据全部返回 status值说明 200:成功 404:没有发现文件、查询或URl 500:服务器产生内部错误
31 说一下fetch 请求方式?
fetch是一种HTTP数据请求的方式,是XMLHttpRequest的一种替代方案。Fetch函数就是原生js,没有使用XMLHttpRequest对象。fetch()方法返回一个Promise解析Response来自Request显示状态(成功与否)的方法。
加分回答
XMLHttpRequest的问题
- -所有的功能全部集中在一个对象上, 容易书写出混乱而且不容易维护的代码
- -采用传统的事件驱动模式, 无法适配新的 Promise API Fetch API的特点
- -精细的功能分割: 头部信息, 请求信息, 响应信息等均分布到不同的对象, 更利于处理各种复杂的数据交互场景
- -使用Promise API, 更利于异步代码的书写 -同源请求也可以自定义不带 cookie,某些服务不需要 cookie 场景下能少些流量
32 说一下有什么方法可以保持前后端实时通信?
保持前后端实时通信的方法有以下几种:
轮询是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。
其缺点也很明显:连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。
- 优点就是实现简单,无需做过多的更改。
- 缺点是轮询的间隔过长,会导致用户不能及时接收到更新的数据;
轮询的间隔过短,会导致查询请求过多,增加服务器端的负担
长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,如果没有新消息,就一直等待。有新消息,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。由于http数据包的头部数据量往往很大(通常有400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。
- 优点是做了优化,有较好的时效性。
- 缺点是保持连接会消耗资源; 服务器没有返回有效数据,程序超时。
iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。
- 优点是消息能够实时到达;浏览器兼容好。
- 缺点是服务器维护一个长连接会增加开销;IE、chrome、Firefox会显示加载没有完成,图标会不停旋转。
WebSocket是类似Socket的TCP长连接的通讯模式,一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端断掉连接前,不需要客户端和服务端重新发起连接请求。【数据链路层】
- 在海量并发和客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。
- 缺点是浏览器支持程度不一致,不支持断开重连。
SSE(Server-Sent Event)是建立在浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。SSE 是单向通道,只能服务器向浏览器发送,因为 streaming 本质上就是下载。
- 优点是SSE 使用 HTTP 协议,现有的服务器软件都支持。SSE 属于轻量级,使用简单;SSE 默认支持断线重连;
加分回答
- 轮询适用于:小型应用,实时性不高
- 长轮询适用于:一些早期的对及时性有一些要求的应用:web IM 聊天
- iframe适用于:客服通信等
- WebSocket适用于:微信、网络互动游戏等
- SSE适用于:金融股票数据、看板等
33 说一下浏览器输入URL发生了什么?
【1、URL解析】:判断浏览器输入的是搜索内容还是URL
【2、查找缓存】:如果能找到缓存则直接返回页面,如果没有缓存则需要发送网络请求页面
- 进行DNS解析操作,根据DNS解析的结果查到服务器IP地址
- 通过ip寻址和arp,找到服务器,并利用三次握手建立TCP连接
- 浏览器生成HTTP报文,发送HTTP请求,等待服务器响应
- 服务器处理请求,并返回给浏览器
- 根据HTTP是否开启长连接,进行TCP的挥手过程
- 浏览器根据收到的静态资源进行页面渲染
34 说一下浏览器如何渲染页面的?
浏览器拿到HTML,先将HTML转换成dom树,再将CSS样式转换成stylesheet,根据dom树和stylesheet创建布局树,对布局树进行分层,为每个图层生成绘制列表,再将图层分成图块,紧接着光栅化将图块转换成位图,最后合成绘制生成页面。
加分回答 分层的目的:避免整个页面渲染,把页面分成多个图层,尤其是动画的时候,把动画独立出一个图层,渲染时只渲染该图层就ok,transform,z-index等,浏览器会自动优化生成图层 光栅化目的:页面如果很长但是可视区很小,避免渲染非可视区的样式造成资源浪费,所以将每个图层又划分成多个小个子,当前只渲染可视区附近区域
1.解析HTML,生成DOM树
2.解析CSS,生成CSSOM树
3.两棵树结合,生成Render树
4.计算布局,绘制页面的所有节点
5.绘制布局
35 说一下重绘、重排区别如何避免?
回流:当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
重绘:当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,所以重绘跳过了创建布局树和分层的阶段。
重排需要重新计算布局树,重绘不需要,重排必定发生重绘,但是涉及到重绘不一定要重排。涉及到重排对性能的消耗更多一些。
触发重排的方法:
- -页面初始渲染,这是开销最大的一次重排
- -添加/删除可见的DOM元素
- -改变元素位置
- -改变元素尺寸,比如边距、填充、边框、宽度和高度等
- -改变元素内容,比如文字数量,图片大小等
- -改变元素字体大小
- -改变浏览器窗口尺寸,比如resize事件发生时
- -激活CSS伪类(例如:
:hover
) - -设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
- -查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等
避免重排的方式
- -样式集中改变
- -使用 absolute 或 fixed 脱离文档流
- -使用GPU加速:transform
加分回答 GPU的过程是以下这几步 :
- 获取DOM并将其分割成多个层(renderLayer)
- 将每个层栅格化,并独立的绘制进位图中
- 将这些位图作为纹理上传至GPU
- 复合多个层来生成最终的屏幕图像(最后的layer) 开启了GPU加速的元素被独立出来,不会再影响其他dom的布局,因为它改变之后,只是相当于被贴上了页面。
36 说一下浏览器垃圾回收机制?
浏览器垃圾回收机制根据数据的存储方式分为栈垃圾回收和堆垃圾回收。
- 栈垃圾回收的方式非常简便,当一个函数执行结束之后,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文,遵循先进后出的原则。
- 堆垃圾回收,当函数直接结束,栈空间处理完成了,但是堆空间的数据虽然没有被引用,但是还是存储在堆空间中,需要垃圾回收器将堆空间中的垃圾数据回收。
为了使垃圾回收达到更好的效果,根据对象的生命周期不一样,使用不同的垃圾回收的算法。
- 在 V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。
- 新生区中使用Scavenge算法,老生区中使用标记-清除算法和标记-整理算法。
加分回答
Scavenge算法:
- 标记:对对象区域中的垃圾进行标记
- 清除垃圾数据和整理碎片化内存:副垃圾回收器会把这些存活的对象复制到空闲区域中,并且有序的排列起来,复制后空闲区域就没有内存碎片了
- 角色翻转:完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域,这样就完成了垃圾对象的回收操作,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去
标记-清除算法:
- 标记:标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。 2.
- 清除:将垃圾数据进行清除。
- 产生内存碎片:对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存。
标记-整理算法
- 标记:和标记 - 清除的标记过程一样,从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素标记为活动对象。
- 整理:让所有存活的对象都向内存的一端移动
- 清除:清理掉端边界以外的内存 V8 是使用副垃圾回收器和主垃圾回收器处理垃圾回收的,不过由于 JavaScript 是运行在主线程之上的,一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿。
为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking)算法
37 说一说事件循环Event loop,宏任务与微任务?
浏览器的事件循环:执行js代码的时候,遇见同步任务,直接推入调用栈中执行,遇到异步任务,将该任务挂起,等到异步任务有返回之后推入到任务队列中,当调用栈中的所有同步任务全部执行完成,将任务队列中的任务按顺序一个一个的推入并执行,重复执行这一系列的行为。
异步任务又分为宏任务和微任务。
- 宏任务:任务队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列。
- 微任务:等宏任务中的主要功能都完成后,渲染引擎不急着去执行下一个宏任务,而是执行当前宏任务中的微任务
宏任务包含:
- 执行script标签内部代码、setTimeout/setInterval
- ajax请、postMessageMessageChannel、setImmediate,I/O(Node.js)
微任务包含:
- Promise、MutonObserver、Object.observe、process.nextTick(Node.js)
加分回答
浏览器和Node 环境下,microtask 任务队列的执行时机不同
- Node端,microtask 在事件循环的各个阶段之间执行
- 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
38 说一说跨域是什么?如何解决跨域问题?
跨域:当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同,就说该接口跨域了。
跨域限制的原因:浏览器为了保证网页的安全,出的同源协议策略。
跨域场景:前后端分离式开发、调用第三方接口
跨域解决方案
cors:目前最常用的一种解决办法,通过设置后端允许跨域实现。
res.setHeader(‘Access-Control-Allow-Origin’, ‘*’);
res.setHeader(“Access-Control-Allow-Methods”, “GET, PUT, OPTIONS, POST”);
node中间件、nginx反向代理:跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。
JSONP:利用的原理是script标签可以跨域请求资源,将回调函数作为参数拼接在url中。后端收到请求,调用该回调函数,并将数据作为参数返回去,注意设置响应头返回文档类型,应该设置成javascript。
postmessage:H5新增API,通过发送和接收API实现跨域通信。
39 说一说vue钩子函数?
钩子函数用来描述一个组件从引入到退出的全过程中的某个过程,整个过程称为生命周期。
钩子函数按照组件生命周期的过程分为,挂载阶段=>更新阶段=>销毁阶段。 每个阶段对应的钩子函数
- 挂载阶段:beforeCreate、created、beforeMounted、mounted
- 更新阶段:beforeUpdate、updated
- 销毁阶段:beforeDestroy、destroyed
每个阶段特点与适合做什么
- created:实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到el属性,el属性,ref属性内容为空数组常用于简单的ajax请求,页面的初始化
- beforeMount:在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数
- mounted:实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问常用于获取VNode信息和操作,ajax请求
- beforeupdate:响应式数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
- updated:虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作避免在这个钩子函数中操作数据,可能陷入死循环
- beforeDestroy:实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例,常用于销毁定时器、解绑全局事件、销毁插件对象等操作
加分回答
父子组件钩子函数在三个阶段的代码执行顺序
挂载:父亲created> 子created > 子mounted> 父亲mounted>
更新:父亲beforeUpdate > 子beforeUpdated > 子updated > 父亲updated
销毁:父亲beforeDestroy> 子beforeDestroy > 子destroyed> 父destroyed
40 说一说组件通信的方式?
Vue组件的通信方式分为两大类,一类是父子组件通信,另一类是任何关系类型组件通信(父子、兄弟、非兄弟)
父子组件通信
父组件传递数据给子组件
- props传递数据:子组件设置
props
属性,定义接收父组件传递过来的参数
子组件传递数据给父组件
- $emit 触发自定义事件:子组件通过
$emit触发
自定义事件,$emit
第二个参数为传递的数值,父组件绑定监听器获取到子组件传递过来的参数 - ref:父组件通过设置子组件
ref
来获取数据
1 |
|
兄弟组件传值
EventBus:
- 创建一个中央事件总线
EventBus
- 兄弟组件通过
$emit
触发自定义事件,$emit
第二个参数为传递的数值 - 另一个兄弟组件通过
$on
监听自定义事件
$parent 或$ root
- 通过共同祖辈
$parent
或者$root
搭建通信桥连
兄弟组件
1 |
|
另一个兄弟组件
1 |
|
祖先传递数据给子孙
$attrs 与$ listeners
- 如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置
inheritAttrs: false
- 在 Parent.vue 中,我们使用
v-bind="$attrs"
将所有未被 props 接收的属性传递给子组件,使用v-on="$listeners"
将所有事件监听器传递给子组件。 - 在 Child.vue 中,我们通过
$attrs.color
获取到祖组件传递的color
属性,并通过$emit('click')
触发祖组件传递的handleClick
事件。
provide 与 inject
- 在祖先组件定义
provide
属性,返回传递的值 - 在后代组件通过
inject
接收组件传递过来的值 inject:[‘foo’]
复杂关系的组件数据传递
vuex
集中管理项目公共数据,Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。
state
用来存放共享变量的地方getter
,可以增加一个getter
派生状态,(相当于store
中的计算属性),用来获得共享变量的值mutations
用来存放修改state
的方法。actions
也是用来存放修改state的方法,不过action
是在mutations
的基础上进行。常用来做一些异步操作
41 说一说computed和watch的区别?
- computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
- watch: 更多的是观察的作用,支持异步,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
加分回答
- computed应用场景:需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
- watch应用场景:需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
42 说一说 v-if 和 v-show区别?
- 作用: 都是控制元素隐藏和显示的指令
- 区别:
- v-show: 控制的元素无论是true还是false,都被渲染出来了,通过display:none控制元素隐藏
- v-if: 控制的元素是true,进行渲染,如果是false不渲染,根本在dom树结构中不显示
- 应用:
- v-show: 适合使用在切换频繁显示/隐藏的元素上
- v-if: 适合使用在切换不频繁,且元素内容很多,渲染一次性能消耗很大的元素上
43 说一说 vue 的 keep-alive ?
<keep-alive>
作用:缓存组件,提升性能,避免重复加载一些不需要经常变动且内容较多的组件。
使用方法:使用<keep-alive>
标签对需要缓存的组件进行包裹,默认情况下被<keep-alive>
标签包裹的组件都会进行缓存
区分被包裹的组件是否缓存有两种方法
- 第一种是给keep-alive 添加属性,组件名称指的是具体组件添加的name,不是路由里面的name。
- include 包含的组件(可以为字符串,数组,以及正则表达式,只有匹配的组件会被缓存)。
- exclude 排除的组件(以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)。
- 第二种也是最常用的一种是,和路由配合使用:在路由中添加meta属性。 使用keep-alive导致组件不重新加载,也就不会重新执行生命周期的函数,如果要解决这个问题,就需要两个属性进入时触发:activated 退出时触发:deactivated
加分回答
<keep-alive>
适用的场景:首页展示固定数据的组件,比如banner九宫格
1 |
|
44 说一说 Vue 中 $nextTick 作用与原理?
Vue 在更新 DOM 时是异步执行的,在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。所以修改完数据,立即在方法中获取DOM,获取的仍然是未修改的DOM。
$nextTick的作用是:该方法中的代码会在当前渲染完成后执行,就解决了异步渲染获取不到更新后DOM的问题了。
$nextTick的原理:$nextTick本质是返回一个Promise
加分回答
应用场景:在钩子函数created()里面想要获取操作Dom,把操作DOM的方法放在$nextTick中
45 说一说 Vue 列表为什么加 key?
为了性能优化 因为vue是虚拟DOM,更新DOM时用diff算法对节点进行一一比对,比如有很多li元素,要在某个位置插入一个li元素,但没有给li上加key,那么在进行运算的时候,就会将所有li元素重新渲染一遍,但是如果有key,那么它就会按照key一一比对li元素,只需要创建新的li元素,插入即可,不需要对其他元素进行修改和重新渲染。
加分回答
key也不能是li元素的index,因为假设我们给数组前插入一个新元素,它的下标是0,那么和原来的第一个元素重复了,整个数组的key都发生了改变,这样就跟没有key的情况一样了
46 说一说vue-router 实现懒加载的方法?
vue-router 实现懒加载的方法有两种:
ES6的impot方式:
1 |
|
VUE中的异步组件进行懒加载方式:
1 |
|
加分回答
vue-router 实现懒加载的作用:性能优化,不用到该路由,不加载该组件。
47 说一说 HashRouter 和 HistoryRouter的区别和原理?
HashRouter和 HistoryRouter的区别:
- history和hash都是利用浏览器的两种特性实现前端路由,history是利用浏览历史记录栈的API实现,hash是监听location对象hash值变化事件来实现
- history的url没有’#’号,hash反之
- 相同的url,history会触发添加到浏览器历史记录栈中,hash不会触发,history需要后端配合,如果后端不配合刷新新页面会出现404,hash不需要。
- HashRouter的原理:通过
window.onhashchange
方法获取新URL中hash值,再做进一步处理 - HistoryRouter的原理:通过
history.pushState
使用它做页面跳转不会触发页面刷新,使用window.onpopstate
监听浏览器的前进和后退,再做其他处理
加分回答
hash模式下url会带有#,需要url更优雅时,可以使用history模式。 需要兼容低版本的浏览器时,建议使用hash模式。 需要添加任意类型数据到记录时,可以使用history模式。
48 说一说Vuex是什么,每个属性是干嘛的,如何使用 ?
Vuex是集中管理项目公共数据的。
Vuex 有state、mutations 、getters、actions、module属性。
- state 属性用来存储公共管理的数据。
- mutations 属性定义改变state中数据的方法, 注意:不要在mutation中的方法中写异步方法ajax,那样数据就不可跟踪了 。
- getters 属性可以认为是定义 store 的计算属性。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
- action属性类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
- moudle属性是将store分割成模块。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块,从上至下进行同样方式的分割
使用方法:
- state :直接以对象方式添加属性
- mutations :通过
store.commit
调用 - action:通过
store.dispatch
方法触发 - getters:直接通过store.getters.调用
加分回答
可以使用mapState、mapMutations、mapAction、mapGetters一次性获取每个属性下对应的多个方法。 VueX在大型项目中比较常用,非关系组件传递数据比较方便。
49 说一说Vue2.0 双向绑定的原理与缺陷?
Vue响应式指的是:组件的data发生变化,立刻触发试图的更新
原理: Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。
通过原生js提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom 核心API:Object.defineProperty
Object.defineProperty API的使用
作用: 用来定义对象属性
特点: 默认情况下定义的数据的属性不能修改 描述属性和存取属性不能同时使用,使用会报错
响应式原理: 获取属性值会触发getter方法 设置属性值会触发setter方法 在setter方法中调用修改dom的方法
Object.defineProperty的缺点
- 一次性递归到底开销很大,如果数据很大,大量的递归导致调用栈溢出
- 不能监听对象的新增属性和删除属性
- 无法正确的监听数组的方法,当监听的下标对应的数据发生改变时
50 说一说Vue3.0 实现数据双向绑定的方法 ?
Vue3.0 是通过Proxy实现的数据双向绑定,Proxy是ES6中新增的一个特性,实现的过程是在目标对象之前设置了一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
用法: ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler)
target: 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler: 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。 加分回答
Object.defineProperty
的问题:
在Vue中,Object.defineProperty
无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。目前只针对以上方法做了hack处理,所以数组属性是检测不到的,有局限性
Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
Proxy的两个优点:可以劫持整个对象,并返回一个新对象,有13种劫持
1 |
|
57 说一说前端性能优化手段?
前端性能优化分为两类,一类是文件加载更快,另一类是文件渲染更快。
加载更快的方法:
- 让传输的数据包更小(压缩文件/图片):图片压缩和文件压缩
- 减少网络请求的次数:雪碧图/精灵图、节流防抖
- 减少渲染的次数:缓存(HTTP缓存、本地缓存、Vue的keep-alive缓存等)
渲染更快的方法:
- 提前渲染:ssr服务器端渲染
- 避免渲染阻塞:CSS放在HTML的head中 JS放在HTML的body底部
- 避免无用渲染:懒加载
- 减少渲染次数:对dom查询进行缓存、将dom操作合并、使用减少重排的标签
雪碧图的应用场景一般是项目中不常更换的一些固定图标组合在一起,比如logo、搜索图标、切换图标等。 电商项目中最常用到的懒加载,一般在查看商品展示的时候通常下拉加载更多,因为商品数据太多,一次性请求过来数据太大且渲染的时间太长。
58 说一说性能优化有哪些性能指标,如何量化?
加载速度、第一个请求响应时间、页面加载时间、交互动作的反馈时间、帧率FPS、异步请求完成时间 Lighthouse、Throttling 、Performance、Network、WebPageTest
常用的性能优化指标 -
Speed Index(lighthouse,速度指数)
TTFB(Network,第一个请求响应时间) -
页面加载时间 - 首次渲染 - 交互动作的反馈时间 - 帧率FPS(动画 ctrl+shift+p) -
异步请求完成时间 使用性能测量工具进行量化 - Chrome DevTools - 开发调试、性能评测 - Audit(Lighthouse) - Throttling
调整网络吞吐 - Performance 性能分析 - Network 网络加载分析 - Lighthouse - 网站整体质量评估 - 还可以提出优化建议 - WebPageTest - 测试多地点(球各地的用户访问你的网站的性能情况) - 全面性能报告(first view,repeat view,waterfall chart 等等) - WebPageTest 还可以进行本地安装,让你的应用在还没上线的时候就可以测试。
加分回答
常用的性能测量API
DNS 解析耗时: domnLookupEnd - domnLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
数据传输耗时: responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd First Byte时间: responseStart - domnLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间: domInteractive - fetchStart DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart http
头部大小: transferSize - encodedBodySize
重定向次数:performance.navigation.redirectCount
重定向耗时: redirectEnd - redirectStart
59 说一说服务端渲染?
SSR是Server Side Render简称;页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的html就可以了。和它对应的是,CSR是Client Side Render简称;客户端在请求时,服务端不做任何处理,直接将前端资源打包后生成的html返回给客户端,此时的html中无任何网页内容,需要客户端去加载执行js代码才能渲染生成页面内容,同时完成事件绑定,然后客户端再去通过ajax请求后端api获取数据更新视图。
服务端渲染的优势:减少网络传输,响应快,用户体验好,首屏渲染快,对搜索引擎友好,搜索引擎爬虫可以看到完整的程序源码,有利于SEO。
在Vue项目中实现服务端渲染方法:Vue在客户端渲染中也是采用一定方法将虚拟DOM渲染为真实DOM的,那么服务端的渲染流程也是通过虚拟DOM的编译来完成的,编译虚拟DOM的方法是renderToString。在Vue中,vue-server-renderer 提供一个名为 createBundleRenderer 的 API,这个API用于创建一个 render,并且自带renderToString方法。
加分回答
使用服务器端渲染 (SSR) 时还需要有一些权衡之处:
- 开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数 (lifecycle hook) 中使用;一些外部扩展库 (external library) 可能需要特殊处理,才能在服务器渲染应用程序中运行。
- 涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
- 更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 (high traffic) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
60 XSS攻击是什么?
XSS是跨站脚本攻击(Cross Site Scripting),不写为CSS是为了避免和层叠样式表(Cascading Style Sheets)的缩写混淆,所以将跨站脚本攻击写为XSS。
攻击者可以通过向Web页面里面插入script代码,当用户浏览这个页面时,就会运行被插入的script代码,达到攻击者的目的。
XSS的危害一般是泄露用户的登录信息cookie,攻击者可以通过cookie绕过登录步骤直接进入站点。
XSS的分类分为反射型和存储型。
反射型就是临时通过url访问网站,网站服务端将恶意代码从url中取出,拼接在HTML中返回给浏览器,用户就会执行恶意代码。
存储型就是将恶意代码以留言的形式保存在服务器数据库,任何访问网站的人都会受到攻击。
预防XSS攻击的方案基本是对数据进行严格的输出编码,比如HTML元素的编码,JavaScript编码,css编码,url编码等等。
加分回答 XSS的危害:
- 获取cookie:网站中的登录一般都是用cookie作为某个用户的身份证明,这是服务器端返回的一串字符。如果cookie被攻击者拿到,那么就可以绕过密码登录。当空间、论坛如果可以被插入script代码,那么进入空间或者论坛的人的账号就可以轻易被攻击者获取。
- 恶意跳转:直接在页面中插入window.location.href进行跳转。
XSS的分类: -
- 反射型XSS(非持久型XSS):通过URL参数直接注入 -
- 存储型XSS(持久型XSS):存储到数据库后读取时注入
XSS的预防: -
- 浏览器的防御和“X-XSS-Protection”有关,默认值为1,即默认打开XSS防御,可以防御反射型的XSS,不过作用有限,只能防御注入到HTML的节点内容或属性的XSS,例如URL参数中包含script标签。不建议只依赖此防御手段。 -
- 防御HTML节点内容,通过转义<为<以及>为>来实现防御HTML节点内容。 -
- 预防HTML属性,通过转义”->&quto来实现防御,一般不转义空格,但是这要求属性必须带引号。 -
- 预防JavaScript代码,通过将数据进行JSON序列化。 -
- 防御富文本是比较复杂的工程,因为富文本可以包含HTML和script,这些难以预测与防御,建议是通过白名单的方式来过滤允许的HTML标签和标签的属性来进行防御,大概的实现方式是: - 将HTML代码段转成树级结构的数据 - 遍历树的每一个节点,过滤节点的类型和属性,或进行特殊处理 - 处理完成后,将树级结构转化成HTML代码 - 开启浏览器XSS防御:Http Only cookie,禁止 JavaScript 读取某些敏感 Cookie,攻击者完成XSS注入后也无法窃取此 Cookie。
61 CSRF攻击是什么?
CSRF跨站点请求伪造(Cross Site Request Forgery)和XSS攻击一样,有巨大的危害性,就是攻击者盗用了用户的身份,以用户的身份发送恶意请求,但是对服务器来说这个请求是合理的,这样就完成了攻击者的目标。
CSRF攻击的过程原理是:
- 用户打开浏览器,访问目标网站A,输入用户名和密码请求登录 - 用户信息在通过认证后,网站A产生一个cookie信息返回给浏览器,这个时候用户以可正常发送请求到网站A
- 用户在没有退出网站A之前在同一个浏览器打开了另一个新网站B。
- 新网站B收到用户请求之后返回一些攻击代码,并发出一个请求要求访问返回cookie的网站A -
- 浏览器收到这些攻击性代码之后根据新网站B的请求在用户不知道的情况下以用户的权限操作了cookie并向网站A服务器发起了合法的请求。
预防CSRF攻击主要有以下策略:
- 使用验证码,在表单中添加一个随机的数字或者字母验证码,强制要求用户和应用进行直接的交互。
- HTTP中Referer字段,检查是不是从正确的域名访问过来,它记录了HTTP请求的来源地址。
- 使用token验证,在HTTP请求头中添加token字段,并且在服务器端建立一个拦截器验证这个token,如果token不对,就拒绝这个请求。
加分回答
验证HTTP Referer字段的好处就是实施起来特别简单,普通的网站开发不需要特别担心CSRF漏洞,只需要在最后面设置一个拦截器来验证referer的值就可以了,不需要改变已有的代码逻辑,非常便捷。但是这个方法也不是万无一失的,虽然referer是浏览器提供的,但是不同的浏览器可能在referer的实现上或多或少有自身的漏洞,所以使用referer的安全保证是通过浏览器实现的。使用token验证的方法要比referer更安全一些,需要把token放在一个HTTP自定义的请求头部中,解决了使用get或者post传参的不便性。
62 说一下Diff算法?
Diff算法比较过程
第一步:patch函数中对新老节点进行比较 如果新节点不存在就销毁老节点 如果老节点不存在,直接创建新的节点 当两个节点是相同节点的时候,进入 patctVnode 的过程,比较两个节点的内部
第二步:patchVnode函数比较两个虚拟节点内部 如果两个虚拟节点完全相同,返回 当前vnode 的children 不是textNode,再分成三种情况 - 有新children,没有旧children,创建新的 - 没有新children,有旧children,删除旧的 新children、旧children都有,执行updateChildren
比较children的差异,这里就是diff算法的核心 当前vnode 的children 是textNode,直接更新text
第三步:updateChildren函数子节点进行比较
第一步 头头比较。若相似,旧头新头指针后移(即
oldStartIdx++
&&newStartIdx++
),真实dom不变,进入下一次循环;不相似,进入第二步。第二步 尾尾比较。若相似,旧尾新尾指针前移(即
oldEndIdx--
&&newEndIdx--
),真实dom不变,进入下一次循环;不相似,进入第三步。第三步 头尾比较。若相似,旧头指针后移,新尾指针前移(即
oldStartIdx++
&&newEndIdx--
),未确认dom序列中的头移到尾,进入下一次循环;不相似,进入第四步。 -第四步 尾头比较。若相似,旧尾指针前移,新头指针后移(即
oldEndIdx--
&&newStartIdx++
),未确认dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。 -第五步 若节点有key且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移(即
newStartIdx++
);否则,vnode对应的dom(vnode[newStartIdx].elm
)插入当前真实dom序列的头部,新头指针后移(即newStartIdx++
)。 - 但结束循环后,有两种情况需要考虑: - 新的字节点数组(newCh)被遍历完(newStartIdx > newEndIdx
)。那就需要把多余的旧dom(oldStartIdx -> oldEndIdx
)都删除,上述例子中就是c,d
; - 新的字节点数组(oldCh)被遍历完(oldStartIdx > oldEndIdx
)。那就需要把多余的新dom(newStartIdx -> newEndIdx
)都添加。