一、网络
1. HTTP/HTTPS
http 和 https 的基本概念
http:超文本传输协议,是互联网比较广泛使用的一个网络协议。是客户端和服务端请求响应的标准,用于从服务端传输超文本的数据到本地浏览器的一个协议。
https:是http的安全版,在http中加入了SSL层。https主要作用是建立一个安全通道,保证数据传输的安全,防止数据在传输的过程中被篡改或窃取
http 和 https 的区别?
- http传输的数据都是未加密的,相当于是明文传输,https是加密过的,安全性更高。
- 默认端口不一致http默认是80,https是443
- https需要CA证书,可能需要一定的费用
HTTP 1.0 和 HTTP 1.1 之间有哪些区别?
- http1.0默认使用非持久连接,而http1.1默认使用持久连接
- http1.0缓存主要使用if-modified-since和expires,http1.1又引入了Etag、if-None-match等
- http1.1还多了HEAD、OPTIONS、PUT等
HTTPS通信(握手)过程
- 客户端向服务器发起请求,请求中包含使用的协议版本号、生成的一个随机数、以及客户端支持的加密方法。
- 服务器端接收到请求后,确认双方使用的加密方法、并给出服务器的证书、以及一个服务器生成的随机数。
- 客户端确认服务器证书有效后,生成一个新的随机数,并使用数字证书中的公钥,加密这个随机数,然后发给服 务器。并且还会提供一个前面所有内容的 hash 的值,用来供服务器检验。
- 服务器使用自己的私钥,来解密客户端发送过来的随机数。并提供前面所有内容的 hash 值来供客户端检验。
- 客户端和服务器端根据约定的加密方法使用前面的三个随机数,生成对话秘钥,以后的对话过程都使用这个秘钥来加密信息。
TLS/SSL的工作原理
- 客户端使用非对称加密与服务器进行通信,实现身份的验证并协商对称加密使用的秘钥。
- 对称加密算法采用协商秘钥对信息以及信息摘要进行加密通信,不同节点之间采用的对称秘钥不同,从而保证信息只能通信双方获取。
https 协议的优/缺点
优点:
- 更加的安全可靠
- 防止数据被篡改和窃取
缺点:
- 由于多了SSL层,所以页面加载的时间会长一点
- SSL证书需要钱
GET方法URL长度限制的原因
HTTP本身没有对URL长度做限制,这个限制是浏览器和服务端做出的。由于IE浏览器设置的范围最小,所以只要不超过过2083个字符的话,所有浏览器都能正常使用。
下面看一下主流浏览器对get方法中url的长度限制范围:
- Microsoft Internet Explorer (Browser):IE浏览器对URL的最大限制为2083个字符,如果超过这个数字,提交按钮没有任何反应。
- Firefox (Browser):对于Firefox浏览器URL的长度限制为 65,536 个字符。
- Safari (Browser):URL最大长度限制为 80,000 个字符。
- Opera (Browser):URL最大长度限制为 190,000 个字符。
- Google (chrome):URL最大长度限制为 8182 个字符。
GET 和 POST 的区别
- get 参数通过 url 传递,post 放在 request body 中。
- get 请求在 url 中传递的参数是有长度限制的,而 post 没有。
- get 比 post 更不安全,因为参数直接暴露在 url 中,所以不能用来传递敏感信息。
- get 请求只能进行 url 编码,而 post 支持多种编码方式
- get 请求参数会被完整保留在浏览历史记录里,而 post 中的参数不会被保留
对keep-alive的理解
HTTP1.0 中默认是在每次请求/应答,客户端和服务器都要新建一个连接,完成之后立即断开连接,这就是短连接。当使用Keep-Alive模式时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接,这就是长连接。其使用方法如下:
- HTTP1.0版本是默认没有Keep-alive的(也就是默认会发送keep-alive),所以要想连接得到保持,必须手动配置发送
Connection: keep-alive字段。若想断开keep-alive连接,需发送Connection:close字段; - HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP连接不断开,等待在同域名下继续用这个通道传输数据。如果需要关闭,需要客户端发送
Connection:close首部字段。
开启Keep-Alive的优点:
- 较少的CPU和内存的使⽤(由于同时打开的连接的减少了);
- 允许请求和应答的HTTP管线化;
- 降低拥塞控制 (TCP连接减少了);
- 减少了后续请求的延迟(⽆需再进⾏握⼿);
- 报告错误⽆需关闭TCP连;
开启Keep-Alive的缺点:
- 长时间的Tcp连接容易导致系统资源无效占用,浪费系统资源。
与缓存相关的HTTP请求头有哪些
强缓存:
- Expires
- Cache-Control
协商缓存:
- Etag、If-None-Match
- Last-Modified、If-Modified-Since
强缓存/协商缓存
强缓存:
- tab未关闭时从内存中读取,关闭tab或浏览器后打开从磁盘内读取
- 内存读取比磁盘读取更快
- 浏览器强制刷新可重新获取最新资源
- Expires设置的是绝对时间,修改电脑时间会导致浏览器时间失效
- max-age设置的是相对时间单位是秒
- max-age 优先级大于 expires,如果同时设置了这两个会以max-age为准
协商缓存:
- 用lathModified是通过获取文件最后修改的时间和if-modified-since(上次修改的时间)进行对比
- etag:首次文件资源时候会通过文件内容生成md5,并设置在etag上。下一次请求资源时候会通过请求头中的if-none-match中的md5(和之前设置在etag的一致)和请求资源的md5对比,如果一致304(用浏览器中的缓存)否则返回新的资源内容
HTTP状态码
(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含义类似,但是期望客户端保持请求方法不变向新的地址发出请求
(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,表明服务器暂时处于超负载或正在停机维护,无法处理请求
2. TCP
TCP和UDP的区别
| UDP | TCP | |
|---|---|---|
| 是否连接 | 无连接 | 面向连接 |
| 是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输(数据顺序和正确性),使用流量控制和拥塞控制 |
| 连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
| 传输方式 | 面向报文 | 面向字节流 |
| 首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
| 适用场景 | 效率要求相对高,对准确性要求相对低的场景。适用于实时应用,例如视频会议、直播 | 效率要求相对低,但对准确性要求相对高的场景。适用于要求可靠传输的应用,例如文件传输 |
为什么tcp需要三次握手
由于客户端和服务端需要可靠传输,所以必须确定双方都有接收和发送的能力。
TCP 三次握手的建立连接的过程就是相互确认初始序号的过程,告诉对方,什么样序号的报文段能够被正确接收。 第三次握手的作用是客户端对服务器端的初始序号的确认。如果只使用两次握手,那么服务器就没有办法知道自己的序号是否 已被确认。同时这样也是为了防止失效的请求报文段被服务器接收,而出现错误的情况。
- 第一次握手,客户端发送SYN包给服务端,确认了客户端有发送的能力
- 第二次握手,服务端发送ACK包和服务端的SYN包到客户端,确认了服务端有接收和发送的能力
- 第三次握手,客户端发送ACK包到服务端,确认了客户端有接收的能力
tcp四次挥手
TCP 使用四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代 表不能再向对方发送数据,连接处于的是半释放的状态。
- 第一次挥手,客户端向服务端发送FIN包,表示客户端向关闭连接,不再发送数据到服务端
- 第二次挥手,服务端向客户端发送ACK包,表示服务端同意关闭连接
- 第三次挥手,服务端向客户端发送FIN包,请求关闭,此时服务端已经关闭连接了不能接收和发送数据
- 第四次挥手,客户端向服务端发送ACK包,同意关闭,服务端收到ACK包后就直接关闭连接。客户端等待一段时间如果没收到服务端回应说明服务端已经关闭了,客户端也就可以关闭连接了。
3. webSocket
什么是 WebSocket?
websocket是在单个TCP连接上进行双向通信的协议,并且浏览器和服务端只需要完成一次握手就可以创建持久性的连接。
WebSoket和Http的区别
相同:
- 基于TCP的应用层协议
- 可靠性传输协议
不相同:
- HTTP是单向通信,客户端发起请求,服务端才能响应。而webSocket则可以双向通信,服务端可主动发消息到客户端
- HTTP同时发出的请求数量会有限制,不同的浏览器限制不一样。websocket连接后,客户端可同时发出的消息会更多。
二、浏览器
1. 浏览器安全
什么是 XSS 攻击?
XSS是一种跨站脚本攻击,通过在网站注入恶意的脚本,在用户的浏览器运行的盗取用户信息等数据,如:cookie。
攻击者可以通过这种攻击方式可以进行以下操作:
- 获取页面的数据,如DOM、cookie、localStorage;
- DOS攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器;
- 破坏页面结构;
- 流量劫持(将链接指向某网站);
如何防御 XSS 攻击
- 对需要插入到 HTML 中的代码做好充分的转义
- 使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击。通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式。
- 对一些敏感信息进行保护,比如 cookie 使用 http-only,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。
什么是 CSRF 攻击?
CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。
CSRF 攻击的本质是利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。
如何防御 CSRF 攻击?
TODO
有哪些可能引起前端安全的问题?
- 跨站脚本 (Cross-Site Scripting, XSS): ⼀种代码注⼊⽅式, 为了与 CSS 区分所以被称作 XSS。早期常⻅于⽹络论坛, 起因是⽹站没有对⽤户的输⼊进⾏严格的限制, 使得攻击者可以将脚本上传到帖⼦让其他⼈浏览到有恶意脚本的⻚⾯, 其注⼊⽅式很简单包括但不限于 JavaScript / CSS / Flash 等;
- iframe的滥⽤: iframe中的内容是由第三⽅来提供的,默认情况下他们不受控制,他们可以在iframe中运⾏JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会破坏前端⽤户体验;
- 跨站点请求伪造(Cross-Site Request Forgeries,CSRF): 指攻击者通过设置好的陷阱,强制对已完成认证的⽤户进⾏⾮预期的个⼈信息或设定信息等某些状态更新,属于被动攻击
- 恶意第三⽅库: ⽆论是后端服务器应⽤还是前端应⽤开发,绝⼤多数时候都是在借助开发框架和各种类库进⾏快速开发,⼀旦第三⽅库被植⼊恶意代码很容易引起安全问题。
进程和线程的区别
- 进程可以看做独立应用,线程不能
- 资源:进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位);线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)。
- 通信方面:线程间可以通过直接共享同一进程中的资源,而进程通信需要借助 进程间通信。
- 调度:进程切换比线程切换的开销要大。线程是CPU调度的基本单位,线程的切换不会引起进程切换,但某个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
浏览器核心进程
- 浏览器进程:主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
- 渲染进程:核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
- GPU 进程:其实, GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
- 网络进程:主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
- 插件进程:主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
所以,打开一个网页,最少需要四个进程:1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程。如果打开的页面有运行插件的话,还需要再加上 1 个插件进程。
浏览器渲染进程的线程有哪些
-
GUI渲染线程:
-
负责渲染浏览器页面,解析HTML、CSS,构建DOM树、构建CSSOM树、构建渲染树和绘制页面;
-
当界面需要重绘或由于某种操作引发回流时,该线程就会执行。
注意:GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
-
-
JS引擎线程:
-
JS引擎线程也称为JS内核,负责处理Javascript脚本程序,解析Javascript脚本,运行代码;
-
JS引擎线程一直等待着任务队列中任务的到来,然后加以处理,一个Tab页中无论什么时候都只有一个JS引擎线程在运行JS程序;
注意:GUI渲染线程与JS引擎线程的互斥关系,所以如果JS执行的时间过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。
-
-
事件触发线程:
-
属于浏览器而不是JS引擎,用来控制事件循环,当JS引擎执行代码块如setTimeOut时(也可是来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件触发线程中;
-
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理;
注意:由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行);
-
-
定时器触发进程:
-
即setInterval与setTimeout所在线程;
-
浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确性;
-
因此使用单独线程来计时并触发定时器,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行,所以定时器中的任务在设定的时间点不一定能够准时执行,定时器只是在指定时间点将任务添加到事件队列中;
注意:W3C在HTML标准中规定,定时器的定时时间不能小于4ms,如果是小于4ms,则默认为4ms。
-
-
异步http请求线程:
- XMLHttpRequest连接后通过浏览器新开一个线程请求;
- 检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,等待JS引擎空闲后执行;
如何实现浏览器内多个标签页之间的通信?
实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信,因此我们可以找一个中介者,让标签页和中介者进行通信,然后让这个中介者来进行消息的转发。通信方法如下:
- 使用 websocket 协议,因为 websocket 协议可以实现服务器推送,所以服务器就可以用来当做这个中介者。标签页通过向服务器发送数据,然后由服务器向其他标签页推送转发。
- 使用 ShareWorker 的方式,shareWorker 会在页面存在的生命周期内创建一个唯一的线程,并且开启多个页面也只会使用同一个线程。这个时候共享线程就可以充当中介者的角色。标签页间通过共享一个线程,然后通过这个共享的线程来实现数据的交换。
- 使用 localStorage 的方式,我们可以在一个标签页对 localStorage 的变化事件进行监听,然后当另一个标签页修改数据的时候,我们就可以通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。
- 使用 postMessage 方法,如果我们能够获得对应标签页的引用,就可以使用postMessage 方法,进行通信。通过window.open方式,获取标签页的引用
- 使用BroadcastChannel方式,可以实现同源下不同窗口Tab页、iframe通信
一个页面从输入 URL 到页面加载显示完成,这个过程 中都发生了什么
- 通过DNS将URL解析成ip
- 检查缓存,但如果输入的URL是资源文件的路径,则会跳过检查缓存这一步
- 发起TCP连接,进行三次握手
- TCP建立成功后发起http请求,服务器响 应 HTTP 请求,浏览器得到 html 代码
- 解析html代码,并请求html代码中其他资源
- 生成DOM树,CSSOM规则树
- 渲染内容到浏览器中
对浏览器内核的理解
浏览器内核主要分成两部分:
- 渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。默认情况下,渲染引擎可以显示 html、xml 文档及图片,它也可以借助插件显示其他类型数据,例如使用 PDF 阅读器插件,可以显示 PDF 格式。
- JS 引擎:解析和执行 javascript 来实现网页的动态效果。
常见浏览器所用内核
(1) IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;
(2) Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,现在是 Blink内核;
(3) Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;
(4) Safari 浏览器内核:Webkit 内核;
(5) Opera 浏览器内核:最初是自己的 Presto 内核,后来加入谷歌大军,从 Webkit 又到了 Blink 内核;
(6) 360浏览器、猎豹浏览器内核:IE + Chrome 双内核;
(7) 搜狗、遨游、QQ 浏览器内核:Trident(兼容模式)+ Webkit(高速模式);
(8) 百度浏览器、世界之窗内核:IE 内核;
(9) 2345浏览器内核:好像以前是 IE 内核,现在也是 IE + Chrome 双内核了;
(10)UC 浏览器内核:这个众口不一,UC 说是他们自己研发的 U3 内核,但好像还是基于 Webkit 和 Trident , 还有说是基于火狐内核。
浏览器的渲染过程
- 解析HTML,生成DOM树
- 解析CSS,生成CSSOM规则树
- 根据CSSOM和DOM,生成Render Tree(渲染树)
- 生成渲染树后,会根据渲染树进行布局(也能称为回流/重排),这个阶段浏览器要做的事情就是清楚知道每个节点在页面中的位置、大小。
- 布局后就是将渲染树循环绘制到屏幕中。
注意: 这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
script标签加载方式
- script 立即停止页面渲染去加载资源文件,当资源加载完毕后立即执行js代码,js代码执行完毕后继续渲染页面;
- async 是在下载完成之后,立即异步加载,加载好后立即执行,多个带async属性的标签,不能保证加载的顺序;
- defer 是在下载完成之后,立即异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果DOM树已经准备好,则立即执行。多个带defer属性的标签,按照顺序执行。
浏览器有哪些操作会造成阻塞
- css的加载会阻塞渲染,因为渲染树是需要DOM树和CSSOM规则树
- css的加载还会影响后面js语句的执行,由于js的可能会操作css样式,因此会在样式表后面的js执行之前加载完。
- JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复继续解析文档。
浏览器渲染优化
-
针对JavaScript:
JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变, 来进 行优化:
- 尽量将JavaScript文件放在body的最后
<script>标签的引入资源方式有三种,有一种就是我们常用的直接引入,还有两种就是使用 async 属性和 defer 属性来异步引入,两者都是去异步加载外部的JS文件,不会阻塞DOM的解析(尽量使用异步加载)。
-
针对CSS:
- link:浏览器会派发一个新的线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码
- @import:GUI渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前不会继续渲染(阻碍浏览器渲染)
- style:GUI直接渲染
外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。所以CSS一般写在headr中,让浏览器尽快发送请求去获取css样式。
所以,在开发过程中,导入外部样式使用link,而不用@import。如果css少,尽可能采用内嵌样式,直接写在style标签中。
-
针对DOM树、CSSOM树
- HTML文件的代码层级尽量不要太深
- 使用语义化的标签,来避免不标准语义化的特殊处理
- 减少CSSD代码的层级,因为选择器是从左向右进行解析的
-
减少回流与重绘
- 操作DOM时,尽量在低层级的DOM节点进行操作
- 不要使用
table布局, 一个小的改动可能会使整个table进行重新布局 - 使用CSS的表达式
- 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
- 将元素先设置
display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
哪些操作会造成内存泄漏?
- 第一种情况是由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
- 第二种情况是设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
- 第三种情况是获取一个 DOM 元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。
- 第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。
Cookie SameSite
在当前访问的网站和请求服务的网站是“跨站”(Cross Site)的情况下,第三方服务设置的 Cookie 就称之为 “第三方 Cookie” 。
如果需要设置第三方Cookie,samesite要为none,但是设置为none有一个要求,就是必须secure属性为true,也就是必须使用https
Cookie跨域
- 只能同站,不允许跨站(需要携带cookie的接口)
samesite要为none,但是设置为none有一个要求,就是必须secure属性为true,也就是必须使用https
Cookie如何应对的 CSRF攻击?
- 放弃Cookie、使用Token!Token的策略,一般就是登陆的时候,服务端在response中,返回一个token字段,然后以后所有的通信,前端就把这个token添加到http请求的头部。
- SameSite Cookies,它表示,只能当前域名的网站发出的http请求,携带这个Cookie。
2.垃圾回收与内存泄漏
垃圾回收的概念
JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。
垃圾回收机制
- Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
- 全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。
- 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
垃圾回收的方式
- 标记清除: 标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。
- 引用计数: 另外一种垃圾回收机制就是引用计数,这个用的相对较少。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。
哪些情况会导致内存泄漏
- 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
- 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
- 脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
- 闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。
三、HTML
对HTML语义化的理解
语义化是指根据内容的结构化(内容语义化),选择合适的标签(代码语义化) 。通俗来讲就是用正确的标签做正确的事情。
语义化的优点如下:
- 对机器友好,带有语义的文字表现力丰富,更适合搜索引擎的爬虫爬取有效信息,有利于SEO。除此之外,语义类还支持读屏软件,根据文章可以自动生成目录;
- 对开发者友好,使用语义类标签增强了可读性,结构更加清晰,开发者能清晰的看出网页的结构,便于团队的开发与维护。
常见的语义化标签:header nav section main article aside footer
script标签中defer和async的区别
defer:异步加载,加载完后会在Html解析完后才执行,如果多个script标签加上defer,最后会按顺序执行
async:也是异步加载,但加载完后立即执行,z如果html还没解析会造成阻塞。多个script中有async,无法保证执行的顺序。
HTML5有哪些更新
- 语义化标签:
headernavsectionmainarticleasidefooter - 媒体标签:
videoaudio - DOM查询操作:
document.querySelector()document.querySelectorAll() - Web存储:
localStoragesessionStorage - 拖放:
draggable属性drag dragstart dragend drop 等事件 - 画布(canvas ): canvas 元素使用 JavaScript 在网页上绘制图像
说一下 HTML5 drag API
- dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发。
- darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。
- dragenter:事件主体是目标元素,在被拖放元素进入某元素时触发。
- dragover:事件主体是目标元素,在被拖放在某元素内移动时触发。
- dragleave:事件主体是目标元素,在被拖放元素移出目标元素是触发。
- drop:事件主体是目标元素,在目标元素完全接受被拖放元素时触发。
- dragend:事件主体是被拖放元素,在整个拖放操作结束时触发。
行内元素有哪些?块级元素有哪些? 空(void)元素有那些?
- 行内元素有:
a b span img input select strong; - 块级元素有:
div ul ol li dl dt dd h1 h2 h3 h4 h5 h6 p; - 空元素:
<br>、<hr>、<img>、<input>、<link>、<meta>等
说一下 web worker
web worker 是运行在后台的 js,独立于其他脚本,不会影响页面的性能。 并且通过 postMessage 将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了。
HTML5 的 input 增加哪些 type?
- color:用于指定颜色的控件 dao
- date:用于输入日期的控件(年,月,日,不包括时间)
- datetime:基于 UTC 时区的日期时间输入控件(时,分,秒及几分之一秒)
- datetime-local:用于输入日期时间控件,不包含时区
- email:用于编辑 e-mail 的字段
- month:用于输入年月的控件,不带时区
- number: 用于输入浮点数的控件
- range:用于输入不精确值控件
- search:用于输入搜索字符串的单行文本字段。换行会被从输入的值中自动移除
- tel:用于输入电话号码的控件
- time:用于输入不含时区的时间控件
- url:用于编辑 URL 的字段
- week:用于输入一个由星期-年组成的日期,日期不包括时区
雪碧图( 精灵图 )?
是把网站上用到的一些图片整合到一张单独的图片中,从而减少你的网站的 HTTP 请求数量,该图片使用 css background 和 background-position 属性渲染。
优点:
- 减少网页的 http 请求,从而加快了网页加载速度,提高用户体验
- 减少图片的体积,因为每个图片都有一个头部信息,把多个图片放到一个图片里,就会 共用同一个头信息,从而减少了字节数
- 更换风格方便,只需要在一张或少张图片上修改图片的颜色或样式,整个网页的风格就 可以改变
缺点:
- CSS Sprites 在开发的时候,要通过 photoshop 或其他工具测量计算每一个背景单元的精确位置
- 在维护的时候比较麻烦,如果页面背景有少许改动,一般就要改这张合并的图片
常⽤的meta标签有哪些
meta 标签由 name 和 content 属性定义,用来描述网页文档的属性,比如网页的作者,网页描述,关键词等,除了HTTP标准固定了一些name作为大家使用的共识,开发者还可以自定义name。
-
charset,用来描述HTML文档的编码类型:<meta charset="UTF-8" > -
keywords,页面关键词:<meta name="keywords" content="关键词" /> -
description,页面描述:<meta name="description" content="页面描述内容" /> -
refresh,页面重定向和刷新:<meta http-equiv="refresh" content="0;url=" /> -
viewport,适配移动端,可以控制视口的大小和比例:<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">其中,
content参数有以下几种:width viewport:宽度(数值/device-width)height viewport:高度(数值/device-height)initial-scale:初始缩放比例maximum-scale:最大缩放比例minimum-scale:最小缩放比例user-scalable:是否允许用户缩放(yes/no)
-
搜索引擎索引方式:
<meta name="robots" content="index,follow" />。其中,content参数有以下几种:all:文件将被检索,且页面上的链接可以被查询;none:文件将不被检索,且页面上的链接不可以被查询;index:文件将被检索;follow:页面上的链接可以被查询;noindex:文件将不被检索;nofollow:页面上的链接不可以被查询。
四、CSS
CSS选择器及其优先级
- !important
- 内联样式
- id 选择器
- 类选择器、伪类选择器、属性选择器
- 标签选择器、伪元素选择器
- 相邻兄弟选择器、子选择器、后代选择器、通配符选择器
注意:
- 如果优先级相同,则最后出现的样式生效
- 继承得到的样式的优先级最低
- 样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。
CSS中可继承与不可继承属性有哪些
无继承性的属性:
- display
- 文本属性:
vertical-aligntext-decorationtext-shadowwhite-space - 盒子模型的属性:
widthheightmarginborderpadding - 背景属性:
backgroundbackground-color等 - 定位属性:
floatpositonlefttopoverflow等 - 生成内容属性:
content - 轮廓样式属性:
outline
有继承性的属性:
-
字体系列属性:
font-familyfont-weightfont-sizefont-style -
文本系列属性:
text-indenttext-alignline-heightword-spacingletter-spacingcolortext-transform -
元素可见性:
visibility -
列表布局属性:
list-style -
光标属性:
cursor
link和@import的区别
- link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。
- link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
- link支持使用Javascript控制DOM去改变样式;而@import不支持。
display:none与visibility:hidden的区别
- 渲染树中:
display:none会让元素从渲染树中移除,并且渲染时不占空间。visibility:hidden则依然存在渲染树中,渲染时还会占据空间,只是内容看不见。 - 继承性:
display:none没有继承性,子孙节点会跟随父节点一起从渲染树中消失,修改子孙节点的属性也无法让元素显示。visibility:hidden是可以继承的,子孙节点都会因继承了visibility:hidden而不显示,但子孙节点可以通过修改自身visibility属性让元素显示 - 修改常规文档流中元素的
display通常会造成文档的重排,但是修改visibility属性只会造成本元素的重绘;
对requestAnimationframe的理解
requestAnimationFrame是专门用来请求动画的API,告诉浏览器希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
优点:
- CPU节能:使用SetTinterval 实现的动画,当页面被隐藏或最小化时,SetTinterval 仍然在后台执行动画任务。而RequestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统走的RequestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。
- 减少DOM操作:requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
执行优先级:
同步 > 微任务 > requestAnimationFrame > 渲染 > 宏任务
对盒模型的理解
盒模型是有margin,border,padding,content组成,盒模型主要分为两种,标准盒模型、怪异盒模型。
标准盒模型:width和height只包含了content
怪异盒模型:width和height包含了border、padding、content
为什么有时候⽤translate来改变位置⽽不是定位?
translate 是 transform 属性的⼀个值。改变transform或opacity不会触发浏览器重新布局(reflow),有可能触发重绘,必会触发复合(compositions)。⽽改变绝对定位会触发重新布局,进⽽触发重绘和复合。transform使浏览器为元素创建⼀个 GPU 图层,但改变绝对定位会使⽤到 CPU。
translate:默认情况下会触发重绘和复合,用了will-change后只会触发复合
translate3d:默认会是元素变成合成层,所以只触发复合
定位:触发重新布局,进⽽触发重绘和复合
line-height 的赋值方式
- 带单位:px 是固定值,而 em 会参考父元素 font-size 值计算自身的行高
- 纯数字:会把比例传递给后代。例如,父级行高为 1.5,子元素字体为 18px,则子元素行高为 1.5 * 18 = 27px
- 百分比:将计算后的值传递给后代
CSS 优化和提高性能的方法有哪些?
- 将写好的css进行打包压缩,可以减小文件体积。
- 减少使用@import,建议使用link,因为后者在页面加载时一起加载,前者是等待页面加载完成之后再进行加载。
- 了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则。
- 尽量减少页面重排、重绘。
display:inline-block 什么时候会显示间隙?
- 有空格时会有间隙,可以删除空格解决;
margin正值时,可以让margin使用负值解决;- 使用
font-size时,可通过设置font-size:0、letter-spacing、word-spacing解决;
z-index属性在什么情况下会失效
- 元素没有设置position属性为非static属性
- 父元素position为relative并设置了zIndex时,子元素的z-index失效。解决:父元素position改为static或修改父元素z-index;
fixed一定会相对于viewport 视口定位吗?
transform、filter、perspective、will-change设为transform、filter、perspective会使fixed不是相对于视觉窗口定位。这些属性本身的特质影响了fixed的参考包含块。
两栏布局的实现
一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现:
- 利用浮动,左侧元素设置固定大小,并左浮动,右侧元素设置overflow: hidden; 这样右边就触发了BFC,BFC的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠。
- 利用flex布局,将左边元素设置为固定宽度200px,将右边的元素设置为flex:1。
- 利用绝对定位,将父级元素设置为相对定位。左边元素设置为absolute定位,并且宽度设置为200px。将右边元素的margin-left的值设置为200px。
三栏布局的实现
三栏布局一般指的是页面中一共有三栏,左右两栏宽度固定,中间自适应的布局,三栏布局的具体实现:
- 利用绝对定位,左右两栏设置为绝对定位,中间设置对应方向大小的margin的值。
- 利用flex布局,左右两栏设置固定大小,中间一栏设置为flex:1。
- 圣杯布局,利用浮动和负边距来实现。父级元素设置左右的 padding,三列均设置向左浮动,中间一列放在最前面,左右两列通过负边距移动到对应的位置
- 双飞翼布局,左右位置的保留是通过中间列的 margin 值来实现的,而不是通过父元素的 padding 来实现的。本质上来说,也是通过浮动和外边距负值来实现的。
水平垂直居中的实现
- 利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过translate来调整元素的中心点到页面的中心
- 利用绝对定位,设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况:
- 使用flex布局,通过align-items:center和justify-content:center设置容器的垂直和水平方向上为居中对齐
如何根据设计稿进行移动端适配?
TODO
对BFC的理解,如何创建BFC
BFC是一个独立的布局环境,可以理解为一个容器,在这个容器中按照一定规则进行物品摆放,并且不会影响其它环境中的物品。如果一个元素符合触发BFC的条件,则BFC中的元素布局不受外部影响。
创建BFC的条件:
- 根元素:body;
- 元素设置浮动:float 除 none 以外的值;
- 元素设置绝对定位:position (absolute、fixed);
- display 值为:inline-block、table-cell、table-caption、flex等;
- overflow 值为:hidden、auto、scroll;
BFC的特点:
- 垂直方向上,自上而下排列,和文档流的排列方式一致。
- 在BFC中上下相邻的两个容器的margin会重叠
- BFC区域不会与浮动的容器发生重叠
- BFC是独立的容器,容器内部元素不会影响外部元素
- 计算BFC的高度时,需要计算浮动元素的高度
BFC的作用:
- 解决margin的重叠问题:由于BFC是一个独立的区域,内部的元素和外部的元素互不影响,将两个元素变为两个BFC,就解决了margin重叠的问题。
- 解决高度塌陷的问题:在对子元素设置浮动后,父元素会发生高度塌陷,也就是父元素的高度变为0。解决这个问题,只需要把父元素变成一个BFC。常用的办法是给父元素设置
overflow:hidden。 - 创建自适应两栏布局:可以用来创建自适应两栏布局:左边的宽度固定,右边的宽度自适应。
元素的层叠顺序
- z-index属性值为正的定位元素
- z-index属性值为0的定位元素
- 行内块元素
- 浮动元素
- 块级元素
- 负的z-index
- 当前层叠上下文元素的背景和边框
五、Javascript
1. JavaScript基础
JavaScript有哪些数据类型,它们的区别?
八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt
这些数据可以分为原始数据类型和引用数据类型:
- 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
- 堆:引用数据类型(对象、数组和函数)
在操作系统中,内存被分为栈区和堆区:
- 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
数据类型检测的方式有哪些
- typeof,其中数组、对象、null都会被判断为object,其他判断都正确。
instanceof可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。constructor有两个作用,一是判断数据的类型,二是对象实例通过constrcutor对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:Object.prototype.toString.call()使用 Object 对象的原型方法 toString 来判断数据类型:
判断数组的方式有哪些
- 通过Object.prototype.toString.call()做判断
- 通过原型链做判断
obj.__proto__ === Array.prototype; - 通过ES6的Array.isArray()做判断
- 通过instanceof做判断
obj instanceof Array - 通过Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)
为什么typeof null的结果是Object?
在判断数据类型时,是根据机器码低位标识来判断的,而null的机器码标识为全0,而对象的机器码低位标识为000。所以typeof null的结果被误判为Object。
intanceof 操作符的实现原理及实现
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
为什么0.1+0.2 ! == 0.3,如何让其相等
计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。这两个数的二进制都是无限循环的数。
对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3
箭头函数与普通函数的区别
- 箭头函数比普通函数更加简洁
- 箭头函数没有自己的this,箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。
- call()、apply()、bind()等方法不能改变箭头函数中this的指向
- 箭头函数不能作为构造函数使用,所以无法通过new关键字创建实例
- 箭头函数没有自己的arguments
- 箭头函数没有prototype
Proxy 可以实现什么功能?
可以用来自定义对象中的操作,target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
new操作符的实现原理
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
Map和Object的区别
- Map的键值可以是任意值,包括函数、对象、基本类型。Object只能是string或Symbol
- Map默认情况下不包含任何键值。Object如果当前对象内没这个键值会顺着原型链往原型上找
- Map中的key是有序的,因此迭代的时候可以按插入的顺序返回值。Object 的键是无序的。
- Map 的键值对个数可以轻易地通过size 属性获取。Object 的键值对个数只能手动计算
Map和weakMap的区别
WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。由于key是弱引用,所以到key的对象被释放掉,其对应的键值也会从weakMap中移除。
Map的键值可以是任意值,包括函数、对象、基本类型。Object只能是string或Symbol。当Map的key为对象的时候,就算这个对象的引用被释放了,但它的键值依然存在Map中。
JavaScript脚本延迟加载的方式有哪些?
延迟加载就是等页面加载完成之后再加载 JavaScript 文件。 js 延迟加载有助于提高页面加载速度。
一般有以下几种方式:
- defer 属性: 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
- async 属性: 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
- 动态创建 DOM 方式: 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
- 使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件
- 让 JS 最后加载: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
JavaScript 类数组对象的定义?
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。
常见的类数组转换为数组的方法有这样几种:
- slice + call:
Array.prototype.slice.call(arrayLike); - splice + call:
Array.prototype.splice.call(arrayLike, 0); - concat + apply:
Array.prototype.concat.apply([], arrayLike); Array.from(arrayLike);
encodeURI、encodeURIComponent 的区别
- encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。
- encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。
对AJAX的理解,实现一个AJAX请求
AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
创建AJAX请求的步骤:
- 创建一个 XMLHttpRequest 对象
- 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
- 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。
- 当对象的属性和监听函数设置完成后,最后调用 send 方法来向服务器发起请求,可以传入参数作为发送的数据体。
JavaScript为什么要进行变量提升,它导致了什么问题?
在解析阶段,JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。
- 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
- 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
for...in和for...of的区别
- for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
- for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
总结: for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
如何使用for...of遍历对象
for…of是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。如果需要遍历的对象是类数组对象,用Array.from转成数组即可。
ajax、axios、fetch的区别
ajax:
- 基于原生XMLHttpRequests开发
- 如果请求内部又包含请求,以此循环,就会出现回调地狱
fetch:
- Fetch 是在 ES6 出现的
- 基于标准 Promise 实现,支持 async/await
- 脱离了XHR,是ES规范里新的实现方式
- fetch不支持abort,不支持超时控制
- fetch没有办法原生监测请求的进度,而XHR可以
- fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
axios:
- 也是基于原生XMLHttpRequests开发
- 支持Promise API
- 监听请求和响应,实现拦截
- 自动转换json数据
2. 原型与原型链
对原型、原型链的理解
- 每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象。当使用这个构造函数创建实例的时候,prototype属性指向的原型对象就成为实例的原型对象。
- 每个对象都拥有一个隐藏的属性[[prototype]],指向它的原型对象,这个属性可以通过
Object.getPrototypeOf(obj)或obj.__proto__来访问。 - 原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链
原型链的终点是什么?
由于Object是构造函数,原型链终点是Object.prototype.__proto__,而Object.prototype.__proto__=== null // true,所以,原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype的下一级是Object.prototype.__proto__。
3. 执行上下文/作用域链/闭包
对闭包的理解
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包的原理
- 每一个函数中有都存有一个 [[Scopes]],[[Scopes]] 中存放的是这个函数的作用域链,一般情况下里面会存有Global(全局变量) 和this(局部变量), 当产生闭包的时候会多一个Closure对象。
- 在编译函数的时候就创建了闭包对象,当编译到子函数中使用了局部变量则引入Closure中,这也说明了子函数是公用一个Closure对象。
- 只有所有的子函数都销毁了,Closure对象才会被销毁
闭包的使用场景
- 变量的私有化
- 可用来封装防抖节流函数
- 柯里化
执行上下文
简单来说执行上下文就是指:
在执行一点JS代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。这一步执行完了,才开始正式的执行程序。
在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。
- 全局上下文:变量定义,函数声明
- 函数上下文:变量定义,函数声明,
this,arguments
实现call、apply 及 bind 函数
-
call 函数的实现步骤:
Function.prototype.myCall = function(context) { // 1.判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 if (typeof this !== "function") { console.error("type error"); } // 2.处理传入的参数,截取第一个参数后的所有参数。 let args = [...arguments].slice(1), result = null; // 3.判断传入上下文对象是否存在,如果不存在,则设置为 window 。 context = context || window; // 4.将函数作为上下文对象的一个属性。 context.fn = this; // 5.使用上下文对象来调用这个方法,并保存返回结果。 result = context.fn(...args); // 6.删除刚才新增的属性。 delete context.fn; // 7.返回结果。 return result; };简单说就是在传入的对象(context)中加上这个函数,并且通过这个对象访问函数调用,调用完删除该对象上的这个函数
-
apply 函数的实现步骤:
Function.prototype.myApply = function(context) { // 判断调用对象是否为函数 if (typeof this !== "function") { throw new TypeError("Error"); } let result = null; // 判断 context 是否存在,如果未传入则为 window context = context || window; // 将函数设为对象的方法 context.fn = this; // 调用方法 if (arguments[1]) { result = context.fn(...arguments[1]); } else { result = context.fn(); } // 将属性删除 delete context.fn; return result; };和call基本一致,只是处理函数的参数方式不同,apply参数是通过一个数组传入
-
bind 函数的实现步骤:
Function.prototype.myBind = function(context) { // 判断调用对象是否为函数 if (typeof this !== "function") { throw new TypeError("Error"); } // 获取参数 var args = [...arguments].slice(1), fn = this; return function Fn() { // 根据调用方式,传入不同绑定值 return fn.apply( this instanceof Fn ? this : context, args.concat(...arguments) ); }; };
4.异步编程
对Promise的理解
Promise 对象是异步编程的一种解决方案,Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。
当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。
Promise方法
Promise有五个常用的方法:then()、catch()、all()、race()、finally。下面就来看一下这些方法。
-
then()
当Promise执行的内容符合成功条件时,调用
resolve函数,失败就调用reject函数。成功的话会触发then的第一个回调,失败触发第二个回调。then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。 -
catch()
Promise对象除了有then方法,还有一个catch方法,该方法相当于
then方法的第二个参数,指向reject的回调函数。不过catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。 -
all()
all方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise对象。只有全部的promise都触发resolve才算完成,只要有一个触发reject都算失败 -
race()
race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,只有有一个触发resolve或reject就会进入then -
finally()
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
对async/await 的理解
- async/await其实是
Generator的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。 - async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中
return一个直接量,async 会把这个直接量通过Promise.resolve()封装成 Promise 对象。 - async 函数没有返回值,又该如何?很容易想到,它会返回
Promise.resolve(undefined)。
六、Vue
1. 基础
Vue的基本原理
当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使用proxy )将它们转为 getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
双向数据绑定的原理
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter
- 需要observe的数据对象进行递归遍历,包括子属性对象的属性,进行数据劫持
- compile解析模板指令,将模板中的变量替换成数据,然后收集依赖,一旦数据有变动,通过Watcher更新视图
- 整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
使用 Object.defineProperty() 来进行数据劫持有什么缺点?
在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。
在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。
- 无法监控到数组下标和长度的变化。
- 添加或删除对象的属性时,Vue 检测不到。因为添加或删除的对象没有在初始化进行响应式处理
MVVM、MVC的区别
(1)MVC
当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作
(2)MVVM
MVVM 分为 Model、View、ViewModel:
- Model代表数据模型,数据和业务逻辑都在Model层中定义;
- View代表UI视图,负责数据的展示;
- ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Computed 和 Watch 的区别
对于Computed:
- 它支持缓存,只有依赖的数据发生了变化,才会重新计算
- 不支持异步,当Computed中有异步操作时,无法监听数据的变化
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed,比如说:购物车总的总金额
对于Watch:
- 它不支持缓存,数据变化时,它就会触发相应的操作
- 支持异步监听
- 当一个属性发生变化时,就需要执行相应的操作
Vue template 到 render 的过程
vue的模版编译过程主要如下:template -> ast -> render函数
-
vue 在模版编译版本的码中会将template转化为render函数:
- 调用parse方法将template转化为ast
- 对静态节点做优化,这个过程主要分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化
- 生成代码
Vue是如何收集依赖的?
- 初 始 化 状 态 initState , 这 中 间 便 会 通 过 defineReactive 将数据变成响应式对象,其中的 getter 部分便是用来依赖收集的。
- 初始化最终会走 mount 过程,其中会实例化 Watcher ,进入 Watcher 中,便会执行 this.get() 方法,get 方法中的 pushTarget 实际上就是把 Dep.target 赋值为当前的 watcher。
- 执行 vm._render() 方法,触发 getter 的时候会调用 dep.depend() 方法,也就会执行 Dep.target.addDep(this)。
- 执行 addDep 方法,然后走到 dep.addSub() 方法,便将当前的 watcher 订阅到这个数据持有的 dep 的 subs 中
对 React 和 Vue 的理解,它们的异同
- Vue由于跟踪每一个组件的依赖收集,能够比较精准的知道需要更新的节点,而且编译的时候标注了静态节点,所以更新时不需要全部更新。React中当某个组件的状态发生变化时,它会以该组件为根,重新渲染全部的子组件。
- vue可以双向绑定,react是纯单向数据流
Vue的性能优化有哪些
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- key保证唯一
- 使用路由懒加载、异步组件
- 第三方模块按需导入
- 使用cdn加载第三方模块
- 压缩代码
computed缓存
- 初始化data和computed,分别代理其set以及get方法, 对data中的所有属性生成唯一的dep实例。
- 对computed中的sum生成唯一watcher,并保存在vm._computedWatchers中
- 执行render函数时会访问sum属性,从而执行initComputed时定义的getter方法,会将Dep.target指向sum的watcher,并调用该属性具体方法sum。
- sum方法中访问this.count,即会调用this.count代理的get方法,将this.count的dep加入sum的watcher,同时该dep中的subs添加这个watcher。
- 设置vm.count = 2,调用count代理的set方法触发dep的notify方法,因为是computed属性,只是将watcher中的dirty设置为true。
- 最后一步vm.sum,访问其get方法时,得知sum的watcher.dirty为true,调用其watcher.evaluate()方法获取新的值。
2. 生命周期
Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。
- beforeCreate(创建前) :数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
- created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到
$el属性。 - beforeMount(挂载前) :在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
- mounted(挂载后) :在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。
- beforeUpdate(更新前) :响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
- updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前) :实例销毁之前调用。这一步,实例仍然完全可用,
this仍能获取到实例。 - destroyed(销毁后) :实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
3. Vue 3.0
Vue3.0有什么更新
- 监测机制的改变,基于代理 Proxy的 observer 实现,提供全语言覆盖的反应性跟踪。
- 消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制,如:只能监测属性,不能监测对象
- 支持自定义渲染器,不需要通过修改源码方式