前言
从20年至今,每一年的大环境都不太一样,行业的起伏关系到企业,企业的兴衰,关系到每一个你我,面试有亘古不变、老生常谈的基础,也有当下环境应运而生的新问题。新的也好,旧的也罢,皆是我们人生中的一关,"关关难过关关过,前路漫漫亦灿灿”。回顾过往,至今已有四个年头了,《2024年我的前端面试准备》在小伙伴们的催促声中姗姗来迟。
2021年我的前端面试准备
本篇文章会持续更新,也会同步到公众号前端面试官,方便大家随时随地学习,完善成为2024大家的前端面试准备
Html5、Css3 篇
1、HTML、XHTML、XML 有什么区别?⭐
XML
: XML
是可扩展标记语言,主要是用来存储和传输数据,而非显示数据,可以用来标记数据,定义数据类型,允许用户对自己的标记语言进行定义。
HTML
:HTML
是超文本标记语言,主要是用来描述网页的一种标记语言,通过标记标签来描述网页。
XHTML:XHTML 是可扩展超文本标记语言,XHTML 基于 XML 和 HTML 而来,也是用来描述网页的标记语言,是更严格的 HTML 版本。例如:XHTML 元素必须被正确地嵌套,标签名必须用小写字母, 文档必须拥有根元素,对于图片需添加 alt 属性等。XHTML 和 HTML 4.01 几乎是相同的,XHTML 是 W3C 标准。
XML和HTML
区别:XML 相比 HTML
语法要求严格。HTML 是预定义的,XML 标签是免费的、自定义的、可扩展的。HTML 的设计目的是显示数据并集中于数据外观,XML 的设计目的是描述数据、存放数据并集中于数据的内容。XML 是一种跨平台的,数据处理和传输的工具。总的来说,XML用来传输数据,而 HTML 和 XHTML 用来描述网页,XHTML 比 HTML 更为严格。
2、 XML和JSON的区别?
JSON
:JSON
是一种轻量级的数据交换格式,它基于 JavaScript 的一个子集,简单的说 JSON 就是一串字符串用于不同平台的数据交换。
XML
:XML 是可扩展标记语言,是标准通用标记语言 (SGML) 的子集,XML 主要是用来存储和传输数据,而非显示数据,可以用来标记数据、定义数据类型,允许用户对自己的标记语言进行定义。
JSON和XML区别:JSON 数据的体积小,传递速度更快,与 JavaScript 数据交互更加方便,更容易解析处理。XML 对数据的描述性比较好。JSON 支持数组,XML 不支持数组。JSON 不支持命名空间,XML 支持命名空间。JSON 容易阅读,XML 难以阅读和解释。JSON 不使用结束标记,XML 有开始和结束标签。JSON 的安全性较低,不支持注释,仅支持 UTF-8 编码。XML 比 JSON 更安全,支持注释,支持各种编码。
3、是否了解W3C的规范?⭐
w3c
标准指的是万维网联盟标准,万维网联盟标准指的不是一个标准,而是一系列标准的集合。web 可以简单分为结构、表现、行为三部分,三部分独立开来使其模块化,w3c 是对 web 做出规范,使代码更加严谨,做出来的网页更加容易使用和维护。
结构标准主要包括 XHTML 和 XML
,比如像标签闭合、标签元素和属性名字小写、标签不乱嵌套、属性必须有属性值、属性值必须用引号括起来、特殊符号用编码表示、定义语言编码等。标签规范可以提高搜索引擎对页面的抓取效率,对 SEO (搜索引擎优化) 很有帮助,越规范的网站搜索排名越靠前。
表现标准主要包括 CSS,行为标准主要包括对象模型(像 W3C DOM,ECMAScript),比如说尽量使用外链的 css 和 js 脚本,提高页面的渲染效率,尽量少使用行内样式,类名要做到见名知意。遵循 w3c 标准可以让我们的页面,我们的程序能够支持所有浏览器,能够满足尽可能多的用户。 W3C 标准的体现,也就是说是开发者在开发过程中怎么去准守 W3C 标准,其实这里面很多规范是为了 XHTML 的,jQurry 不符合 W3C 标准。
4、什么是语义化标签?⭐⭐
语义化标签就是标签语义化,让标签有自己的含义,使浏览器和搜索引擎能直观的认识标签的用途和内容。 虽然可以采用 DIV + CSS 的方式布局页面,但 DIV 标签本身没有特殊含义,文档结构不够清晰,不利于浏览器对页面的读取,在分离 CSS 样式后,体验不友好。
使用语义化标签可以使代码结构清晰,可读性高,便于团队开放和维护。在页面没有加载 CSS 的情况下也能呈现良好的结构,易于阅读。有利于 SEO(搜索引擎优化)。
语义化标签是 H5 新特性,语义化标签有:
定义页面的头部, 定义页面尾部, 定义导航链接, 内容标签等。5、常用的块级元素和行内元素有哪一些? ⭐
块级元素:div、p、h1~h6、ol、ul、li、table、form。 行内标签:a、span、img、input、lable、button。 行内块元素:img、input、button。
6、行内元素和块级元素的区别?⭐
块级元素:默认换行,独占一行,可设置宽高 (宽度是父容器的100%),块级元素可以嵌套任意元素,块级文字不能放入其他块级元素。
行内元素:默认不换行,设置宽高无效 (默认宽度是本身内容宽度),不能包含块级元素,只能包含文本或者其它行内元素,设置 margin,padding 上下无效。
行内块元素:综合块级元素与行内元素的特性,可设宽高(默认是内容宽高),也可以设置内外边距。
转换:display:block,display:inline,display:inline-block。
7、css盒子模型有几种类型?它们区别是什么 ⭐
根据盒子大小的计算方式不同,css 盒子模型分成了两种类型,分别是 W3C 标准盒子模型和怪异盒子模型也叫 IE 盒子模型。 标准盒子模型:设置的宽度 = 内容的宽度,盒子的宽度 = 内容的宽度 + padding2 + margin2 + border2。 IE盒子模型:设置的宽度 = 盒子的宽度,内容的宽度 = 盒子的宽度 - padding2 - margin2 - border2。 默认情况下都是标准盒子模型,设置 IE 盒子模型:box-sizing:border-box,设置标准模型:box-sizing:content-box
8、标签上title与alt属性有什么区别?
alt
是给搜索引擎识别,在图像无法显示时的替代文本。
title
是元素的注释信息,主要是给用户解读。
当鼠标放到文字或是图片上时有 title 文字显示。在 IE 浏览器中 alt 起到了 title 的作用,变成文字提示。
9、 H5新特性有哪些?⭐⭐
语义化标签
<header>
定义文档的头部区域<nav>
定义导航链接 -<article>
内容标签<section>
定义文档某个区域<aside>
侧边栏标签<footer>
定义了文档的尾部区域<figure>
规定独立的流内容(图像、图表、照片、代码等)<figcaption>
定义<figure>
元素的标题- 增强表单功能 (类型和属性)
- 音频视频:
<Audio> 、<video>
- 画布:
<Canvas>
- SVG 绘图
- 地理位置
- 拖拽 APL
WebStorage
(本地存储:LocalStorage / SessionStorage
)
10、css3的新特性有哪些?⭐⭐
- 选择器:层级选择器,属性选择器,状态伪类选择器,结构伪类选择器,伪元素选择器
- 文本效果:文本阴影 ,文本自动换行,文本溢出,(单词拆分,文本拆分)
- 边框:圆角边框
border-radius
,边框阴影box-shadow
,边框图片border-image
- 背景:渐变背景,多重背景 (设定背景图像的尺寸,指定背景图像的位置区域,背景的绘制)
- 透明度:
opacity
( 取值0-1,通常用于做元素的遮罩效果) - 高斯模糊:
filter
- 渐变:
background: linear-gradient
(线性渐变,径向渐变 ,文字渐变) - 过渡:
transition
- 2D转换 / 3D转换:
transform
- 动画:
Animation
(@keyframes 动画帧) - 媒体查询:
@media
- 多列布局 (兼容性不好,还不够成熟)
- 弹性布局 (flex)
- 网格布局
11、css的引用有哪些,link和@import的区别?⭐
css
的引用有哪些:
内联方式(直接在 html
标签中的 style
样式)
嵌入方式(在 < style >
标签下书写 css
代码)
链接方式(使用 link 引入外部的 css
文件)
link 和 @import
的区别:
link
和 import
写法不同,link
通过 标签的 href 属性引入,import 通过@import url()
引入。
link
是 XHTML
标签,还可以定义其他事务,@import
属于 CSS
范畴,只能加载 CSS
。
link
无兼容问题,@import
兼容IE5
以上。
link
支持使用 Javascript
控制 DOM
去改变样式,@import
不支持改变样式。
link
是连接整个css
文件,@import
可以模块化引入css
文件。
link
引用 CSS
时,在页面加载时同时加载 css
,而 @import
会把 css
文件放在页面的最底部,导致 css
最后才加载完毕,等到页面完全加载才加载 css
,导致页面留白时间长,影响用户体验。
12、href和src
的区别?⭐
请求资源不同:href
超文本引用,用来建立当前元素和文档之间的连接,常用的是link、a
标签。src
会将指向的资源下载并引用到当前文档中,常用的标签有 script,img,iframe
标签。
作用结果不同:href 用于在当前文档和引用资源之间确立联系,src
用于替换当前内容。
浏览器解析方式不同:herf 引用的资源时,浏览器会将其识别为 CSS
文档,并行下载资源并且不会停止对当前文档的处理。当浏览器解析到 src
时,会暂停其他资源的下载和处理,直接将该资源下载,编译,执行完毕。
13、CSS
常用尺寸单位有哪些?应用场景?⭐⭐
px
:像素,相对长度单位,它的大小取决于屏幕的分辨率,是一个固定值,不能够自适应。
em
:相对长度的单位,相对于当前对象内文本的字体尺寸,未设置则默认是浏览器默认字体尺寸。
rem
:CSS3
中新增的一个相对长度单位,相对于根元素 <html>
的 font-size
字体大小,根元素字体大小未设置,使用浏览器默认字体大小。
vw
:相对于视口的宽度。视口被均分为100单位的 vw。
vh
:相对视口高度,视口被均分为100单位的 vh。
vmin
:相对于视口宽度或高度中较小的那个。其中最小的那个被均分为100单位的 vmin。
vmax
:相对于视口宽度或高度中较大的那个。其中最大的那个被均分为100单位的 vmax。
cm
:厘米,绝对长度单位。
mm
:毫米,绝对长度单位。
in
:英寸,绝对长度单位。
%
:百法比
应用场景:
在移动端网页开发中,页面要做成响应式的,可使用 rem
配合媒体查询实现。原理:通过媒体查询,能够在屏幕尺寸发生改变时,重置 html
根元素的字体大小,页面中的元素都是使用rem
为单位设置的尺寸,因此只要改变根元素字体大小,页面中的其他元素的尺寸就自动跟着修改。
利用 vw
和rem
实现响应式。原理:由于 vw
被更多浏览器兼容之后,在做移动端响应式页面时,通常使用 vw
配合 rem。原理是使用vw
设置根元素 html
字体的大小,当窗口大小发生改变,vw 代表的尺寸随着修改,无需加入媒体查询,页面中的其他元素仍使用rem
为单位,就可实现响应式。
14. 请解释CSS的盒模型是什么,并描述其组成部分。
答案:CSS的盒模型是用于布局和定位元素的概念。它由内容区域、内边距、边框和外边距组成,这些部分依次包裹在元素周围。
15. 解释CSS中的选择器及其优先级。
答案:CSS选择器用于选择要应用样式的HTML元素。选择器的优先级规则是:内联样式 > ID选择器 > 类选择器、属性选择器、伪类选择器 > 元素选择器 > 通用选择器。同时,使用!important可以提升样式的优先级。
16. 解释CSS中的浮动(float)是如何工作的,并提供一个示例。
答案:浮动(float)是CSS中用于实现元素的左浮动或右浮动,使其脱离文档流并环绕在其周围的元素。例如:
.float-example {
float: left;
width: 200px;
height: 200px;
}
17. 解释CSS中的定位(position
)属性及其不同的取值。
答案:定位(position
)属性用于控制元素的定位方式。常见的取值有:static
(默认,按照文档流定位)、relative
(相对定位)、absolute
(绝对定位)、fixed
(固定定位)和sticky
(粘性定位)。
18. 解释CSS中的层叠顺序(z-index
)是如何工作的。
答案:层叠顺序(z-index
)用于控制元素在垂直方向上的堆叠顺序。具有较高层叠顺序值的元素将显示在较低层叠顺序值的元素之上。默认情况下,层叠顺序值为auto。
19. 解释CSS中的伪类和伪元素的区别,并给出一个示例。
答案:伪类用于向选择器添加特殊的状态,如:hover、:active等。伪元素用于向选择器添加特殊的元素,如::before、::after等。例如:
/* 伪类示例 */
a:hover {
color: red;
}
/* 伪元素示例 */
p::before {
content: "前缀";
}
20. 解释CSS中的盒子模型的两种模式:标准模式和怪异模式。
答案:标准模式是按照W3C标准解析渲染页面的模式。怪异模式是兼容旧版本浏览器的解析渲染页面的模式。可以通过声明来指定使用哪种模式。
21. 解释CSS中的BFC是什么,它的作用是什么?
答案:BFC
(块级格式化上下文)是CSS
中的一种渲染模式,它创建了一个独立的渲染环境,其中的元素按照一定的规则进行布局和定位。BFC
的作用包括:清除浮动、防止外边距重叠等。
22. 解释CSS
中的flexbox
布局是什么,它的优势是什么?
答案:flexbox
布局是一种用于创建灵活的、响应式的布局的CSS模块。它通过flex容器和flex项目的组合来实现强大的布局能力。其优势包括简单易用、自适应性强、对齐和分布控制灵活等。
23.解释CSS中的媒体查询是什么,它的作用是什么?
答案:媒体查询是CSS中的一种技术,用于根据设备的特性和属性来应用不同的样式。通过媒体查询,可以根据屏幕尺寸、设备类型、分辨率等条件来优化页面的布局和样式。
JS 篇
1、JS数据类型有哪些?区别?⭐⭐⭐
JS 的数据类型分为两类,分别是基本数据类型和引用数据类型。它们主要区别是在内存中的存储方式不同。
基本数据类型:number 数字、string 字符串、boolean 布尔值、null 空值、undefined 未定义、symbol 唯一值、BigInt 最大值。基本数据类型有固定的大小和值,存放在栈中,可以直接访问,而引用数据类型不确定大小,但是其引用地址是固定的,因此,它的地址存在栈中,指向存储在堆中的对象。 引用数据类型:Object (包括普通对象,数组,正则,日期,Math 数学函数等)。引用数据类型是存放在堆中的对象,在栈中保存的是对象在堆中的引用地址(引用变量),通过引用地址可以快速查找到保存在堆中的对象。 Symbol 是 Es6 新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以做 object 的 key。 BigInt 也是 ES6 新出的一种数据类型,BigInt 可以表示任意大的整数,能够解决解精度缺失的问题 (超过 Number 类型支持范围的数值都会失去精度)。使用方法:(1) 整数末尾直接加n:647326483767797n。(2) 调用 BigInt() 构造函数:BigInt("647326483767797")。
2、JS中检测数据类型的有哪些?⭐⭐
- typeof:常用于判断基本数据类型,除了 null 检测为 object。对于引用数据类型除了 function 返回 function,其余全部返回 object。
- instanceof:主要用于检测引用数据类型,不适合用来检测基本数据类型。如果检测的类型在当前实例的原型链上,则返回 true,说明这个实例属于这个类型,否则返回 false。例如: A instanceof B,判断 B 在不在 A 的原型链上,如果在就返回 true,如果找到原型链的尽头 null 都没找到,就返回 false。(由于原型链的指向可以随意改动,导致检测不准确)
- constructor:获取实例的构造函数判断和某个类型是否相同,如果相同就说明该数据是符合那个数据类型的。使用方法是:"实例.constructor"。constructor 可以检测出除了 undefined 和 null 以外的其他类型,因为undefined 和 null 没有原生构造函数。(不可靠,容易被修改)
- object.prototype.toString.call( ):适用于所有类型的判断检测,检测方法是: - Object.prototype.toString.call(数据) ,返回的是该数据类型的字符串。
3、JS中的栈和堆是什么?优缺点?⭐⭐⭐
JS 的变量都储存到内存中,内存中开辟了两个区域储存变量,分别是栈区域和堆区域。 栈与堆实际上是操作系统对进程占用的内存空间的两种管理方式。
栈:
- 栈是一种先进后出的数据解构,由操作系统自动分配内存空间,自动释放,占固定的大小空间。
- 栈存储的是基本数据类型的值以及引用数据类型的引用地址。
- 栈中存储的数据的生命周期随着当前环境的执行完成而结束。
堆:
堆由操作系统动态分配内存空间,大小不定也不会自动释放,一般由程序员分配释放也可由垃圾回收机制回收。 栈存储的是对象和复杂数据结构,存储的是对象的实际数据,而不是对象的引用。 引用数据类型只有在引用的它的变量不在时,被垃圾回收机制回收。
栈和堆的优缺点:
栈相对于堆存取速度更快,且栈内存中数据是可以共享的,但内存空间有限。 堆存取效率相对较低,但内存空间大。 栈内存可以及时得到回收,相对来说更容易管理内存空间,但存储在栈中的数据大小和生存期必须是确定的,缺乏灵活性。 堆的内存是操作系统动态分配的,方便存储和开辟内存空间。有垃圾回收机制,生存周期比较灵活。
4、深克隆和浅克隆?⭐⭐⭐
浅克隆:
克隆对象的第一层属性。 如果是基本数据类型,直接将存储在栈中的值赋值给对应的变量,原始值改变不会影响。 如果是引用数据类型,则克隆的是对象的引用地址,改变引用地址,新对象也会跟着改变,想要改变这种继承的现象就要使用深度克隆。 在 JS 中可以通过 Object.assign( ) 或者扩展运算符 ... 合并对象实现浅克隆。
深克隆:
克隆对象各个层级的属性。 深克隆是指创建一个与原对象完全相同的新对象 (数据源不同,数据地址已变化)。 可以通过递归的方式实现深克隆,也可以通过简单粗暴的方式实现 JSON.parse (JSON.stringify(obj))。但需要注意:时间对象会变成字符串的形式。RegExp、Error 对象序列化的结果将只得到空对象。函数、undefined 序列化的结果会把函数或 undefined 丢失。NaN、Infinity 序列化的结果会变成 null。如果对象中存在循环引用的情况也无法正确实现深拷贝。JSON.stringify() 只能序列化对象的可枚举的自有属性。
5、JS垃圾回收机制?⭐⭐
- 内存泄漏:JS 代码运行时,需要分配内存空间存储变量和值,当这些变量不再作用时,需要释放内存,如果没有及时释放,就会引起内存泄漏,堆积起来会影响性能甚至造成系统崩溃。垃圾回收机制就是为了防止内存泄漏,及时释放不再使用的内存,提高程序性能。
垃圾回收机制:垃圾回收机制是一种自动管理内存的机制,它会自动监测和回收不再使用的对象,从而释放内存空间。实现的原理主要有标记清除、引用计数、复制算法。
- 标记清除算法:垃圾回收器会定期扫描内存中的对象,标记那些可达对象和不可达对象。可达对象指的是正在被使用的对象。不可达对象指的是不再被引用的对象。垃圾回收器会将不可达对象标记为垃圾对象,并将他们从内存中清除。该算法的优点是可以处理循环引用的情况,缺点是由于垃圾回收器的工作需要消耗一定的系统资源,因此如果程序中存在大量的内存占用或者存在频繁创建和销毁对象的操作,执行垃圾回收操作的时间会比较长,对性能造成一定的影响。
- 引用计数算法:垃圾回收器会记录每个对象被引用的次数,当对象被引用的次数为0时,该对象就会被清除。该算法的优点是实现较为简单,但无法处理循环引用的情况,即两个对象相互引用,但是它们的引用次数都不为 0,导致内存无法被释放,可能会导致内存泄漏。
- 复制算法:将内存空间划分为两个相等的区域,每次只使用一个区域,这个区域满时,将其中存活的对象复制到另外一个区域,再将原区域的对象全部清除。优点是可以避免由于产生大量内存碎片而引发的内存分配失败问题。 存在问题?怎么优化?
虽然浏览器可以自动的进行垃圾回收,但是当代码比较复杂时,垃圾回收对性能的消耗比较大,所以应该尽量减少垃圾回收。需要根据具体应用的需求和环境进行优化。
- 对数组进行优化。清空数组时,赋值为[ ],同时将数组的长度设置为0。
- 对象复用,尽量减少对象的创建和销毁次数。
- 不再使用的对象就设置为 null。
- 函数优化,函数功能单一化。
- 尽早释放资源。
- 使用闭包,在闭包中的变量不会被垃圾回收机制回收。
6、JS哪些操作会造成内存泄露?⭐⭐
意外的全局变量。由于使用未声明的变量而意外的创建了一个全局变量,而使用这个变量一直留在内存中无法被回收。 没有清理的DOM元素引用。获取一个DOM元素的引用,元素被删除了,由于保留了元素的引用,所以也无法被回收。 被遗忘的定时器或者回调函数。 闭包。
7、闭包?⭐⭐⭐
什么是闭包?
因为作用域链的存在,函数的内部可以直接读取全局变量,而函数内部无法读取另一个函数内部的局部变量,如果想读取函数内部的局部变量,可以通过闭包来实现。闭包就是在一个函数内部创建另外一个函数,让你可以在一个内层函数中访问到外层函数的局部变量。简单来说,闭包就是可以读取其他函数内部局部变量的函数,本质上,闭包是将函数内部和函数外部连接起来的桥梁。
为什么要使用闭包?
局部变量无法共享和长久的保存,而全局变量可能造成变量污染。 闭包可以读取函数内部的局部变量,且不会被垃圾回收机制回收,可以长期保存。
闭包的作用?
- 在函数外部可以访问函数内部的局部变量。
- 可以使函数内部的变量在函数执行结束之后不被销毁 ,长久保存在内存中,不会被垃圾回收机制回收。
- 使用闭包,可以封装自己的函数代码,实现模块化。
- 保护:避免命名冲突。
- 保存:解决循环绑定引发的索引问题。
闭包的缺点?
由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。如何解决:在销毁函数之前,将不使用的局部变量全部删除。
闭包的应用?
- 能够模仿块级作用域。
- 设计模式中的单例模式。
- for 循环中的保留 i 的操作。
- 防抖和节流。
- 函数柯里化。
- 在构造函数中定义特权方法。
- Vue 中数据响应式 Observer 中使用闭包。
8、什么是原型链?⭐⭐⭐
每个函数身上都有一个 prototype 的原型对象,并且有一个__proto__的指针指向下一级原型对象,如果一个对象的属性或方法在自身中找不到,那么就会去 prototype 原型对象中查找,如果还找不到继续向上查找直到 null,当_proto_指针指向 null 时形成一个链条,这个链条叫做原型链。 在原型链中,对象可以继承原型对象的属性和方法。如果想在构造函数中添加属性和方法,可以将它们添加到构造函数的 prototype 属性中,这样通过该构造函数创建的对象都可以访问到这些属性和方法。 原型链的特点是:对象可以沿着原型链向上查找属性和方法,实现了属性和方法的共享和继承。
9、JS继承的方法有哪些?优缺点?⭐⭐
-
JS继承的方法有以下几种:原型链继承、构造函数继承、组合继承、原型式继承和寄生式继承,寄生组合式继承,ES6 Class实现继承。
-
继承的目的是:重复利用另外一个对象的属性和方法。
-
原型链继承:将父类的实例作为子类的原型,从而实现对父类属性和方法的继承。优点:写法方便简洁,容易理解。缺点:不能传递参数和共享所有继承的属性和方法,当一个发生改变另外一个随之改变。
-
构造函数继承:在子类的构造函数中调用父类的构造函数,使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上,从而实现对父类实例属性的继承。优点:解决了原型链继承不能传参的问题和父类的原型共享的问题。缺点:方法都在构造函数中定义,因此无法实现函数复用。
-
组合继承:将原型链继承和构造函数继承结合起来,既可以实现对父类原型属性和方法的继承,又可以实现对父类实例属性的继承。优点: 解决了原型链继承和构造函数继承造成的影响。缺点: 无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
-
原型式继承:通过创建一个临时构造函数来实现对父类的属性和方法的继承。优点:不需要单独创建构造函数。缺点:属性中包含的引用值始终会在相关对象间共享。
-
寄生式继承:在原型式继承的基础上,通过在临时构造函数中添加方法和属性,从而实现对父类的继承。优点:写法简单,不需要单独创建构造函数。缺点:通过寄生式继承给对象添加函数会导致函数难以重用。
-
寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。优点:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变。缺点:代码复杂。
-
ES6 Class实现继承:ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面 (Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法加到 this 上面 (所以必须先调用super方法),然后再用子类的构造函数修改 this。需要注意的是,class 关键字只是原型的语法糖,JS继承仍然是基于原型实现的。 优点:语法简单易懂,操作更方便。缺点:并不是所有的浏览器都支持 class 关键字 lass Person。
10、new操作符具体都干了什么?⭐⭐
- 创建一个新对象 obj。
- 将该对象与构造函数通过原型链连接起来(设置该对象的构造函数)。
- 将构造函数中的 this 绑定到该对象上。
- 根据构造函数返回类型作判断,如果是值类型则返回新对象 obj,如果返回对象,则返回构造函数里的对象。
// 手写 new 操作符
function mockNew(constructor, ...args) {
// 1.创建一个新对象 obj
const obj = {};
// 2.把构造函数当参数传入,新对象指向构造函数原型对象
obj.__proto__ = constructor.prototype;
// 3.通过 apply 将构建函数的 this 指向新对象
let result = constructor.apply(obj, args);
// 4.根据返回值判断
return result instanceof Object ? result : obj;
}
11、JS的几种具体异常类型(报错)
- SyntaxError:语法错误
- ReferenceError:引用错误
- RangeError:范围错误
- typeError:类型错误
- URLError:与 url 相关参数不正确
- EvalError:全局函数 eval 执行错误
12、什么是事件冒泡?什么是事件委托?
- 事件冒泡:在一个对象上触发某类事件,这个事件会向这个对象的的父级传播,从里到外,直至它被处理或者到达了 对象层次的最顶层,即 document 对象。这个过程就是事件冒泡。
- 事件委托:事件委托就是利用事件冒泡,指定一个事件处理程序,就可以管理某一类型的所有事件。原理:在元素的父级元素添加事件,点击元素时,因为事件冒泡的作用实现事件委托。简单来说,事件委托就是将子元素的事件通过冒泡的形式交由父元素来执行。优点:使用事件委托可以减少代码执行优化资源。
13、事件对象?
- event:事件对象。
- currentTarget:绑定的事件。
- target:触发的事件。
- eventPhase:返回当前触发的阶段(捕获阶段1,事件派发阶段2,冒泡阶段3)
- type:返回当前event对象的事件名。
14、undefined 和 null 区别?⭐
undefind 是全局对象的一个属性,当一个变量没有被赋值或者一个函数没有返回值或者某个对象不存在某个属性却去访问或者函数定义了形参但没有传递实参,这时候都是 undefined ,undefined 通过 typeof 判断类型是 undefined。 null 代表空值,代表一个空对象指针,代表对象的值未设置,相当于一个对象没有设置指针地址就是 null。null 通过 typeof 判断类型是 object。 undefined 表示一个变量初始状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。当需要释放一个对象时,直接赋值为 null 即可,对象被赋值了null 以后,对象对应的堆内存中的值就是游离状态了,GC 会择机回收该值并释放内存。因此,需要释放某个对象,就将变量设置为 null,即表示该对象已经被清空,目前无效状态。 null 是 javascript 的关键字,和其它语言一样都是代表空值, undefined 却是 javascript 才有的。是为了区分空指针对象和未初始化的变量,它是一个预定义的全局变量。
15、说一说伪数组和数组的区别?
- 伪数组它的类型不是 Array,而是 Object,而数组类型是 Array。
- 伪数组可以使用的 length 属性查看长度,也可以使用 index 获取某个元素,但是不能使用数组的其他方法,也不能改变长度,遍历使用 for in 方法。 伪数组转换成真数组方法:
- (1)Array.prototype.slice.call(伪数组) 、
- (2)[].slice.call(伪数组)
- (3)Array.from(伪数组),转换后的数组长度由 length 属性决定,索引不连续时转换结果是连续的,会自动补位。
16、对于数组去重都有哪些方法?⭐⭐
双重 for 循环:这是一个最笨的方法。
- 对象属性 key:利用对象属性名 key 不可以重复这一特点,如果对象中不存在,就 push 进空数组。
- for 循环 + indexOf:主要是利用 indexOf 的特性,查找元素返回下标,找不到就返回-1。-1就 push 进空数组。
- for 循环 + sort 排序:利用数组的 sort 排序方法去重,如果第 i 项 和 i-1 项不一致,就 push 进空数组。 filter + indexOf:利用 filter 过滤配合 indexOf 查找元素,判断返回元素下标和过滤数组的 index 是否相等。
- Set:Es6 中新增了数据类型 Set,Set 的最大一个特点就是数据不重复,可以用作数组去重。new Set 方法,返回是一个类数组,需要结合扩展运算符...,转成真实数组。
- set + Array.from:Set 去重结合 Array.from 转成真实数组。Array.from(new Set(原数组))
- for 循环 + includes:includes 用来判断一个数组是否包含一个指定的值,是就返回 true,否则返回 false。判断空数组是否包含原数组的某个元素,不包含就 push 进空数组。
- reduce + includes:利用 reduce 遍历结合 includes去重。
17、 数组的基本操作方法?⭐⭐
数组的基本操作方法:
-
push:往数组尾部添加一个元素。 返回数组的长度。
-
unshift:往数组头部添加一个元素。返回数组的长度。
-
pop:从数组尾部删除一个元素。返回删除的元素。
-
shift:从数组头部删除一个元素。返回删除的元素。
-
slice(开始位置,结束位置):截取数组一部分。只有一个元素则是开始位置,直到截取全部
-
splice(开始位置,长度,插入元素):删除、修改数组中的一部分元素。
-
reverse:反转数组。返回反转后的数组。
-
sort:对数组的元素进行排序。返回排序后的数组。
-
join:把数组变成字符串。若括号里什么都不写,则默认用逗号分隔。
-
toString:把数组变成字符串。
-
split:把字符串变成数组。
-
concat:合并数组,并返回结果。扩展运算符也可以合并数组。
-
Math.min:返回数组最小值元素。
-
Math.max:返回数组最大值元素。
-
length:获取当前数组的长度。 查找方法:
-
indexOf:查找数组元素,返回第一个找到的元素下标,找不到返回-1。
-
lastIndexOf:查找数组元素,返回最后一个找到的元素下标,找不到返回-1,从后向前搜索
-
includes: 查找数组是否包含某一元素,包含则返回 true,不包含返回 false。
-
find:查找满足函数条件的第一个值,找不到返回 undefined。
-
findIndex: 查找满足函数条件的第一个值得下标,找不到返回 -1。 数组类的静态方法:
-
Array.of:将一数值转化为数组。
-
Array.from:将类数组转化为数组。 数组填充:
-
fill(value,start,end):用一个固定值填充一个数组中特定的元素。 迭代方法:
-
for…in:遍历数组。会遍历数组内所有可枚举的属性,包括原型上的属性和方法。
-
forEach:遍历数组。不会生成新数组,也不改变原数组,回调函数接收三个值:( 数组的元素,索引,当前数组)
-
map:通过指定函数处理数组的每个元素,并返回处理后的数组。不会对空数组进行检测,不会改变原始数组。
-
filter:过滤,检测数组元素,并返回符合条件所有元素的数组。
-
every:检测数组元素的每个元素是否都符合条件,都符合则返回 true,否则为 false。
-
some:检测数组元素中是否有元素符合指定条件,有则返回 true,若所有元素都不满足判断条件,则返回 false。
-
修改原数组:push、pop、shift、unshift、splice、reverse、sort、fill。
-
不修改原数组:slice、concat、indexOf、lastIndexOf、join、toString、filter、every、some、forEach、map、find、findIndex。
18、说一下this指向?⭐⭐
-
全局作用域:无论是否在严格模式下,this 指向 window 对象。严格模式下全局作用域中函数中的 this 等于 undefined。不是严格模式时,全局作用域中函数中的 this 指向 window。
-
- 对象中的函数:对象的函数中的 this 指向调用函数的对象实例。谁调用函数 this,this 就指向谁。
-
事件处理函数:在事件处理函数中,this 指向触发事件的目标对象。
-
构造函数:构造函数中的 this 指向构造函数创建的对象实例。
-
箭头函数:this 对应定义时所在的对象,也就是上一级作用域中的 this。箭头函数的 this 不会被改变。
-
嵌套函数:嵌套函数中的 this 不会继承外层函数的 this 值。
-
new:由 new 调用的话,this 指向新创建的对象。
-
call、apply、bind:由 call、apply、bind 调用,this 指向指定的对象。
-
定时器:定时器中的 this,指向的是 window。
19、 js中call、apply、bind有什么区别?⭐⭐
- 原生 JS 提供了 call、apply、bind 三种方式来修改 this 指向。
- call 和 bind 是选项式参数,apply 是数组式参数。
- call、apply 会立即执行,bind 返回一个新函数,不会立即执行。
- call、apply 临时改变 this 指向一次,bind 永久改变 this 指向。
- 应用场景:call 用于对象的继承 、伪数组转换成真数组。apply 用于找出数组中的最大值和最小值以及数组合并。
- bind 用于 vue 或者 react 框架中改变函数的 this 指向。
20、箭头函数和普通函数有什么区别?⭐⭐
普通函数的 this 指针指向调用者,可以修改。
箭头函数没有自己的 this,它的 this 是继承而来,默认指向在定义它时所处的对象 (父级作用域),不能修改。
21、JQ对象和DOM元素之间如何转换?
DOM转JQ对象:("div")。 JQ对象转DOM:可以通过[index] 或者 .get(index)方法,例如 ("div").get(1)。
22、JS模块化有哪些?
commonjs、es6、amd、cmd
23、如何操作DOM元素?⭐
原生操作DOM元素:
直接给相应的元素加 id ,然后再 document.getElementById("id") 获取。 vue操作DOM元素:
- 获取/操作根元素 DOM:$root。
- 获取/操作父元素 DOM:$parent。
- 获取/操作子元素 DOM: children。
- 使用 ref,给相应的元素加 ref=“name” 然后再 this.$refs.name 获取到该元素。
24、防抖与节流的区别,并分别用代码表示 ⭐⭐
防抖:触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。作用:防止事件连续或高频触发,让它只触发一次或者最后一次。
节流:高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。作用:降低事件触发的频率,比如1s内最多执行一次。
区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行一次。
//防抖和节流函数都是用于防止事件高频率的重复触发
//防抖: 在一定时间内,如果方法没用再次被触发,则执行最后一次
<body>
<button>点我</button>
<script>
var btn = document.querySelector("button");
var tiemr = null;
btn.onclick = function () {
clearInterval(tiemr);
tiemr = setTimeout(() => {
console.log("我触发了")
}, 500)
}
</script>
</body>
//节流:在规定时间内只只执行一次或数次
<body>
<button>按钮</button>
<script>
var btn = document.querySelector("button");
var jie = true;
btn.onclick = function() {
if(jie) {
jie = flase;
console.log('发送了请求');
setTimeout(()=>{
jie = true;
},2000)
}
}
</script>
</body>
24、数组迭代的方法有哪些? ⭐
- for。
- for in:可以遍历对象, 首个行参是 key。
- for of:只能遍历数组,首个行参是 value:。
- forEach:for的增强版,特殊简化版,不支持在循环中添加删除操作。
- while :先判断后执行。
- do while:先执行后判断。
- some:所有返回值都为真则返回真,否则返回假。 every:反之。
25、for循环和forEach有什么区别?⭐⭐
- for 循环的 return 是终止循环 forEach 是返回参数。
- for 循环实际上是可以使用 break 和 continue 去终止循环的,但是 forEach 不行。
- forEach 不支持在循环中添加删除操作。
- for 多数时候都可以使用,一般我们需要知道循环次数。而 forEach 更适合于集合对象的遍历和操作。
26、使用JQ和vue的区别?
JQ 是函数库,本质上还是操控 JS,只不过更简便了。
vue 是 mvvm 框架,核心是双向数据绑定,
一般情况下不需要操控 DOM 元素,而是操控数据为主。
TypeScript
1. 解释TypeScript和JavaScript之间的关系。
答案:TypeScript是JavaScript的超集,它添加了静态类型和其他一些特性。TypeScript代码可以编译成JavaScript代码,因此可以在任何支持JavaScript的环境中运行。
2. TypeScript中的类型注解是什么?如何使用类型注解?
答案:类型注解是指在变量、函数参数、函数返回值等地方显式地声明类型信息。可以使用冒号(:)后跟类型来添加类型注解。例如:
let num: number = 10;
function add(a: number, b: number): number {
return a + b;
}
3. TypeScript中的接口是什么?如何定义和使用接口?
答案:接口是一种用于定义对象的结构和类型的语法。可以使用interface关键字来定义接口。例如:
interface Person {
name: string;
age: number;
}
function greet(person: Person) {
console.log(`Hello, ${person.name}!`);
}
let john: Person = { name: "John", age: 25 };
greet(john); // 输出 "Hello, John!"
4. TypeScript中的类是什么?如何定义和使用类?
答案:类是一种用于创建对象的蓝图,它包含属性和方法。可以使用class关键字来定义类。例如:
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
let john = new Person("John", 25);
john.greet(); // 输出 "Hello, John!"
5. TypeScript中的泛型是什么?如何使用泛型?
答案:泛型是一种用于创建可重用代码的工具,它允许在定义函数、类或接口时使用占位符类型。可以使用尖括号(<>)来指定泛型类型。例如:
function identity<T>(value: T): T {
return value;
}
let result = identity<string>("Hello");
console.log(result); // 输出 "Hello"
6. TypeScript中的枚举是什么?如何定义和使用枚举?
答案:枚举是一种用于定义命名常量集合的语法。可以使用enum关键字来定义枚举。例如:
enum Color {
Red,
Green,
Blue,
}
let color: Color = Color.Green;
console.log(color); // 输出 1
7. TypeScript中的模块是什么?如何导出和导入模块?
答案:模块是用于组织和封装代码的单元。可以使用export关键字将模块中的变量、函数、类等导出,以便其他模块可以使用。可以使用import关键字来导入其他模块的导出。例如:
// module.ts
export function greet(name: string) {
console.log(`Hello, ${name}!`);
}
// main.ts
import { greet } from "./module";
greet("John"); // 输出 "Hello, John!"
8. TypeScript中的类型推断是什么?如何使用类型推断?
答案:类型推断是指TypeScript根据上下文自动推断变量的类型,而无需显式地添加类型注解。例如:
let num = 10; // 推断为 number 类型
let str = "Hello"; // 推断为 string 类型
9. TypeScript中的命名空间是什么?如何定义和使用命名空间?
答案:命名空间是一种用于组织和封装代码的机制,它避免了全局命名冲突。可以使用namespace关键字来定义命名空间。例如:
namespace MyNamespace {
export function greet(name: string) {
console.log(`Hello, ${name}!`);
}
}
MyNamespace.greet("John"); // 输出 "Hello, John!"
10. TypeScript中的类型别名是什么?如何定义和使用类型别名?
答案:类型别名是给类型起一个别名,以便在代码中更方便地引用。可以使用type关键字来定义类型别名。例如:
type Point = { x: number; y: number };
function printPoint(point: Point) {
console.log(`(${point.x}, ${point.y})`);
}
let p: Point = { x: 1, y: 2 };
printPoint(p); // 输出 "(1, 2)"
VUE2
1. Vue.js是什么?它有哪些特点?
答案:Vue.js是一个用于构建用户界面的JavaScript框架。它具有以下特点:
- 响应式数据绑定:通过使用Vue的数据绑定语法,可以实现数据的自动更新。 组件化开发:Vue允许将页面划分为独立的组件,提高了代码的可维护性和复用性。 虚拟DOM:Vue使用虚拟DOM来跟踪页面上的变化,并高效地更新实际的DOM。
- 指令系统:Vue提供了丰富的内置指令,用于处理常见的DOM操作和逻辑控制。 生态系统:Vue拥有庞大的生态系统,包括插件、工具和第三方库,可以满足各种开发需求。
2. Vue中的双向数据绑定是如何实现的?
答案:Vue中的双向数据绑定是通过v-model指令实现的。v-model可以在表单元素(如、、)上创建双向数据绑定。当用户输入改变表单元素的值时,数据模型会自动更新;反之,当数据模型的值改变时,表单元素也会自动更新。
3. Vue中的生命周期钩子有哪些?它们的执行顺序是怎样的?
答案:Vue中的生命周期钩子包括beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy和destroyed。它们的执行顺序如下:
beforeCreate created beforeMount mounted beforeUpdate updated beforeDestroy destroyed
4. Vue中的计算属性和监听器有什么区别?
答案:计算属性是基于依赖的属性,它根据其依赖的数据动态计算得出值。计算属性具有缓存机制,只有在依赖的数据发生变化时才会重新计算。监听器是用于监听数据的变化并执行相应的操作。当数据发生变化时,监听器会立即执行指定的回调函数。
5. Vue中的组件通信有哪些方式?
答案:Vue中的组件通信方式包括:
父子组件通信:通过props向子组件传递数据,子组件通过事件向父组件发送消息。 子父组件通信:子组件通过$emit触发事件,父组件通过监听事件并响应。 兄弟组件通信:通过共享的父组件来传递数据或通过事件总线(Event Bus)进行通信。 跨级组件通信:通过provide和inject来在祖先组件中提供数据,然后在后代组件中使用。
6. Vue中的路由是如何实现的?
答案:Vue中的路由是通过Vue Router实现的。Vue Router是Vue.js官方提供的路由管理器,它允许开发者在Vue应用中实现单页面应用(SPA)。Vue Router通过配置路由映射关系,将URL路径与组件进行关联,并提供导航功能,使用户可以在不刷新页面的情况下切换视图。
7. Vue中的指令有哪些?举例说明它们的用法。
答案:Vue中常用的指令包括:
- v-if:根据表达式的值条件性地渲染元素。
- v-for:根据数组或对象的数据进行循环渲染。
- v-bind:用于动态绑定属性或响应式地更新属性。
- v-on:用于监听DOM事件并执行相应的方法。
- v-model:用于在表单元素上实现双向数据绑定。 例如:
<div v-if="show">显示内容</div>
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
<img v-bind:src="imageUrl">
<button v-on:click="handleClick">点击按钮</button>
<input v-model="message">
8. Vue中的watch和computed有什么区别?
答案:watch和computed都可以用于监听数据的变化,但它们的用法和实现方式略有不同。watch用于监听指定的数据变化,并在数据变化时执行相应的操作。computed用于根据依赖的数据动态计算得出一个新的值,并将该值缓存起来,只有在依赖的数据发生变化时才会重新计算。
9. Vue中的mixin是什么?它有什么作用?
答案:Mixin是一种用于在多个组件之间共享代码的方式。Mixin可以包含组件选项(如数据、方法、生命周期钩子等),并将其合并到使用Mixin的组件中。这样可以实现代码的复用和组件的扩展,减少重复编写相似代码的工作。
10. Vue中的keep-alive是什么?它有什么作用?
答案:是Vue中的一个内置组件,用于缓存动态组件。当组件包裹在中时,组件的状态将被保留,包括它的实例、状态和DOM结构。这样可以避免在组件切换时重复创建和销毁组件,提高性能和用户体验。
11. 请解释Vue.js中的依赖注入(Dependency Injection)是什么?它在Vue中的应用场景是什么?
答案:依赖注入是一种设计模式,用于将依赖关系从一个组件传递到另一个组件。在Vue中,依赖注入通过provide和inject选项实现。父组件通过provide提供数据,然后子组件通过inject注入这些数据。它在跨多个层级的组件通信中非常有用。
12. Vue.js中的渲染函数(Render Function)是什么?它与模板(Template)有什么区别?
答案:渲染函数是一种用JavaScript代码编写组件的方式,它可以动态地生成虚拟DOM。与模板相比,渲染函数提供了更大的灵活性和控制力,可以处理更复杂的逻辑和动态渲染需求。
13. Vue.js中的插槽(Slot)是什么?请提供一个具有命名插槽和作用域插槽的示例。
答案:插槽是一种用于在组件中扩展内容的机制。命名插槽允许父组件向子组件插入具有特定名称的内容,而作用域插槽允许子组件将数据传递给父组件。示例:
<!-- 父组件 -->
<template>
<div>
<slot name="header"></slot>
<slot :data="data"></slot>
</div>
</template>
<!-- 子组件 -->
<template>
<div>
<slot name="header">默认标题</slot>
<slot :data="computedData">{{ computedData }}</slot>
</div>
</template>
14. Vue.js中的动画系统是如何工作的?请提供一个简单的动画示例。
答案:Vue.js的动画系统通过CSS过渡和动画类实现。通过在元素上添加过渡类或动画类,可以触发相应的过渡效果或动画效果。示例:
<transition name="fade">
<div v-if="show">显示内容</div>
</transition>
<!-- CSS样式 -->
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
15. Vue.js中的错误处理机制是什么?如何捕获和处理Vue组件中的错误?
答案:Vue.js提供了全局的错误处理机制和组件级别的错误处理机制。全局错误处理可以通过errorCaptured钩子函数捕获和处理错误。组件级别的错误处理可以通过errorCaptured钩子函数或errorHandler选项捕获和处理错误。
16. Vue.js中的服务端渲染(SSR)是什么?它有哪些优势和限制?
答案:服务端渲染是指在服务器上生成HTML内容并将其发送到浏览器进行渲染的过程。Vue.js可以进行服务端渲染,提供更好的首次加载性能和SEO优化。然而,服务端渲染也带来了一些限制,如增加了服务器负载和开发复杂性。
17. Vue.js中的响应式数组有哪些限制?如何解决这些限制?
答案:Vue.js的响应式系统对于数组的变异方法(如push、pop、splice等)是无法追踪的。为了解决这个限制,Vue提供了一些特殊的方法,如Vue.set、vm.$set和Array.prototype.splice。这些方法可以用于更新数组并保持响应式。
18. Vue.js中的性能优化有哪些常见的技巧?
答案:常见的Vue.js性能优化技巧包括:
使用v-if和v-for时注意避免不必要的渲染。 合理使用computed属性和watch监听器。 使用keep-alive组件缓存组件状态。 使用异步组件进行按需加载。 避免在模板中使用复杂的表达式。 使用key属性管理组件和元素的复用。 合理使用懒加载和分割代码。
19. Vue.js中的路由导航守卫有哪些?它们的执行顺序是怎样的?
答案:Vue.js中的路由导航守卫包括全局前置守卫、全局解析守卫、全局后置守卫、路由独享守卫和组件内守卫。它们的执行顺序如下:
全局前置守卫(beforeEach) 路由独享守卫(beforeEnter) 解析守卫(beforeResolve) 组件内守卫(beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave) 全局后置守卫(afterEach)
20. Vue.js中的单元测试是如何进行的?请提供一个简单的单元测试示例。
答案:Vue.js的单元测试可以使用工具如Jest或Mocha进行。示例:
// 组件代码
// MyComponent.vue
<template>
<div class="my-component">
<span>{{ message }}</span>
<button @click="increment">增加</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello',
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
// 单元测试代码
// MyComponent.spec.js
import { shallowMount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'
describe('MyComponent', () => {
it('renders message correctly', () => {
const wrapper = shallowMount(MyComponent)
expect(wrapper.find('span').text()).toBe('Hello')
})
it('increments count when button is clicked', () => {
const wrapper = shallowMount(MyComponent)
wrapper.find('button').trigger('click')
expect(wrapper.vm.count).toBe(1)
})
})
VUE3
1. Vue.js 3中的Composition API是什么?它与Options API有什么区别?
答案:Composition API是Vue.js 3中引入的一种新的组织组件逻辑的方式。它允许开发者通过函数的方式组织和重用逻辑,而不是通过选项对象。相比之下,Options API是Vue.js 2中常用的组织组件逻辑的方式,通过选项对象中的属性来定义组件的数据、方法等。
2. Vue.js 3中的Teleport是什么?请给出一个Teleport的示例。
答案:Teleport是Vue.js 3中引入的一种机制,用于将组件的内容渲染到DOM树中的任意位置。示例:
<template>
<div>
<button @click="showModal = true">打开模态框</button>
<teleport to="body">
<modal v-if="showModal" @close="showModal = false">模态框内容</modal>
</teleport>
</div>
</template>
3. Vue.js 3中的响应式系统是如何工作的?它与Vue.js 2中的响应式系统有什么区别?
答案:Vue.js 3中的响应式系统使用了Proxy对象来实现。与Vue.js 2中的响应式系统相比,Vue.js 3的响应式系统具有更好的性能和更细粒度的追踪,能够更准确地检测到数据的变化,并且支持嵌套的响应式数据。
4. Vue.js 3中的Suspense是什么?它的作用是什么?
答案:Suspense是Vue.js 3中引入的一种机制,用于处理异步组件的加载状态。它可以在异步组件加载完成之前显示一个占位符,并在加载完成后渲染异步组件的内容。这样可以更好地处理异步组件的加载过程,提供更好的用户体验。
5. Vue.js 3中的provide和inject有什么作用?请给出一个provide和inject的示例。
答案:provide和inject用于实现组件之间的依赖注入。通过在父组件中使用provide提供数据,然后在子组件中使用inject注入这些数据。示例:
// 父组件
const Parent = {
provide: {
message: 'Hello'
},
// ...
}
// 子组件
const Child = {
inject: ['message'],
created() {
console.log(this.message); // 输出:Hello
},
// ...
}
6. Vue.js 3中的动画系统有哪些改进?请列举几个改进之处。
答案:Vue.js 3中的动画系统相比Vue.js 2有以下改进之处:
-
更好的性能:Vue.js 3的动画系统使用了更高效的动画引擎,提供了更好的性能。
-
更简洁的语法:Vue.js 3的动画系统使用了更简洁的语法,使得动画的定义和使用更加直观和方便。
-
支持更多的动画特性:Vue.js 3的动画系统支持更多的动画特性,如交互式动画和更复杂的动画效果。
-
Vue.js 3中的静态提升(Static Tree Hoisting)是什么?它有什么优势?
答案:静态提升是Vue.js 3中的一项优化技术,通过在编译阶段将静态节点提升为常量,从而减少了运行时的开销。这项优化技术可以提高组件的渲染性能,并减少生成的代码体积。
7. Vue.js 3中的Fragment是什么?它的作用是什么?
答案:Fragment是Vue.js 3中引入的一种机制,用于在组件中返回多个根节点。在Vue.js 2中,组件的模板只能有一个 Vue.js 3中的Composition API中的ref和reactive有什么区别?什么时候使用哪个? 答案:ref用于创建一个响应式的基本数据类型,而reactive用于创建一个响应式的对象。当需要创建一个简单的响应式数据时,可以使用ref,当需要创建一个包含多个属性的响应式对象时,可以使用reactive。
8. Vue.js 3中的watchEffect和watch有什么区别?什么时候使用哪个?
答案:watchEffect用于监听响应式数据的变化,并在回调函数中执行相应的操作。它会自动追踪依赖,并在依赖变化时重新运行回调函数。watch用于监听指定的响应式数据,并在其变化时执行相应的操作。它可以精确地指定要监听的数据,并提供更多的配置选项。一般来说,如果只需要监听一个响应式数据的变化并执行相应操作,可以使用watchEffect;如果需要更细粒度的控制,可以使用watch。
9. Vue.js 3中的v-model指令在使用时有哪些注意事项?
答案:在使用v-model指令时,有以下注意事项:
v-model指令必须与一个表单元素一起使用,如、、等。 当使用自定义组件时,组件内部必须实现modelValue属性和update:modelValue事件,以支持v-model的双向绑定。 可以使用.lazy修饰符实现在输入框失去焦点时更新数据。 可以使用.trim修饰符自动去除输入框内容的首尾空格。 可以使用.number修饰符将输入框的值转换为数字类型。
10. Vue.js 3中的provide和inject是否支持响应式数据?
答案:默认情况下,provide和inject不支持响应式数据。如果需要在provide中提供一个响应式数据,可以使用ref或reactive将数据包装起来。然后在inject中使用toRefs或toRef将数据解构出来,以获取响应式的引用。
11. Vue.js 3中的nextTick方法有什么作用?在什么情况下使用它?
答案:nextTick方法用于在下次DOM更新循环结束之后执行回调函数。它可以用来确保在更新DOM后执行某些操作,如操作更新后的DOM元素或获取更新后的计算属性的值。通常在需要等待DOM更新完成后进行操作的情况下使用nextTick。
12. Vue.js 3中的和组件有什么区别?
答案:组件用于将组件的内容渲染到DOM树中的任意位置,而组件用于在组件进入或离开DOM树时应用过渡效果。主要用于组件的位置移动,而主要用于组件的显示和隐藏过渡。
13. Vue.js 3中的v-for指令中的key属性有什么作用?为什么要使用它?
答案:v-for指令中的key属性用于给每个迭代项设置一个唯一的标识符。它的作用是帮助Vue.js跟踪每个节点的身份,以便在数据发生变化时高效地更新DOM。使用key属性可以避免出现错误的节点更新或重新排序的问题。
React
1. 什么是React?它的核心概念是什么?
答案:React是一个用于构建用户界面的JavaScript库。它的核心概念是组件化和声明式编程。React将用户界面拆分为独立的可重用组件,并使用声明式语法描述组件的状态和UI的关系,使得构建复杂的UI变得简单和可维护。
2. 什么是JSX?它与HTML有什么区别?
答案:JSX是一种JavaScript的语法扩展,用于在React中描述UI的结构。它类似于HTML,但有一些区别:
3. 什么是React组件?它们有哪两种类型?
答案:React组件是构建用户界面的独立单元。React组件有两种类型:
函数组件:使用函数来定义组件,接收props作为参数,并返回一个React元素。 类组件:使用ES6类来定义组件,继承自React.Component类,通过render方法返回一个React元素。
4. 什么是状态(state)和属性(props)?它们之间有什么区别?
答案:状态(state)是组件自身管理的数据,可以通过setState方法来更新。属性(props)是从父组件传递给子组件的数据,子组件无法直接修改props,只能通过父组件的更新来改变props。
区别:
状态(state)是组件内部的数据,可以在组件中自由修改和管理。 属性(props)是从父组件传递给子组件的数据,子组件无法直接修改,只能接收和使用。
5. 什么是React生命周期方法?列举一些常用的生命周期方法。
答案:React生命周期方法是在组件不同阶段执行的特定方法。以下是一些常用的React生命周期方法:
componentDidMount:组件挂载后立即调用。 componentDidUpdate:组件更新后调用。 componentWillUnmount:组件卸载前调用。 shouldComponentUpdate:决定组件是否需要重新渲染。 getDerivedStateFromProps:根据props的变化来更新状态。
6. 什么是React Hooks?它们的作用是什么?
答案:React Hooks是React 16.8版本引入的一种特性,用于在函数组件中使用状态和其他React特性。Hooks提供了一种无需编写类组件的方式来管理状态和处理副作用,使得函数组件具有类组件的能力。
7. 什么是React Router?它的作用是什么?
答案:React Router是React中用于处理路由的库。它提供了一种在单页面应用中实现导航和路由功能的方式。React Router可以帮助开发者实现页面之间的切换、URL参数的传递、嵌套路由等功能。
8. 什么是React Context?它的作用是什么?
答案:React Context是一种用于在组件树中共享数据的机制。它可以避免通过props一层层传递数据,使得跨组件的数据共享变得更加简单和高效。React Context提供了一个Provider和Consumer组件,用于提供和消费共享的数据。
9. 什么是React的协调(Reconciliation)过程?它是如何工作的?
答案:React的协调过程是指React在进行组件更新时,通过比较新旧虚拟DOM树的差异,仅对需要更新的部分进行实际的DOM操作。协调过程的工作方式如下:
React会逐层比较新旧虚拟DOM树的节点,并找出差异。 对于每个差异,React会生成相应的DOM操作指令,如插入、更新或删除节点。 React会将所有的DOM操作指令批量执行,以减少对真实DOM的操作次数。
10. 什么是React的事件合成(SyntheticEvent)?它的作用是什么?
答案:React的事件合成是一种在React中处理事件的机制。它是React为了提高性能和跨浏览器兼容性而实现的一种事件系统。事件合成的作用包括:
提供了一种统一的方式来处理事件,无需考虑浏览器兼容性。 可以通过事件委托的方式将事件处理程序绑定到父组件,提高性能。 可以访问原生事件对象的属性和方法。
11. 什么是React的Fiber架构?它解决了什么问题?
答案:React的Fiber架构是React 16版本引入的一种新的协调算法和架构。它旨在解决长时间渲染阻塞主线程的问题,提高应用的性能和用户体验。Fiber架构通过将渲染过程分解为多个小任务,并使用优先级调度算法来动态分配时间片,使得React可以在每个帧中执行一部分任务,从而实现平滑的用户界面和更好的响应性能。
12. 什么是React的错误边界(Error Boundary)?它的作用是什么?
答案:React的错误边界是一种用于处理组件错误的机制。它允许组件捕获并处理其子组件中发生的JavaScript错误,以避免整个应用崩溃。错误边界的作用包括:
捕获并处理组件树中的错误,防止错误导致整个应用崩溃。 提供一种优雅的方式来显示错误信息或备用UI。 可以用于记录错误和发送错误报告。
网络
1. 什么是HTTP?它是如何工作的?
答案:HTTP(Hypertext Transfer Protocol)是一种用于在Web上传输数据的协议。它使用客户端-服务器模型,客户端发送HTTP请求到服务器,服务器返回HTTP响应。HTTP的工作流程如下:
客户端发送HTTP请求到指定的URL。 服务器接收请求并处理,然后返回HTTP响应。 客户端接收响应并解析,从中获取所需的数据。
2. 什么是HTTPS?与HTTP有什么区别?
答案:HTTPS(Hypertext Transfer Protocol Secure)是HTTP的安全版本,通过使用SSL(Secure Sockets Layer)或TLS(Transport Layer Security)协议对通信进行加密和身份验证。与HTTP相比,HTTPS具有以下区别:
数据在传输过程中通过加密进行保护,提供更高的安全性。 使用数字证书对服务器进行身份验证,防止中间人攻击。 使用默认端口443。
3. 什么是跨域请求?它是如何解决的?
答案:跨域请求是指在浏览器中向不同域名、端口或协议发送的请求。由于浏览器的同源策略(Same-Origin Policy)限制,跨域请求会受到限制。为了解决跨域问题,可以使用以下方法:
JSONP(JSON with Padding):通过动态创建
4. 什么是缓存?在前端中如何使用缓存来提高性能?
答案:缓存是将数据或资源存储在临时存储中,以便在后续请求中重复使用,从而提高性能和减少网络流量。在前端中,可以使用以下方式来利用缓存:
HTTP缓存:通过设置适当的缓存头(如Cache-Control和Expires)来指示浏览器缓存响应。 资源缓存:使用文件指纹或版本号来重命名静态资源文件,以便在文件内容变化时使浏览器重新下载。 数据缓存:使用内存缓存、浏览器本地存储(如localStorage)或服务端缓存(如Redis)来存储数据,避免重复请求。
5. 什么是CDN?它的作用是什么?
答案:CDN(Content Delivery Network)是一种分布式网络架构,用于在全球各地提供高性能、低延迟的内容传输服务。CDN的作用包括:
将静态资源(如图片、样式表、脚本等)缓存到离用户更近的服务器上,提供更快的加载速度。 分发网络流量,减轻源服务器的负载压力。 提供内容压缩、数据压缩和缓存等优化技术,提高用户体验。
6. 什么是网页加载性能优化?可以采取哪些措施来改善网页加载性能?
答案:网页加载性能优化是指通过各种技术手段来减少网页加载时间并提高用户体验。可以采取以下措施来改善网页加载性能:
压缩和合并资源文件(如CSS和JavaScript),减少文件大小和请求数量。 使用图像压缩和适当的格式选择来减小图像文件大小。 使用浏览器缓存和HTTP缓存头来缓存静态资源。 使用懒加载延迟加载非关键资源,提高初始加载速度。 使用CDN(内容分发网络)来分发静态资源,减少网络延迟。 优化关键渲染路径,尽早呈现页面内容。
7. 什么是网页性能监测和分析?可以使用哪些工具来监测和分析网页性能?
答案:网页性能监测和分析是指通过测量和收集有关网页加载和交互性能的数据,以便识别性能瓶颈并进行优化。可以使用以下工具来监测和分析网页性能:
Web性能API:浏览器提供的JavaScript API,可通过performance对象来收集性能数据。 Lighthouse:一种开源工具,可提供关于网页性能、可访问性和最佳实践的综合报告。 WebPagetest:在线工具,可测量网页加载时间并提供详细的性能分析报告。 Chrome开发者工具:浏览器内置的开发者工具,提供了性能分析、网络监控和页面审查等功能。
8. 什么是渐进式图像加载(Progressive Image Loading)?它如何改善网页加载性能?
答案:渐进式图像加载是一种技术,通过逐步加载图像的模糊或低分辨率版本,然后逐渐提高图像的清晰度,以改善网页加载性能和用户体验。渐进式图像加载的好处包括:
用户可以更快地看到页面内容,提高感知速度。 逐步加载图像可以减少网页整体的加载时间。 渐进式图像加载可以提供平滑的过渡效果,避免页面内容突然闪烁或变化。
9. 什么是前端资源优先级(Resource Prioritization)?如何设置资源的优先级?
答案:前端资源优先级是指为不同类型的资源分配加载优先级,以优化网页加载性能。可以使用以下方法设置资源的优先级:
使用标签来指定资源的预加载,以确保关键资源尽早加载。 使用标签来指定可能在未来页面中使用的资源,以提前加载。 使用标签来指定要预解析的域名,以减少DNS查找时间。 使用标签来指定要预连接的域名,以减少建立连接的时间。
浏览器
1.解释一下浏览器的工作原理。
答案:浏览器的工作原理包括以下几个关键步骤:
解析:浏览器将接收到的HTML、CSS和JavaScript代码解析成DOM树、CSSOM树和JavaScript引擎可执行的代码。 渲染:浏览器使用DOM树和CSSOM树构建渲染树,然后根据渲染树进行布局(计算元素的位置和大小)和绘制(将元素绘制到屏幕上)。 布局和绘制:浏览器根据渲染树的变化进行布局和绘制,然后将最终的页面呈现给用户。 JavaScript引擎执行:浏览器的JavaScript引擎解释和执行JavaScript代码,并根据需要更新渲染树和重新渲染页面。
2. 什么是重绘(Repaint)和重排(Reflow)?它们之间有什么区别?
答案:重绘是指当元素的外观(如颜色、背景等)发生改变,但布局不受影响时的更新过程。重绘不会导致元素的位置或大小发生变化。
重排是指当元素的布局属性(如宽度、高度、位置等)发生改变时的更新过程。重排会导致浏览器重新计算渲染树和重新绘制页面的一部分或全部。
区别在于重绘只涉及外观的更改,而重排涉及布局的更改。重排比重绘更消耗性能,因为它需要重新计算布局和绘制整个页面。
3. 什么是事件冒泡和事件捕获?它们之间有什么区别?
答案:事件冒泡和事件捕获是指浏览器处理事件时的两种不同的传播方式。
事件冒泡是指事件从最内层的元素开始触发,然后逐级向上传播到父元素,直到传播到最外层的元素。
事件捕获是指事件从最外层的元素开始触发,然后逐级向下传播到最内层的元素。
区别在于传播方向的不同。事件冒泡是从内向外传播,而事件捕获是从外向内传播。
4. 解释一下同步和异步的JavaScript代码执行方式。
答案:同步代码是按照顺序执行的代码,每个任务必须等待前一个任务完成后才能执行。同步代码会阻塞后续代码的执行,直到当前任务完成。
异步代码是不按照顺序执行的代码,它会在后台执行,不会阻塞后续代码的执行。异步代码通常使用回调函数、Promise、async/await等方式来处理异步操作的结果。
通过异步执行,可以避免阻塞主线程,提高页面的响应性能。
5. 什么是事件循环(Event Loop)?它在JavaScript中的作用是什么?
答案:事件循环是JavaScript中处理异步代码执行的机制。它负责管理调度和执行异步任务,并将它们添加到执行队列中。
在JavaScript中,事件循环的作用是确保异步任务按照正确的顺序执行,并且不会阻塞主线程。它通过不断地从执行队列中取出任务并执行,以实现非阻塞的异步操作。
6. 解释一下浏览器的垃圾回收机制是如何工作的。
答案:浏览器的垃圾回收机制是一种自动管理内存的机制,用于检测和回收不再使用的对象,以释放内存资源。
垃圾回收机制通过标记-清除算法实现。它的工作原理如下:
标记阶段:垃圾回收器会从根对象(如全局对象)开始,递归遍历所有对象,并标记仍然可访问的对象。 清除阶段:垃圾回收器会扫描堆内存,清除未被标记的对象,并回收它们所占用的内存空间。 垃圾回收机制的目标是识别和回收不再使用的对象,以避免内存泄漏和提高内存利用率。
7. 解释一下浏览器的同源策略(Same-Origin Policy)及其限制。
答案:同源策略是浏览器的一项安全机制,用于限制来自不同源的网页之间的交互。同源是指协议、域名和端口号完全相同。
同源策略的限制包括:
脚本访问限制:不同源的脚本无法直接访问彼此的数据和操作。 DOM访问限制:不同源的网页无法通过JavaScript访问彼此的DOM元素。 Cookie限制:不同源的网页无法读取或修改彼此的Cookie。 AJAX请求限制:不同源的网页无法通过AJAX请求访问彼此的数据。 同源策略的存在可以防止恶意网站获取用户的敏感信息或进行恶意操作。
8. 什么是Web Workers?它们在浏览器中的作用是什么?
答案:Web Workers是一种浏览器提供的JavaScript API,用于在后台线程中执行耗时的计算任务,以避免阻塞主线程。
Web Workers的作用是提高浏览器的响应性能,使得在执行复杂计算或处理大量数据时,不会影响用户界面的流畅性。
Web Workers通过将任务委托给后台线程来实现并行处理,从而充分利用多核处理器的能力。它们可以与主线程进行通信,但不能直接访问DOM或执行UI相关的操作。
9. 解释一下浏览器缓存(Browser Cache)是什么,以及它的作用是什么?
答案:浏览器缓存是浏览器在本地存储Web页面和资源的副本,以便在后续访问时可以快速加载。它的作用是减少对服务器的请求次数和网络传输量,提高页面加载速度和用户体验。
浏览器缓存通过在首次请求时将资源保存到本地,并在后续请求时检查资源是否已经存在并且没有过期来工作。如果资源已经存在且未过期,浏览器会直接从缓存中加载资源,而不是从服务器重新下载。
10. 什么是重定向(Redirect)?它在浏览器中的作用是什么?
答案:重定向是指当浏览器请求一个URL时,服务器返回一个不同的URL,从而将浏览器的请求重定向到新的URL上。
重定向在浏览器中的作用是实现页面的跳转、URL的修改或资源的重定向。它可以用于多种情况,例如处理旧链接的跳转、实现URL的规范化、处理用户认证等。
重定向通过在HTTP响应中设置特定的状态码(如301永久重定向、302临时重定向)和Location头部字段来实现。
11. 什么是浏览器存储(Browser Storage)?它有哪些不同的存储机制?
答案:浏览器存储是浏览器提供的一种在客户端存储数据的机制,用于在不同的网页间共享数据或持久保存数据。
浏览器存储有以下不同的存储机制:
Cookie:小型文本文件,可以存储少量数据,并在每次HTTP请求中自动发送到服务器。 Web Storage(localStorage和sessionStorage):可以存储较大量的数据,以键值对的形式存储在浏览器中。 IndexedDB:一种高级的客户端数据库,可以存储大量结构化数据,并支持索引和事务操作。 Cache API:用于缓存网络请求的响应,以便离线访问或提高页面加载速度。 不同的存储机制适用于不同的需求,开发者可以根据具体情况选择合适的存储方式。
计算机相关
关于OSI七层模型和TCp四层模型
OSI
分层:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
TCP/IP
模型:应用层、传输层、网络层、网络接口层
应用层协议(常用):HTTP、RTSP、FTP
传输层协议:TCP、UDP
1、OSI的七层模型是什么?
ISO于1978年开发的一套标准架构ISO模型,被引用来说明数据通信协议的结构和功能。
OSI在功能上可以划分为两组:
网络群组:物理层、数据链路层、网络层
使用者群组:传输层、会话层、表示层、应用层
OSI 七层网络模型 | TCP/IP 四层概念模型 | 对应网络协议 |
---|---|---|
7:应用层 | 应用层 | HTTP 、RTSP TFTP(简单文本传输协议)、 FTP、 NFS(数域筛法,数据加密)、 WAIS`(广域信息查询系统) |
6:表示层 | 应用层 | Telnet (internet远程登陆服务的标准协议)、Rlogin 、SNMP (网络管理协议)、Gopher |
5:会话层 | 应用层 | SMTP (简单邮件传输协议)、DNS (域名系统) |
4:传输层 | 传输层 | TCP (传输控制协议)、UDP (用户数据报协议)) |
3:网络层 | 应用层 | ARP (地域解析协议)、RARP 、AKP 、UUCP (Unix to Unix copy) |
2:数据链路层 | 应用层 | FDDI (光纤分布式数据接口)、Ethernet、Arpanet、PDN (公用数据网)、SLIP (串行线路网际协议)PPP (点对点协议,通过拨号或专线方建立点对点连接发送数据) |
1:物理层 | 应用层 | SMTP (简单邮件传输协议)、DNS (域名系统) |
其中高层(7、6、5、4层)定义了应用程序的功能,下面三层(3、2、1层)主要面向通过网络的端到端的数据流
2、tcp/udp
属于哪一层?
传输层
3、tcp/udp
有哪些优缺点?
(1)tcp
是面向连接的,udp
是面向无连接的
tcp
在通信之前必须通过三次握手机制与对方建立连接,而udp通信不必与对方建立连接,不管对方的状态就直接把数据发送给对方
(2)tcp
连接过程耗时,udp
不耗时
(3)tcp
连接过程中出现的延时增加了被攻击的可能,安全性不高,而udp
不需要连接,安全性较高
(4)tcp
是可靠的,保证数据传输的正确性,不易丢包,udp
是不可靠的,易丢包
tcp
可靠的四大手段:
顺序编号:tcp
在传输文件的时候,会将文件拆分为多个tcp数据包,每个装满的数据包大小大约在1k左右,tcp
协议为保证可靠传输,会将这些数据包顺序编号
确认机制:当数据包成功的被发送方发送给接收方,接收方会根据tcp
协议反馈给发送方一个成功接收的ACK
信号,信号中包含了当前包的序号
超时重传:当发送方发送数据包给接收方时,会为每一个数据包设置一个定时器,当在设定的时间内,发送方仍没有收到接收方的ACK
信号,会再次发送该数据包,直到收到接收方的ACK信号或者连接已断开
校验信息:tcp
首部校验信息较多,udp
首部校验信息较少
(5)tcp
传输速率较慢,实时性差,udp
传输速率较快
tcp
建立连接需要耗时,并且tcp
首部信息太多,每次传输的有用信息较少,实时性差
(6)tcp
是流模式,udp
是数据包模式
tcp
只要不超过缓冲区的大小就可以连续发送数据到缓冲区上,接收端只要缓冲区上有数据就可以读取,可以一次读取多个数据包,而udp
一次只能读取一个数据包,数据包之间独立
4、tcp/udp
的使用场合?
(1)对数据可靠性的要求。tcp
适用于可靠性高的场合,udp适用于可靠性低的场合
(2)应用的实时性。tcp
有延时较大,udp
演示较小
(3)网络的可靠性。网络不好的情况下使用tcp
,网络条件好的情况下,使用udp
5、PPP
协议属于哪一层协议?
数据链路层
写在最后
我是伊人a,很高兴认识你。
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
- 本文首发于掘金,未经许可禁止转载💌