Basic questions for front-end
HTTP
http 和 https 的基本概念
http: 是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。
https:是以安全为目标的 HTTP 通道,即 HTTP 下 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性。
http 和 https 的区别及优缺点?
- http 是超文本传输协议,信息是明文传输,HTTPS 协议要比 http 协议
安全
,https 是具有安全性的 ssl 加密传输协议,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。 - http 协议的
默认端口
为 80,https 的默认端口为 443。 - http 的连接很简单,是无状态的。https 握手阶段比较
费时
,会使页面加载时间延长 50%,增加 10%~20%的耗电。 - https
缓存
不如 http 高效,会增加数据开销。 - Https 协议需要 ca 证书,费用较高,功能越强大的
证书费
用越高。 - SSL 证书需要绑定
IP
,不能再同一个 IP 上绑定多个域名,IPV4 资源支持不了这种消耗。
https 协议的工作原理
客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤:
- 客户端使用 https url 访问服务器,则要求 web 服务器
建立 ssl 链接
。 - web 服务器接收到客户端的请求之后,会
将网站的证书(证书中包含了公钥),传输给客户端
。 - 客户端和 web 服务器端开始
协商 SSL 链接的安全等级
,也就是加密等级。 - 客户端浏览器通过双方协商一致的安全等级,
建立会话密钥
,然后通过网站的公钥来加密会话密钥,并传送给网站。 - web 服务器
通过自己的私钥解密出会话密钥
。 - web 服务器
通过会话密钥加密与客户端之间的通信
。
HTTP 请求跨域问题
跨域的原理
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的
同源策略
造成的。
同源策略,是浏览器对 JavaScript 实施的安全限制,只要协议、域名、端口
有任何一个不同,都被当作是不同的域。
跨域原理,即是通过各种方式,避开浏览器的安全限制
。解决方案
最初做项目的时候,使用的是jsonp,但存在一些问题,使用get请求不安全,携带数据较小,后来也用过iframe,但只有主域相同才行,也是存在些问题,后来通过了解和学习发现使用代理和proxy代理配合起来使用比较方便,就引导后台按这种方式做下服务器配置,在开发中使用proxy,在服务器上使用nginx代理,这样开发过程中彼此都方便,效率也高;现在h5新特性还有 windows.postMessage()
JSONP:
ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链 接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是 返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。步骤:
- 去创建一个script标签
- script的src属性设置接口地址
- 接口参数,必须要带一个自定义函数名,要不然后台无法返回数据
- 通过定义函数名去接受返回的数据
1 |
|
- 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代理 目前常用方式,通过服务器设置代理
- window.postMessage() 利用h5新特性window.postMessage()
Cookie、sessionStorage、localStorage 的区别
相同点:
- 存储在客户端
不同点:
- cookie数据大小不能超过4k;sessionStorage和localStorage的存储比cookie大得多,可以达到5M+
- cookie设置的过期时间之前一直有效;localStorage永久存储,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除
- cookie的数据会自动的传递到服务器;sessionStorage和localStorage数据保存在本地
TCP
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连接成功)状态,完成三次握手。
TCP 四次挥手
客户端进程发出连接释放报文
,并且停止发送数据。释放数据报文首部,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连接的时间要比客户端早一些
。
TCP/IP / 如何保证数据包传输的有序可靠?
对字节流分段并进行编号然后通过 ACK 回复
和超时重发
这两个机制来保证。
- (1)为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区;
- 并为每个已发送的数据包启动一个超时定时器;
- 如在定时器超时之前收到了对方发来的应答信息(可能是对本包的应答,也可以是对本包后续包的应答),则释放该数据包占用的缓冲区;
- 否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止。
- 接收方收到数据包后,先进行CRC校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去。
TCP和UDP的区别
- TCP是面向
链接
的,而UDP是面向无连接的。 - TCP仅支持
单播传输
,UDP 提供了单播,多播,广播的功能。 - TCP的三次握手保证了连接的
可靠性
; UDP是无连接的、不可靠的一种数据传输协议,首先不可靠性体现在无连接上,通信都不需要建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收。 - UDP的
头部开销
比TCP的更小,数据传输速率更高
,实时性更好
。
粘包问题分析与对策
CP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
粘包出现原因
简单得说,在流传输中出现,UDP不会出现粘包,因为它有消息边界
粘包情况有两种,一种是粘在一起的包都是完整的数据包
,另一种情况是粘在一起的包有不完整的包
。
为了避免粘包现象,可采取以下几种措施:
- 对于发送方引起的粘包现象,用户可通过编程设置来避免,
TCP提供了强制数据立即传送的操作指令push
,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满; - 对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、
提高接收进程优先级等措施
,使其及时接收数据,从而尽量避免出现粘包现象; - 由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。
分包多发
。
以上提到的三种措施,都有其不足之处。
- 第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。
- 第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。
- 第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。
一种比较周全的对策是:接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开。实验证明这种方法是高效可行的。
浏览器
从输入URL到页面加载的全过程
- 首先在浏览器中输入URL
- 查找缓存:浏览器先查看浏览器缓存-系统缓存-路由缓存中是否有该地址页面,如果有则显示页面内容。如果没有则进行下一步。
- 浏览器缓存:浏览器会记录DNS一段时间,因此,只是第一个地方解析DNS请求;
- 操作系统缓存:如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统, 获取操作系统的记录(保存最近的DNS查询缓存);
- 路由器缓存:如果上述两个步骤均不能成功获取DNS记录,继续搜索路由器缓存;
- ISP缓存:若上述均失败,继续向ISP搜索。
- DNS域名解析:浏览器向DNS服务器发起请求,解析该URL中的域名对应的IP地址。
DNS服务器是基于UDP的,因此会用到UDP协议。
。 - 建立TCP连接:解析出IP地址后,根据IP地址和默认80端口,和服务器建立TCP连接
- 发起HTTP请求:浏览器发起读取文件的HTTP请求,,该请求报文作为TCP三次握手的第三次数据发送给服务器
- 服务器响应请求并返回结果:服务器对浏览器请求做出响应,并把对应的html文件发送给浏览器
- 关闭TCP连接:通过四次挥手释放TCP连接
- 浏览器渲染:客户端(浏览器)解析HTML内容并渲染出来,浏览器接收到数据包后的解析流程为:
- 构建DOM树:词法分析然后解析成DOM树(dom tree),是由dom元素及属性节点组成,树的根是document对象
- 构建CSS规则树:生成CSS规则树(CSS Rule Tree)
- 构建render树:Web浏览器将DOM和CSSOM结合,并构建出渲染树(render tree)
- 布局(Layout):计算出每个节点在屏幕中的位置
- 绘制(Painting):即遍历render树,并使用UI后端层绘制每个节点。
JS引擎解析过程:调用JS引擎执行JS代码(JS的解释阶段,预处理阶段,执行阶段生成执行上下文,VO,作用域链、回收机制等等)
- 创建window对象:window对象也叫全局执行环境,当页面产生时就被创建,所有的全局变量和函数都属于window的属性和方法,而DOM Tree也会映射在window的doucment对象上。当关闭网页或者关闭浏览器时,全局执行环境会被销毁。
- 加载文件:完成js引擎分析它的语法与词法是否合法,如果合法进入预编译
- 预编译:在预编译的过程中,浏览器会寻找全局变量声明,把它作为window的属性加入到window对象中,并给变量赋值为’undefined’;寻找全局函数声明,把它作为window的方法加入到window对象中,并将函数体赋值给他(匿名函数是不参与预编译的,因为它是变量)。而变量提升作为不合理的地方在ES6中已经解决了,函数提升还存在。
- 解释执行:执行到变量就赋值,如果变量没有被定义,也就没有被预编译直接赋值,在ES5非严格模式下这个变量会成为window的一个属性,也就是成为全局变量。string、int这样的值就是直接把值放在变量的存储空间里,object对象就是把指针指向变量的存储空间。函数执行,就将函数的环境推入一个环境的栈中,执行完成后再弹出,控制权交还给之前的环境。JS作用域其实就是这样的执行流机制实现的。
浏览器重绘与回流的区别?
重排/回流(Reflow)
:当DOM
的变化影响了元素的几何信息,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。表现为重新生成布局,重新排列元素。重绘(Repaint)
: 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。表现为某些元素的外观被改变
单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分
重排和重绘代价是高昂的,它们会破坏用户体验,并且让UI展示非常迟缓,而相比之下重排的性能影响更大,在两者无法避免的情况下,一般我们宁可选择代价更小的重绘。
『重绘』不一定会出现『重排』,『重排』必然会出现『重绘』。
如何触发回流和重绘?
任何改变用来构建渲染树的信息都会导致一次回流或重绘:
- 添加、删除、更新DOM节点
- 通过display: none隐藏一个DOM节点-触发回流和重绘
- 通过visibility: hidden隐藏一个DOM节点-只触发重绘,因为没有几何变化
- 移动或者给页面中的DOM节点添加动画
- 添加一个样式表,调整样式属性
- 用户行为,例如调整窗口大小,改变字号,或者滚动。
如何避免回流和重绘?
集中改变样式
,不要一条一条地修改 DOM 的样式。不要把 DOM 结点的属性值放在循环里当成循环里的变量。
为动画的 HTML 元件使用
fixed
或absoult
的position
,那么修改他们的 CSS 是不会 reflow 的。不使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。
尽量只修改
position:absolute
或fixed
元素,对其他元素影响不大动画开始
GPU
加速,translate
使用3D
变化提升为合成层
将元素提升为合成层有以下优点:
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
- 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
- 对于 transform 和 opacity 效果,不会触发 layout 和 paint
提升合成层的最好方式是使用 CSS 的 will-change 属性:
1 |
|
HTML && CSS
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选择器(0100)
- 类选择器/属性选择器/伪类选择器(0010)
- 元素选择器/伪元素选择器(0001)
- 关系选择器/通配符选择器(0000)
带!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 值)
让一个元素水平垂直居中
水平居中
对于 行内元素 :
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%。(4)flex布局使用
justify-content:center
垂直居中
- 利用
line-height
实现居中,这种方法适合纯文字类 - 通过设置父容器 相对定位 ,子级设置
绝对定位
,标签通过margin实现自适应居中 - 弹性布局 flex :父级设置display: flex; 子级设置margin为auto实现自适应居中
- 父级设置相对定位,子级设置绝对定位,并且通过位移 transform 实现
table 布局
,父级通过转换成表格形式,然后子级设置 vertical-align 实现
。(需要注意的是:vertical-align: middle使用的前提条件是内联元素以及display值为table-cell的元素)
- 利用
隐藏页面中某个元素的方法
1.opacity:0
,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定 一些事件,如click 事件,那么点击该区域,也能触发点击事件的
2.visibility:hidden
,该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已 经绑定的事件 ,隐藏对应元素,在文档布局中仍保留原来的空间(重绘)
3.display:none
,把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素。 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)
用CSS实现三角符号
1 |
|
页面布局
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适配的优缺点
- 在我们设计图上所量取的大小即为我们可以设置的像素大小,即所量即所设
- 缺点破坏完美视口
清除浮动的方式
- 添加额外标签
1 |
|
- 父级添加overflow属性,或者设置高度
- 建立伪类选择器清除浮动
JS、TS、ES6
JS中的8种数据类型及区别
包括值类型(基本对象类型)和引用类型(复杂对象类型)
基本类型(值类型): Number(数字),String(字符串),Boolean(布尔),Symbol(符号),null(空),undefined(未定义)在内存中占据固定大小,保存在栈内存中
引用类型(复杂数据类型): Object(对象)、Function(函数)。其他还有Array(数组)、Date(日期)、RegExp(正则表达式)、特殊的基本包装类型(String、Number、Boolean) 以及单体内置对象(Global、Math)等 引用类型的值是对象 保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址。
JS中的数据类型检测方案
1.typeof
优点:能够快速区分基本数据类型
缺点:不能将Object、Array和Null区分,都返回object
2.instanceof
优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象
缺点:Number,Boolean,String基本数据类型不能判断
3.Object.prototype.toString.call()
优点:精准判断数据类型
缺点:写法繁琐不容易记,推荐进行封装后使用
var && let && const
ES6之前创建变量用的是var,之后创建变量用的是let/const
三者区别:
- var定义的变量,
没有块的概念,可以跨块访问
, 不能跨函数访问。
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。 - var可以
先使用,后声明
,因为存在变量提升;let必须先声明后使用。 - var是允许在相同作用域内
重复声明同一个变量
的,而let与const不允许这一现象。 - 在全局上下文中,基于let声明的全局变量和全局对象GO(window)没有任何关系 ;
var声明的变量会和GO有映射关系; 会产生暂时性死区
:
1 |
|
- let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于let的块作用特征即可解决
JS垃圾回收机制
- 项目中,如果存在大量不被释放的内存(堆/栈/上下文),页面性能会变得很慢。当某些代码操作不能被合理释放,就会造成内存泄漏。我们尽可能减少使用闭包,因为它会消耗内存。
- 浏览器垃圾回收机制/内存回收机制:
1 |
|
标记清除:在
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)保存:如果当前上下文不被释放【只要上下文中的某个东西被外部占用即可】,则存储的这些私有变量也不会被释放,可以供其下级上下文中调取使用,相当于把一些值保存起来了;
我们把函数执行形成私有上下文,来保护和保存私有变量机制称为闭包
。
1 |
|
稍全面的回答: 在js中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
闭包的特性:
1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。
1.1.闭包是密闭的容器,,类似于set、map容器,存储数据的
1.2.闭包是一个对象,存放数据的格式为 key-value 形式
2、函数嵌套函数
3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除
闭包形成的条件:
- 函数的嵌套
- 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
闭包的用途:
- 模仿块级作用域
- 保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
- 封装私有化变量
- 创建模块
闭包应用场景
闭包的两个场景,闭包的两大作用:
保存/保护
。 在开发中, 其实我们随处可见闭包的身影, 大部分前端JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送ajax请求成功|失败的回调;setTimeout的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。闭包的优点:延长局部变量的生命周期
闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
JS 中 this 的五种情况
- 作为普通函数执行时,
this
指向window
。 - 当函数作为对象的方法被调用时,
this
就会指向该对象
。 - 构造器调用,
this
指向返回的这个对象
。 - 箭头函数 箭头函数的
this
绑定看的是this所在函数定义在哪个对象下
,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。 - 基于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 事件循环
1 |
|
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
介绍节流防抖原理、区别以及应用
节流
:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
防抖
:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!
使用场景
:
节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……
防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。
JavaScript
\1. Promise 的理解
Promise 是一种为了避免回调地狱的异步解决方案 2. Promise 是一种状态机: pending(进行中)、fulfilled(已成功)和rejected(已失败) 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
回调地狱
回调函数中嵌套回调函数的情况就叫做回调地狱。
回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
一、Promise是什么?
Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大。
ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
二、Promise是为解决什么问题而产生的?
promise是为解决异步处理回调金字塔问题而产生的
三、Promise的两个特点
1、Promise对象的状态不受外界影响
1)pending 初始状态
2)fulfilled 成功状态
3)rejected 失败状态
Promise 有以上三种状态,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态
2、Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled或者由pending变成rejected
四、Promise的三个缺点
1)无法取消Promise,一旦新建它就会立即执行,无法中途取消
2)如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
3)当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成
五、Promise在哪存放成功回调序列和失败回调序列?
1)onResolvedCallbacks 成功后要执行的回调序列 是一个数组
2)onRejectedCallbacks 失败后要执行的回调序列 是一个数组
以上两个数组存放在Promise 创建实例时给Promise这个类传的函数中,默认都是空数组。
每次实例then的时候 传入 onFulfilled 成功回调 onRejected 失败回调,如果此时的状态是pending 则将onFulfilled和onRejected push到对应的成功回调序列数组和失败回调序列数组中,如果此时的状态是fulfilled 则onFulfilled立即执行,如果此时的状态是rejected则onRejected立即执行
上述序列中的回调函数执行的时候 是有顺序的,即按照顺序依次执行
\2. 箭头函数和普通函数的区别
箭头函数与普通函数的区别在于: 1、箭头函数没有this,所以需要通过查找作用域链来确定this的值,这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this, 2、箭头函数没有自己的arguments对象,但是可以访问外围函数的arguments对象 3、不能通过new关键字调用,同样也没有new.target值和原型
1、语法更加简洁、清晰
2、箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。
3、箭头函数继承而来的this指向永远不变
4、.call()/.apply()/.bind()无法改变箭头函数中this的指向
5、箭头函数不能作为构造函数使用
6、箭头函数没有自己的arguments,可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表
7、箭头函数没有原型prototype
8、箭头函数不能用作Generator函数,不能使用yeild关键字
9、箭头函数不具有super,不具有new.target
\3. ES6新特性
1、let( let 允许创建块级作用域(最靠近的一个花括号内有效),不具备变量提升,不允许重复声明: )、const( const 允许创建块级作用域(最靠近的一个花括号内有效)、变量声明不提升、const 在声明时必须被赋值、声明时大写变量(默认规则): )、block作用域
2、箭头函数 ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体:
3、函数默认参数值
ES6 中允许你对函数参数设置默认值:
4、对象超类
ES6 允许在对象中使用 super 方法:
5、Map VS WeakMap
ES6 中两种新的数据结构集:Map 和 WeakMap。事实上每个对象都可以看作是一个 Map。
一个对象由多个 key-val 对构成,在 Map 中,任何类型都可以作为对象的 key,如:
6、类
ES6 中有 class 语法。值得注意是,这里的 class 不是新的对象继承模型,它只是原型链的语法糖表现形式。
函数中使用 static 关键词定义构造函数的的方法和属性:
\4. Var let const 的区别
共同点:都能声明变量
不同点:var 在ECMAScript 的所有版本中都可以使用,而const和let只能在ECMAScript6【ES2015】及更晚中使用
var let const
作用域 函数作用域 块作用域 块作用域
声明提升 能 不能 不能
重复声明 能 不能 不能
全局声明时为window对象的属性 是 不是 不是
var
ECMAScript6 增加了let 和 const 之后要尽可能少使用var。因为let 和 const 申明的变量有了更加明确的作用域、声明位置以及不变的值。
优先使用const来声明变量,只在提前知道未来会修改时,再使用let。
let
因为let作用域为块作用域!!!!【得要时刻记住这一点】
不能进行条件式声明
for循环使用let来声明迭代变量不会导致迭代变量外渗透。
const
声明时得直接初始化变量,且不能修改const声明的变量的值
该限制只适用于它指向的变量的引用,如果它一个对象的,则可以修改这个对象的内部的属性。
\5. 实现继承的几种方式
原型链继承
父类的实例作为子类的原型
function Woman(){
}
Woman.prototype= new People();
Woman.prototype.name = ‘haixia’;
let womanObj = new Woman();
优点:
简单易于实现,父类的新增的实例与属性子类都能访问
缺点:
可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
无法实现多继承
创建子类实例时,不能向父类构造函数中传参数
借用构造函数继承(伪造对象、经典继承)
复制父类的实例属性给子类
function Woman(name){
//继承了People
People.call(this); //People.call(this,’wangxiaoxia’);
this.name = name || ‘renbo’
}
let womanObj = new Woman();
优点:
解决了子类构造函数向父类构造函数中传递参数
可以实现多继承(call或者apply多个父类)
缺点:
方法都在构造函数中定义,无法复用
不能继承原型属性/方法,只能继承父类的实例属性和方法
实例继承(原型式继承)
function Wonman(name){
let instance = new People();
instance.name = name || ‘wangxiaoxia’;
return instance;
}
let wonmanObj = new Wonman();
优点:
不限制调用方式
简单,易实现
缺点:不能多次继承
\6. Null 和 undefined 的区别
undefined和null的区别:. ● undefined 表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化),一个没有传入实参的形参变量的值为undefined,如果一个函数什么都不返回,则该函数默认返回undefined。. null 则表示”什么都没有”,即”空值”。. ● Javascript将未赋值的变量默认值设为 undefined ;Javascript从来不会将变量设为 null 。. 它是用来让程序员表明某个用var声明的变量时没有值的;
\7. Call bind apply的区别
apply方法
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
call方法
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
bind方法
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
\8. 前端缓存的理解 或者 前端数据持久化的理解
前端缓存分为HTTP缓存和浏览器缓存
其中HTTP缓存是在HTTP请求传输时用到的缓存,主要在服务器代码上设置;而浏览器缓存则主要由前端开发在前端js上进行设置。
缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种,如下:
①不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求
②存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存
③存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
①协商缓存生效,返回304
②协商缓存失效,返回200和请求结果
\9. 防抖和节流
防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版
\10. 闭包
1、变量作用域
要理解闭包,首先要理解 JavasSript 的特殊的变量作用域。
变量的作用域无非就两种:全局变量和局部变量。
JavasSript 语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。
注意点:在函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明的是一个全局变量!
2、如何从外部读取函数内部的局部变量?
出于种种原因,我们有时候需要获取到函数内部的局部变量。但是,上面已经说过了,正常情况下,这是办不到的!只有通过变通的方法才能实现。
那就是在函数内部,再定义一个函数。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。
这就是 JavasSript 语言特有的”链式作用域”结构(chain scope),
子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取它的内部变量了吗!
3、闭包的概念
上面代码中的 f2 函数,就是闭包。
各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。
由于在 JavaScript 中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成”定义在一个函数内部的函数”。
所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
4、闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在 f1 调用后被自动清除。
为什么会这样呢?原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是 “nAdd=function(){n+=1}” 这一行,首先在 nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。
5、使用闭包的注意点
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值
\11. 数组去重
一、利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(new Set(arr))
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
//[1, “true”, true, 15, false, undefined, null, NaN, “NaN”, 0, “a”, {}, {}]
不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。
二、利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j–;
}
}
}
return arr;
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
//[1, “true”, 15, false, undefined, NaN, NaN, “NaN”, “a”, {…}, {…}] //NaN和{}没有去重,两个null直接消失了
双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
三、利用indexOf去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log(‘type error!’)
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
// [1, “true”, true, 15, false, undefined, null, NaN, NaN, “NaN”, 0, “a”, {…}, {…}] //NaN、{}没有去重
新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。
四、利用sort()
function unique(arr) {
if (!Array.isArray(arr)) {
console.log(‘type error!’)
return;
}
arr = arr.sort()
var arrry= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
// [0, 1, 15, “NaN”, NaN, NaN, {…}, {…}, “a”, false, null, true, “true”, undefined] //NaN、{}没有去重
利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。
六、利用includes
function unique(arr) {
if (!Array.isArray(arr)) {
console.log(‘type error!’)
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
//[1, “true”, true, 15, false, undefined, null, NaN, “NaN”, 0, “a”, {…}, {…}] //{}没有去重
七、利用hasOwnProperty
function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
//[1, “true”, true, 15, false, undefined, null, NaN, “NaN”, 0, “a”, {…}] //所有的都去重了
利用hasOwnProperty 判断是否存在对象属性
八、利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
//[1, “true”, true, 15, false, undefined, null, “NaN”, 0, “a”, {…}, {…}]
九、利用递归去重
function unique(arr) {
var array= arr;
var len = array.length;
array.sort(function(a,b){ //排序后更加方便去重
return a - b;
})
function loop(index){
if(index >= 1){
if(array[index] === array[index-1]){
array.splice(index,1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len-1);
return array;
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
//[1, “a”, “true”, true, 15, false, 1, {…}, null, NaN, NaN, “NaN”, 0, “a”, {…}, undefined]
十、利用Map数据结构去重
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有该key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果没有该key值
array .push(arr[i]);
}
}
return array ;
}
var arr = [1,1,’true’,’true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,’NaN’, 0, 0, ‘a’, ‘a’,{},{}];
console.log(unique(arr))
//[1, “a”, “true”, true, 15, false, 1, {…}, null, NaN, NaN, “NaN”, 0, “a”, {…}, undefined]
创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果
\12. 深浅拷贝
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
深拷贝和浅拷贝的示意图大致如下:
示意图
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
\13. 原型链
那什么是原型链呢?
简单理解就是原型组成的链,对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了。
原型对象和实例之间有什么作用呢?
通过一个构造函数创建出来的多个实例,如果都要添加一个方法,给每个实例去添加并不是一个明智的选择。这时就该用上原型了。
在实例的原型上添加一个方法,这个原型的所有实例便都有了这个方法。
prototype:
prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象; 这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象);
proto:
proto 是原型链查询中实际用到的,它总是指向 prototype,换句话说就是指向构造函数的原型对象,它是对象独有的。注意,为什么Foo构造也有这个属性呢,因为再js的宇宙里万物皆对象,包括函数
constructor:
我们看到途中最中间灰色模块有一个constructor属性,这个又是做什么用的呢?**
**
每个函数都有一个原型对象,该原型对象有一个constructor属性,指向创建对象的函数本身。
此外,我们还可以使用constructor属性,所有的实例对象都可以访问constructor属性,constructor属性是创建实例对象的函数的引用。我们可以使用constructor属性验证实例的原型类型(与操作符instanceof非常类似)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tbxes1Jd-1646559635648)(D:\Typora\原型链.png)]
\14. Require 和 import
require和import的区别
1.import在代码编译时被加载,所以必须放在文件开头,require在代码运行时被加载,所以require理论上可以运用在代码的任何地方,所以import性能更好。
2.import引入的对象被修改时,源对象也会被修改,相当于浅拷贝,require引入的对象被修改时,源对象不会被修改,官网称值拷贝,我们可以理解为深拷贝。
3.import有利于tree-shaking(移除JavaScript上下文中未引用的代码),require对tree-shaking不友好。 4.import会触发代码分割(把代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件),require不会触发。
5.import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法,require 是 AMD规范引入方式。
目前所有的引擎都还没有实现import,import最终都会被转码为require,在webpack打包中,import和require都会变为_webpack_require_。
CSS
\1. 常见的块级元素和行内块元素,以及它们有何不同
块级元素和内联元素的区别:
1.块级元素,宽度默认是它容器的100%,各占据一行,垂直方向排列;内联元素,都是同一行,水平方向排列;
2.块级元素,能容纳其他块元素或者内联元素;内联元素,只能容纳文本或其他内联元素;
3.块级元素中height,line-height以及margin和padding都可以控制;行内元素设置width无效,height无效(可以设置line-height),margin上下无效,padding上下无效
2.行内元素和块级元素有哪些
常见的内联元素:
a - 锚点
select - 项目选择
span - 常用内联容器,定义文本内区块
strong - 粗体强调
img - 图片
input - 输入框
label - 表格标签
textarea - 多行文本输入框
br - 换行
常见的块级元素
div
ul ,li 非排序列表
form - 交互表单
table - 表格
h1 -h6 标题
hr - 水平分隔线
p - 段落
dl -dt-dd - 定义列表
address - 地址
blockquote - 块引用
fieldset - form控制组
\2. 常见选择器
标签选择器
HTML标签名称作为选择器
//语法
标签名 {
属性1:属性值1;
属性2:属性值2;
属性3:属性值3;
…
类选择器
//语法
.类名 {
属性1.属性值1;
属性2.属性值2;
属性3.属性值3;
…
}
id选择器
#id名 {
属性1:属性值1;
…
口诀:样式#定义,结构id调用,只能调用一次,别人切勿使用
通配符选择器
选取页面中所有元素(标签)
{
属性名1: 属性名1;
…
}CSS的复合选择器
建立在基础选择器之上,对基础选择器进行组合形成的
后代选择器/包含选择器(重要)
#元素2只要包含在元素1里面即可,无论是儿子还是孙子
子选择器(重要)
只能选择某元素的最近一级的子元素
元素1>元素2 {样式声明}
并集选择器(重要)
可以选择多组标签为他们定义相同的样式,通常用于集体声明
标签1,标签2 {样式声明}
约定语法规范:并集选择器喜欢竖着写
伪类选择器
可以为某些选择器添加一些特殊的效果
链接伪类选择器
#写的时候按照顺序来
a:link #选择未被访问的链接
a:visited #选择所有已经被访问过的链接
a:hover #选择鼠标指针位于其上的链接
a:active #选择活动链接(鼠标按下未弹起的链接)
:foucus伪类选择器
用于选取获得焦点的表单元素
input:foucus {
background-color: red;
}
\3. px em 和 rem的区别
一、px是固定的像素,一旦设置了就无法因为适应页面大小而改变。
二、em和rem相对于px更具有灵活性,他们是相对长度单位,意思是长度不是定死了的,更适用于响应式布局。
三、em是相对于其父元素来设置字体大小的,一般都是以的“font-size”为基准。这样就会存在一个问题,进行任何元素设置,都有可能需要知道他父元素的大小。而Rem是相对于根元素,这样就意味着,我们只需要在根元素确定一个参考值
总之:对于em和rem的区别一句话概括:
em相对于父元素,rem相对于根元素。
\4. 水平垂直居中的几种方法
1 使用flex布局
利用flex的alignItems:center垂直居中,justifycontent:center水平居中
2 利用相对定位和绝对定位的margin:auto
相对定位下,使用绝对定位将上下左右都设置为0,再设置margin:auto即可实现居中
3 利用相对定位和绝对定位,再加上外边距和平移的配合
相对定位下,使用绝对定位,利用margin偏移外容器的50%,再利用translate平移回补自身宽高的50%即可
4 利用textAlign和verticalAlign
利用textAlign:center实现行内元素的水平居中,再利用verticalAlign:middle实现行内元素的垂直居中,前提是要先加上伪元素并给设置高度为100%,用过elementUI的可以去看看其消息弹窗居中实现方式就是如此
5 其他
上面都是在未知外容器和自身宽高下实现水平垂直居中的,如果已知其宽高,可以有更多种简单的方式实现居中,其原理无非是利用绝对定位的top/left偏移、margin偏移、padding填充,在此就不分析了。还有就是单纯文字的居中利用lineHeight和textAlign即可实现。
\5. 盒模型的理解
盒模型包括margin、border、padding、content四个部分,主要的设置属性是margin、border、padding。
盒子模型又分为两种W3C和IE盒子。
W3C的元素宽度=content的宽度
IE的元素宽度=content+padding+border
盒模型就是用来做容器,为了把内容打包和整理,为了不让页面显得杂乱无章。一个好的包装能够给用户不一样地体验。并且如果没有好的包装,再好的内容也不会也看下去的欲望。这就像一个干净整洁的桌面和堆满杂物的桌面给人的观感效果。
所以,合理灵活的应用好盒模型是前端的必要基础!!!
\6. Flex布局
一、Flex 布局是什么?
Flex 是 Flexible Box 的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。
任何一个容器都可以指定为 Flex 布局。
注意,设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。
二、基本概念
采用 Flex 布局的元素,称为 Flex 容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称”项目”。
三、容器的属性
以下6个属性设置在容器上。
flex-direction
flex-wrap
flex-flow
justify-content
align-items
align-content
\7. 怎么解决浮动中塌陷的问题
第一种:开启BFC
根据W3C的标准,在页面中元素都有一个隐含的属性 Block Formatting Context,简称BFC,默认是关闭的;
开启元素BFC后,元素将会具有以下特性:
父元素的垂直外边距不会和子元素重叠
开启BFC的元素不会被浮动元素所覆盖
开启BFC的元素可以包含浮动元素
开启BFC的方法:。
** overflow【常见,副作用最小】**
overflow设置为非visible的值。推荐使用hidden(副作用最小)
a) auto (溢出显示滚动条)
b) scroll (默认就显示滚动条)
c) hidden (溢出隐藏)[常用]
注:但在IE6及以下的浏览器中不支持BFC,所以使用这种方式不兼容IE6。在IE6中有类似BFC的隐含属性 hasLayout,开启方式很多,推荐使用zoom:1
第二种:在浮动元素后添加元素,并设置其clear属性
第三种:br元素的clear属性
br元素本身没有高度,所以也就不会存在IE低版本下最小高度问题。只需要在浮动元素下添加一句 :
※第四种:after伪类【各大公司推荐】
可以通过after伪类向元素的最后添加一个空白的块元素,然后对其清除浮动,和第二种方法原理相同,可达到相同的效果,而且不会在页面中添加多余的div,这是最推荐的方式,几乎没有副作用
\8. CSS3新特性
CSS3 是 CSS 规范的最新版本,在 CSS2.1 的基础上增加了很多强大的新功能,以帮助开发人员解决一些实际面临的问题,并且不再需要非语义标签、复杂的 JavaScript 脚本以及图片。 例如,CSS3 支持圆角、多背景、透明度、阴影、动画、图表等功能。 CSS1 和 CSS2.1 都是单一的规范,其中 CSS1 主要定义了网页对象的基本样式,如字体、颜色、背景、边框等,CSS2 添加了高级概念,如浮动、定位、高级选择器(如子选择器、相邻选择器和通用选择器等)。 整个 CSS3 的规范发布不会因为部分存在争论而影响其他模块的推进。 对于浏览器来说,可以根据需要,决定哪些 CSS 功能被支持。
\9. 前端常见的布局方式
一、静态布局
静态布局是最为原始的布局方式,没有什么技术性可言,往往是计算机行业刚刚入门的小白使用的布局方式。制作的网页上的元素尺寸一律以px为单位
布局特点: 页面上的布局是按最初写代码时候的布局方式进行布局的,常规的pc网站是进行设置了宽度值进行布局的,不会随着pc端的屏幕的大小而变化。
优点: 这种布局方式不管是对资深的前端开发工程师还是刚入门的小白来说都是最简单的,最让人容易以接受、学习的,没有我们所说的兼容性的问题。这种布局方式大多用在门户网站和企业的官网上,这些官网的设备的尺寸是固定的,这种布局方式往往是最简单的方法。
缺点: 不会随着pc端的屏幕大小而变化。
二、弹性布局(flexbox)
弹性布局可以简便、完整、响应的实现各种页面上的布局。与静态不同的是,使用em或rem单位(lem=16px,1rem=10px)进行相对布局,相对使用百分比更加方便、灵活,相应同时支持浏览器的字体大小调整和缩放的等正常显示。
优点:
1.适应性强,在做多种不同的屏幕分辨率不同的界面是非常使用。
2.随意按照宽度、比例划分元素的宽高。
3.可以轻松的改变元素的显示顺序。
4.网页布局实现快捷,维护起来更加容易。
如果做移动端时,如果客户对细微的之处的要求不高,使用弹性布局进行制作是最好的选择,一份css+一份js调节font-size搞定。
缺点: 浏览器兼容性较差,只能兼容到IE9及以上。
三、自适应布局(bootstrap)
自适应布局分别为不同屏幕不同分辨率定义布局,即是创建多个静态页面,每个静态页面对应一个屏幕分辨率的一个范围内。在改变不同的屏幕分辨率可以切换到不同的静态布局上,但是布局中的元素位置会发生改变,但是在每个静态布局中,页面中的元素不会随着窗口大小的调整发生变化。使用 @media 媒体查询给不同尺寸和介质的设备切换不同的样式。在优秀的响应范围设计下可以给适配范围内的设备最好的体验,在同一个设备下实际还是固定的布局。
优点:
1.对网站的复杂程度兼容性更大;
2.对开发工程师来说制作的成本代价更低;
3.代码执行效果更高效;
4.测试时更加容易,运营相对更加精准。
缺点: 在现如今的移动端设计百花齐放的时期之下,同一个网站往往需要为不同的设备制作不同的页面,不但会增加开发成本,还会因为客户的需求改变时,可能会改动多套代码、流程相比较来说较繁琐。
四、流式布局(fluid)
流式布局的布局方式是页面的元素的宽度按照屏幕的分辨率进行适配的调整,但是整体布局不变,也称之为栅栏系统。使用%百分比定义宽度,高度大都是用px来固定住,可以根据可视区域 (viewport) 和父元素的实时尺寸进行调整,尽可能的适应各种分辨率。往往配合 max-width/min-width 等属性控制尺寸流动范围以免过大或者过小影响阅读。
缺点: 屏幕大小变化时,页面元素也随之变化但是布局不变。这就会因为如果屏幕太大或太小都会布局时元素无法正常显示。
五、响应式布局
响应式布局是css3增加的新布局方式,该布局方式2010年提出来的一个概念,说白了就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本。这个概念是为解决移动互联网浏览而诞生的。响应式布局可以为不同终端的用户提供更加舒适的界面和更好的用户体验,而且随着目前大屏幕移动设备的普及,用“大势所趋”来形容也不为过。响应式几乎成为优秀页面布局的标准。
设计方法: 媒体查询+流式布局。通常使用@media媒体查询,和网格系统配合相对布局单位进行布局,实际上说白了就是综合响应式等技术通过css给单一网页不同设备分辨率返回不式时的技术。
优点: 适应pc端和移动端,如果有足够的耐心,页面效果会很完美。
缺点:
1.只能适应主流的宽高;
2.如果匹配足够多的设备屏幕的大小,对于工程师来说工作量不小,设计更需要多个版本,工作量增大。
六、浮动布局
浮动布局进行调用浮动属性改变页面中元素的位置,浮动布局应该是目前各大网站用的最多的一种布局方式了,但是也特别复杂。浮动元素是脱离文档流的,但不脱离文本流。浮动元素有左浮动(float : left)和右浮动(float : right)两种
优点: 兼容性比较好
缺点: 浮动带来的影响比较多,页面宽度不够的时候会影响布局。
七、定位布局
定位布局时利用position属性控制页面元素设置一些不规则布局。
HTML
\1. HTML的语义化标签
\2. 前端优化的解决方案
我们的优化原则有以下几个:
能缓存的,尽量强缓存。
引入外部资源时不要出现超时、404的状况。
减少HTTP请求数。
合理设置cookie的大小以及过期时间。
合理利用懒加载
网页内容的优化
1、懒加载数据。
首先根据标签的left和top属性判断是否显示在了屏幕中(如果显示在屏幕中,其left和top属性值应该是在0到窗口长宽之间)。
如果显示在屏幕中,则将src标签的内容替换为图片的url。
2、使用外部引入的css和js文件,并且引入的css和js越少越好(HTTP2.0不适用)。
这里可以使用webpack打包这些文件,也可以使用强缓存与协商缓存来缓存这些文件。
3、不要在中缩放图片。
img计算缩放也需要时间
4、避免重定向。
重定向会重新渲染网页。
5、尽量不要用iframe。
因为iframe会阻塞渲染。
6、使用base64编码将图片嵌入到样式表中,减少请求数(由于base64会比一般的图片大一点,只适用于那些体积比较小但是很常用的图片)。
7、使用雪碧图(精灵图):
通过使用background-position:-xpx -ypx;来调整图片的位置,不过HTTP2不适用,原因为HTTP2实际上是多路复用的,只用一个TCP连接,所以多个图片的请求也是在同一个TCP连接里,这样能省下非常多的请求时间,但坏处就是单连接开销很大,如果要传多个大文件,就很麻烦。
8、要有网站小图标favicon.ico。
如果没有小图标,会引起404,拖慢网页加载进度。
9、能使用jpeg就不要用png,能使用png8就不要用png24。
(1)色彩丰富的、大的图片切成jpg的;
(2)尺寸小的,色彩不丰富的和背景透明的切成gif或者png8的;
(3)半透明的切成png24。
10、使用canvas压缩图片。
css的优化
1、避免使用@import。
使用@import相当于将引入的css放在了页面底部,因为使用@import引用的文件只有在引用它的那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载,这时才去下载,然后下载后开始解析、构建render tree等一系列操作。因此使用@import会拖慢渲染的过程。
2、将样式表放在head中。
如果放在body中,可能出现在浏览器下载好css样式表之前,组件就已经加载好了的情况,这可能会导致重新渲染。
3、避免使用css表达式。
如:expression((new Date()).getHours()%2 ? “#B8D4FF” : “#F08A00” );
解析表达式和计算都需要时间。
JavaScript的优化
1、尽量减少DOM访问。
2、使用事件代理(减少DOM操作)。
3、把脚本放在底部(加载脚本时会阻塞页面渲染)。
4、合理使用节流函数和防抖函数。
使用CDN优化加载速度
CDN即内容分发网络。它依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
针对webpack打包优化
针对webpack打包优化主要是减少打包后的代码体积,主要的措施有:
1、进行tree-shaking
2、使用UglifyJS等插件压缩代码
3、分割代码、按需加载
我们可以使用webpack-bundle-analyzer这个插件来查看每部分代码的加载耗时,进而分析可以优化的地方
\3. HTML5新特性
html5总的来说比html4多了十个新特性,但其不支持ie8及ie8以下版本的浏览器
一、语义标签
二、增强型表单
三、视频和音频
四、Canvas绘图
五、SVG绘图
六、地理定位
七、拖放API
八、WebWorker
九、WebStorage
十、WebSocket
详细地址 https://www.cnblogs.com/binguo666/p/10928907.html
\4. 常见的浏览器兼容问题
1.不同浏览器的标签默认的外补丁(margin)和内补丁(padding)不同
解决方案:css里增加通配符*{margin:0;padding:0}
2.IE6双边距问题;在IE6中设置了float,同时又设置margin,就会出现边距问题
解决方案:设置display:inline;
3.当标签的高度设置小于10px,在IE6、IE7中会超出自己设置的高度
解决方案:设置display:inline;
4.图片默认有间距
解决方案:使用float为img布局
5.IE9以下浏览器不能使用opacity
解决方案:opacity:0.5;filter:alfha(opacity=50);filter:progid:DXlmageTransform.Microsoft.Alfha(style=0,opacity=50);
6.边距重叠问题;
解决方案: 当相邻两个元素都设置了margin边距时,margin将取最大值,舍弃最小值;
7.cursor:hand显示手型在safari上不支持
解决方案:统一使用cursor:pointer;
8.两个块级元素,父元素设置了overflow:auto;子元素设置了position:relative;且高度大于父元素,在IE6、IE7会被隐藏而不是溢出;
解决方案:父级元素设置position:relative
9.const问题
说明:Firefox下,可以使用const关键字来定义常量;IE下,只能使用var关键字来定义常量。
解决方法:统一使用var关键字来定义常量。
10.event.srcElement问题
问题说明:IE下,event对象有srcElement属性,但是没有target属性;Firefox下,event对象有target属性,但是没有srcElement属性。
解决方法:使用srcObj = event.srcElement?event.srcElement:event.target;
11.事件绑定
IE:dom.attachEvent();
其他浏览器:dom.addEventListener();
标准浏览器采用事件捕获的方式对应IE的事件冒泡机制(即标准由最外元素至最内元素或者IE由最内元素到最外元素)最后标准方亦觉得IE这方面的比较合理,所以便将事件冒泡纳入了标准,这也是addEventListener第三个参数的由来,而且事件冒泡作为了默认值。
12.操作tr的html
在ie9以下,不能操作tr的innerHTML
13.ajax略有不同
IE:ActiveXObject
其他:xmlHttpReuest
14.对象宽高赋值问题
问题说明:FireFox中类似obj.style.height = imgObj.height的语句无效。
CSS
1.cursor:hand VS cursor:pointerfirefox不支持hand,但ie支持pointer
解决方法: 统一使用pointer
- innerText在IE中能正常工作,但在FireFox中却不行.
需用textContent。
- CSS透明
IE:filter:progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=60)。
FF:opacity:0.6。
- css中的width和padding
在IE7和FF中width宽度不包括padding,在Ie6中包括padding.
- FF和IEBOX模型解释不一致导致相差2px
详细地址 常见的浏览器兼容 - 知乎 (zhihu.com)
Vue
\1. Vuex
1.1 关于VueX
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
状态管理模式。
把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!这就是“状态管理模式”。
应用场景有:单页应用中,组件之间的数据状态。 应用实例: 1、购物车功能; 2、下单页面有选择优惠券按钮,点击进入优惠券页面,选择后返回到下单页,数据会绑定回来,显示已选择的优惠券; 3、登录状态等等
Vuex有哪几种属性?
有五种,分别是 State、 Getter、Mutation 、Action、 Module
Vuex的State特性
1、Vuex就是一个仓库,仓库里面放了很多对象。其中state就是数据源存放地,对应于一般Vue对象里面的data
2、state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据发生改变,依赖这个数据的组件也会发生更新
3、它通过mapState把全局的 state 和 getters 映射到当前组件的 computed 计算属性中
Vuex的Getter特性
1、getters 可以对State进行计算操作,它就是Store的计算属性
2、虽然在组件内也可以做计算属性,但是getters 可以在多组件之间复用
3、如果一个状态只在一个组件内使用,是可以不用getters
Vuex的Mutation特性
Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。
Vuex的Module特性
Module 可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
使用Vuex的好处?
1、多层嵌套的组件、兄弟组件间的状态会更好管理维护。 2、缓存一些当前要使用请求远程或本地的数据集(刷新后会自己销毁)。 3、有了第二条,就可以减少向服务器的请求,节省资源。如果你的用户足够多,那么每多出一个请求,对公司来说,都是一大笔钱。 4、对开发者来说,如果你的项目足够复杂,团队的规模也不仅是一个人,数据集中处理更利于程序的稳定和维护
\2. 双向绑定的原理
MVC模式
以往的MVC模式是单向绑定,即Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新
MVVM模式
MVVM模式就是Model–View–ViewModel模式。它实现了View的变动,自动反映在 ViewModel,反之亦然。对于双向绑定的理解,就是用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。再说细点,就是在单向绑定的基础上给可输入元素input、textare等添加了change(input)事件,(change事件触发,View的状态就被更新了)来动态修改model。
双向绑定原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
\3. 组件间传递数据
父组件向子组件传递数据,使用props属性;子组件向父组件中传递数据,在子组件中使用$emit派发事件,父组件中使用v-on
监听事件;缺点:组件嵌套层次多的话,传递数据比较麻烦。
祖先组件通过依赖注入(inject / provide)的方式,向其所有子孙后代传递数据;缺点:无法监听数据修改的来源,不支持响应式。
通过属性$root / $parent / $children /
ref,访问根组件、父级组件、子组件中的数据;缺点:要求组件之间要有传递性。
通过事件总线(event bus)的方式,可以实现任意两个组件间进行数据传递;缺点:不支持响应式,这个概念是vue1.0版本中的,现在已经废弃。
通过 VueJs 的状态管理模式 Vuex,实现多个组件进行数据共享,推荐使用这种方式进行项目中各组件间的数据传递。
\4. Vue项目优化
Vue 项目性能优化实践 - 知乎 (zhihu.com)
\5. MVVM和MVC
MVC模式
以往的MVC模式是单向绑定,即Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新
MVVM模式
MVVM模式就是Model–View–ViewModel模式。它实现了View的变动,自动反映在 ViewModel,反之亦然。对于双向绑定的理解,就是用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。再说细点,就是在单向绑定的基础上给可输入元素input、textare等添加了change(input)事件,(change事件触发,View的状态就被更新了)来动态修改model。
在MVVM框架下视图和模型是不能直接通信的,只能通过ViewModel进行交互,它能够监听到数据的变化,然后通知视图进行自动更新,而当用户操作视图时,VM也能监听到视图的变化,然后通知数据做相应改动,这实际上就实现了数据的双向绑定。并且V和VM可以进行通信。
MVVM模式的优点:
低耦合:View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性: 可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
独立开发: 开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面的设计。
mvc与mvvm的区别:
MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
- MVC中Controller演变成MVVM中的ViewModel
-MVVM通过数据来显示视图层而不是节点操作
-MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验等问题。
\6. Computed和Watch
computed 和 watch 区分使用场景
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
\7. V-for 和 v-if同时使用的问题
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
(1)v-for 遍历必须为 item 添加 key
在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff 。
(2)v-for 遍历避免同时使用 v-if
v-for 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成 computed 属性
\8. 什么时候使用$.nextTick()
你在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。原因是什么呢,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 vm.someData = ‘new value’,DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
\9. 路由的原理
- 何为前端路由?
路由(Router)这个概念最先是后端出现的,是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。
前端随着 ajax 的流行,数据请求可以在不刷新浏览器的情况下进行。异步交互体验中最盛行的就是 SPA —— 单页应用。单页应用不仅仅是在页面交互时无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。
- 前端Router基本功能
一个基本的前端路由至少应该提供以下功能:
前端Router可以控制浏览器的 history,使的浏览器不会在 URL 发生改变时刷新整个页面。
前端Router需要维护一个 URL 历史栈,通过这个栈可以返回之前页面,进入下一个页面。
前端路由实现原理就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容。但是这样存在一个问题,就是 url 每次变化的时候,都会造成页面的刷新。那解决问题的思路便是在改变 url 的情况下,保证页面的不刷新。目前 Router有两种实现方式 History 和 hash。
History 和 Hash 对比
hash 使用 # 后面的内容模拟一个完整路径,不太美观。
hash 在请求时不会发送给服务器,用户手动刷新页面,后端接受到了也是同一个地址。
History 直接修改浏览器 URL,用户手动刷新页面,后端接受到是不同的地址,需要后端做处理跳转到统一的html页面
\10. 常用的事件修饰符
①.stop:阻止冒泡
–对于嵌套的两级,如果子父级元素都存在click事件,点击子级元素会触发父级元素的事件;如果子级元素设置@click.stop的话就不会触发父级的click事件
②.prevent:阻止默认行为
–对于如<a href=“www.baidu.com” @click.prevent=“linkMethod”>百度自带事件的,添加prevent事件后,href跳转路径将不会触发
③.self:仅绑定元素自身触发,防止事件冒泡
–对于嵌套的两级,如果子父级元素都存在click事件,点击子级元素会触发父级元素的事件;如果父级元素设置@click.self的话就不会被子级元素的click事件影响
④.once: 事件只触发一次(常用表单提交)
⑤.passive: 滚动事件的默认行为 (即滚动行为) 将会立即触发,不能和.prevent 一起使用,浏览器内核线程在每个事件执行时查询prevent,造成卡顿,使用passive将会跳过内核线程查询,进而提升流畅度
⑥.capture: 对于冒泡事件,且存在多个冒泡事件时,存在该修饰符的会优先执行,如果有多个,则从外到内执行
⑦.native: 将vue组件转换为一个普通的HTML标签,如果该修饰符用在普通html标签上是不起任何作用的
浏览器原理相关
\1. 常用HTTP请求
1、GET方法
GET方法用于使用给定的URI从给定服务器中检索信息,即从指定资源中请求数据。使用GET方法的请求应该只是检索数据,并且不应对数据产生其他影响。
在GET请求的URL中发送查询字符串(名称/值对),需要这样写:
/test/demo_form.php?name1=value1&name2=value2
1
说明:
GET请求是可以缓存的,我们可以从浏览器历史记录中查找到GET请求,还可以把它收藏到书签中;且GET请求有长度限制,仅用于请求数据(不修改)。
注:因GET请求的不安全性,在处理敏感数据时,绝不可以使用GET请求。
2、POST方法
POST方法用于将数据发送到服务器以创建或更新资源,它要求服务器确认请求中包含的内容作为由URI区分的Web资源的另一个下属。
POST请求永远不会被缓存,且对数据长度没有限制;我们无法从浏览器历史记录中查找到POST请求。
3、HEAD方法
HEAD方法与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容。
4、PUT方法
PUT方法用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容。
它会将包含的元素放在所提供的URI下,如果URI指示的是当前资源,则会被改变。如果URI未指示当前资源,则服务器可以使用该URI创建资源。
5、DELETE方法
DELETE方法用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容。
6、CONNECT方法
CONNECT方法用来建立到给定URI标识的服务器的隧道;它通过简单的TCP / IP隧道更改请求连接,通常实使用解码的HTTP代理来进行SSL编码的通信(HTTPS)。
7、OPTIONS方法
OPTIONS方法用来描述了目标资源的通信选项,会返回服务器支持预定义URL的HTTP策略。
8、TRACE方法
TRACE方法用于沿着目标资源的路径执行消息环回测试;它回应收到的请求,以便客户可以看到中间服务器进行了哪些(假设任何)进度或增量。
\2. Get和Post的区别
GET方法
GET是获取的意思,顾名思义就是获取信息。
GET是默认的HTTP请求方法。
GET方法把参数通过key/value形式存放在URL里面,如果参数是英文数字原样显示,如果是中文或者其他字符加密(Base64)URL长度一般有限制所以GET方法的参数长度不能太长。由于参数显示再地址栏所以不安全,一般需要保密的请求不使用GET。
POST方法
POST是邮件的意思,顾名思义就像一封信一样将参数放在信封里面传输。它用于修改服务器上的数据,一般这些数据是应该保密的,就像信件一样,信的内容只能收信的人看见。例入当用户输入账号和密码登录时账号和密码作为参数通过HTTP请求传输到服务器,这时候肯定不能用GET方法将账号密码直接显示再URL上,这时候就应该用POST方法保证数据的保密性。
POST和GET的区别
GET提交的数据放在URL中,POST则不会。这是最显而易见的差别。这点意味着GET更不安全(POST也不安全,因为HTTP是明文传输抓包就能获取数据内容,要想安全还得加密)
GET回退浏览器无害,POST会再次提交请求(GET方法回退后浏览器再缓存中拿结果,POST每次都会创建新资源)
GET提交的数据大小有限制(是因为浏览器对URL的长度有限制,GET本身没有限制),POST没有
GET可以被保存为书签,POST不可以。这一点也能感受到。
GET能被缓存,POST不能
GET只允许ASCII字符,POST没有限制
GET会保存再浏览器历史记录中,POST不会。这点也能感受到。
总之,两者之间没有本质区别,区别就在于数据存储的位置。各自有适用环境,根据需求选择合适的方法即可。
\3. 跨域的解决办法
同源策略
同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略来对脚本和请求进行校验,若不同源,则禁止使用。
同源的定义
那如果判断是否同源?主要根据三个维度,域名,协议,端口三个都相同才算同源。
举个 :
同源策略的作用
①无法用js读取非同源的Cookie、LocalStorage 和 IndexDB
这个主要是为了防止恶意网站通过js获取用户其他网站的cookie等用户信息。
②无法用js获取非同源的DOM
防止恶意网站通过iframe获取页面dom,从而窃取页面的信息。
③无法用js发送非同源的AJAX请求
防止恶意的请求攻击服务器窃取数据信息。
那是不是说非同源的请求就无法实现呢?也不是,这就引出了我们本文主要阐述的解决跨域请求问题的方法。
jsonp
jsonp能实现跨域是利用了img、script和link标签自身的跨域能力。
我们知道当img或者script中的src是一个链接的时候,浏览器会请求这个链接获取资源,那么这个链接如果是跨域的,浏览器也会请求,从而达到了跨域请求的一个功能。
用法
var script = document.createElement(‘script’);
script.src = ‘http://localhost:3000/api/test.do?a=1&b=2&callback=cb‘;
$(‘body’).append(script);
function cb(res){
// do something
console.log(res)
}
可以看到,我们创建一个script标签,将src改成我们要请求的接口,并将script添加在body中,那么当浏览器解析到这个script时,会想src对应的服务器发送一个get请求,并将参数带过去。
然后当浏览器接收到服务端返回的数据,就会触发参数中callbak对应的回调函数cb,从而完成整个get请求。
优点
简单粗暴
缺点
①只支持get请求
②需要后台配合,将返回结果包装成callback(res)的形式
防范
那如果黑客植入script脚本通过jsonp的方式对服务器进行攻击,怎么办?
可以通过页面设置的内容安全协议csp进行防范。
cors跨域
cors 是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),它允许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制
cors 需要浏览器和服务器同时支持,整个 CORS通信过程,都是浏览器自动完成不需要用户参与,对于开发者来说,cors的代码和正常的 ajax 没有什么差别,浏览器一旦发现跨域请求,就会添加一些附加的头信息
但是,cors不支持ie10及以下版本。
简单请求和复杂请求
浏览器将cors请求分为简单请求和复杂请求。
简单请求则直接发送请求到服务器,只请求一次。
而复杂请求在正式请求前都会有预检请求,在浏览器中都能看到有OPTIONS请求,用于向服务器请求权限信息的,需要请求两次。
那如何区分是简单请求还是复杂请求呢?
简单请求
简单请求必须要同时满足下面三个条件:
请求方式只能是:GET、POST、HEAD
HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
content-type的类型
类型描述application/json消息主体是序列化后的 JSON 字符串application/x-www-form-urlencoded数据被编码为键值对。这是标准的编码格式multipart/form-data需要在表单中进行文件上传时,就需要使用该格式。常见的媒体格式是上传文件之时使用的text/plain数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符
application/json:
作用: 告诉服务器请求的主题内容是json格式的字符串,服务器端会对json字符串进行解析,
好处: 前端人员不需要关心数据结构的复杂度,只要是标准的json格式就能提交成功。
application/x-www-form-urlencoded:是Jquery的Ajax请求默认方式
作用:在请求发送过程中会对数据进行序列化处理,以键值对形式?key1=value1&key2=value2的方式发送到服务器。
好处: 所有浏览器都支持。
复杂请求
不满足简单请求的条件,那么就是复杂请求。
复杂请求会在正式请求发送之前,先发一个预检请求进行校验,校验通过后才能进行正式请求。
举个
浏览器现在要发送一个put的复杂请求,那么在put请求发送之前,浏览器先发送一个options请求。
options请求头信息:
OPTIONS /cors HTTP/1.1
Origin: localhost:3000
Access-Control-Request-Method: PUT // 表示使用的什么HTTP请求方法
Access-Control-Request-Headers: X-Custom-Header // 表示浏览器发送的自定义字段
Host: localhost:3000
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0…
服务器收到options请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应
options响应头信息
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://localhost:3000 // 表示http://localhost:3000可以访问数据
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
当options请求通过之后发出正式的HTTP请求,倘若options请求不通过,则服务器不允许此次访问,从而抛出错误
options请求通过之后的,浏览器发出发请求
PUT /cors HTTP/1.1
Origin: http://api.zhenai.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0…
options请求缓存
那这样的话,如果页面存在大量的复杂请求,岂不是每个请求前面都要进行一次options的请求,那不会造成大量资源的浪费么?
如果基于cors请求的方法来解决跨域问题,那么复杂请求之前是需要进行一个options的请求的,但我们可以通过对options请求进行缓存来减轻请求的压力。
在options请求中,我们可以通过设置响应头的参数Access-Control-Max-Age来对结果进行缓存
比如: Access-Control-Max-Age: 600 表示对options检验结果进行十分钟的缓存
url变化会导致缓存失效,需要重新验证options请求的返回值
预检不关心post data
header变化,如果是去掉了自定义的header使得请求变成简单请求,不会发送options请求。如果是增加其他的header,是会重新验证Access-Control-Allow-Headers的值。
cookie变化,只要后端允许发送cookie,cookie值变化不会导致缓存失效。
该字段的兼容性如下:
nginx
nginx解决跨域的问题跟之前的方法有所不同,它是通过服务器的方向代理,将前端访问域名跟后端服务域名映射到同源的地址下,从而实现前端服务和后端服务的同源,那自然不存在跨域的问题了。
举个 :
前端服务:http://localhost:3000,
前端页面路由:http://localhost:3000/page.html,
后端服务:http://localhost:3001,
后端接口路由:http://localhost:3001/api/test.do
可以看出,两个服务处于跨域的状态
通过nginx的配置进行反向代理,即可实现前后端服务同源,如下:
server
{
listen 80;
server_name localhost;
location = / {
proxy_pass http://localhost:3000;
}
location /api {
proxy_pass http://localhost:3001;
#指定允许跨域的方法,*代表所有
add_header Access-Control-Allow-Methods *;
#预检命令的缓存,如果不缓存每次会发送两次请求
add_header Access-Control-Max-Age 3600;
#带cookie请求需要加上这个字段,并设置为true
add_header Access-Control-Allow-Credentials true;
#表示允许这个域跨域调用(客户端发送请求的域名和端口)
#$http_origin动态获取请求客户端请求的域 不用*的原因是带cookie的请求不支持*号
add_header Access-Control-Allow-Origin $http_origin;
#表示请求头的字段 动态获取
add_header Access-Control-Allow-Headers
$http_access_control_request_headers;
#OPTIONS预检命令,预检命令通过时才发送请求
#检查请求的类型是不是预检命令
if ($request_method = OPTIONS){
return 200;
}
其实nginx不仅仅只是用于解决跨域问题,而是涉及到很多服务器资源分配的处理,在此就不详细探讨了。
vue proxyTable
其实,在我们主流使用的MVVM框架中,配置项里面也提供解决跨域问题的能力,继续举个 ,以vue2.x为例,我们可以通过在config/index.js中添加配置项实现跨域请求:
proxyTable: {
‘/apis’: {
// 测试环境
target: ‘http://www.zhenai.com/‘, // 接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
‘^/apis’: ‘’ //需要rewrite重写的,
}
}
}
原理
其实原理很简单,就是在我们使用npm run dev命中,启动了一个node服务,然后将前端发出的请求发送到node服务,再将该服务转发到原本的后台服务,在这过程中实现了一层代理,由一个node服务发送一个请求到另外一个后台服务,自然也没有了浏览器所限制的跨域问题。
\4. URL输出到页面的全过程
1 浏览器根据请求的 URL 交给 DNS 域名解析,找到真实 IP ,向服务器发起请求;
2 服务器交给后台处理完成后返回数据,浏览器接收⽂件( HTML、JS、CSS 、图象等);
3 浏览器对加载到的资源( HTML、JS、CSS 等)进⾏语法解析,建立相应的内部数据结构 (如 HTML 的 DOM);
4 载⼊解析到的资源⽂件,渲染页面,完成。
————————————————
客户端网址中输入url
将输入的url发送到DNS获得该域名对应的WEB服务器的ip地址
客户端游览器与WEB服务器建立TCP连接
客户端游览器向WEB服务器发送HTTP或HTTPS请求
WEB服务器响应请求,返回指定的URL数据或错误信息
客户端拿到WEB服务器的数据后解析源文件,进行页面排版,显示基础页面
分析页面中的超链接,渲染页面
————————————————
详细简版:
1 从浏览器接收 url 到开启⽹络请求线程(这⼀部分可以展开浏览器的机制以及进程与线程 之间的关系)
2 开启⽹络线程到发出⼀个完整的 HTTP 请求(这⼀部分涉及到dns查询, TCP/IP 请求,五层因特⽹协议栈等知识)
3 从服务器接收到请求到对应后台接收到请求(这⼀部分可能涉及到负载均衡,安全拦截以及后台内部的处理等等)
4 后台和前台的 HTTP 交互(这⼀部分包括 HTTP 头部、响应码、报⽂结构、 cookie 等知 识,可以提下静态资源 的 cookie 优化,以及编码解码,如 gzip 压缩等)
6 单独拎出来的缓存问题, HTTP 的缓存(这部分包括http缓存头部, ETag , catchcontrol 等)
浏览器接收到 HTTP 数据包后的解析流程(解析 html、 词法分析然后解析成 dom 树、解析 css ⽣成 css 规则树、合并成 render 树,然后 layout 、 painting 渲染、复合图层的合成、 GPU 绘制、外链资源的处理、 loaded 和 DOMContentLoaded 等)
7 CSS 的可视化格式模型(元素的渲染规则,如包含块,控制框, BFC , IFC 等概念)
JS 引擎解析过程( JS 的解释阶段,预处理阶段,执⾏阶段⽣成执⾏上下⽂, VO ,作 ⽤域链、回收机制等等)
8 其它(可以拓展不同的知识模块,如跨域,web安全, hybrid 模式等等内容)
\5. 常用的请求状态码
500 内部服务器错误
Internal Server Error 500内部服务器错误,服务器遇到未知无法解决的问题。
一般情况下,出现500响应状态的原因有很多种,但是主要的是“程序代码和服务器配置”两个问题。相对于代码而言,就是对站点进行升级,网页改版,新增加了一些常用的插件。就比如WordPress插件的版本可能就需要更高版本的PHP才能兼容。
而相对服务器而言的话,更多的是在系统版本升级导致,就比如最开始使用的是Windows Server 2003,后期想要升级2008、2012等版本的时候配置稍有不慎就会导致Internal Server Error 500。
404 请求错误
Not Found 404 错误请求,因发送的请求语法错误,服务器无法正常读取。
相信绝大多数的人都见过404的状态码,当用户试图请求Web服务器上一个不存在的资源时,就会触发Not Found404。出现404状态码可能是链接失效导致,也有可能是URL拼写错误,还有可能是因为Web服务器将所请求的资源移到了其他的地方。一般的网站都会设置自定义页面以防链接失效所产生不良的影响。
403 禁止访问
Forbidden 403 禁止访问,客户端没有权利访问所请求内容,服务器拒绝本次请求。
状态码403通常代表客户端错误,是指的服务器端有能力处理该请求,但是拒绝授权访问。这个状态码类似于401,但是进入该状态后不能再继续进行验证,该访问是长期禁止的,并且与应用逻辑密切相关,比如密码不正确等。
400 错误请求
Bad Request 400 错误请求,因发送的请求语法错误,服务器无法正常读取。
状态码400表示该语法无效,服务器无法理解该请求。客服端不应该在未经修改的情况下重复此请求。一般会因为前端提交数据的字段名称,或者是字段类型和后台的实体类不一致,导致无法封装。
401 未经授权
Unauthorized 401 未经授权,需要身份验证后才能获取所请求的内容,类似于403错误.不同点是.401错误后,只要正确输入帐号密码,验证即可通过。
状态码401就是Web服务器认为,客户端发送的HTTP数据流浪是正确的,但是进入URL资源的时候需要身份验证,而客户端尚未提供相关的验证信息,或者是已提供但是没有通过验证。这也是通常所知的“HTTP基本验证”。
200 请求成功
200 OK 请求成功,表示已经请求成功,默认情况下的状态码为200的响应就可以被缓存了。
不同请求方式对于请求成功的意义如下:
GET: 已经取得资源,并将资源添加到响应的消息体中。
HEAD: 响应的消息体为头部信息。
POST: 响应的消息体中包含此次请求的结果。
TRACE: 响应的消息体中包含服务器接收到的请求信息。
PUT 和 DELETE 的请求成功通常并不是响应200OK的状态码而是 204No Content 表示无内容(或者 201Created表示一个资源首次被创建成功)。
206 部分内容
Partial Content 206 部分内容,当客户端通过使用range头字段进行文件分段下载时使用该状态码。
状态码206表示服务器已经成功处理了部分GET请求。类似于FlashGet或者迅雷这类的HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
301 永久重定向
Moved Permanently 301 永久移动,该状态码表示所请求的URI资源路径已经改变,新的URL会在响应的Location:头字段里找到。
尽管标准要求浏览器在收到该响应并进行重定向时不应该修改http method和body,但是有一些浏览器可能会有问题。所以最好是在应对GET 或 HEAD 方法时使用301,其他情况使用308 来替代301。
302 临时重定向
Found 302临时移动,该状态码表示所请求的URI资源路径临时改变,并且还可能继续改变.因此客户端在以后访问时还得继续使用该URI.新的URL会在响应的Location:头字段里找到。
即使规范要求浏览器在重定向时保证请求方法和请求主体不变,但并不是所有的用户代理都会遵循这一点,你依然可以看到有缺陷的软件的存在。所以推荐仅在响应 GET 或 HEAD 方法时采用 302 状态码,而在其他时候使用 307 Temporary Redirect 来替代,因为在这些场景下方法变换是明确禁止的。
502 无效网关
Bad Gateway 502 网关错误,服务器作为网关且从上游服务器获取到了一个无效的HTTP响应。
404 请求错误
Not Found 404 错误请求,因发送的请求语法错误,服务器无法正常读取。
相信绝大多数的人都见过404的状态码,当用户试图请求Web服务器上一个不存在的资源时,就会触发Not Found404。出现404状态码可能是链接失效导致,也有可能是URL拼写错误,还有可能是因为Web服务器将所请求的资源移到了其他的地方。一般的网站都会设置自定义页面以防链接失效所产生不良的影响。
403 禁止访问
Forbidden 403 禁止访问,客户端没有权利访问所请求内容,服务器拒绝本次请求。
状态码403通常代表客户端错误,是指的服务器端有能力处理该请求,但是拒绝授权访问。这个状态码类似于401,但是进入该状态后不能再继续进行验证,该访问是长期禁止的,并且与应用逻辑密切相关,比如密码不正确等。
400 错误请求
Bad Request 400 错误请求,因发送的请求语法错误,服务器无法正常读取。
状态码400表示该语法无效,服务器无法理解该请求。客服端不应该在未经修改的情况下重复此请求。一般会因为前端提交数据的字段名称,或者是字段类型和后台的实体类不一致,导致无法封装。
401 未经授权
Unauthorized 401 未经授权,需要身份验证后才能获取所请求的内容,类似于403错误.不同点是.401错误后,只要正确输入帐号密码,验证即可通过。
状态码401就是Web服务器认为,客户端发送的HTTP数据流浪是正确的,但是进入URL资源的时候需要身份验证,而客户端尚未提供相关的验证信息,或者是已提供但是没有通过验证。这也是通常所知的“HTTP基本验证”。
200 请求成功
200 OK 请求成功,表示已经请求成功,默认情况下的状态码为200的响应就可以被缓存了。
不同请求方式对于请求成功的意义如下:
GET: 已经取得资源,并将资源添加到响应的消息体中。
HEAD: 响应的消息体为头部信息。
POST: 响应的消息体中包含此次请求的结果。
TRACE: 响应的消息体中包含服务器接收到的请求信息。
PUT 和 DELETE 的请求成功通常并不是响应200OK的状态码而是 204No Content 表示无内容(或者 201Created表示一个资源首次被创建成功)。
206 部分内容
Partial Content 206 部分内容,当客户端通过使用range头字段进行文件分段下载时使用该状态码。
状态码206表示服务器已经成功处理了部分GET请求。类似于FlashGet或者迅雷这类的HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
301 永久重定向
Moved Permanently 301 永久移动,该状态码表示所请求的URI资源路径已经改变,新的URL会在响应的Location:头字段里找到。
尽管标准要求浏览器在收到该响应并进行重定向时不应该修改http method和body,但是有一些浏览器可能会有问题。所以最好是在应对GET 或 HEAD 方法时使用301,其他情况使用308 来替代301。
302 临时重定向
Found 302临时移动,该状态码表示所请求的URI资源路径临时改变,并且还可能继续改变.因此客户端在以后访问时还得继续使用该URI.新的URL会在响应的Location:头字段里找到。
即使规范要求浏览器在重定向时保证请求方法和请求主体不变,但并不是所有的用户代理都会遵循这一点,你依然可以看到有缺陷的软件的存在。所以推荐仅在响应 GET 或 HEAD 方法时采用 302 状态码,而在其他时候使用 307 Temporary Redirect 来替代,因为在这些场景下方法变换是明确禁止的。
502 无效网关
Bad Gateway 502 网关错误,服务器作为网关且从上游服务器获取到了一个无效的HTTP响应。
bad gateway502代表您所访问的网站出了问题,因为502 Bad Gateway 服务器作为网关或者代理时,是为了完成访问下一个服务器,但该服务器返回了非法的应答。也许是暂时的,也许是永久的。建议大家稍等一下再从新访问试试。
————————————————
原文链接:https://blog.csdn.net/m0_50861631/article/details/123313909
vite和webpack
Webpack
Webpack
是一个模块打包工具,使得工程中的各种资源能够被打包成一个整体的bundle.js文件。Webpack
具有很高的可配置性和灵活性,使得开发者可以使用各种插件和配置文件来优化它们的工作流程。Webpack适用于大型、复杂的项目,它可以处理多种不同类型的文件(如js、css、图片等),并根据需求进行转换、压缩和打包。但是,Webpack的配置可能比较复杂,需要花费一定的时间和精力进行学习和调试。
Vite
Vite
是一个快速、轻量级的现代Web开发构建工具,它利用现代浏览器的原生ES模块加载功能,实现了开发环境中的快速冷重载和构建速度。Vite的开发体验非常好,因为它能够在开发时实时更新页面,而不需要对整个项目进行重新构建。相比于Webpack的构建过程,Vite的开发速度更快,也更适合小型、简单的项目。但是,Vite目前还不支持像Webpack那样的插件生态系统,因此其可扩展性还有待提高。
Vite相比于Webpack打包更快
在Webpack
中,每次修改代码后都需要对整个项目进行重新编译,然后重新生成大量的代码和资源文件。而在Vite
中,它使用了浏览器原生的ES模块加载器,当开发者修改代码后,Vite
会即时在浏览器中编译和打包代码,然后将更改的部分直接传递给浏览器,并重新加载这部分代码。因此,Vite
的编译和打包速度比Webpack
更快,因为它避免了重复的编译和打包步骤,以及更高效地利用了现代浏览器的功能。 另外,Vite
还使用了缓存机制和按需加载的方式,这也是它快速打包的原因之一。当开发者第一次访问项目时,Vite
会对项目进行编译和打包,并缓存生成的文件。这样,当开发者下一次打开项目时,Vite
只需要编译和打包发生更改的部分,而不需要重新编译和打包整个项目。这种按需加载的方式也能够进一步提高Vite
的打包速度。
相比Webpack5中使用的MFSU技术
相比于Webpack5
中使用的MFSU技术
,Vite的打包速度可能会快很多,尤其是在开发环境下。因为MFSU虽然能够提高Webpack的构建速度,但是它仍然需要在内存中存储所有的模块,而Vite则是利用现代浏览器的功能进行实时编译和打包,可以减少打包的冗余操作,从而进一步提高打包速度
另外,Vite在生产环境下也能够获得较好的性能表现。虽然Vite在生产环境下会将所有的代码打包成一个文件,但是它仍然可以充分利用浏览器的缓存机制,减少对于静态资源的请求,从而提高页面加载速度。同时,Vite还支持预构建和预渲染技术,能够在构建时生成静态HTML文件,加速页面的首次加载。
Vite插件推荐
Vite 是一个功能强大的开发构建工具,在插件方面也有很多的选择。以下是一些常用的 Vite 插件库推荐:
@vitejs/plugin-vue
:官方提供的 Vue 插件,用于在 Vite 中编译 Vue 单文件组件。unplugin-vue-components
:一个可插拔的 Vue 组件库,支持按需加载、自动导入组件等。vite-plugin-postcss
:一个 PostCSS 插件,可以在 Vite 中使用 PostCSS 进行 CSS 预处理。vite-plugin-style-import
:一个样式导入插件,支持在 Vue、React 等框架中按需加载样式文件。vite-plugin-typescript
:官方提供的 TypeScript 插件,用于在 Vite 中编译 TypeScript 文件。vite-tsconfig-paths
:一个 TypeScript 路径别名插件,可以在 Vite 中使用路径别名。vite-plugin-md
:一个 Markdown 插件,用于在 Vite 中编译 Markdown 文件。vite-plugin-eslint
:一个 ESLint 插件,可以在 Vite 中使用 ESLint 进行代码检查。
总结
总的来说,Vite
打包相比于Webpack
更快的原因主要是因为它使用了现代浏览器的功能和缓存机制,避免了重复编译和打包的步骤,以及更高效地利用了现有的资源和模块。
链接:https://juejin.cn/post/7229314985044951095
定位
对比之前,我们先要搞懂,vite与webpack的定位以及关系才可以。
那前端社区中常谈到的这些工具webpack
、rollup
、parcel
、esbuild
、vite
、vue-cli
、create-react-app
、umi
他们之间的关系是怎样的。
webpack
、rollup
、parcel
、esbuild
都是打包工具,代码写好之后,我们需要对代码进行压缩
、合并
、转换
、分割
、打包
等操作,这些工作需要打包工具去完成。vue-cli
、create-react-app
、umi
是基于webpack
的上层封装,通过简单的配置就可以快速创建出一个项目,把更多的时间放在业务开发上。vite
开发环境依赖esbuild
进行预构建,生产环境则依赖rollup
进行打包,并且充分利用了现代浏览器的特性,比如http2
、ES module
,vite
是站在众多巨人肩膀上的一个产物, 类似webpack + webpack-dev-server
的结合体,是一个非常棒的前端项目的构建工具。
运行原理
首先,我们从运行原理上分析一下,vite为什么比webpack快。
webpack运行原理
当我们使用webpack启动项目时,webpack会根据我们配置文件(webpack.config.js
) 中的入口文件(entry
),分析出项目项目所有依赖关系,然后打包成一个文件(bundle.js
),交给浏览器去加载渲染。
这样就会带来一个问题,项目越大,需要打包的东西越多,启动时间越长。
关于ES module
在讲vite运行原理之前,我们先说一下ES module
目前,绝大多数现代浏览器都已经支持ES module
了, 我们只需要在<script>
标签中添加type="module"
,就可以使用ES module
了。
下面这段代码是可以直接在浏览器中运行的。
1 |
|
vite运行原理
在<script type="module">
中,浏览器遇到内部的import引用时,会自动发起http请求,去加载对应的模块。
vite也正是利用了ES module
这个特性,使用vite运行项目时,首先会用esbuild
进行预构建,将所有模块转换为es module
,不需要对我们整个项目进行编译打包,而是在浏览器需要加载某个模块时,拦截浏览器发出的请求,根据请求进行按需编译
,然后返回给浏览器。
这样一来,首次启动项目(冷启动)时,自然也就比webpack快很多了,并且项目大小对vite启动速度的影响也很小。
构建方式
我们再来看一下,vite与webpack在项目构建上有哪些区别。
webpack
webpack是基于nodejs
运行的,但js
只能单线程运行,无法利用多核CPU的优势,当项目越来越大时,构建速度也就越来越慢了。
vite
vite预构建
与按需编译
的过程,都是使用esbuild
完成的。
esbuild
是用go
语言编写的,可以充分利用多核CPU的优势,所以vite开发环境下的预构建
与按需编译
速度,都是非常快的。
http2
vite充分利用了http2可以并发请求的优势,这也是速度快的一个主要原因。 接下来,我们了解一下http2的来龙去脉。
在之前http1的时候,浏览器对同一个域名的请求,是有并发限制的,一般为6个,如果并发请求6个以上,就会造成阻塞问题,所以在http1的时代,我们要减少打包产物的文件数量,减少并发请求,来提高项目的加载速度。
2015年以后,http2出现了,他可以并发发送多个请求,不会出现http1的并发限制。这时候,将打包产物分成多个小模块,并行去加载,反而会更快。
vite也充分利用了这一优势,对项目资源进行了合理的拆分,访问项目时,同时加载多个模块,来提升项目访问速度。
热更新
vite速度快的另一个原因是与webpack不同的热更新机制。
我们首先来了解一下什么是HMR。
模块热替换(hot module replacement - HMR),该功能可以实现应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面,也就是我们常说的热更新
。
vite与webpack虽然都支持HMR,但两个工具的实现原理是不一样的。
webpack
webpack项目中,每次修改文件,都会对整个项目重新进行打包,这对大项目来说,是非常不友好的。
虽然webpack现在有了缓存机制,但还是无法从根本上解决这个问题。
vite
vite项目中,监听到文件变更后,会用websocket
通知浏览器,重新发起新的请求,只对该模块进行重新编译,然后进行替换。
并且基于es module
的特性,vite利用浏览器的缓存策略,针对源码模块(我们自己写的代码)做了协商缓存
处理,针对依赖模块(第三方库)做了强缓存
处理,这样我们项目的访问的速度也就更快了。
生产环境
vite生产环境下,为什么使用rollup打包呢?
Rollup 是一款 ES Module
打包器, 从作用上来看,Rollup
与 Webpack
非常类似。不过相比于 Webpack
,Rollup
要小巧的多,打包生成的文件更小。 因为小巧,自然在这种特定的打包环境下,Rollup
的打包速度也要比 Webpack
快很多。
vite正是基于es module
的特性实现的,所以使用rollup要更合适一些。
vite生产环境下,为什么不用esbuild打包呢?
尽管esbuild的打包速度比rollup更快,但 Vite 目前的插件 API 与使用 esbuild 作为打包器并不兼容,rollup插件api与基础建设更加完善,所以在生产环境vite使用rollup打包会更稳定一些。
如果后面esbuild基础建设与生态更加完善后,esbuild还是更有优势的。
所以使用vite可能会带来开发环境与生产环境打包结果不一致的问题。
使用成本
除了速度上的区别,我们再分析一下,vite与webpack的使用成本。
webpack
如果我们使用webpack自己去搭建项目脚手架时,需要配置比较多的东西, 比如:跨域
、代码压缩
、代码分割
、css预处理器的代码转换
、样式兼容性
、vue/react代码解析
、图片压缩
、代码热更新
、es降级
、ts转换
等等,远不止这些。
概念和配置项太多,我们需要了解各种loader、plugin的使用,并且需要根据项目场景,对配置不断进行优化,心智负担太大。
所以就出现了一些基于webpack上层封装的脚手架,如:vue-cli
、create-react-app
、umi
等。
vite
vite对我们常用功能都做了内置,比如:css 预处理器
、html 预处理器
、hash 命名
、异步加载
、分包
、压缩
、HMR
等等,我们可以很轻松的通过配置项去配置。
并且vite官方也提供了一些官方模板、社区模板,我们可以快速地创建出一个带有最佳预设项目,不需要关心太多的配置项。
vite的出现,降低了我们的学习成本、增加了开发体验,我们就可以把更多的时间放在业务开发上了,打磨出更好产品。
————————————————
链接:https://juejin.cn/post/7283682738497765413
计网八股文
简述OSI七层协议
OSI七层协议包括:物理层,数据链路层,网络层,运输层,会话层,表示层, 应用层
简述TCP/IP五层协议
TCP/IP五层协议包括:物理层,数据链路层,网络层,运输层,应用层
物理层有什么作用
主要解决两台物理机之间的通信,通过二进制比特流的传输来实现,二进制数据表现为电流电压上的强弱,到达目的地再转化为二进制机器码。网卡、集线器工作在这一层。
数据链路层有什么作用
在不可靠的物理介质上提供可靠的传输,接收来自物理层的位流形式的数据,并封装成帧,传送到上一层;同样,也将来自上层的数据帧,拆装为位流形式的数据转发到物理层。这一层在物理层提供的比特流的基础上,通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路。提供物理地址寻址功能。交换机工作在这一层。
网络层有什么作用
将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方,通过路由选择算法为分组通过通信子网选择最佳路径。路由器工作在这一层。
传输层有什么作用
传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。
会话层有什么作用
建立会话:身份验证,权限鉴定等; 保持会话:对该会话进行维护,在会话维持期间两者可以随时使用这条会话传输局; 断开会话:当应用程序或应用层规定的超时时间到期后,OSI会话层才会释放这条会话。
表示层有什么作用
对数据格式进行编译,对收到或发出的数据根据应用层的特征进行处理,如处理为文字、图片、音频、视频、文档等,还可以对压缩文件进行解压缩、对加密文件进行解密等。
应用层有什么作用
提供应用层协议,如HTTP协议,FTP协议等等,方便应用程序之间进行通信。
TCP与UDP区别
TCP作为面向流的协议,提供可靠的、面向连接的运输服务,并且提供点对点通信 UDP作为面向报文的协议,不提供可靠交付,并且不需要连接,不仅仅对点对点,也支持多播和广播
为何TCP可靠
TCP有三次握手建立连接,四次挥手关闭连接的机制。 除此之外还有滑动窗口和拥塞控制算法。最最关键的是还保留超时重传的机制。 对于每份报文也存在校验,保证每份报文可靠性。
为何UDP不可靠
UDP面向数据报无连接的,数据报发出去,就不保留数据备份了。 仅仅在IP数据报头部加入校验和复用。 UDP没有服务器和客户端的概念。 UDP报文过长的话是交给IP切成小段,如果某段报废报文就废了。
简述TCP粘包现象
TCP是面向流协议,发送的单位是字节流,因此会将多个小尺寸数据被封装在一个tcp报文中发出去的可能性。 可以简单的理解成客户端调用了两次send,服务器端一个recv就把信息都读出来了。
TCP粘包现象处理方法
固定发送信息长度,或在两个信息之间加入分隔符。
简述TCP协议的滑动窗口
滑动窗口是传输层进行流量控制的一种措施,接收方通过通告发 送方自己的窗口大小,从而控制发送方的发送速度,防止发送方发送速度过快而导致自己被淹没。
简述TCP协议的拥塞控制
拥塞是指一个或者多个交换点的数据报超载,TCP又会有重传机制,导致过载。 为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量.
当cwnd < ssthresh 时,使用慢开始算法。 当cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。 当cwnd = ssthresh 时,即可使用慢开始算法,也可使用拥塞避免算法。
慢开始:由小到大逐渐增加拥塞窗口的大小,每接一次报文,cwnd指数增加。
拥塞避免:cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1。
快恢复之前的策略:发送方判断网络出现拥塞,就把ssthresh设置为出现拥塞时发送方窗口值的一半,继续执行慢开始,之后进行拥塞避免。
快恢复:发送方判断网络出现拥塞,就把ssthresh设置为出现拥塞时发送方窗口值的一半,并把cwnd设置为ssthresh的一半,之后进行拥塞避免。
简述快重传
如果在超时重传定时器溢出之前,接收到连续的三个重复冗余ACK,发送端便知晓哪个报文段在传输过程中丢失了,于是重发该报文段,不需要等待超时重传定时器溢出再发送该报文。
TCP三次握手过程
- 第一次握手:客户端将标志位SYN置为1,随机产生一个值序列号seq=x,并将该数据包发送给服务端,客户端 进入syn_sent状态,等待服务端确认。
- 第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和 ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,服务端进入syn_rcvd状态。
- 第三次握手:客户端收到确认后检查,如果正确则将标志位ACK为1,ack=y+1,并将该数据包发送给服务端,服务端进行检查如果正确则连接建立成功,客户端和服务端进入established状态,完成三次握手,随后客户端和服务端之间可以开始传输 数据了
为什么TCP握手需要三次,两次行不行?
不行。TCP进行可靠传输的关键就在于维护一个序列号,三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值。
如果只是两次握手, 至多只有客户端的起始序列号能被确认, 服务器端的序列号则得不到确认。
简述半连接队列
TCP握手中,当服务器处于SYN_RCVD 状态,服务器会把此种状态下请求连接放在一个队列里,该队列称为半连接队列。
简述SYN攻击
SYN攻击即利用TCP协议缺陷,通过发送大量的半连接请求,占用半连接队列,耗费CPU和内存资源。
优化方式:
- 缩短SYN Timeout时间
- 记录IP,若连续受到某个IP的重复SYN报文,从这个IP地址来的包会被一概丢弃。
TCP四次挥手过程
- 第一次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入fin_wait_1状态。
- 第二次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1,服务端进入Close_wait状态。此时TCP连接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接收。
- 第三次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入Last_ack状态。
- 第四次挥手:客户端收到FIN后,客户端进入Time_wait状态,接着发送一个ACK给服务端,确认后,服务端进入Closed状态,完成四次挥手。
为什么TCP挥手需要4次
主要原因是当服务端收到客户端的 FIN 数据包后,服务端可能还有数据没发完,不会立即close。
所以服务端会先将 ACK 发过去告诉客户端我收到你的断开请求了,但请再给我一点时间,这段时间用来发送剩下的数据报文,发完之后再将 FIN 包发给客户端表示现在可以断了。之后客户端需要收到 FIN 包后发送 ACK 确认断开信息给服务端。
为什么四次挥手释放连接时需要等待2MSL
MSL即报文最大生存时间。设置2MSL可以保证上一次连接的报文已经在网络中消失,不会出现与新TCP连接报文冲突的情况。
简述DNS协议
DNS协议是基于UDP的应用层协议,它的功能是根据用户输入的域名,解析出该域名对应的IP地址,从而给客户端进行访问。
简述DNS解析过程
1、客户机发出查询请求,在本地计算机缓存查找,若没有找到,就会将请求发送给dns服务器
2、本地dns服务器会在自己的区域里面查找,找到即根据此记录进行解析,若没有找到,就会在本地的缓存里面查找
3、本地服务器没有找到客户机查询的信息,就会将此请求发送到根域名dns服务器
4、根域名服务器解析客户机请求的根域部分,它把包含的下一级的dns服务器的地址返回到客户机的dns服务器地址
5、客户机的dns服务器根据返回的信息接着访问下一级的dns服务器
6、这样递归的方法一级一级接近查询的目标,最后在有目标域名的服务器上面得到相应的IP信息
7、客户机的本地的dns服务器会将查询结果返回给我们的客户机
8、客户机根据得到的ip信息访问目标主机,完成解析过程
简述HTTP协议
http协议是超文本传输协议。它是基于TCP协议的应用层传输协议,即客户端和服务端进行数据传输的一种规则。该协议本身HTTP 是一种无状态的协议。
简述cookie
HTTP 协议本身是无状态的,为了使其能处理更加复杂的逻辑,HTTP/1.1 引入 Cookie 来保存状态信息。
Cookie是由服务端产生的,再发送给客户端保存,当客户端再次访问的时候,服务器可根据cookie辨识客户端是哪个,以此可以做个性化推送,免账号密码登录等等。
简述session
session用于标记特定客户端信息,存在在服务器的一个文件里。 一般客户端带Cookie对服务器进行访问,可通过cookie中的session id从整个session中查询到服务器记录的关于客户端的信息。
简述http状态码和对应的信息
1XX:接收的信息正在处理
2XX:请求正常处理完毕
3XX:重定向
4XX:客户端错误
5XX:服务端错误
常见错误码: 301:永久重定向 302:临时重定向 304:资源没修改,用之前缓存就行 400:客户端请求的报文有错误 403:表示服务器禁止访问资源 404:表示请求的资源在服务器上不存在或未找到
转发和重定向的区别
转发是服务器行为。服务器直接向目标地址访问URL,将相应内容读取之后发给浏览器,用户浏览器地址栏URL不变,转发页面和转发到的页面可以共享request里面的数据。
重定向是利用服务器返回的状态码来实现的,如果服务器返回301或者302,浏览器收到新的消息后自动跳转到新的网址重新请求资源。用户的地址栏url会发生改变,而且不能共享数据。
简述http1.0
规定了请求头和请求尾,响应头和响应尾(get post)
每一个请求都是一个单独的连接,做不到连接的复用
简述http1.1的改进
HTTP1.1默认开启长连接,在一个TCP连接上可以传送多个HTTP请求和响应。使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
服务端无法主动push
简述HTTP短连接与长连接区别
HTTP中的长连接短连接指HTTP底层TCP的连接。
短连接: 客户端与服务器进行一次HTTP连接操作,就进行一次TCP连接,连接结束TCP关闭连接。
长连接:如果HTTP头部带有参数keep-alive,即开启长连接网页完成打开后,底层用于传输数据的TCP连接不会直接关闭,会根据服务器设置的保持时间保持连接,保持时间过后连接关闭。
简述http2.0的改进
提出多路复用。多路复用前,文件时串行传输的,请求a文件,b文件只能等待,并且连接数过多。引入多路复用,a文件b文件可以同时传输。
引入了二进制数据帧。其中帧对数据进行顺序标识,有了序列id,服务器就可以进行并行传输数据。
http与https的区别
http所有传输的内容都是明文,并且客户端和服务器端都无法验证对方的身份。 https具有安全性的ssl加密传输协议,加密采用对称加密, https协议需要到ca申请证书,一般免费证书很少,需要交费。
简述TLS/SSL, HTTP, HTTPS的关系
SSL全称为Secure Sockets Layer即安全套接层,其继任为TLSTransport Layer Security传输层安全协议,均用于在传输层为数据通讯提供安全支持。
可以将HTTPS协议简单理解为HTTP协议+TLS/SSL
https的连接过程
- 浏览器将支持的加密算法信息发给服务器
- 服务器选择一套浏览器支持的加密算法,以证书的形式回发给浏览器
- 客户端(SSL/TLS)解析证书验证证书合法性,生成对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,用服务器的公钥对客户端密钥进行非对称加密。
- 客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端对称密钥发送给服务器
- 服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
- 服务器将加密后的密文发送给客户端
- 客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成
Get与Post区别
Get:指定资源请求数据,刷新无害,Get请求的数据会附加到URL中,传输数据的大小受到url的限制。
Post:向指定资源提交要被处理的数据。刷新会使数据会被重复提交。post在发送数据前会先将请求头发送给服务器进行确认,然后才真正发送数据。
Get方法参数有大小限制吗
一般HTTP协议里并不限制参数大小限制。但一般由于get请求是直接附加到地址栏里面的,由于浏览器地址栏有长度限制,因此使GET请求在浏览器实现层面上看会有长度限制。
了解REST API吗
REST API全称为表述性状态转移(Representational State Transfer,REST)即利用HTTP中get、post、put、delete以及其他的HTTP方法构成REST中数据资源的增删改查操作:
- Create : POST
- Read : GET
- Update : PUT/PATCH
- Delete: DELETE
浏览器中输入一个网址后,具体发生了什么
- 进行DNS解析操作,根据DNS解析的结果查到服务器IP地址
- 通过ip寻址和arp,找到服务器,并利用三次握手建立TCP连接
- 浏览器生成HTTP报文,发送HTTP请求,等待服务器响应
- 服务器处理请求,并返回给浏览器
- 根据HTTP是否开启长连接,进行TCP的挥手过程
- 浏览器根据收到的静态资源进行页面渲染
1.五层网络协议
2.OSI七层模型
3.TCP三次握手,四次挥手
4.TCP和UDP的区别
5.TCP是如何保证可靠传输的
6.HTTP协议的特点
7.HTTP报文格式
8.HTTP各版本区别
9.HTTPS和HTTP的区别
10.SSL过程
11.浏览器中输入URL到返回的过程
12.DNS解析的过程
13.cookie和session的区别和联系
14.TCP重传机制
15.http常用响应码
16.get请求和post请求的区别
1.五层网络协议
- 应用层
定义的是应用进程间的通信和交互的规则。
- 运输层
主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务。
由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,
- 网络层
选择合适的网间路由和交换结点,确保数据及时传送。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。
- 数据链路层
- 物理层
2.OSI七层模型
- 应用层:网络服务与最终用户的一个接口。HTTP,FTP,SMTP
- 表示层:数据的表示,安全,压缩。
- 会话层:建立,管理,终止会话。对应主机进程,指本地主机与远程主机正在进行的会话
- 运输层:定义传输数据的协议端口号,以及流控和差错校验。TCP,UDP
- 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。ICMP IGMP IP
- 数据链路层:建立逻辑连接,进行硬件地址寻址,差错校验
- 物理层:建立,维护,断开物理连接
3.TCP三次握手,四次挥手
TCP协议目的是为了保证数据能够在两端准确连续的流动,可以想象两个建立起TCP通道的设备如同接起了一根水管,数据就是水管中的水,由一头流向了另一头。然而TCP为了能让一个设备连接多个水管,也就是让一个设备同时与多个设备交互信息,他必须保证不同水管之间不能产生串联或互相影响。
为了确保数据能够正确分发,TCP用一种叫TCB(传输控制块)的数据结构把发给不同设备的数据封装起来。一个TCB数据块包含了数据发送双方对应的socket信息以及拥有装载数据的缓存区。
三次握手
0)准备工作
最开始的客户端和服务器都处于CLOSE状态。主动打开连接的是客户端,被动打开的是服务器
TCP服务器进程先创建传输控制块TCB,时刻准备接收客户端进程的请求,此时服务器就进入LISTEN状态
1)一次握手
TCP客户端也是先创建了传输控制块TCB,然后向服务器发出连接请求报文,SYN,报文首部种的同部位(SYN=1),同时选择一个序列号seq=x
此时,TCP进入SYN-SENT(同步已发送状态)。TCP规定,SYN报文段不能携带数据,但需要消耗一个序号
如果是发送,则需要发送一个标志位SYN=1,同时选择一个初始序列号seq=x(随机,客户端或服务器每发送一次+1)
2)二次握手
TCP服务器收到请求报文后,如果同意连接,则发出确认报文。
收到报文,则需要发送一个确认标志位ACK=1,并且确认序列号ack=x+1,然后发出报文,需要一个发送标志位SYN=1,同时选择一个初始序列号seq=y(随机)
3)TCP客户端收到确认后,还要向服务器给出确认。
收到报文,发送一个确认标志位ACK=1,确认序列号ack=y+1,seq=x+1 。
为什么TCP客户端还要最后再发一次确认呢?
主要防止已经失效的连接请求报文又突然传送到了服务器,从而产生错误
四次挥手
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于established状态。然后客户端主动关闭,服务器被动关闭。
1)TCP客户端收到一个FIN(关闭连接),用来关闭客户端到服务器的数据传送
2)服务器收到这个FIN,它发回一个ACK
客户端发送完所有的数据,请求关闭连接,向服务器申请关闭,但与此同时,服务器未必就把所有的数据全部向客户端发送完毕,所以在第二步和第三步之间,服务器可以继续数据传输
3)服务器关闭客户端的连接,发送一个FIN给客户端
4)客户端发回ACK报文确认,并将确认序号设置为收到序号+1
为什么客户端最后还要等待2MSL
去向ACK消息最大存活时间(MSL)+ 来向FIN消息的最大存活时间(MSL)
第一,保证客户端发送的最后一个ACK报文能够到达服务器
第二,释放的端口可能重连刚断开的服务器端口,这样依然存活在网络里的老的TCP报文可能与新的TCP连接报文冲突
如果已经建立连接了,但是客户端突然出现故障了怎么办
TCP还设有一个保活计时器。客户端出现故障,服务器不能一直等下去
4.TCP和UDP的区别
- TCP是可靠传输,UDP是不可靠传输;
- TCP面向连接,UDP无连接;
- TCP传输数据有序,UDP不保证数据的有序性;
- TCP不保存数据边界,UDP保留数据边界;
- TCP传输速度相对UDP较慢;
- TCP有流量控制和拥塞控制,UDP没有;
- TCP是重量级协议,UDP是轻量级协议;
- TCP首部较长20字节,UDP首部较短8字节;
5.TCP是如何保证可靠传输的
- 数据包校验:目的是检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报文段不给出响应,这时TCP发送数据段超时后会重发
- 对失序数据包进行重排序:TCP报文段作为IP数据报来传输,可能会失序
- 丢弃重复数据
- 应答机制:当TCP收到发自TCP连接另一端的数据,它将发送一个确认。
- 超时重发
- 流量控制:TCP 的接收端只允许另一端发送接收端缓冲区所能接纳的数据
6.HTTP协议的特点
- HTTP允许传输任意类型的数据。传输的类型由Content-Type加以标记
- 无状态。对于客户端每次发送的请求,服务器都认为是一次新的请求,上一次会话和下一次会话之间没有联系
- 支持客户端/服务器模式
7.HTTP报文格式
- 请求行:包括请求方法,url,http版本
- 请求头部:以“属性名:属性值”形式保存,服务端跟据请求头获取客户端的信息
- 空行
- 请求体:用户的请求数据,用户名,密码等等
8.HTTP各版本区别
HTTP1.0和HTTP1.1的区别
- 长连接:HTTP1.1支持长连接和请求的流水线处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。
- 节约带宽:HTTP1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,客户端收到100才开始把请求body发送到服务器。返回401就可以不用发送请求body了
- HOST域:HTTP1.0没有HOST域。由于虚拟技术的发展,一个物理主机可能存在多个ip地址
- 缓存处理:HTTP1.1引入了更多的缓存策略
- 错误通知的管理:在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
HTTP1.1和HTTP2.0的区别
- 多路复用:HTTP2.0使用了多路复用,做到了同一个连接并发处理多个请求
- 头部数据压缩:HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩
- 服务器推送:服务端推送是一种在客户端请求之前发送数据的机制。主要是为了改善延迟。它允许服务端通过本地的方式推送资源给浏览器
长连接是一个http请求结束,tcp连接不会马上关闭,而是会继续存在看是否有下一条http请求,但是一次只能通过一条http请求****多路复用是在长连接的基础上,去掉了一次只能通过一条http请求的限制,同一条tcp连接可以一次通过多条http请求
9.HTTPS和HTTP的区别
- HTTP是超文本传输协议,信息是明文传输;HTTPS则是具有安全性的ssl加密传输协议
- HTTP端口80,HTTPS端口443
10.SSL过程
1)客户端提交https请求
2)服务器响应客户,并把数字证书和证书公钥发给客户端,客户端在本地找到CA中心的公钥(根证书),验证证书公钥。
3)客户端验证证书公钥的有效性(能否用证书公钥解开数字证书,解开后得到服务端的公钥)
4)有效后,生成一个会话密钥
5)用服务端的公钥加密这个会话密钥后,发送给服务器
6)服务器收到公钥加密的会话密钥后,用自己的私钥解密,得到会话密钥
7)客户端与服务器双方利用这个会话密钥加密要传输的数据进行通信
11.浏览器中输入URL到返回的过程
1)解析域名,通过dns解析获取IP地址
2)浏览器通过ip地址与网站主机建立TCP连接
3)浏览器向主机发送一个HTTP请求(底层涉及到数据封装,将IP模块封装成网络包,ip头部,加上MAC头部,经过网卡,交换机,路由器)
4)服务器响应请求,返回响应数据(上一部反向的过程)
5)浏览器解析响应内容,渲染呈现
详细版:
1)URL解析
- 首先判断输入的是否是一个合法的url还是一个待搜索的关键词,并且跟据你输入的内容进行自动完成,字符编码等操作,还会有些安全检查,访问限制
2)DNS查询
- 如下
3)TCP连接
- 每层都要进行封装,应用层(HTTP数据)-> 传输层(HTTP数据 + TCP首部)-> 网络层(HTTP数据 + TCP首部 + IP地址)-> 数据链路层(HTTP数据 + TCP首部 + IP地址 + MAC地址)
- 服务端是一个反向删除首部的过程
- 传输层在建立连接前还会进行TCP三次握手
4)处理请求
- 主要是进行一些验证,验证是否配置虚拟主机,是否需要重定向
5)接收响应
- 浏览器接收来自服务器的响应资源,会对资源进行分析。首先查看响应头,跟据不同的码做不同的事情。如果响应资源进行了压缩,还会进行解压,还会做缓存
6)渲染页面
12.DNS解析的过程
1)浏览器先搜索自己的DNS缓存
2)如果没有,搜索操作系统的dns缓存和host文件
3)没有则发送到本地域名服务器,没有则向根域名服务器,顶级域名服务器,权限域名服务器
4)找到后,反向返回,并对应的都会保存这个IP地址
13.cookie和session的区别和联系
cookie:
就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务端发送请求的时候都会带上这些特殊的信息。
cookie工作流程:
- servlet创建cookie,保存少量数据,发送给浏览器
- 浏览器获得服务器发送的cookie数据,将自动的保存到浏览器端
- 下次访问时,浏览器将自动携带cookie数据发送到服务器
session:
session对象可以存储用户在程序中的一些数据,用户在系统中不同的web页面之间进行跳转时,存储在session中的数据不会丢失。
区别:
cookie是存储在客户端的,session是存储在服务端的。
cookie是对客户端可见的,session不存在泄露的风险
单个cookie保存的数据<=4kB,一个站点最多保存20个cookie。对于session来说没有上限,但出于服务端性能的考虑不要放太多。
联系:
session的实现依赖于cookie。当服务器创建session后,sessionid通常以cookie的形式存储在客户端。每次HTTP请求,sessionid都会随着cookie被传递到服务器端,这样服务端就可以找到来自于哪个客户端。
14.TCP重传机制
- 超时重传
设定一个计时器,当超过指定的时间后,没有收到对方的ack确认应答报文,就会重发该数据
有两种情况会使用超时重传,数据包丢失和确认应答丢失
- 快速重传
不以时间为驱动,而是以数据驱动重传
发送端收到三个ack=2的确认,知道了seq2还没有收到,就会在定时器过期之前,重传丢失的seq2
- SACK(选择性确认)
需要在TCP头部字段里加一个SACK的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息就可以只重传丢失的数据
- D-SACK
其主要使用了SACK来告诉发送方有哪些数据被重复接收了
可以让发送方知道,是发出去的包丢了,还是接收方回应的ack包丢了。还是被网络延迟了
15.http常用响应码
- 100:请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分
- 200:成功
- 404:服务器无法跟据客户端的请求找到资源
- 500:服务器内部错误
16.get请求和post请求的区别
- get请求参数通过url传递(参数直接暴露在url上,相对不安全),post的参数放在请求体中
- get请求可以缓存,post不可以
- 链接:https://www.nowcoder.com/discuss/406143598242127872
HTTP1.1 和 HTTP1.0 的区别有哪些?
1.长链接
早期 HTTP1.0 的每一次请求都伴随着一次三次握手的过程,并且是串行的请求,增加了不必要的性能开销
HTTP1.1 新增了长链接的通讯方式,减少了性能损耗
2.管道
HTTP1.0 只有串行发送,没有管道
HTTP1.1 增加了管道的概念,使得在同一个 TCP 链接当中可以同时发出多个请求
3.断点续传
HTTP1.0 不支持断点续传
HTTP1.1 新增了 range 字段,用来指定数据字节位置,开启了断点续传的时代
4.Host头处理
HTTP1.0 任务主机只有一个节点,所以并没有传 HOST
HTTP1.1 时代,虚拟机技术越来越发达,一台机器上也有可能有很多节点,故增加了 HOST 信息
5.缓存处理
在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准
HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
6.错误状态响应码
在HTTP1.1中新增了24个错误状态响应码,如410(Gone)表示服务器上的某个资源被永久性的删除等。
HTTP2 和 HTTP1.1 的区别是什么?
1.头部压缩
在 HTTP2 当中,如果你发出了多个请求,并且它们的头部(header)是相同的,那么 HTTP2 协议会帮你消除同样的部分。(其实就是在客户端和服务端维护一张索引表来实现)
2.二进制格式
HTTP1.1 采用明文的形式
HTTP/2 全⾯采⽤了⼆进制格式,头信息和数据体都是⼆进制
3.数据流
HTTP/2 的数据包不是按顺序发送的,同⼀个连接⾥⾯连续的数据包,可能属于不同的回应。(对数据包做了标记,标志其属于哪一个请求,其中规定客户端发出的数据流编号为奇数,服务器发出的数据流编号为偶数。客户端还可以指定数据流的优先级,优先级⾼的请求,服务器就先响应该请求)
HTTP3 和 HTTP2 的区别是什么?
1.协议不同
HTTP2 是基于 TCP 协议实现的
HTTP3 是基于 UDP 协议实现的
2.QUIC
HTTP3 新增了 QUIC 协议来实现可靠性的传输
3.握手次数
HTTP2 是基于 HTTPS 实现的,建立连接需要先进行 TCP 3次握手,然后再进行 TLS 3次握手,总共6次握手
HTTP3 只需要 QUIC 的3次握手
4.IO多路复用
如:在⼀个连接中,服务器收到了客户端 A 和 B 的两个请求,但是发现在处理 A 的过程中⾮常耗时,索性就先回应 A 已经处理好的部分,再接着回应 B 请求,最后再回应 A 请求剩下的部分。
HTTP/2 可以在⼀个连接中并发多个请求或回应。
5.服务器推送
服务器可以主动向客户端发送请
————————————————
原文链接:https://blog.csdn.net/ThinPikachu/article/details/123572108
vue八股文
一.谈一谈对MVVM的理解?
MVVM是Model-View-ViewModel的缩写。MVVM是一种设计思想。
Model层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
View代表UI组件,他负责将数据模型转换为UI展现出来,View是一个同步View和Model的对象。
在MVVM架构下,View和Model之间并没有直接的联系,而是通过ViewModel进行交互,Model和ViewModel之间的交互是双向的,因此View数据的变化会同步到Model中,而Model数据的变化也会立即反应到View上。
对于ViewModel通过双向数据绑定把View层和Model层连接了起来,而ViewheModel之间的同步工作完全是自动的,无需人为干涉,因此开发者只需要关注业务逻辑,不需要手动操作DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由MVVM来统一管理、
二.解释一下对Vue生命周期的理解
什么是vue生命周期
vue生命周期的作用是什么?
vue生命周期有几个阶段
第一次页面加载会触发哪几个钩子
DOM渲染在哪个周期就已经完成
多组件(父子组件)中生命周期的调用顺序
1.什么是vue生命周期
对于vue来说,生命周期就是一个vue实例从创建到销毁的过程。
2.vue生命周期的作用是什么
生命周期的过程中会运行着一些叫做生命周期的函数,给予了开发者在不同的生命周期阶段添加业务代码的能力。
其实和回调一个概念,当系统执行到某处时,检查是否含有hook(钩子),有的话就是执行回调。
通俗的说,hook就是在程序中运行,在某个特定的位置,框架的开发者设计好了一个钩子来告诉我们当前程序已经运行到特定的位置了,会触发一个回调函数,并提供给我们,让我们可以在生命周期的特定阶段进行相关业务代码的编写。
3.vue生命周期有几个阶段
他可以分为8个阶段:创建前/后,载入前/后,更新前/后,销毁前/后。
beforeCreate:是new Vue()之后触发的第一钩子,在当前阶段data,methods,computed以及watch上的数据和方法都不能被访问。
created:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与DOM进行交互,如果非要想,可以通过vm.$nextTick来访问DOM、
beforeMount:在挂载之前,在这之前template模板已经导入渲染函数编译。而当前阶段虚拟DOM已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
mounted:在挂载完成后发生,当前阶段,真实的DOM挂载完毕,数据完成双向绑定,可以访问DOm节点,使用$refs属性对DOM进行操作。
beforeUpdate:发生在更新之前,也就是响应式数据发生更新,虚拟DOM重新渲染之前被触发,你可以在当前阶段更改数据,不会造成重渲染。
updated:发生在更新完成之后,当前阶段组件DOM已经完成更新。要注意的是避免在此期间更改数据,因为这可能导致无限循环的更新。
beforeDestory:发生在实例销毁之前,在当前阶段实例完全可以使用,我们可以在这时进行善后收尾工作,比如去除计时器。
destoryed:发生在实例销毁之后,这个时候只剩下DOM空壳。组件已被拆解,数据绑定被卸除,监听别移除,子实例也别销毁。
第一次页面加载会触发哪几个钩子
会触发4个钩子,分别是:beforeCreate,created,beforeMount,mounted
DOM渲染在哪个周期就已经完成
DOM渲染是在mounted阶段完成,此阶段真实的DOM挂载完毕,数据完成双向绑定,可以访问到DOM节点。
多组件(父子组件)中生命周期的调用顺序
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
三.说一下router与route的区别
$route对象表示当前的路由信息,包含了当前URL解析得到的信息。包含当前的路径,参数,query对象等。
$route.path:字符串,对应当前的路由路径,总是解析为绝对路径,如”/foo/bar”。
$route.params:一个key/value对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
$route.query:一个key/value对象,表示URL查询参数。例如对于路径”/foo?user=1”,则有$route.query.user==1,如果没有查询参数,则是一个空对象。
$route.hash:当前路由的hash值(不带#),如果没有hash值,则为空字符串。
$route.fullPath:完成解析后的URL,包含查询参数和hash的完整路径。
$route.matched:数组,包含当前匹配的路径中所包含的所有片段对应的配置参数对象。
$route.name:当前路径名字
$route.meta:路由元信息
$route对象出现在多个地方:
组件内的this.$route和route watcher回调(监测变化处理)
router.match(location)的返回值
scrollBehavior方法的参数
导航钩子的参数,例如router.beforeEach导航守卫的钩子函数中,to和from都是这个路由信息对象。
$router对象是全局路由的实例,是router构造方法的实例。
$router对象常用的方法有:
push:向history栈添加一个新的记录
go:页面路由跳转前进或者后退
replace:替换当前的页面,不会向history栈添加一个新的记录
四.VueRouter有哪几种导航守卫?
全局前置/钩子:beforeEach,beforeResolve,afterEach
路由独享的守卫:beforeEnter
组件内的守卫:beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave
五. Vue的路由两种模式hash模式和history模式的实现原理
后面hash值的变化,不会导致浏览器向服务器发出请求,浏览器不发请求,就不会刷新页面;通过监听hashchage事件可以知道hash发生了哪些变化,然后根据hash变化来实现更新页面部分内容的操作。
history模式的实现,主要是HTML5标准发布的两个API了,pushState和replaceState,这两个API
可以在改变URL,但不会发送请求,这样还可以监听URl变化来实现更新页面部分内容的操作。
两种模式的区别:
首先是在URL展示上,hash模式有”#”,history模式没有
刷新页面时,hash模式可以正常加载到hash值对应的页面,而history没有处理的话,会返回404,一般需要后端将所有页面都配置重定向到首页路由
在兼容性上,hash可以支持低版本浏览器和IE
六.说一下v-if与v-show的区别
共同点:都是动态显示DOM元素
区别点:
手段
v-if是动态的向DOM树内添加或者删除DOM元素
v-show是通过设置DOM元素的display样式属性控制显隐
2.编译过程
v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件
v-show只是简单的基于css切换
3.编译条件
v-if是惰性的,如果初始条件为假,则什么也不做,只有在条件第一次变为真时才开始局部编译。
v-show是任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留
4.性能消耗
v-if有更高的切换消耗
v-show有更高的初始渲染消耗
5.使用场景
v-if使用运营条件不大可能改变
v-show适合频繁切换
七.vue为什么采用异步渲染
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染;所以为了性能考虑,vue会在本轮数据更新后,再去异步更新视图。
异步渲染的原理:
调用notify()方法,通知watcher进行更新操作
依次调用watcher的update方法
对watcher进行去重操作(通过id)放到队列里
执行完后异步清空这个队列,nextTick进行批量更新操作
八.组件中写name选项有哪些好处?
可以通过名字找到对应的组件(递归组件:组件自身调用自身)
可以通过name属性实现缓存功能(keep-alive)
可以通过name来识别组件(跨级组件通信时重要)
使用vue-devtools调试工具里显示的组件名称是由vue中的name决定的
九.keep-alive的实现原理是什么
keep-alive组件是vue的内部组件,用于缓存内部组件实例。这样做的目的是在于,keep-alive内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建实例带来的开销,另一方面可以保留组件的状态。
keep-alive具有include和exclude属性,通过他们可以控制哪些组件进入缓存。另外它还提供了max属性,通过它可设置最大缓存数,当缓存的实例超过该数时,vue会移除最久没有使用的组件缓存。
十.说一下Vue SSR实现原理
app.js作为客户端与服务端的公用入口,导出Vue根实例,供客户端entry与服务端entry使用。客户端entry主要作用挂载到DOM上,服务端entry除了创建和返回实例,还需要进行路由匹配与数据预获取。
webpack为客户端打包一个ClientBundle,为服务端打包一个ServerBundle
服务器接收到请求时,会根据url,加载相应的组件,获取和解析异步数据,创建一个读取Server Bundle的BundleRenderer,然后生成html发送给客户端。
客户端混合,客户端收到服务端传来的DOM与自己生成的DOM进行对比,把不同的DOM激活,使其可以响应后续变化,这个过程叫做客户端激活(也就是转换为单页应用)。为确保混合成功,客户端与服务器端需要共享一台数据。在服务器端,可以在渲染之前获取数据,填充到store里,这样,在客户端挂载到DOM之前,可以直接从store里面取数据。首页的动态数据通过window.INITIAL_STATE发送到客户端
VueSSR的原理,主要就是通过vue-server-renderer把Vue组件输出成一个完整的HTML,输出到客户端,到达客户端重新展开为一个单页应用。
————————————————
原文链接:https://blog.csdn.net/weixin_63267832/article/details/135150525
基础知识
MVVM的理解
MVVM是一种软件架构模式,MVVM 分为 Model、View、ViewModel:
Model代表数据模型,数据和业务逻辑都在Model层中定义;
View代表UI视图,负责数据的展示;
ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
vue和react的区别,有什么相同
不同:
模版语法不同,react采用JSX语法,vue使用基于HTML的模版语法
数据绑定不同,vue 使用双向数据绑定,react 则需要手动控制组件的状态和属性。
状态管理不同,vue使用vuex状态管理,react使用redux状态管理
组件通信不同,vue使用props和事件的方式进行父子组件通信,react则通过props和回调函数的方式进行通信。
生命周期不同,vue有8个生命周期钩子,react有10个
响应式原理不同,vue使用双向绑定来实现数据更新,react则通过单向数据流来实现
相同:
组件化开发:Vue 和 React 都采用了组件化开发的方式,将用户界面划分为独立、可复用的组件,从而使代码更加模块化、可维护和可扩展。
虚拟 DOM:Vue 和 React 都使用虚拟 DOM 技术,通过在 JavaScript 和真实 DOM 之间建立一个轻量级的虚拟 DOM 层,实现高效的 DOM 更新和渲染。
响应式更新:Vue 和 React 都支持响应式更新,即当数据发生变化时,会自动更新相关的组件和视图,以保持用户界面的同步性。
集成能力:Vue 和 React 都具有良好的集成能力,可以与其他库和框架进行整合,例如 Vue 可以与 Vuex、Vue Router 等配套使用,React 可以与 Redux、React Router 等配套使用。
Vue2和Vue3有哪些区别
Vue2使用的是optionsAPI ,Vue3使用composition API,更好的组织代码,提高代码可维护性
Vue3使用Proxy代理实现了新的响应式系统,比Vue2有着更好的性能和更准确的数据变化追踪能力。
Vue3引入了Teleprot组件,可以将DOM元素渲染到DOM数的其他位置,用于创建模态框、弹出框等。
Vue3全局API名称发生了变化,同时新增了watchEffect、Hooks等功能
Vue3对TypeScript的支持更加友好
Vue3核心库的依赖更少,减少打包体积
3支持更好的Tree Shanking,可以更加精确的按需要引入模块
更多区别查看我之前文章「Vue3全家桶之—— Vue3 Composition API」
SPA的理解,有什么优缺点
SPA(单页应用)是一种前端应用程序的架构模式,它通过在加载应用程序时只加载单个 HTML 页面,并通过使用 JavaScript 动态地更新页面内容,从而实现无刷新的用户体验。
SPA和多页面有什么区别
区别
页面加载方式:在多页面应用中,每个页面都是独立的 HTML 文件,每次导航时需要重新加载整个页面。而在 SPA 中,初始加载时只加载一个 HTML 页面,后续的导航通过 JavaScript 动态地更新页面内容,无需重新加载整个页面。
用户体验:SPA 提供了流畅、快速的用户体验,因为页面切换时无需等待整个页面的重新加载,只有需要的数据和资源会被加载,减少了页面刷新的延迟。多页面应用则可能会有页面刷新的延迟,给用户带来较长的等待时间。
代码复用:SPA 通常采用组件化开发的方式,可以在不同的页面中复用组件,提高代码的可维护性和可扩展性。多页面应用的每个页面都是独立的,组件复用的机会较少。
路由管理:在多页面应用中,页面之间的导航和路由由服务器处理,每个页面对应一个不同的 URL。而在 SPA 中,前端负责管理页面的导航和路由,通过前端路由库(如 React Router 或 Vue Router)来管理不同路径对应的组件。
SEO(搜索引擎优化):由于多页面应用的每个页面都是独立的 HTML 文件,搜索引擎可以直接索引和抓取每个页面的内容,有利于搜索引擎优化。相比之下,SPA 的内容是通过 JavaScript 动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容,需要采取额外的优化措施。
服务器负载:SPA 只需初始加载时获取 HTML、CSS 和 JavaScript 文件,后续的页面更新和数据获取通常通过 API 请求完成,减轻了服务器的负载。而多页面应用每次导航都需要从服务器获取整个页面的内容。
优点
用户体验:SPA 提供了流畅、快速的用户体验,在页面加载后,只有需要的数据和资源会被加载,减少了页面刷新的延迟。
响应式交互:由于 SPA 依赖于异步数据加载和前端路由,可以实现实时更新和动态加载内容,使用户可以快速地与应用程序交互。
代码复用:SPA 通常采用组件化开发的方式,提高了代码的可维护性和可扩展性。
服务器负载较低:由于只有初始页面加载时需要从服务器获取 HTML、CSS 和 JavaScript 文件,减轻了服务器的负载。
缺点:
首次加载时间:SPA 首次加载时需要下载较大的 JavaScript 文件,这可能导致初始加载时间较长。
SEO(搜索引擎优化)问题:由于 SPA 的内容是通过 JavaScript 动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容。
内存占用:SPA 在用户浏览应用程序时保持单个页面的状态,这可能导致较高的内存占用。
安全性:由于 SPA 通常使用 API 进行数据获取,因此需要特别注意安全性。
Vue的性能优化有哪些
编码阶段
v-if和v-for不一起使用
v-for保证key的唯一性
使用keep-alive缓存组件
v-if和v-show酌情使用
路由懒加载、异步组件
图片懒加载
节流防抖
第三方模块按需引入
服务端与渲染
打包优化
压缩代码
使用CDN加载第三方模块
抽离公共文件
用户体验
骨架屏
客户端缓存
SEO优化
预渲染
服务端渲染
合理使用 meta 标签
Vue生命周期
创建前后:
beforeCreate(创建前): 数据观测和初始化事件还未开始,不能访问data、computed、watch、methods上的数据方法。
created(创建后):实例创建完成,可以访问data、computed、watch、methods上的数据方法,但此时渲染节点还未挂在到DOM上,所以不能访问。
挂载前后:
beforeMount(挂载前): Vue实例还未挂在到页面HTML上,此时可以发起服务器请求
mounted(挂载后):Vue实例已经挂在完毕,可以操作DOM
更新前后:
beforeUpdate(更新前): 数据更新之前调用,还未渲染页面
updated(更新后):DOM重新渲染,此时数据和界面都是新的。
销毁前后:
beforeDestorye(销毁前):实例销毁前调用,这时候能够获取到this
destoryed(销毁后):实例销毁后调用,实例完全被销毁。
常用的属性、指令有哪些
属性:
data:用于定义组件的初始数据。
props:用于传递数据给子组件。
computed:用于定义计算属性。
methods:用于定义组件的方法。
watch:用于监听组件的数据变化。
components:用于注册子组件。可以通过 components 属性将其他组件注册为当前组件的子组件,从而在模板中使用这些子组件。
指令:
v-if:条件渲染指令,根据表达式的真假来决定是否渲染元素。
v-show:条件显示指令,根据表达式的真假来决定元素的显示和隐藏。
v-for:列表渲染指令,用于根据数据源循环渲染元素列表。
v-bind:属性绑定指令,用于动态绑定元素属性到 Vue 实例的数据。
v-on:事件绑定指令,用于监听 DOM 事件,并执行对应的 Vue 方法。
v-model:双向数据绑定指令,用于在表单元素和 Vue 实例的数据之间建立双向绑定关系。
v-text:文本插值指令,用于将数据插入到元素的文本内容中。
v-html:HTML 插值指令,用于将数据作为 HTML 解析并插入到元素中。
Computed 和 Watch 的区别
computed计算属性,通过对已有的属性值进行计算得到一个新值。它需要依赖于其他的数据,当数据发生变化时,computed会自动计算更新。computed属性值会被缓存,只有当依赖数据发生变化时才会重新计算,这样可以避免重复计算提高性能。
watch用于监听数据的变化,并在变化时执行一些操作。它可以监听单个数据或者数组,当数据发生变化时会执行对应的回调函数,和computed不同的是watch不会有缓存。
Vue组件通信
父传子
props
$children
$refs
子传父
$emit
$parent
兄弟组件
provied
inject
eventBus
Vuex
常见的事件修饰符及其作用
.stop阻止冒泡
.prevent阻止默认事件
.capture :与事件冒泡的方向相反,事件捕获由外到内;
.self :只会触发自己范围内的事件,不包含子元素;
.once:只会触发一次。
v-if和v-show的区别
v-if元素不可见,直接删除DOM,有更高的切换消耗。 v-show通过设置元素display: none控制显示隐藏,更高的初始渲染消耗。
v-html 的原理
会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。
v-model 是如何实现的,语法糖实际是什么?
Vue 中数据双向绑定是一个指令v-model,可以绑定一个响应式数据到视图,同时视图的变化能改变该值。
当作用在表单上:通过v-bind:value绑定数据,v-on:input来监听数据变化并修改value
当作用在组件上:本质上是一个父子通信语法糖,通过props和$emit实现。
data为什么是一个函数而不是对象
因为对象是一个引用类型,如果data是一个对象的情况下会造成多个组件共用一个data,data为一个函数,每个组件都会有自己的私有数据空间,不会干扰其他组件的运行。
mixin 和 mixins 区别
mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。
路由的hash和history模式的区别
hash模式 开发中默认的模式,地址栏URL后携带#,后面为路由。 原理是通过onhashchange()事件监听hash值变化,在页面hash值发生变化后,window就可以监听到事件改变,并按照规则加载相应的代码。hash值变化对应的URL都会被记录下来,这样就能实现浏览器历史页面前进后退。
history模式 history模式中URL没有#,这样相对hash模式更好看,但是需要后台配置支持。
history原理是使用HTML5 history提供的pushState、replaceState两个API,用于浏览器记录历史浏览栈,并且在修改URL时不会触发页面刷新和后台数据请求。
router和route的区别
$route 是路由信息,包括path、params、query、name等路由信息参数
$router 是路由实例,包含了路由跳转方法、钩子函数等
如何设置动态路由
params传参
路由配置: /index/:id
路由跳转:this.$router.push({name: ‘index’, params: {id: “zs”}});
路由参数获取:$route.params.id
最后形成的路由:/index/zs
query传参
路由配置:/index正常的路由配置
路由跳转:this.$rouetr.push({path: ‘index’, query:{id: “zs”}});
路由参数获取:$route.query.id
最后形成的路由:/index?id=zs
区别
获取参数方式不一样,一个通过$route.params,一个通过 $route.query
参数的生命周期不一样,query参数在URL地址栏中显示不容易丢失,params参数不会在地址栏显示,刷新后会消失
路由守卫
全局前置钩子:beforeEach、beforeResolve、afterEach
路由独享守卫:beforeEnter
组件内钩子:beforeRouterEnter、beforeRouterUpdate、beforeRouterLeave
Vue中key的作用
key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,减少DOM操作量,提高性能。
为什么不建议用index作为key?
如果将数组下标作为key值,那么当列表发生变化时,可能会导致key值发生改变,从而引发不必要的组件重新渲染,甚至会导致性能问题。例如,当删除列表中某个元素时,其后面的所有元素的下标都会发生改变,导致Vue重新渲染整个列表。
为什么v-for和v-if不能一起使用
v-for比v-if优先级更高,一起使用的话每次渲染列表时都要执行一次条件判断,造成不必要的计算,影响性能。
原理知识
双向数据绑定的原理
采用数据劫持结合发布者-订阅者模式的方式,data数据在初始化的时候,会实例化一个Observe类,在它会将data数据进行递归遍历,并通过Object.defineProperty方法,给每个值添加上一个getter和一个setter。在数据读取的时候会触发getter进行依赖(Watcher)收集,当数据改变时,会触发setter,对刚刚收集的依赖进行触发,并且更新watcher通知视图进行渲染。
使用 Object.defineProperty() 来进行数据劫持有什么缺点?
该方法只能监听到数据的修改,监听不到数据的新增和删除,从而不能触发组件更新渲染。vue2中会对数组的新增删除方法push、pop、shift、unshift、splice、sort、reserve通过重写的形式,在拦截里面进行手动收集触发依赖更新。
和Vue3相比有什么区别?
Vue3采用了Proxy代理的方式,Proxy是ES6引入的一个新特性,它提供了一个用于创建代理对象的构造函数。它是对整个对象的监听和拦截,可以对对象所有操作进行处理。而Object.defineProperty只能监听单个属性的读写,无法监听新增、删除等操作。
Vue是如何收集依赖的?
依赖收集发生在defineReactive()方法中,在方法内new Dep()实例化一个Dep()实例,然后在getter中通过dep.depend()方法对数据依赖进行收集,然后在settter中通过dep.notify()通知更新。整个Dep其实就是一个观察者,吧收集的依赖存储起来,在需要的时候进行调用。在收集数据依赖的时候,会为数据创建一个Watcher,当数据发生改变通知每个Watcher,由Wathcer进行更新渲染。
slot是什么?有什么作用?原理是什么?
slot插槽,一般在封装组件的时候使用,在组件内不知道以那种形式来展示内容时,可以用slot来占据位置,最终展示形式由父组件以内容形式传递过来,主要分为三种:
默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
对keep-alive的理解,它是如何实现的,具体缓存的是什么?
keep-alive是Vue.js的一个内置组件。它能够将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。
include 字符串或正则表达式,只有名称匹配的组件会被匹配;
exclude 字符串或正则表达式,任何名称匹配的组件都不会被缓存;
max 数字,最多可以缓存多少组件实例。
2 个生命周期 activated , deactivated
activated:当缓存的组件被激活时,该钩子函数被调用。可以在该钩子函数中进行一些状态恢复、数据更新等操作。
deactivated:当缓存的组件被停用时,该钩子函数被调用。可以在该钩子函数中进行一些状态保存、数据清理等操作。
keep-alive内部其实是一个函数式组件,没有template标签。在render中通过获取组件的name和include、exclude进行匹配。匹配不成功,则不需要进行缓存,直接返回该组件的vnode。
匹配成功就进行缓存,获取组件的key在cache中进行查找,如果存在,则将他原来位置上的 key 给移除,同时将这个组件的 key 放到数组最后面(LRU)也就实现了max功能。
不存在的话,就需要对组件进行缓存。将当前组件push(key)添加到尾部,然后再判断当前缓存的max是否超出指定个数,如果超出直接将第一个组件销毁(缓存淘汰策略LRU)。
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
$nextTick 原理及作用
Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。 nextTick是将回调函数放到一个异步队列中,保证在异步更新DOM的watcher后面,从而获取到更新后的DOM。
因为在created()钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在nextTick()的回调函数中。
Vue模版编译原理
模版编译主要过程:template —> ast —> render,分别对象三个方法
parse 函数解析 template
optimize 函数优化静态内容
generate 函数创建 render 函数字符串
调用parse方法,将template转化为AST(抽象语法树),AST定义了三种类型,一种html标签,一种文本,一种插值表达式,并且通过 children 这个字段层层嵌套形成了树状的结构。
optimize方法对AST树进行静态内容优化,分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化。
generate将AST抽象语法树编译成 render字符串,最后通过new Function(render)生成可执行的render函数
Vuex
Vuex 的原理
Vuex是专门为Vue设计的状态管理,当Vue从store中读取数据后,数据发生改变,组件中的数据也会发生变化。
Vue Components 负责接收用户操作交互行为,执行dispatch触发对应的action进行回应
dispatch唯一能执行action的方法
action用来接收components的交互行为,包含异步同步操作
commit对mutation进行提交,唯一能执行mutation的方法
mutation唯一可以修改state状态的方法
state页面状态管理容器,用于存储状态
getters读取state方法
Vue组件接收交互行为,调用dispatch方法触发action相关处理,若页面状态需要改变,则调用commit方法提交mutation修改state,通过getters获取到state新值,重新渲染Vue Components,界面随之更新。
Vuex中action和mutation的区别
mutation更专注于修改state,必须是同步执行。
action提交的是mutation,而不是直接更新数据,可以是异步的,如业务代码,异步请求。
action可以包含多个mutation
Vuex 和 localStorage 的区别
Vuex存储在内存中,页面关闭刷新就会消失。而localstorage存储在本地,读取内存比读取硬盘速度要快
Vuex应用于组件之间的传值,localstorage主要用于不同页面之间的传递
Vuex是响应式的,localstorage需要刷新
虚拟DOM
对虚拟DOM的理解
虚拟DOM就是用JS对象来表述DOM节点,是对真实DOM的一层抽象。可以通过一些列操作使这个棵树映射到真实DOM上。
如在Vue中,会把代码转换为虚拟DOM,在最终渲染到页面,在每次数据发生变化前,都会缓存一份虚拟DOM,通过diff算法来对比新旧虚拟DOM记录到一个对象中按需更新,最后创建真实DOM,从而提升页面渲染性能。
虚拟DOM就一定比真实DOM更快吗
虚拟DOM不一定比真实DOM更快,而是在特定情况下可以提供更好的性能。
在复杂情况下,虚拟DOM可以比真实DOM操作更快,因为它是在内存中维护一个虚拟的DOM树,将真实DOM操作转换为对虚拟DOM的操作,然后通过diff算法找出需要更新的部分,最后只变更这部分到真实DOM就可以。在频繁变更下,它可以批量处理这些变化从而减少对真实DOM的访问和操作,减少浏览器的回流重绘,提高页面渲染性能。
而在一下简单场景下,直接操作真实DOM可能会更快,当更新操作很少或者只是局部改变时,直接操作真实DOM比操作虚拟DOM更高效,省去了虚拟DOM的计算、对比开销。
虚拟DOM的解析过程
首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。
DIFF算法原理
diff的目的是找出差异,最小化的更新视图。 diff算法发生在视图更新阶段,当数据发生变化的时候,diff会对新旧虚拟DOM进行对比,只渲染有变化的部分。
对比是不是同类型标签,不是同类型直接替换
如果是同类型标签,执行patchVnode方法,判断新旧vnode是否相等。如果相等,直接返回。
新旧vnode不相等,需要比对新旧节点,比对原则是以新节点为主,主要分为以下几种。
newVnode 和 oldVnode都有文本节点,用新节点替换旧节点。
newVnode有子节点,oldVnode没有,新增newVnode的子节点。
newVnode没有子节点,oldVnode有子节点,删除oldVnode中的子节点。
newVnode和oldVnode都有子节点,通过updateChildren对比子节点。
双端diff
updateChildren方法用来对比子节点是否相同,将新旧节点同级进行比对,减少比对次数。会创建4个指针,分别指向新旧两个节点的首尾,首和尾指针向中间移动。
每次对比下两个头指针指向的节点、两个尾指针指向的节点,头和尾指向的节点,是不是 key是一样的,也就是可复用的。如果是重复的,直接patch更新一下,如果是头尾节点,需要进行移动位置,结果以新节点的为主。
如果都没有可以复用的节点,就从旧的vnode中查找,然后进行移动,没有找到就插入一个新节点。
当比对结束后,此时新节点还有剩余,就批量增加,如果旧节点有剩余就批量删除。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_25741071/article/details/132883638
操作系统八股文
计算机基础知识八股文(操作系统篇)_操作系统八股文-CSDN博客