前端经典面试题


基础知识

说一下 http与https

1
2
3
4
回答:
HTTP是用于在Web浏览器和Web服务器之间传输信息的协议,而HTTPS是HTTP协议的安全版本,
通过加密和身份验证确保数据的安全性和真实性。HTTPS使用SSL / TLS协议加密数据传输,并常用于处理敏感信息和保护用户隐私。

  • 知识点:
    当谈到HTTP(Hypertext Transfer Protocol)和HTTPS(Hypertext Transfer Protocol Secure)时,这两者都是用于在计算机网络上传输数据的协议。
    HTTP 是一种无状态的协议,它使用TCP作为传输层协议,用于在Web浏览器和Web服务器之间传输信息,常用于页面请求和响应。HTTP的常见端口号是80。
    然而,HTTPS是在HTTP上加密和身份验证的安全版本。它使用公钥加密算法(如SSL / TLS)来保护数据的传输,并确保通信的机密性和完整性。HTTPS的常见端口号是443。
    HTTP和HTTPS的主要区别在于安全性。HTTPS通过加密和数字证书验证确保了数据的安全性和真实性。在HTTPS中,服务器和浏览器之间的通信是通过使用SSL / TLS协议来进行加密和保护的。
    对于前端开发而言,了解HTTP和HTTPS的基本概念非常重要。当我们在开发网站时,需要考虑使用HTTPS来加强对数据的保护,特别是在进行用户登录、交易或传输敏感信息时。同时,还要了解如何在前端页面中正确地处理HTTP和HTTPS请求,以确保数据的安全性和正确性。

    https 协议的缺点

    1. https 握手阶段比较费时,

    2. 会使页面加载时间延长 50%,增加 10%~20%的耗电。

    3. https 缓存不如 http 高效,会增加数据开销。 SSL 证书也需要钱,功能越强大的证书费用越高。

    4. SSL 证书需要绑定 IP,不能再同一个 ip 上绑定多个域名,ipv4 资源支持不了这种消耗


TCP的三次握手四次挥手

1
2
3
4
5
6
7
回答:
TCP三次握手是建立TCP连接的一种过程。
首先,客户端发送一个请求连接的SYN报文给服务器。
然后服务器收到并确认客户端的请求,发送一个带有自己SYN和确认号的报文作为回应。
最后,客户端收到服务器的确认后,再发送一个确认报文给服务器。
这样,双方都知道彼此准备好进行数据传输,TCP连接便建立成功。这个过程确保了通信双方的可靠性和同步性,
以保证后续的数据传输的正常进行。
1
2
3
4
5
6
7
8
目标: 关闭连接(四次挥手) 
不能直接一次性断开连接(双方知晓), 万一还有什么数据没有传完, 造成数据的丢失!
这和有礼貌的好友道别一样:(a:客户端 b:服务端)
1、一开始A想要回家离开,但是呢?怕B还有事情要交代,那么呢?只好先向B打招呼,我要走了,请求停止交谈(请求断开连接)
(此时,a到B的连接没有断开,依旧可以进行通信);
2、同意A的请求,说好的,但是我这里可能还有一些话(数据)没说完。我检查看看, 你等等, 等我说完你再走。
3、B确实没啥要补充的了,就告知你我可以撤了
4、A说好的,知道了,88;(B得知A走开了,关闭了自己的连接 )
  • 知识点:TCP三次握手是在建立TCP连接时的一种握手过程。它确保了通信双方的可靠性和同步性。以下是TCP三次握手的步骤:

    1. 第一次握手(SYN):客户端向服务器发送一个SYN报文,请求建立连接。报文中包含随机生成的初始序列号(ISN)。
    2. 第二次握手(SYN + ACK):服务器收到客户端的SYN报文后,确认接收,并发送一个带有自己的SYN和确认号(ACK)的报文作为回应。服务器还会生成自己的初始序列号(ISN)并发送给客户端。
    3. 第三次握手(ACK):客户端收到服务器的确认后,发送一个ACK报文作为回应。该报文中的确认号是服务器发送的SYN + ACK报文的序列号加1。

    通过这个三次握手过程,两个端点(客户端和服务器)可以确认彼此都已准备好进行数据传输。在握手完成后,TCP连接就建立起来了,双方可以开始传输数据。


TCP 和 UDP 的区别

1
2
3
4
5
6
7
8
回答:
(1)TCP 是面向连接的,udp 是无连接的即发送数据前不需要先建立链接。
(2)TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,
即不保证可靠交付。 并且因为 tcp 可靠, 面向连接,不会丢失数据因此适合大数据量的交换。
(3)TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包)。
(4)TCP 只能是 1 对 1 的,UDP 支持 1 对 1,1 对多。
(5)TCP 的首部较大为 20 字节,而 UDP 只有 8 字节。
(6)TCP 是面向连接的可靠性传输,而 UDP 是不可靠的。

https常见状态码

1
2
3
4
5
6
7
8
9
10
11
12
回答:
200 OK:请求成功。服务器成功处理了请求并返回了请求的资源。
301 Moved Permanently:永久重定向。请求的资源被永久移动到其他位置。
302 Found:临时重定向。请求的资源临时移动到其他位置。
400 Bad Request:请求错误。服务器无法理解请求的语法或参数。
401 Unauthorized:未授权。需要身份验证或认证才能访问请求的资源。
403 Forbidden:禁止访问。服务器拒绝请求访问所请求的资源。
404 Not Found:未找到。服务器找不到请求的资源。
500 Internal Server Error:内部服务器错误。服务器在处理请求时遇到了错误。
502 Bad Gateway:网关错误。作为代理或网关的服务器从上游服务器接收到无效的响应。
503 Service Unavailable:服务不可用。服务器暂时无法处理请求,通常是由于过载或维护。


输入URL解析过程

1
2
3
4
5
6
7
8
1.DNS解析:将URL中的域名解析为对应的IP地址。
2.建立TCP连接:使用解析得到的IP地址,浏览器与服务器建立TCP连接。
3.发送HTTP请求:浏览器向服务器发送HTTP请求,包括请求的方法、URI和请求头信息。
4.服务器处理请求:服务器接收到请求后,根据请求的URI和方法,处理请求并准备响应。
5.响应HTTP请求:服务器生成HTTP响应,包括状态码、响应头和响应体,并发送回浏览器。
接收并渲染页面:浏览器接收到服务器的响应后,解析响应体中的HTML代码,并构建DOM树。
然后,根据CSS样式和JavaScript代码对页面进行渲染,最终将渲染结果呈现在用户的屏幕上。


GET 和 POST 的区别

1
2
3
回答:
GET是通过URL传递数据,参数暴露在URL上,适合传输少量数据,不安全,可被缓存,幂等性。
POST是通过请求体传递数据,参数不暴露在URL上,适合传输大量数据,安全,不可缓存,不一定具有幂等性
  • 知识点:
  1. 数据传递方式:GET请求将数据通过URL的查询参数进行传递,数据会附加在URL的后面,例如:http://example.com/path?key1=value1&key2=value2。而POST请求将数据包含在请求体中,不会直接暴露在URL中。
  2. 数据长度限制:GET请求的传输数据长度有限制,通常在几千个字符左右。而POST请求没有严格的长度限制,可以传输较大的数据。
  3. 数据安全性:GET请求的参数会暴露在URL中,可能被浏览器历史记录、服务器日志记录等所记录,不适合传输敏感信息。POST请求的参数不会直接暴露在URL中,因此相对来说更安全。
  4. 缓存:GET请求通常可以被浏览器缓存,因为GET请求无副作用,多次请求同一URL可以从缓存中获取响应。POST请求默认是不可缓存的,因为POST请求可能对服务器端产生副作用。
  5. 幂等性:GET请求是幂等的,多次重复请求对服务器产生的结果是一样的,不会产生副作用。而POST请求一般不是幂等的,多次请求可能会产生不同的结果,可能对服务器产生副作用。

浏览器常见存储方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
回答:
cookie、sessionStorage和localStorage是在客户端存储数据的三种常见方式,它们之间有一些区别:
存储位置:
cookie: 存储在客户端浏览器中,并在每次HTTP请求时都会自动发送到服务器。
sessionStorage: 存储在浏览器的会话存储空间中,仅在当前会话期间有效,浏览器关闭后会被清除。
localStorage: 存储在浏览器的持久化存储空间中,除非手动清除,否则会一直保留。
容量限制:
cookie: 大小限制为4KB,每个域名下的cookie总数也有限制。
sessionStorage和localStorage: 大小限制一般为5MB或更大,可以存储较大数量的数据。
数据生命周期:
cookie: 可以设置过期时间,存储在客户端并在过期时间之前有效。
sessionStorage: 仅在当前会话期间有效,即浏览器关闭或标签页关闭后会被清除。
localStorage: 永久有效,除非手动清除或代码删除。
数据访问权限:
cookie: 具有域名访问权限,可以在不同页面、不同标签间共享。
sessionStorage: 仅对创建数据的页面及其后续页面有效,不同标签页之间数据不共享。
localStorage: 对创建数据的页面及其后续页面有效,不同标签页之间数据共享。




HTML基础

对 HTML 语义化标签的理解

1
2
3
4
5
回答:
语义化的HTML是一种合理使用具有适当语义的HTML元素的编码方式。
使用正确的标签描述内容,使代码具有良好的结构和可读性。
语义化的HTML有助于提高可访问性、SEO友好以及代码的可维护性。

  • 常见语义化标签:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <header>:定义文档或节的页眉,通常包含网站的logo、导航栏和页面的标题。
    <nav>:用于定义页面的导航部分,包含页面的主要导航链接。
    <main>:定义文档的主要内容,每个文档只能有一个<main>标签。
    <article>:用于定义独立的文章或内容块,如博客文章、新闻报道等。
    <section>:用于将相关内容组织在一起,如页面的不同章节、主题内容等。
    <aside>:定义页面的附属内容,通常是侧边栏、辅助栏或广告等。
    <footer>:定义文档或节的页脚,通常包含版权信息、联系方式等。
    <figure>和<figcaption>:<figure>用于包含媒体内容(如图片、音频、视频等),而<figcaption>则用于给媒体内容添加标题或说明。
    <time>:用于标记时间,可以表示具体的日期、时间或日期时间。
    <mark>:用于标记文本中的突出部分,通常用黄色背景突出显示。

Doctype 的作用是什么

1
2
3
4
5
回答:
Doctype(文档类型声明)指示浏览器使用哪种HTML规范解析文档。
它告诉浏览器如何处理页面的渲染模式和布局。
Doctype声明必须在HTML文档的开头进行指定,否则浏览器可能会以混杂模式解析页面。
混杂模式,向后兼容,模拟老式浏览器,防止浏览器无法兼容页面

如何优化网页的加载性能

1
2
3
4
5
6
7
8
回答:

使用合适的Doctype声明,以避免浏览器进入混杂模式。
将CSS样式表放在页面的 <head> 标签中,并进行合理的压缩和合并。
将JavaScript脚本放在页面底部,或使用异步加载或延迟加载。
优化图片,使用适当的格式、压缩和懒加载等。
使用浏览器缓存和CDN来加速资源加载。


meta标签的作用

1
2
3
回答:
<meta> 标签是HTML中的元数据标签,用于提供关于网页的元数据信息。
它通常位于文档的 <head> 部分,提供关于网页的描述、字符编码、视口设置等
  • 知识点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    charset:  指定文档的字符编码,如 <meta charset="UTF-8">。
    name 和 content: 用于指定元数据的名称和值,如 <meta name="description" content="网页描述">。
    http-equiv 和 content:用于提供与HTTP相关的元数据,如<meta http-equiv="refresh" content="5;url=example.com">
    用于设置页面的自动刷新。
    viewport:用于定义可视区域的设置,特别用于响应式设计,如 <meta name="viewport" content="width=device-width, initial-scale=1.0">
    用于确保适应不同设备的屏幕宽度。
    name="robots":用于控制搜索引擎爬虫的行为,如<meta name="robots" content="index,follow"> 用于指示允许搜索引擎索引页面并跟踪链接。
    name="author":用于指定网页作者。
    name="keywords":用于指定网页关键词。
    http-equiv="X-UA-Compatible":用于控制浏览器的兼容性模式,如 <meta http-equiv="X-UA-Compatible" content="IE=edge">
    用于告诉IE使用最新版本的渲染引擎。

a 标签的 href 属性和 target 属性

1
2
3
4
5
6
回答:
href 属性是 <a> 标签中最重要的属性之一。它指定了链接的目标URL,可以是其他网页、文件、锚点或电子邮件地址等
target 属性用于指定链接在何处打开。常见的取值有:
_blank:在新的浏览器窗口或标签页中打开链接。
_self:在当前窗口或标签页中打开链接(默认值)。


img标签的常用属性及其作用

1
2
3
4
5
回答:
src:用于指定图像文件的URL,必填属性。
alt:用于提供图像的替代文本,用于在图像无法显示时展示给用户。建议提供有意义的描述
title:用于提供图像的额外提示信息,通常在鼠标悬停时显示


HTML5 对比 4 有哪些不同之处?

image-20231009094913211

contenteditable添加这个属性后可以直接进行修改标签内容选项

draggable这个属性必须写全等于true 使用后的标签可以进行拖拽

hidden属性相当于display:none进行隐藏使用

CSS基础

CSS选择器优先级

1
2
3
4
5
6

ID选择器:100。
类选择器、属性选择器和伪类选择器:10。
元素选择器和伪元素选择器:1。
行内样式拥有比选择器优先级更高的优先级(css层叠是用同为相加)
还有一些特殊情况,例如使用 !important 关键字或者内联样式表会影响选择器的优先级。!important 关键字会使规则具有最高的优先级

隐藏元素方法

1
2
3
4
5
6
7
8
9
display: none;:这是最常见的一种隐藏元素的方法。通过将元素的显示属性设置为 none,
可以完全隐藏该元素,并且不会在页面中占据任何空间。
visibility: hidden;:通过将元素的可见性属性设置为 hidden,可以隐藏元素,但该元素仍然占据页面中的空间。
opacity: 0;:通过将元素的透明度属性设置为 0,可以使元素变得完全透明,从而达到隐藏的效果。
与 display: none; 不同,该方法仍然保留元素在文档流中的位置。
position: absolute; 或 position: fixed;:通过将元素的定位属性设置为 absolute 或 fixed,
并设置 left 或 top 属性将元素移出屏幕之外,实现元素的隐藏效果。这种方法可以用于隐藏某个特定区域或元素。
transform: scale(0);属性可以用来改变元素的大小,并且也可以通过设置缩放值来实现隐藏效果。
但需要注意的是,使用缩放来隐藏元素可能会导致元素仍然占据页面中的空间。

让元素居中的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1.水平垂直居中(Flexbox):使用 Flexbox 布局可以很方便地实现元素的水平和垂直居中。
.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
2.水平垂直居中(Grid):使用 CSS Grid 布局也可以实现元素的水平和垂直居中。
.container {
display: grid;
place-items: center; /* 水平垂直居中 */
}
3.水平居中(块级元素):对于块级元素,可以使用 margin 属性将左右外边距设置为 "auto" 来实现水平居中。
.element {
margin-left: auto;
margin-right: auto;
}
4.水平居中(行内元素):对于行内元素,可以使用父容器的 text-align 属性来实现水平居中。
.container {
text-align: center;
}
5.水平垂直居中(绝对定位):使用绝对定位结合 transform 属性可以实现元素的水平和垂直居中。
.element {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

定位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


静态定位(Static Positioning):这是元素默认的定位方式。静态定位的元素根据正常文档流进行布局,不受其他定位方式的影响。

相对定位(Relative Positioning):通过设置position: relative;
可以将元素相对于其原始位置进行定位。相对定位时,可以使用top、right、bottom和left属性来指定元素相对于其原始位置的偏移量。

绝对定位(Absolute Positioning):通过设置position: absolute;
可以将元素相对于最近的已定位祖先元素进行定位,如果没有已定位的祖先元素,则相对于文档的初始坐标进行定位。绝对定位时,可以使用top、right、bottom和left属性来指定元素相对于其定位参考点的偏移量。

固定定位(Fixed Positioning):通过设置position: fixed;
可以将元素相对于浏览器窗口进行定位,即使页面滚动,元素也会保持在固定的位置。固定定位时,
可以使用top、right、bottom和left属性来指定元素相对于浏览器窗口的偏移量。

粘性定位(Sticky Positioning):通过设置position: sticky;
可以将元素相对于其父元素或视口进行定位。粘性定位的元素在滚动到特定位置时会变为固定定位,否则按照正常文档流进行布局

css中的大小单位

1
2
3
4
5
6
7
8
9
10
11
12
13


px(像素):px是绝对长度单位,表示相对于显示设备的物理像素。它是最常用的单位,通常用于制定具体的尺寸。

em:em是相对长度单位,它相对于父元素的字体大小来计算。如果一个元素的字体大小是16px,那么设置为1em,就相当于16px。
如果嵌套在另一个元素中,其父元素的字体大小为20px,那么1em就相当于20px。通常用于调整字体大小。

rem:rem也是相对长度单位,但它相对于根元素(即html元素)的字体大小来计算。与em不同,rem是相对于根元素的字体大小而不是父元素的字体大小。
它通常用于制作响应式布局。例如,如果根元素的字体大小是16px,那么设置为1rem就等于16px,而无论嵌套在任何元素中。

vw(视窗宽度)和vh(视窗高度):vw和vh是相对于视口宽度和视口高度的单位。1vw等于视口宽度的1%,1vh等于视口高度的1%。
它们通常用于制作响应式布局,并可以根据视口大小自动调整元素的尺寸。

css清除浮动

1
2
3
4
5
6
7
8
9
10
11
造成原因:浮动脱离标准流,不占位置(父盒子不会被撑开)
解决方法
1.定高法:直接给父亲写固定高度
2.添加一个盒子给盒子一个(clear:both;)
3.overflow:hidden; 溢出隐藏,出发了BFC,解决了浮动影响
4.利用伪元素的方法 并将其应用于父元素。
.clearfix::after {
content: "";
display: table;
clear: both;
}

谈谈你对BFC的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BFC,即块级格式化上下文(Block Formatting Context),是CSS中一个非常重要的概念。BFC是页面上一个独立的渲染区域,决定了其中元素的布局及相互影响。

BFC的形成:一个元素会成为一个BFC,如果满足以下条件之一:
根元素(<html>)
浮动元素(float属性不为none)
绝对定位元素(position为absolute或fixed)
行内块元素(display为inline-block)
overflow值不为visible的块级元素
BFC应用:
1.处理块级元素的上下合并问题
2.处理margin塌陷
3.清除浮动
4.实现自适应布局
左边固定,右边自适应
flex==>display:flex,左边定宽,右边flex:1
浮动 ==> 先浮动占位置,再中间盒子overflow:hidden
定位 ==> 先定位,再设置padding即可

什么是CSS Sprites以及它的好处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
精灵图/雪碧图
是一种优化网页加载性能的技术,它将多个小的图像合并成一个大的图像,并通过CSS的background-position属性来显示所需部分的图像

减少HTTP请求数量:在使用CSS Sprites之前,每个小图像都需要发送一个HTTP请求。
而使用CSS Sprites后,只需要发送一个HTTP请求来加载合并后的大图像,从而减少了页面加载时的HTTP请求数量。
这可以有效降低加载时间,提升网页性能。

减小图像文件大小:合并后的大图像通常会比原始的小图像文件大小更小。
这是因为合并后的图像可以更好地利用图像压缩算法,从而减小文件大小。较小的文件大小也有助于减少网络传输的时间和成本。

提高渲染速度:浏览器在下载完毕CSS Sprites图像后,只需要渲染一次该图像,
然后使用不同的background-position来显示需要的部分。相比多次渲染多个小图像,这种方式可以显著提高渲染速度,尤其是在含有大量图像的页面上。

更好的用户体验:由于CSS Sprites能够减少页面加载时间和渲染时间,
网页的响应速度会更快,用户可以更快地浏览和交互。这可以提供更好的用户体验,减少用户的等待时间和流失率。

注意:CSS Sprites适用于小图标、按钮、背景图等多个小图像的场景。对于大型图像或者需要动态修改的图像,不适合使用CSS Sprites。

你对盒子模型的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
所有元素都表示为一个个矩形的盒子,再用 CSS 去决定这些盒子的大小尺寸、显示位置、以及其他属性(如颜色、背景、边框等)。
盒模型它由以下几部分组成
内容 content
内边距 padding
边框 border
外边距 margin

盒子模型分为两种类型:
在CSS3中,我们可以通过设置 box-sizing 的值来决定具体使用何种盒模型:
content-box 标准盒模型
border-box 怪异盒模型


标准盒模型计算大小 content+padding +margin+border
怪异盒模型计算大小
在怪异盒模型下,元素的 width 和 height 值却不是 content 的实际宽高,而是去除 margin 后剩下的元素占用区域的宽高
盒子占据页面宽度 = margin + width + margin


你对Flex布局的理解

链接地址

1
可查看我的专题  移动端布局   查看flex具体理解

javaScript基础

解释下什么是变量声明提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
变量提升(hoisting),是负责解析执行代码的 JavaScript 引擎的工作方式产生的一个特性。
JS引擎在运行一份代码的时候,会按照下面的步骤进行工作:
1. 首先,对代码进行预解析,并获取声明的所有变量
2. 然后,将这些变量的声明语句统一放到代码的最前面
3. 最后,开始一行一行运行代码
例如:
console.log(a)
var a = 1
function b() {
console.log(a)
}
b() // 1
执行的步骤
1. JS引擎将 `var a = 1` 分解为两个部分:变量声明语句 `var a = undefined` 和变量赋值语句 `a = 1`
2. JS引擎将 `var a = undefined` 放到代码的最前面,而 `a = 1` 保留在原地
变量的这一转换过程,就被称为变量的声明提升。
而这是不规范, 不合理的, 我们用的 let 就没有这个变量提升的问题

JS 的参数是以什么方式进行传递的

1
2
3
4
基本数据类型和复杂数据类型的数据在传递时,会有不同的表现。
基本类型:是值传递
基本类型的传递方式比较简单,是按照 `值传递` 进行的。
复杂类型: 传递的是地址! (变量中存的就是地址)

JS垃圾回收是怎么做的

JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。

正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题,

但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况。

内存的生命周期

  1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存

  2. 内存使用:即读写内存,也就是使用变量、函数等

  3. 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存

    全局变量一般不会回收, 一般局部变量的的值, 不用了, 会被自动回收掉

垃圾回收算法说明

所谓垃圾回收, 核心思想就是如何判断内存是否已经不再会被使用了, 如果是, 就视为垃圾, 释放掉

下面介绍两种常见的浏览器垃圾回收算法: 引用计数 和 标记清除法

引用计数

IE采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。

如果没有任何变量指向它了,说明该对象已经不再需要了。

但它却存在一个致命的问题:循环引用。

如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。

标记清除算法

现代的浏览器已经不再使用引用计数算法了。

现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。

标记清除法:

  • 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。

  • 简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。

  • 凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)。

参考文章:JavaScript内存管理


谈谈你对 JS作用域链的理解

JavaScript 在执⾏过程中会创建一个个的可执⾏上下⽂。 (每个函数执行都会创建这么一个可执行上下文)

每个可执⾏上下⽂的词法环境中包含了对外部词法环境的引⽤,可通过该引⽤来获取外部词法环境中的变量和声明等。

这些引⽤串联起来,⼀直指向全局的词法环境,形成一个链式结构,被称为作⽤域链。

简而言之: 函数内部 可以访问到 函数外部作用域的变量, 而外部函数还可以访问到全局作用域的变量,

这样的变量作用域访问的链式结构, 被称之为作用域链

js全局有全局可执行上下文, 每个函数调用时, 有着函数的可执行上下文, 会入js调用栈

每个可执行上下文, 都有者对于外部上下文词法作用域的引用, 外部上下文也有着对于再外部的上下文词法作用域的引用

=> 就形成了作用域链

谈谈你对闭包的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
闭包=内层函数+外层函数的变量
作用:封闭数据,提供操作,外部也可以访问函数内部的变量(保证数据的私密性)
问题:可能会引起内存泄漏
基本格式:
function outer() {
let i =1
function fn(){
log(i)
}
return fn
}
const fun =outer()
fun()

谈谈你对原型链的理解

要讲清楚这个问题,主要着重这几个方面:

  • 什么是原型对象

  • 构造函数, 原型对象, 实例的三角关系图

  • 原型链如何形成

利用原型解决构造函数方法内存浪费问题
1.prototype 原型对象 每一个构造函数都有一个prototype属性 指向另一个对象
我们可以吧不变的方法直接定义再prototype对象上 这样所有对象实例就可以共享
2.原型对象里面的函数this指向还是实例对象
3.prototype中的constructor用来指向是哪一个函数
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象
则必须手动利用constructor指回原来的构造函数
4.对象原型__proto__指向构造函数的prototype原型对象
注意__proto__非js标准[[prototype]]意义相同 只读属性 也有一个指向创建该实例的counstructor

image-20231127104741195

谈谈对于继承的理解

原型继承

原型继承: 通过改造原型链, 利用原型链的语法, 实现继承方法!定义Person构造函数
定义Student构造函数
原型继承: 利用原型链, 继承于父级构造函数, 继承原型上的方法
语法: 子构造函数.prototype = new 父构造函数()

image-20231127191556841

组合继承

组合继承有时候也叫伪经典继承,指的是将原型链 和 借用构造函数 call 技术组合到一块,

从而发挥二者之长的一种继承模式,其背后的思路: 是使用原型链实现对原型属性和方法的继承 (主要是方法),

而通过借用构造函数来实现对实例属性构造的继承。这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它的自己的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 定义Person构造函数
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function () {
console.log('人类会说话')
}

// 2. 定义Student构造函数
function Student (name, age, className) {
Person.call(this, name, age) // 实现构造属性的继承
this.className = className
}
// 3. 原型继承: 利用原型链, 继承于父级构造函数, 继承原型上的方法
// 语法: 子构造函数.prototype = new 父构造函数()
Student.prototype = new Person()
Student.prototype.study = function() {
console.log('学生在学习')
}
let stu = new Student('张三', 18, '80期')
stu.say()
console.log(stu)
// 方法通过 原型继承
// 属性通过 父构造函数的.call(this, name, age)

寄生组合继承

student实例上有 name age, 而原型 __proto__上不需要再有这些属性, 所以利用 Object.create 改装下

Object.create(参数对象),

  1. Object.create 会创建一个新对象,
  2. 并且这个新对象的__proto__ 会指向传入的参数对象

image-20231127191824970

es6 - class 实现继承 extends

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 继承关键字 => extends
class Person {
constructor (name, age) {
this.name = name
this.age = age
}
jump () {
console.log('会跳')
}
}

class Teacher extends Person {
constructor (name, age, lesson) {
super(name, age) // extends 中, 必须调用 super(), 会触发执行父类的构造函数
this.lesson = lesson
console.log('构造函数执行了')
}
sayHello () {
console.log('会打招呼')
}
}

let teacher1 = new Teacher('zs', 18, '体育')
console.log(teacher1)

如何判断是否是数组?

方法一:使用 toString 方法

1
2
3
4
5
6
function isArray(arg) {
return Object.prototype.toString.call(arg) === '[object Array]'
}

let arr = [1,2,3]
isArray(arr) // true

方法二:使用 ES6 新增的 Array.isArray 方法

1
2
let arr = [1,2,3]
Array.isArray(arr) // true

谈谈你对this的理解

this 是一个在运行时才进行绑定的引用,在不同的情况下它可能会被绑定不同的对象。

默认绑定

默认绑定 (指向window的情况) (函数调用模式 fn() )

默认情况下,this 会被绑定到全局对象上,比如在浏览器环境中就为window对象,在node.js环境下为global对象。

隐式绑定

隐式绑定 (谁调用, this指向谁) (方法调用模式 obj.fn() )

如果函数的调用是从对象上发起时,则该函数中的 this 会被自动隐式绑定为对象

显式绑定

显式绑定 (又叫做硬绑定) (上下文调用模式, 想让this指向谁, this就指向谁)

硬绑定 => call apply bind

1
2
3
4
5
改变this指向
1.fun.call(指向谁,实参...) //调用函数改变this指向
2.fun.apply(指向谁,[数组传实参]) //调用函数改变this指向
Math,max.apple(max,数组) 用来计算数组最大值
3.fun.bind(指向,实参...) //不调用函数有返回值要重新调

new 绑定

new 绑定 (构造函数模式)

另外,在使用 new 创建对象时也会进行 this 绑定

当使用 new 调用构造函数时,会创建一个新的对象并将该对象绑定到构造函数的 this


箭头函数中的this指向什么

箭头函数不同于传统函数,它其实没有属于⾃⼰的 this

它所谓的 this 是, 捕获其外层 上下⽂的 this 值作为⾃⼰的 this 值。

并且由于箭头函数没有属于⾃⼰的 this ,它是不能被 new 调⽤的。


Promise 的静态方法

promise的三个状态: pending(默认) fulfilled(成功) rejected(失败)

  1. resolve函数被执行时, 会将promise的状态从 pending 改成 fulfilled 成功
  2. reject函数被执行时, 会将promise的状态从pending 改成 rejected 失败

Promise.reject()

1
2
3
new Promise((resolve, reject) => {
reject()
})

Promise.resolve()

1
2
3
new Promise((resolve, reject) => {
resolve()
})

Promise.all([promise1, promise2, promise3]) 等待原则, 是在所有promise都完成后执行, 可以用于处理一些并发的任务

1
2
3
4
// 后面的.then中配置的函数, 是在前面的所有promise都完成后执行, 可以用于处理一些并发的任务
Promise.all([promise1, promise2, promise3]).then((values) => {
// values 是一个数组, 会收集前面promise的结果 values[0] => promise1的成功的结果
})

Promise.race([promise1, promise2, promise3]) 赛跑, 竞速原则, 只要三个promise中有一个满足条件, 就会执行.then(用的较少)

宏任务 微任务 是什么

EventLoop事件循环队列

javascript是一门单线程执行的编程语言(同时只能做一件事) 同步任务和异步任务

同步任务:非耗时任务值的是在主线程上排队执行的任务

异步任务:耗时任务,异步任务由javascript委托给宿主环境进行执行 当异步任务执行完成后,会通知javascript主线程执行异步回调函数

宏任务和微任务

宏任务: 主线程代码, setTimeout 等属于宏任务, 上一个宏任务执行完, 才会考虑执行下一个宏任务

微任务: promise .then .catch的需要执行的内容, 属于微任务, 满足条件的微任务, 会被添加到当前宏任务的最后去执行

注意:async 函数只有从 await 往下才是异步的开始

async/await是什么?

ES7 标准中新增的 async 函数,从目前的内部实现来说其实就是 Generator 函数的语法糖。

它基于 Promise,并与所有现存的基于Promise 的 API 兼容。

async 关键字

  1. async 关键字用于声明⼀个异步函数(如 async function asyncTask1() {...}

  2. async 会⾃动将常规函数转换成 Promise,返回值也是⼀个 Promise 对象

  3. async 函数内部可以使⽤ await

await 关键字

  1. await 用于等待异步的功能执⾏完毕 var result = await someAsyncCall()

  2. await 放置在 Promise 调⽤之前,会强制async函数中其他代码等待,直到 Promise 完成并返回结果

  3. await 只能与 Promise ⼀起使⽤

  4. await 只能在 async 函数内部使⽤

相较于 Promise,async/await有何优势?

  1. 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
  2. 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
  3. 调试时的阅读性, 也相对更友好

深拷贝 浅拷贝

直接复制对象时应为复杂数据类型复制的是地址指向所以修改直接将原先的也修改了进而衍生出来的拷贝

浅拷贝

拷贝对象 Object.assgin() / 展开运算符{…obj}

拷贝数组 Array.protoype.concat() / […arr]

深拷贝

1.递归实现深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
//处理数组 必须先处理数组因为数组也是对象
if (oldObj[k] instanceof Array) {
newObj[k]=[]
// newObj[k] 就是一个[]hobby // oldObj[k] 是 ['唱', '跳']
deepCopy(newObj[k],oldObj[k])
} else if(oldObj[k] instanceof Object){
newObj[k]={}
deepCopy(newObj[k],oldObj[k])
} else {
// k属性名 oldObj[k] 属性值 // newObj{k}===o.uname 进行添加属性
newObj[k] = oldObj[k]
}
}
}

2.lodash/cloneDeep 使用库

1
2
3
引入js库实现深拷贝
<script src="https://lib.baomitu.com/lodash.js/3.10.1/lodash.js"></script>
const o = _.cloneDeep(obj)

3.通过JSON.stringify()实现

1
const o= JSON.parse(JSON.stringify(obj))

DOM

DOM的事件流是什么

事件流

⼜称为事件传播,是⻚⾯中接收事件的顺序。DOM2级事件规定的事件流包括了3个阶段:

  • 事件捕获阶段(capture phase)
  • 处于⽬标阶段(target phase)
  • 事件冒泡阶段(bubbling phase)
  1. 事件捕获阶段,为截获事件提供了机会
  2. 实际的⽬标元素接收到事件
  3. 事件冒泡阶段,可在这个阶段对事件做出响应

说说什么是事件委托

事件委托,就是利用了事件冒泡的机制,在较上层位置的元素上添加一个事件监听函数,

来管理该元素及其所有子孙元素上的某一类的所有事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<ul id="list">
<li>111</li>
<li>222</li>
<li>333</li>
</ul>

<script type="text/javascript">
// ⽗元素
var list = document.getElementById('list');
// 为⽗元素绑定事件,委托管理它的所有⼦元素li的点击事件
list.onclick = function (event) {
var currentTarget = event.target;
if (currentTarget.tagName.toLowerCase() === 'li') {
alert(currentTarget.innerText)
}
}
</script>

在绑定大量事件的时候,可以选择事件委托

优点

  • 事件委托可以减少事件注册数量,节省内存占⽤!
  • 当新增⼦元素时,⽆需再次做事件绑定,因此非常适合动态添加元素 (vue解析模板时, 会对新创建的元素, 额外进行绑定的)

浏览器底层原理

浏览器是如何解析CSS选择器的

在生成渲染树的过程中,渲染引擎会根据选择器提供的信息来遍历 DOM 树,找到对应的 DOM 节点后将样式规则附加到上面。

从左往右:.mod-nav => h3 => span

  1. 遍历所有的元素, 找有 .mod-nav 类的节点

  2. .mod-nav 开始遍历所有的⼦孙节点 headerdivh3ul ….

    遍历所有的后代元素后, 知道了, 整个子孙后代只有一个 h3

  3. 找到 h3 , 还要继续重新遍历 h3 的所有子孙节点, 去找 span

问题: 会进行大量树形结构子孙节点的遍历, 这是非常消耗成本的!

这在真实页面中⼀棵 DOM 树的节点成百上千的情况下,这种遍历方式的效率会非常的低,根本不适合采用。

从右往左:span => h3 => .mod-nav

  1. 先找到所有的 span 节点 ,然后基于每⼀个 span 再向上查找 h3

  2. h3 再向上查找 .mod-nav 的节点

  3. 最后触及根元素 html 结束该分⽀遍历

从右向左的匹配规则, 只有第一次会遍历所有元素找节点, 而剩下的就是在看父辈祖辈是否满足选择器的条件, 匹配效率大大提升!

因此,浏览器遵循 “从右往左” 的规则来解析 CSS 选择器!

浏览器是如何进行界面渲染的

  1. 获取 HTML ⽂件并进⾏解析,生成一棵 DOM 树(DOM Tree)

  2. 解析 HTML 的同时也会解析 CSS,⽣成样式规则(Style Rules)

  3. 根据 DOM 树和样式规则,生成一棵渲染树(Render Tree)

  4. 进行布局(Layout)(重排),即为每个节点分配⼀个在屏幕上应显示的确切坐标位置

  5. 进⾏绘制(Paint)(重绘),遍历渲染树节点,调⽤ GPU(图形处理器) 将元素呈现出来

重绘(repaint)和重排(回流reflow)是什么

重排

重排是指部分或整个渲染树需要重新分析,并且节点的尺⼨需要重新计算。

表现为 重新⽣成布局,重新排列元素。

重绘

重绘是由于节点的⼏何属性发⽣改变,或由于样式发⽣改变(例如:改变元素背景⾊)。

表现为某些元素的外观被改变。或者重排后, 进行重新绘制!

两者的关系

重绘不⼀定会出现重排,重排必定会触发重绘。

每个页面至少需要一次回流+重绘。(初始化渲染)

重排和重绘的代价都很⾼昂,频繁重排重绘, 会破坏⽤户体验、让界面显示变迟缓。

我们需要尽可能避免频繁触发重排和重绘, 尤其是重排

何时会触发重排

重排什么时候发生?

1、添加或者删除可见的DOM元素;

2、元素位置改变;

3、元素尺寸改变——边距、填充、边框、宽度和高度

4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

5、页面渲染初始化;

6、浏览器窗口尺寸改变——resize事件发生时;

浏览器对重绘重排的优化

如果每句JS操作都去重排重绘的话,浏览器可能就会受不了!

所以浏览器会优化这些操作,浏览器会维护1个队列,把所有会引起重排、重绘的操作放入这个队列

等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理

这样就会让多次的重排、重绘变成了一次重排重绘。

虽然有了浏览器的优化,但有时候我们写的一些代码可能会强制浏览器提前flush队列,这样浏览器的优化可能起不到作用了。

比如当你请求向浏览器获取一些样式信息的时候(保证获取结果的准确性),就会让浏览器flush队列

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. 请求了getComputedStyle()

前端如何实现即时通讯

基于Web的前端,存在以下几种可实现即时通讯的方式:(详细可自行百度)

  • 短轮询 (历史方案)

    开个定时器, 每隔一段时间发请求 (实时性不强)

  • Comet - ajax长轮询(历史方案)

    发送一个请求, 服务器只要数据不更新, 就一直阻塞 (服务器压力过大)

  • SSE

    (利用了http协议, 流数据的传输, 并不是严格意义的双向通信, 无法复用连接)

  • WebSocket (主流)

    性能和效率都高!

什么是浏览器同源策略

首先,同源是指资源地址的 “协议 + 域名 + 端⼝” 三者都相同,即使两个不同域名指向了同⼀ IP 地址,也被判断为⾮同源。

下面是一些地址的同源判断示例:

以下不同地址的页面, 去请求一个接口: http://store.company.com/getInfo

image-20231211150238816

同源策略是 浏览器 的一种⽤于隔离潜在恶意⽂件的重要安全保护机制 !!! (服务器没有这个策略限制)

在浏览器中,⼤部分内容都受同源策略限制,除了以下三个资源获取类型的标签:

  • <img>
  • <link>
  • script

如何实现跨域获取数据

JSONP:出现早,兼容性好 临时想出来的方案,缺点是只支持get请求不支持post请求

CORS:出现晚 w3c标准,属于ajax根本方案


前端工程化