前端必备八股文
八股文——
旧时科举考试的一种文体,也称制义、制艺、时文、八比文。通俗来说(八股文就是指文章的八个部分)
而计算机技术相关岗位面试中,八股文多指面试中常出现的问题,往往都是一些看起来高大上但是实际工作中没啥用的东西(科普结束,原文开始)
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 //动态创建 script 2 var script = document.createElement('script'); 3 4 // 设置回调函数 5 function getData(data) { 6 console.log(data); 7 } 8 9 //设置 script 的 src 属性,并设置请求地址 10 script.src = 'http://localhost:3000/?callback=getData'; 11 12 // 让 script 生效 13 document.body.appendChild(script);
-
JSONP 的缺点:
JSON 只支持 get,因为 script 标签只能使用 get 请求; JSONP 需要后端配合返回指定格式的数据。 -
document.domain 基础域名相同 子域名不同
-
window.name 利用在一个浏览器窗口内,载入所有的域名都是共享一个window.name
-
CORS CORS(Cross-origin resource sharing)跨域资源共享 服务器设置对CORS的支持原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求
-
proxy代理 目前常用方式,通过服务器设置代理
-
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 属性:
#target { will-change: transform; }
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实现三角符号
/*记忆口诀:盒子宽高均为零,三面边框皆透明。 */ div:after{ position: absolute; width: 0px; height: 0px; content: " "; border-right: 100px solid transparent; border-top: 100px solid #ff0; border-left: 100px solid transparent; border-bottom: 100px solid transparent; }
页面布局
1.Flex 布局
布局的传统解决方案,基于盒状模型,依赖 display 属性 + position 属性 + float 属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。
Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。指定容器 display: flex 即可。 简单的分为容器属性和元素属性。
容器的属性:
- flex-direction:决定主轴的方向(即子 item 的排列方法)flex-direction: row | row-reverse | column | column-reverse;
- flex-wrap:决定换行规则 flex-wrap: nowrap | wrap | wrap-reverse;
- flex-flow: .box { flex-flow: || ; }
- justify-content:对其方式,水平主轴对齐方式
- align-items:对齐方式,竖直轴线方向
- align-content
项目的属性(元素的属性):
- order 属性:定义项目的排列顺序,顺序越小,排列越靠前,默认为 0
- flex-grow 属性:定义项目的放大比例,即使存在空间,也不会放大
- flex-shrink 属性:定义了项目的缩小比例,当空间不足的情况下会等比例的缩小,如果 定义个 item 的 flow-shrink 为 0,则为不缩小
- flex-basis 属性:定义了在分配多余的空间,项目占据的空间。
- flex:是 flex-grow 和 flex-shrink、flex-basis 的简写,默认值为 0 1 auto。
- align-self:允许单个项目与其他项目不一样的对齐方式,可以覆盖
- align-items,默认属 性为 auto,表示继承父元素的 align-items 比如说,用 flex 实现圣杯布局
2.Rem 布局
首先 Rem 相对于根(html)的 font-size 大小来计算。简单的说它就是一个相对单例 如:font-size:10px;,那么(1rem = 10px)了解计算原理后首先解决怎么在不同设备上设置 html 的 font-size 大小。其实 rem 布局的本质是等比缩放,一般是基于宽度。
优点:可以快速适用移动端布局,字体,图片高度
缺点:
①目前 ie 不支持,对 pc 页面来讲使用次数不多;
②数据量大:所有的图片,盒子都需要我们去给一个准确的值;才能保证不同机型的适配;
③在响应式布局中,必须通过 js 来动态控制根元素 font-size 的大小。也就是说 css 样式和 js 代码有一定的耦合性。且必须将改变 font-size 的代码放在 css 样式之前。
3.百分比布局
通过百分比单位 " % " 来实现响应式的效果。通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。 直观的理解,我们可能会认为子元素的百分比完全相对于直接父元素,height 百分比相 对于 height,width 百分比相对于 width。 padding、border、margin 等等不论是垂直方向还是水平方向,都相对于直接父元素的 width。 除了 border-radius 外,还有比如 translate、background-size 等都是相对于自身的。
缺点:
(1)计算困难
(2)各个属性中如果使用百分比,相对父元素的属性并不是唯一的。造成我们使用百分比单位容易使布局问题变得复杂。
4.浮动布局
浮动布局:当元素浮动以后可以向左或向右移动,直到它的外边缘碰到包含它的框或者另外一个浮动元素的边框为止。元素浮动以后会脱离正常的文档流,所以文档的普通流中的框就变的好像浮动元素不存在一样。
优点
这样做的优点就是在图文混排的时候可以很好的使文字环绕在图片周围。另外当元素浮动了起来之后,它有着块级元素的一些性质例如可以设置宽高等,但它与inline-block还是有一些区别的,第一个就是关于横向排序的时候,float可以设置方向而inline-block方向是固定的;还有一个就是inline-block在使用时有时会有空白间隙的问题
缺点
最明显的缺点就是浮动元素一旦脱离了文档流,就无法撑起父元素,会造成父级元素高度塌陷
。
如何使用rem或viewport进行移动端适配
rem适配原理:
改变了一个元素在不同设备上占据的css像素的个数
rem适配的优缺点
- 优点:没有破坏完美视口
- 缺点:px值转换rem太过于复杂(下面我们使用less来解决这个问题)
viewport适配的原理
viewport适配方案中,每一个元素在不同设备上占据的css像素的个数是一样的。但是css像素和物理像素的比例是不一样的,等比的
viewport适配的优缺点
- 在我们设计图上所量取的大小即为我们可以设置的像素大小,即所量即所设
- 缺点破坏完美视口
清除浮动的方式
- 添加额外标签
<div class="parent"> //添加额外标签并且添加clear属性 <div style="clear:both"></div> //也可以加一个br标签 </div>
- 父级添加overflow属性,或者设置高度
- 建立伪类选择器清除浮动
//在css中添加:after伪元素 .parent:after{ /* 设置添加子元素的内容是空 */ content: ''; /* 设置添加子元素为块级元素 */ display: block; /* 设置添加的子元素的高度0 */ height: 0; /* 设置添加子元素看不见 */ visibility: hidden; /* 设置clear:both */ clear: both; }
JS、TS、ES6
JS中的8种数据类型及区别
包括值类型(基本对象类型)和引用类型(复杂对象类型)
基本类型(值类型): Number(数字),String(字符串),Boolean(布尔),Symbol(符号),null(空),undefined(未定义)在内存中占据固定大小,保存在栈内存中
引用类型(复杂数据类型): Object(对象)、Function(函数)。其他还有Array(数组)、Date(日期)、RegExp(正则表达式)、特殊的基本包装类型(String、Number、Boolean) 以及单体内置对象(Global、Math)等 引用类型的值是对象 保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址。
JS中的数据类型检测方案
1.typeof
console.log(typeof 1); // number console.log(typeof true); // boolean console.log(typeof 'mc'); // string console.log(typeof function(){}); // function console.log(typeof console.log()); // function console.log(typeof []); // object console.log(typeof {}); // object console.log(typeof null); // object console.log(typeof undefined); // undefined
优点:能够快速区分基本数据类型
缺点:不能将Object、Array和Null区分,都返回object
2.instanceof
console.log(1 instanceof Number); // false console.log(true instanceof Boolean); // false console.log('str' instanceof String); // false console.log([] instanceof Array); // true console.log(function(){} instanceof Function); // true console.log({} instanceof Object); // true
优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象
缺点:Number,Boolean,String基本数据类型不能判断
3.Object.prototype.toString.call()
var toString = Object.prototype.toString; console.log(toString.call(1)); //[object Number] console.log(toString.call(true)); //[object Boolean] console.log(toString.call('mc')); //[object String] console.log(toString.call([])); //[object Array] console.log(toString.call({})); //[object Object] console.log(toString.call(function(){})); //[object Function] console.log(toString.call(undefined)); //[object Undefined] console.log(toString.call(null)); //[object Null]
优点:精准判断数据类型
缺点:写法繁琐不容易记,推荐进行封装后使用
var && let && const
ES6之前创建变量用的是var,之后创建变量用的是let/const
三者区别:
- var定义的变量,
没有块的概念,可以跨块访问
, 不能跨函数访问。
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。 - var可以
先使用,后声明
,因为存在变量提升;let必须先声明后使用。 - var是允许在相同作用域内
重复声明同一个变量
的,而let与const不允许这一现象。 - 在全局上下文中,基于let声明的全局变量和全局对象GO(window)没有任何关系 ;
var声明的变量会和GO有映射关系; 会产生暂时性死区
:
暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined 如:console.log(typeof a) //undefined 而:console.log(typeof a)//未声明之前不能使用 let a
- let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于let的块作用特征即可解决
JS垃圾回收机制
-
项目中,如果存在大量不被释放的内存(堆/栈/上下文),页面性能会变得很慢。当某些代码操作不能被合理释放,就会造成内存泄漏。我们尽可能减少使用闭包,因为它会消耗内存。
-
浏览器垃圾回收机制/内存回收机制:
浏览器的Javascript具有自动垃圾回收机制(GC:Garbage Collecation),垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。
-
标记清除:在
js
中,最常用的垃圾回收机制是标记清除:当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,会被标记为“离开环境”。垃圾回收器会销毁那些带标记的值并回收它们所占用的内存空间。
谷歌浏览器:“查找引用”,浏览器不定时去查找当前内存的引用,如果没有被占用了,浏览器会回收它;如果被占用,就不能回收。
IE浏览器:“引用计数法”,当前内存被占用一次,计数累加1次,移除占用就减1,减到0时,浏览器就回收它。 -
优化手段:内存优化 ; 手动释放:取消内存的占用即可。
(1)堆内存:fn = null 【null:空指针对象】
(2)栈内存:把上下文中,被外部占用的堆的占用取消即可。
-
内存泄漏
在 JS 中,常见的内存泄露主要有 4 种,全局变量、闭包、DOM 元素的引用、定时器
作用域和作用域链
创建函数的时候,已经声明了当前函数的作用域==>当前创建函数所处的上下文
。如果是在全局下创建的函数就是[[scope]]:EC(G)
,函数执行的时候,形成一个全新的私有上下文EC(FN)
,供字符串代码执行(进栈执行)
定义:简单来说作用域就是变量与函数的可访问范围,由当前环境与上层环境的一系列变量对象组成
1.全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
2.函数作用域:在固定的代码片段才能被访问
作用:作用域最大的用处就是隔离变量
,不同作用域下同名变量不会有冲突。
作用域链参考链接一般情况下,变量到 创建该变量 的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
闭包的两大作用:保存/保护
-
闭包的概念
函数执行时形成的私有上下文EC(FN),正常情况下,代码执行完会出栈后释放;但是特殊情况下,如果当前私有上下文中的某个东西被上下文以外的事物占用了,则上下文不会出栈释放,从而形成不销毁的上下文。 函数执行函数执行过程中,会形成一个全新的私有上下文,可能会被释放,可能不会被释放,不论释放与否,他的作用是:
(1)保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,保护自己的私有变量不受外界干扰(操作自己的私有变量和外界没有关系);
(2)保存:如果当前上下文不被释放【只要上下文中的某个东西被外部占用即可】,则存储的这些私有变量也不会被释放,可以供其下级上下文中调取使用,相当于把一些值保存起来了;
我们把函数执行形成私有上下文,来保护和保存私有变量机制称为闭包
。
闭包是指有权访问另一个函数作用域中的变量的函数--《JavaScript高级程序设计》
稍全面的回答: 在js中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
-
闭包的特性:
-
1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。
1.1.闭包是密闭的容器,,类似于set、map容器,存储数据的
1.2.闭包是一个对象,存放数据的格式为 key-value 形式
-
2、函数嵌套函数
-
3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除
-
-
闭包形成的条件:
- 函数的嵌套
- 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
-
闭包的用途:
- 模仿块级作用域
- 保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
- 封装私有化变量
- 创建模块
-
闭包应用场景
闭包的两个场景,闭包的两大作用:
保存/保护
。 在开发中, 其实我们随处可见闭包的身影, 大部分前端JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送ajax请求成功|失败的回调;setTimeout的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。 -
闭包的优点:延长局部变量的生命周期
-
闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
JS 中 this 的五种情况
- 作为普通函数执行时,
this
指向window
。 - 当函数作为对象的方法被调用时,
this
就会指向该对象
。 - 构造器调用,
this
指向返回的这个对象
。 - 箭头函数 箭头函数的
this
绑定看的是this所在函数定义在哪个对象下
,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。 - 基于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 事件循环
JS
是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.then
,MutationObserver
,宏任务的话就是setImmediate setTimeout setInterval
JS运行的环境。一般为浏览器或者Node。 在浏览器环境中,有JS 引擎线程和渲染线程,且两个线程互斥。 Node环境中,只有JS 线程。 不同环境执行机制有差异,不同任务进入不同Event Queue队列。 当主程结束,先执行准备好微任务,然后再执行准备好的宏任务,一个轮询结束。
浏览器中的事件环(Event Loop)
事件环的运行机制是,先会执行栈中的内容,栈中的内容执行后执行微任务,微任务清空后再执行宏任务,先取出一个宏任务,再去执行微任务,然后在取宏任务清微任务这样不停的循环。
-
eventLoop 是由JS的宿主环境(浏览器)来实现的;
-
事件循环可以简单的描述为以下四个步骤:
- 函数入栈,当Stack中执行到异步任务的时候,就将他丢给WebAPIs,接着执行同步任务,直到Stack为空;
- 此期间WebAPIs完成这个事件,把回调函数放入队列中等待执行(微任务放到微任务队列,宏任务放到宏任务队列)
- 执行栈为空时,Event Loop把微任务队列执行清空;
- 微任务队列清空后,进入宏任务队列,取队列的第一项任务放入Stack(栈)中执行,执行完成后,查看微任务队列是否有任务,有的话,清空微任务队列。重复4,继续从宏任务中取任务执行,执行完成之后,继续清空微任务,如此反复循环,直至清空所有的任务。
浏览器中的任务源(task):
宏任务(macrotask)
:
宿主环境提供的,比如浏览器
ajax、setTimeout、setInterval、setTmmediate(只兼容ie)、script、requestAnimationFrame、messageChannel、UI渲染、一些浏览器api微任务(microtask)
:
语言本身提供的,比如promise.then
then、queueMicrotask(基于then)、mutationObserver(浏览器提供)、messageChannel 、mutationObersve
介绍节流防抖原理、区别以及应用
节流
:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
防抖
:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!
使用场景
:
节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……
防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。
/** * 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。 * @param fn要被节流的函数 * @param delay规定的时间 */ function throttle(fn, delay) { //记录上一次函数触发的时间 var lastTime = 0; return function(){ //记录当前函数触发的时间 var nowTime = Date.now(); if(nowTime - lastTime > delay){ //修正this指向问题 fn.call(this); //同步执行结束时间 lastTime = nowTime; } } } document.onscroll = throttle(function () { console.log('scllor事件被触发了' + Date.now()); }, 200); /** * 防抖函数 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效 * @param fn要被节流的函数 * @param delay规定的时间 */ function debounce(fn, delay) { //记录上一次的延时器 var timer = null; return function () { //清除上一次的演示器 clearTimeout(timer); //重新设置新的延时器 timer = setTimeout(function(){ //修正this指向问题 fn.apply(this); }, delay); } } document.getElementById('btn').onclick = debounce(function () { console.log('按钮被点击了' + Date.now()); }, 1000);