当前位置:   article > 正文

面试题汇总_在服务器上的某个目录中,有一个公司信息的文本文件,包含了公司的基本介绍。编写一

在服务器上的某个目录中,有一个公司信息的文本文件,包含了公司的基本介绍。编写一

简历小炒

自我介绍

面试官您好,我叫xx,我本科毕业于xxxxxxxxxx专业,上一份工作是在成都做前端开发工程师,擅长使用vue框架进行开发项目,vue2,vue3都在项目中使用过

我本人的性格比较的稳重,爱好晨跑目前是离职状态

上一个项目是一个家政类型的管理项目,这个项目使用vue3+ts,配合AntDsign组件库,

和其他插件进行开发,这个项目以下几个模块构成

客户管理帮助我们管理客户信息,在客户信息管理中我们还能对客户进行短信的消息推广,这里我们调用了一个阿里云短信服务的api.

职工管理是用来记录职工信息以及客户对员工的评价、

订单管理负责管理预约订单、一次性保洁订单、包月家务、

财务管理用于统计收入和支出,并记录工人工资发放等

以及其他的系统设置,各模块数据的展示等

为什么离职

想来一线城市追求一个更好的发展,我相信自己的技能和潜力,所以我决定主动寻找一个更具挑战性的环境,能够让我不断成长和发展。

面试官问你有什么优点

优点是学习能力强,对于新的事物有较强的接受能力,在面对前端中新出现的技术或者新特性时能够快速了解学习,并且上手,工作方面做事情有条理性,计划性,能够明确每天的任务,按时完成项目任务

面试官问你有什么缺点

面对很多人进行演说的时候会紧张,但我已经意识到了这个问题,并且在逐步改善自己,如果有幸进入贵公司,我也尝试主动参与公司内部的演讲机会,以提升我的经验和自信心。

薪资构成是怎么样的

基本工资(7000)+项目奖金+通勤餐补

半年一次绩效考核

公司规模 公司地址

50人左右

上家公司技术团队的规模在20人左右

我们项目组有7个人,1前端,3后端,组长负责统筹,测试一个,ui一个

xxxxxxxxxxxxxxxx

住在馨美佳苑

面试问你手上有offer吗

目前我手上有一个offer,但是我更倾向加入咱们公司,因为不管是发展前景还是岗位方向技术的匹配度方面,咱们公司都是我最理想的选择。

未来几年的规划

前端---->java---->全栈----->项目负责人

你还有什么问题吗

1.可以介绍一下当前正在进行的项目吗?

2.办公地点在哪

3.面试流程的下一步是什么?大概需要多长时间?

4.技术团队的规模

一.专业技能方面

1.HTML5

1.新特性

1.语义化标签如

语义化标签能够更清晰地表达文档的结构和内容

2.表单控件和属性增多如日期选择器,颜色选择器,placeholderrequiredautofocus等

3.多媒体支持和音视频播放,MediaSourceStream API

4.canvas2D绘图,实现复杂图形动画效果

5.提供storage本地存储

6.提供webSocket通信,实现浏览器与服务器之间的实时双向通信

2.HTML5的拖放功能实现元素的拖拽

在需要可拖动的元素上设置draggable属性为true

3.Web Workers

作用:后台线程中执行JavaScript代码,以避免阻塞主线程

好处:1.提高页面响应性能,2.并行处理,3.分离UI逻辑和计算逻辑

注意:Web Workers不能直接访问DOM,也不能执行一些主线程特有的操作。它主要用于执行纯粹的计算任务、数据处理或网络请求等,并通过消息机制与主线程进行通信和交互。

使用方法:

1.创建worker.js文件

2.主js中创建var worker = new Worker(‘worker.js’);

3.可以使用onmessage事件监听来自Web Worker的消息,可以使用worker.postMessage()方法向Web Worker发送消息

worker.onmessage = function(event) {`
  `var message = event.data;`
  `// 处理接收到的消息`
`};`

`worker.postMessage('消息来自主线程');
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4.worker.js中可以通过self.onmessage事件监听来自主线程的消息,并通过self.postMessage()方法将结果发送回主线程。

`self.onmessage = function(event) {`
  `var message = event.data;`
  `// 处理接收到的消息`
  `// 发送结果回主线程`
  `self.postMessage('Hello from Web Worker!');`
`};`
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5.终止Web Worker:worker.terminate();

4.请解释Web Socket是什么,并说明它与HTTP的区别。使用方法

Web Socket是HTML5中提供的一种用于实现双向通信的协议和API。它允许在客户端和服务器之间建立持久的、全双工的通信连接,可以通过这个连接实时地发送和接收数据。

1.连接方式不同

Web Socket建立的是一种持久连接,通过握手过程建立连接后,可以在连接保持打开的状态下进行实时的双向通信

而HTTP是一种无状态的协议,每次请求-响应后就会关闭连接,需要重新建立连接来发送下一个请求

2.通信效率

由于Web Socket建立的是长连接,可以避免每次通信都需要重新建立连接的开销,减少了通信的延迟和数据传输的额外负担。

HTTP在每次请求-响应时都需要进行握手、建立连接和断开连接等操作,相对较慢。

3.服务器推送

Web Socket允许服务器主动向客户端推送数据,可以实时更新数据并及时通知客户端。

而HTTP是基于请求-响应模式的,客户端需要不断发送请求来获取最新数据。

总结:

Web Socket在实现实时双向通信方面更加高效和实用,特别适合需要实时性的应用场景,如实时数据更新、多人协作和即时聊天等。

而HTTP更适用于传统的请求-响应模式,用于获取静态或动态内容的场景。

使用方法

说一下websocket,断网了怎么处理websocket;
心跳检测,去检查这个链接是否成功

5.SEO(搜索引擎优化)

SEO(搜索引擎优化)是通过优化网站内容和结构,提高网站在搜索引擎中的排名和曝光度的过程。

HTML5中引入的语义化标签,旨在提高网站的SEO效果,还需要考虑其他因素,如关键字优化、网站速度优化、友好的URL结构等。

6.如何在HTML5中实现响应式设计

1.使用CSS媒体查询(Media Queries)

2.弹性网格布局(Flexible Grid Layout)

3.CSS网格布局(CSS Grid Layout

4.视口(Viewport)设置

7.,HTTP和HTTPS

HTTP是不加密的传输协议,

HTTPS是基于HTTP协议的安全版本,通过使用SSL(Secure Sockets Layer)或TLS(Transport Layer Security)协议对传输的数据进行加密和身份验证

8.浏览器数据存储区别
  1. Cookie:Cookie 是一小段文本数据,由服务器发送到浏览器并存储在客户端。它主要用于记录用户的身份认证、会话状态等信息。Cookie 的特点包括:
    • 存储容量有限,一般为 4KB 左右。
    • 每次请求都会将 Cookie 信息发送到服务器,增加网络传输开销。
    • 可设置过期时间,可以在指定时间后自动删除。
  2. LocalStorage:LocalStorage 是 HTML5 提供的一种持久化的本地存储方案,用于在浏览器中保存键值对数据。它的特点包括:
    • 存储容量较大,一般为 5MB 左右。
    • 数据在本地持久存储,不受浏览器关闭或页面刷新的影响。
    • 数据存储在客户端,不会随着每次请求都发送到服务器。
  3. SessionStorage:SessionStorage 与 LocalStorage 类似,也是 HTML5 提供的本地存储方案,用于在浏览器中保存键值对数据。但 SessionStorage 的特点是:
    • 存储容量较大,一般为 5MB 左右。
    • 数据在会话期间有效,会话结束后自动清除。
    • 数据存储在客户端,不会随着每次请求都发送到服务器。
9.盒模型

CSS中用来描述元素布局和渲染的概念。它将每个HTML元素视为一个矩形的盒子,这个盒子由四个部分组成:内容区域(content)、内边距(padding)、边框(border)和外边距(margin)

标准盒模型(content-box):

总宽度 = width + padding+ border+ margin
总高度 = height + padding+ border+ margin

IE盒模型(border-box):内间距和边框被包括在宽度中

总宽度 = width + margin
总高度 = height + margin

10.BFC是什么 怎么处理BFC的问题

BFC(块级格式化上下文)是 CSS 中的一个概念,它是页面中一个独立的渲染区域,具有自己的布局规则。BFC 可以用来解决一些布局问题,例如清除浮动、防止 margin 重叠等。

BFC 的特性包括:

  1. 内部的块级元素在垂直方向上按照从上到下的顺序进行排列。
  2. BFC 区域不会与浮动元素重叠。
  3. BFC 的区域不会影响外部的元素,即外部的元素与 BFC 区域之间的边距不会重叠。
  4. BFC 区域可以包含浮动的子元素,并且计算高度时会包括这些浮动的子元素。
  5. BFC 区域可以阻止内部的元素逃逸到外部的元素中。

高度塌陷的原因是浮动元素或绝对定位元素脱离了文档流,导致父元素无法正确计算其高度。结果是父元素的高度变为零,从而影响了页面布局。

1.
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}
2.使用 overflow: auto 或 overflow: hidden 清除浮动:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
11.http状态码
  • 1xx(信息性状态码):指示请求正在处理或需要进一步操作。
  • 2xx(成功状态码):指示请求已成功处理并返回所需的结果。
    • 200(OK):请求成功,正常返回结果。
    • 201(Created):请求成功,并且服务器已创建了新的资源。
    • 204(No Content):请求成功,但响应中不包含实体内容。
    • 206 (Patial Content):表示客户端进行了范围请求,并且服务器成功执行了这部分的GET请求,响应报文中包含由Content-Range指定范围的实体内容。
  • 3xx(重定向状态码):指示需要进一步操作以完成请求。
    • 301(Moved Permanently):请求的资源已永久移动到新的URL。
    • 302(Found):请求的资源临时移动到新的URL。
    • 304(Not Modified):请求的资源未修改,可以使用缓存的版本。
  • 4xx(客户端错误状态码):指示请求包含错误或无法完成请求。
    • 400(Bad Request):请求无效或格式错误。
    • 401(Unauthorized):请求需要身份验证。
    • 403 (Forbidden):服务器拒绝该次访问(访问权限出现问题)
    • 404(Not Found):请求的资源不存在。
  • 5xx(服务器错误状态码):指示服务器在处理请求时发生错误。
    • 500(Internal Server Error):服务器遇到了意外错误。
    • 503(Service Unavailable):服务器暂时无法处理请求。
12.从输入URL到渲染页面的过程
  1. DNS解析:浏览器会将URL中的域名发送给DNS服务器进行解析,以获取与该域名相对应的IP地址。DNS服务器返回IP地址信息给浏览器。

  2. 建立TCP连接:浏览器使用HTTP协议通过socket API建立与Web服务器的TCP连接。

  3. 发送HTTP请求:浏览器向Web服务器发送HTTP请求,其中包括请求方法(GET, POST, PUT等)、请求头部(例如Accept-Language、Cookies等)和请求主体(仅适用于某些请求方法,如POST)。

  4. 接收HTTP响应:Web服务器接收到请求,并向浏览器返回HTTP响应,其中包括状态码(比如200 OK, 404 Not Found)、响应头部(例如Content-Type、Set-Cookie等)和响应主体(通常为HTML、CSS、JavaScript等)。

  5. 处理响应内容:浏览器根据响应头部中的Content-Type字段确定响应主体的类型,并将其传递给渲染引擎进行渲染。如果响应主体是HTML文档,则渲染引擎会根据HTML标记和CSS样式表将其转换为可视化的页面。同时,浏览器会执行页面中嵌入的JavaScript代码,以实现一些动态交互效果。

  6. 显示页面:渲染引擎完成页面渲染后,将其呈现到浏览器窗口中显示给用户。

    1.输入URL

    2.访问hosts解析,如果没有解析访问DNS解析

    3.TCP握手

    4.HTTP请求

    5.HTTP响应返回数据

    6.浏览器解析并渲染页面

13.请解释一下浏览器缓存是什么,以及如何使用HTTP头来控制缓存。

浏览器缓存是一种机制,允许浏览器在第一次请求资源时将其保存在本地,以便在未来的请求中可以直接使用本地的缓存副本,而不必重新从服务器下载。这样可以大大减少请求的数量和响应时间,提高用户体验和网站性能。

(1).Cache-Control:这个头用来控制缓存的行为,可以设置值为max-age,表示缓存的最大时间。
(2).Expires:这个头用来设置过期时间,它指定了资源的到期时间,之后就需要重新请求服务器。
(3).Last-Modified / If-Modified-Since:这两个头用于检查资源是否已经被修改。
(4).ETag / If-None-Match:这两个头也用于检查资源是否已经被修改。

通过使用这些HTTP头,服务器可以控制浏览器缓存的行为,从而提高网站的性能和用户体验。同时,开发者也可以根据需要调整这些头的设置,以便更好地控制缓存。

2.CSS

1.什么是CSS盒模型?它有哪些属性?

CSS盒模型是指在网页布局中,每个元素都被看作一个盒子,它包含了内容区域、内边距、边框和外边距。CSS盒模型的属性包括width、height、padding、border和margin等。

2.CSS中的伪类和伪元素有什么区别?

伪类用于选择元素的特定状态或行为,如:hover、:active、:nth-child等。

伪元素则用于创建并操作元素的特定部分,如::before、::after等。

主要区别在于伪类选择的是元素的某个状态,而伪元素选择的是元素的某个部分

3.CSS中的层叠样式表(CSS cascade)是什么?它的工作原理是怎样的

CSS中的层叠样式表(CSS cascade)是一种规则,用于解决多个CSS规则之间的冲突。层叠样式表通过选择器的特定性和样式的来源来确定哪个样式将被应用,具有更高特定性和来源优先级的样式将覆盖其他样式。

4.请解释一下CSS中的选择器优先级,并提供一个示例。

!important>内联样式>id>
class=属性选择器=伪类选择器>
标签=伪元素>
兄弟=子=后代=*

5.css3动画

1.过渡(Transitions):通过过渡效果可以实现元素在状态变化时的平滑过渡。你可以指定属性的变化时间、变化类型(线性、缓动等)和延迟等。例如:

.box {
  width: 100px;
  height: 100px;
  background-color: red;
  transition: width 1s ease-in-out;
}

.box:hover {
  width: 200px;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.关键帧动画(Keyframe Animations):使用关键帧动画可以定义元素在不同帧之间的状态和过渡效果。你可以指定多个关键帧,并设置每个关键帧的属性值。例如:

@keyframes slidein {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}

.box {
  width: 100px;
  height: 100px;
  background-color: red;
  animation: slidein 1s ease-in-out;
}

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

3.变换(Transforms):变换效果可以改变元素的形状、大小、位置和方向等。常用的变换效果包括平移、缩放、旋转和倾斜等。例如:

​ 1.平移(Translation):可以通过平移变换将元素沿着 X 和 Y 轴进行移动。使用 translate()translateX()translateY() 函数来指定平移的距离。

transform: translate(50px, 100px); /* 沿 X 轴移动 50 像素,沿 Y 轴移动 100 像素 */
transform: translateX(50px); /* 沿 X 轴移动 50 像素 */
transform: translateY(100px); /* 沿 Y 轴移动 100 像素 */
  • 1
  • 2
  • 3

​ 2.缩放(Scale):可以通过缩放变换改变元素的大小。使用 scale()scaleX()scaleY() 函数来指定缩放因子。

transform: scale(1.5); /* 按照 1.5 倍缩放元素 */
transform: scaleX(1.5); /* 按照 X 轴方向 1.5 倍缩放元素 */
transform: scaleY(1.5); /* 按照 Y 轴方向 1.5 倍缩放元素 */
  • 1
  • 2
  • 3

​ 3.旋转(Rotation):可以通过旋转变换使元素绕其原点旋转。使用 rotate() 函数来指定旋转角度。

transform: rotate(45deg); /* 顺时针旋转元素 45 度 */
  • 1

​ 4.倾斜(Skew):可以通过倾斜变换使元素沿 X 和 Y 轴方向发生倾斜。使用 skew()skewX()skewY() 函数来指定倾斜角度。

transform: skew(30deg, 10deg); /* 沿 X 轴倾斜 30 度,沿 Y 轴倾斜 10 度 */
transform: skewX(30deg); /* 沿 X 轴倾斜 30 度 */
transform: skewY(10deg); /* 沿 Y 轴倾斜 10 度 */
  • 1
  • 2
  • 3
6.flex:1 是哪些属性的缩写,对应的属性代表什么含义?

flex:1;flex-grow: 1;flex-shrink: 1;flex-basis: 0%; 的缩写。

  • flex-grow 属性定义项目的放大比例,默认值为 0,即如果存在剩余空间,也不放大。如果所有项目的 flex-grow 属性都为 1,则它们将等分剩余空间(如果有的话)。如果一个项目的 flex-grow 属性为 2,其他项目都为 1,则前者将比其他项目多一倍的剩余空间。
  • flex-shrink 属性定义了项目的缩小比例,默认值为 1,即如果空间不足,该项目将缩小。如果所有项目的 flex-shrink 属性都为 1,则当空间不足时,它们将等比例缩小。如果一个项目的 flex-shrink 属性为 0,其他项目都为 1,则空间不足时,前者不会缩小。
  • flex-basis 属性定义了在分配多余空间之前,"item"占据的主轴空间(main size)。默认值为 auto,即项目的本来大小。但是,当设置为 0% 时,它将占据尽可能少的空间。
7.实现元素的水平垂直居中
1.使用 flex 布局
.parent {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center; /* 垂直居中 */
}
2.使用绝对定位和 transform 属性
.parent {
  position: relative;
}

.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 水平垂直居中 */
}
3.使用table布局
.parent {
  display: table;
  width: 100%;
  height: 100%;
}

.child {
  display: table-cell;
  vertical-align: middle; /* 垂直居中 */
  text-align: center; /* 水平居中 */
}
  • 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
  • 28
  • 29

3.JavaScript

1.闭包的概念(使用场景)

闭包是指在一个函数内部定义的函数,该内部函数可以访问其外部函数的变量、参数和其他内部函数,即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的作用域。

它可以用于创建私有变量、模块化开发、实现函数柯里化等场景

不会造成变量污染,但可能造成内存泄漏

场景:保护变量,模块化开发,vue3中的setup函数

2.解释一下原型和原型链

原型:原型就是一个为对象实例定义了一些公共属性和公共方法的对象模板。

原型链:每个JavaScript对象(除了 null 和 undefined)都有一个内部属性 [[Prototype]],它指向该对象的原型。当我们访问对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript会沿着原型链向上查找(这个原型对象也有他的原型),直到找到该属性或方法或者到达原型链的顶端(Object.prototype = null)

3.ES6新增哪些内容

1.let const

2.箭头函数()=>{},箭头函数没有this指向

3.模板字符串``

4.解构赋值[]{}

5.class关键字,类

6.import,export,模块化语法

7.map,set(去重),symbol

8.promise,异步编程解决方式,解决了回调地狱问题

9.可选链操作符?

10.async/await

11.展开运算符…

4.script异步加载方式

defer,同时下载,等页面加载完后加载js

async,同时下载,如果有js下载好,暂停加载页面,执行js

5,数组去重的方法

1.使用 Set 数据结构:利用 Set 的特性,将数组转换为 Set,然后再将 Set 转换回数组,即可实现去重。

`const array = [1, 2, 3, 3, 4, 4, 5];`
`const uniqueArray = [...new Set(array)];`
`console.log(uniqueArray); // [1, 2, 3, 4, 5]`
  • 1
  • 2
  • 3

2.使用 filter() 方法:遍历数组,通过 filter() 方法筛选出只包含首次出现的元素的新数组。

`const array = [1, 2, 3, 3, 4, 4, 5];`
`const uniqueArray = array.filter((value, index, self) => self.indexOf(value) === index);`
`console.log(uniqueArray); // [1, 2, 3, 4, 5]`
  • 1
  • 2
  • 3

3.使用 reduce() 方法:利用 reduce() 方法遍历数组,将首次出现的元素加入结果数组中

`const array = [1, 2, 3, 3, 4, 4, 5];`
`const uniqueArray = array.reduce((result, current) => {`
  `if (!result.includes(current)) {`
    `result.push(current);`
  `}`
  `return result;`
`}, []);`
`console.log(uniqueArray); // [1, 2, 3, 4, 5]`
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4.创建一个新数组,遍历旧数组,使用indexof()/included()判断数组中的值是否存在于新数组,不存在则添加到新数组.

如果数组中有对象的话,怎么去重
`const newArray = array.filter((*item*, *index*, *self*) => {`

 `return *index* === *self*.findIndex(*obj* => (`

  `typeof *obj* === 'object' ? JSON.stringify(*obj*) === JSON.stringify(*item*) : *obj* === *item*`

 `));`

`});`

`console.log(newArray);`
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
6.原生 ajax 的流程
// 创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();

// 设置回调函数
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 请求完成且成功
    var response = xhr.responseText;
    // 处理服务器返回的数据
    console.log(response);
  } else {
    // 请求未完成或出现错误
    console.log("Error: " + xhr.status);
  }
};

// 配置请求参数
var method = "GET";
var url = "https://api.example.com/data";
var async = true;

// 打开连接
xhr.open(method, url, async);

// 设置请求头(可选)
xhr.setRequestHeader("Content-Type", "application/json");

// 发送请求
xhr.send();

// 可以在此处继续执行其他操作,而无需等待响应

  • 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
  • 28
  • 29
  • 30
  • 31
  • 32
7.typeof 和instanceof的区别
  • typeof 是用来判断变量的类型,适用于基本数据类型函数未声明变量
  • instanceof 是用来判断对象是否是某个构造函数的实例。它是基于对象的原型链进行判断的。
console.log(typeof []); // 输出: "object"
console.log(typeof null); // 输出: "object"
var arr = [];
console.log(arr instanceof Array); // 输出: true

var str = "Hello";
console.log(str instanceof String); // 输出: false(字符串字面量不是 String 对象的实例)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
为什么str instanceof String为false

这是因为 str 是一个字符串字面量(基本数据类型),而不是一个 String 对象。在 JavaScript 中,字符串字面量被自动包装成相应的 String 对象,以便在需要时进行字符串操作。尽管 str 看起来像一个字符串对象,但它实际上是一个基本的原始值。

当使用 instanceof 运算符检查一个对象的类型时,它会检查对象是否是指定类型的实例。由于 str 不是 String 对象的实例,而是一个原始字符串值,因此 str instanceof String 返回 false。 要注意的是,虽然 str 不是 String 对象的实例,但它仍然可以执行字符串的操作和方法,因为 JavaScript 在需要时会自动将其转换为 String 对象。

使用**Object.prototype.toString.call(obj)**进行类型检查

8.JS数据类型
  1. 基本数据类型(Primitive Types):
    • Number: 表示数值类型,包括整数和浮点数。
    • String: 表示字符串类型,用于存储文本数据。
    • Boolean: 表示布尔类型,只有两个值:true 和 false。
    • null: 表示空值。
    • undefined: 表示未定义的值。
    • Symbol: 表示唯一的标识符,ES6 引入的新类型。
    • BigInt: 表示任意精度的整数,用于处理超过 Number 类型范围的整数值,ES2020 引入的新类型。
  2. 引用数据类型(Reference Types):
    • Object: 表示复杂数据类型,可以存储多个键值对组成的属性和方法。
      • 包括普通对象、数组、函数、正则表达式等。

注意,JavaScript 中的变量是无类型的,即变量可以在任何时刻持有任何类型的值。同一个变量可以在不同的上下文中存储不同类型的值。这种动态特性是 JavaScript 的一大特点。

9.数组的方法,区分是否改变原数组
  1. 不改变原数组的方法(Non-Mutating Array Methods):

    • Array.concat(): 连接多个数组或值,返回一个新的数组。
    • Array.slice(): 截取数组的一部分,返回一个新的数组。
    • Array.join(): 将数组的所有元素连接成一个字符串。
    • Array.includes(): 检查数组是否包含指定的元素。
    • Array.indexOf(): 返回指定元素在数组中的索引。
    • Array.lastIndexOf(): 返回指定元素在数组中最后出现的索引。
    • Array.map(): 根据回调函数对数组的每个元素进行操作,返回一个新的数组。
    • Array.filter(): 根据条件筛选数组的元素,返回一个新的数组。
    • Array.reduce(): 对数组的每个元素执行归纳操作,返回一个累积结果。
    • Array.reduceRight(): 与 reduce() 类似,但从数组的末尾开始归纳。
    • Array.some(): 检查数组是否至少有一个元素满足条件。
    • Array.every(): 检查数组的所有元素是否都满足条件。
    • Array.find(): 返回数组中满足条件的第一个元素。
    • Array.findIndex(): 返回数组中满足条件的第一个元素的索引。
  2. 改变原数组的方法(Mutating Array Methods):

    • Array.pop(): 移除并返回数组的最后一个元素。
    • Array.push(): 向数组末尾添加一个或多个元素,并返回新的长度。
    • Array.shift(): 移除并返回数组的第一个元素。
    • Array.unshift(): 向数组的开头添加一个或多个元素,并返回新的长度。
    • Array.reverse(): 颠倒数组中元素的顺序。
    • Array.sort(): 对数组元素进行排序。
    • Array.splice(): 在指定位置删除或添加元素,并返回被删除的元素。
    • Array.fill(): 用指定的值填充数组的所有元素。
    • Array.copyWithin(): 将指定位置的元素复制到其他位置。
    • Array.flat(): 将嵌套的数组展平为一个新的数组。
    • Array.flatMap(): 对数组的每个元素执行映射操作,并将结果展平为一个新的数组。
    方法参数返回值
    concatitem1, item2, …, itemN新的合并后的数组
    copyWithintarget, start, end修改后的原数组
    entries包含数组键值对的迭代器对象
    everycallback(element, index, array)布尔值
    fillvalue, start, end修改后的原数组
    filtercallback(element, index, array)符合条件的新数组
    findcallback(element, index, array)满足条件的第一个元素
    findIndexcallback(element, index, array)满足条件的第一个元素的索引
    flatdepth展开后的新数组
    forEachcallback(element, index, array)
    includessearchElement, fromIndex布尔值
    indexOfsearchElement, fromIndex指定元素的索引
    joinseparator(分隔符,默认为逗号)连接后的字符串
    keys包含数组索引的迭代器对象
    lastIndexOfsearchElement, fromIndex指定元素在数组中最后一次出现的索引
    mapcallback(element, index, array)映射后的新数组
    pop被移除的元素
    pushelement1, element2, …新数组的长度
    reducecallback(accumulator, currentValue, index, array), initialValue累积的结果
    reduceRightcallback(accumulator, currentValue, index, array), initialValue累积的结果
    reverse反转后的原数组
    shift被移除的元素
    slicestart, end包含指定元素的新数组
    somecallback(element, index, array)布尔值
    sortcompareFunction(排序比较函数,可选)排序后的原数组
    splicestart, deleteCount(要删除的元素个数), item1, item2, …包含被删除元素的数组
    toLocaleString数组的字符串表示
    toString数组的字符串表示
    unshiftelement1, element2, …新数组的长度
    values包含数组值的迭代器对象

需要注意的是,改变原数组的方法会直接修改原始数组,而不是创建一个新的数组。如果不想改变原数组,应该使用不改变原数组的方法。

10.this关键字

this 关键字用于指代当前执行上下文中的对象

  1. 全局上下文中的 this:在全局作用域中,this 指代全局对象,在浏览器环境中通常是 window 对象,在 Node.js 环境中是 global 对象。

需要注意的是,函数中的 this 是在运行时确定的,而不是在函数定义时确定的。这意味着同一个函数在不同的调用环境下,其 this 的值可能不同。(谁调用,指向谁)

11.promise

它通过封装异步任务并提供链式调用的方式来解决回调地狱的问题,并提供了更优雅和可读性强的代码编写方式。

promise本身既不是异步也不是同步的,只有.then回调的时候是异步的,promise是微任务,事件轮询机制,,它通过不断从任务队列中取出任务并执行来保证任务的顺序性和异步操作的完成。在 Promise 中,异步操作的回调函数会被放入任务队列中,并在适当的时机被事件循环取出并执行。

Promsie.all([Promsie1,Promsie2,Promsie3])

当所有给定的 promise 都 resolve 时,新的 promise 才会 resolve,并且其结果数组将成为新 promise 的结果。

如果任意一个 promise 被 reject,由 `Promise.all` 返回的 promise 就会立即 reject,并且带有的就是这个 error。
  • 1
  • 2
  • 3

Promise.allSettled([Promsie1,Promsie2,Promsie3])

Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何,都会被返回。结果数组具有:

{status:"fulfilled", value:result} 对于成功的响应,
{status:"rejected", reason:error} 对于 error。
  • 1
  • 2
  • 3
  • 4

Promise.race([Promsie1,Promsie2,Promsie3])

只等待第一个 settled 的 promise 并获取其结果(或 error),不论成功还是失败
  • 1

Promise.any([Promsie1,Promsie2,Promsie3])

只等待第一个 fulfilled(成功) 的 promise,如果给出的 promise 都 rejected(失败)
那么返回的 promise 会带有 AggregateError —— 一个特殊的 error 对象,在其 errors 属性中存储着所有 promise error。
  • 1
  • 2
12.事件轮询(event loop)
  1. 执行同步任务:首先执行当前执行栈中的同步任务,直到执行栈为空。

  2. 执行一个宏任务,然后执行微任务

  3. 执行微任务:检查微任务队列,依次执行队列中的所有微任务。微任务包括 Promise 的回调函数、MutationObserver 的回调函数等。

  4. 更新渲染:如果浏览器环境下,会执行渲染操作,更新页面的视图。

  5. 执行宏任务:从宏任务队列中选择一个任务执行。宏任务包括 setTimeoutsetIntervalI/O 操作等。

    执行完同步任务后,执行一个宏任务,然后执行一轮微任务,微任务都执行完后,执行一个宏任务,在查看有无微任务,继续执行,直到完毕

在浏览器环境中,script 标签的加载和执行被认为是宏任务。当浏览器解析到 script 标签时,它会将该标签的内容作为脚本代码加载并执行。

13.map和forEach区别

1.返回值

map()方法会返回一个新的数组,该数组包含对原数组中每个元素进行操作后的结果;

forEach()方法没有返回值,只是对数组中的每个元素进行操作。

2.使用场景

map()方法常用于需要对数组中的每个元素进行转换或映射操作的场景,例如生成一个新的数组,其中的元素是原数组中每个元素经过某种计算或处理后的结果。

forEach()方法则更适合在遍历数组并执行一些操作时使用,但不需要生成新的数组。

3.回调函数参数

map()中,回调函数可以接收三个参数:当前元素、当前索引和原数组;

forEach()中,回调函数可以接收三个参数:当前元素、当前索引和原数组。

总结:

需要生成新数组用map()方法,不需要生成新数组用forEach()方法

const doubledNumbers = numbers.map((num) => num * 2);

const doubledNumbers = numbers.map((num) => {  return num * 2; });
  • 1
  • 2
  • 3

这意味着在map()方法中,回调函数必须返回一个值,否则新的数组中对应的位置将是undefined

map不加{}时可以不使用return

14.性能优化方案

1.压缩和合并文件,压缩和合并CSS、JavaScript和图像等文件,减少网络传输的数据量

2.图片优化:使用适当的图片格式、压缩图片大小、懒加载等技术来减少图片的加载时间和带宽消耗。使用精灵图

3.减少HTTP请求

4.使用CDN:将静态资源部署到内容分发网络(CDN)上,使用户能够从离其最近的服务器获取资源,减少网络延迟,提高访问速度。

15.说一下防抖和节流
  1. 防抖(Debounce): 防抖是指在连续触发某个事件时,只执行最后一次触发的操作,而忽略之前的触发。通俗地说,就是在等待一段时间后执行操作,如果在等待时间内再次触发了该事件,则重新计时等待。

应用场景:

  • 搜索框输入联想:用户连续输入时,等待一段时间后再发送请求,以减少请求次数。
  • 窗口大小调整:用户调整窗口大小时,等待一段时间后再触发对应的操作。
  1. 节流(Throttle): 节流是指在一定时间间隔内只执行一次操作,即限制触发操作的频率。不论触发频率多高,都会按照固定的时间间隔执行操作。

应用场景:

  • 页面滚动事件:滚动过程中触发某些操作,节流可以限制操作的频率,减少触发次数,避免过多的计算和渲染操作。

  • 鼠标移动事件:鼠标连续移动时触发某些操作,节流可以限制操作的频率,减少触发次数,提高性能。

    区别: 防抖和节流的主要区别在于执行操作的时机。

    防抖是等待一段时间后执行最后一次触发的操作

    而节流是按照固定的时间间隔执行操作

16.promise的基本原理
  1. Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。
  2. 初始状态为 pending,当异步操作成功完成时,状态会变为 fulfilled,当异步操作失败时,状态会变为 rejected。
  3. 一旦状态发生改变,Promise 对象的状态就不会再改变。
  4. 每次调用 then 方法都会返回一个新的 Promise 对象,实现了链式调用
  5. catch 方法可以用来集中处理错误,并提供统一的错误处理逻辑。
  6. 方法:Promise.all(所有 Promise 对象都成功),Promise.race(第一个 Promise 对象完成),Promise.allSettled(所有 Promise 对象都完成
  7. Promise.all() 方法的 resolve 回调函数接收一个数组作为参数,这个数组包含了每个 Promise 对象的 resolve 回调函数的返回值,失败返回的回调函数接收第一个变为 reject 状态的 Promise 对象的返回值。
17.函数实现深浅拷贝
//浅拷贝:
const obj1 = { a: 1, b: { c: 2 } };
// 1.使用 Object.assign 实现浅拷贝
const obj2 = Object.assign({}, obj1);
// 2.使用展开运算符实现浅拷贝
const obj3 = { ...obj1 };

//深拷贝
// 1.使用 JSON 序列化和反序列化实现深拷贝
const obj2 = JSON.parse(JSON.stringify(obj1));
// 2.使用递归遍历实现深拷贝!
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let result = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key]);
    }
  }
  return result;
}
const arr1 = [1, [2, 3], { a: 4 }];
const arr2 = deepClone(arr1);
console.log(arr2); // [1, [2, 3], { a: 4 }]
  • 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
18.new 一个对象的过程
function ClassA(){
	this.name = "123"
}
var p = new ClassA();

  • 1
  • 2
  • 3
  • 4
  • 5

1、 创建空对象;
  var obj = {};
2、 设置新对象obj的constructor属性为构造函数的名称,设置新对象obj的__proto__属性指向构造函数的prototype对象;
  obj.proto = ClassA.prototype;
3、 使用新对象调用函数,函数中的this被指向新实例对象:
  ClassA.call(obj);  //通过 call(),obj能够使用属于另一个对象的方法。
4、 将初始化完毕的obj返回,赋给变量p

19.for in 和for of的区别

for of是es6引入的,用于遍历 所有迭代器iterator

1.循环数组 区别一:for in 和 for of 都可以循环数组,for in 输出的是数组的index下标,而for of 输出的是数组的每一项的值。

const arr = [1,2,3,4]
 
// for ... in
for (const key in arr){
    console.log(key) // 输出 0,1,2,3
    }
 
// for ... of
for (const key of arr){
    console.log(key) // 输出 1,2,3,4
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.循环对象 区别二:for in 可以遍历对象,for of 不能遍历对象,只能遍历带有iterator接口的,例如Set,Map,String,Array

const object = { name: 'lx', age: 23 }
    // for ... in
    for (const key in object) {
      console.log(key) // 输出 name,age
      console.log(object[key]) // 输出 lx,23
    }
 
    // for ... of
    for (const key of object) {
      console.log(key) // 报错 Uncaught TypeError: object is not iterable
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.数组对象

const list = [{ name: 'lx' }, { age: 23 }]
    for (const val of list) {
      console.log(val) // 输出{ name: 'lx' }, { age: 23 }
      for (const key in val) {
        console.log(val[key]) // 输出 lx,23
      }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

总结:for in适合遍历对象,for of适合遍历数组。for in遍历的是数组的索引,对象的属性,以及原型链上的属性。

20.有没有遇到过 promise卡在pending状态 ,有没有想过为什么会出现这种情况
  1. 异步操作未完成:Promise通常用于处理异步操作。如果异步操作尚未完成,Promise将保持在pending状态,直到操作完成或被拒绝。这可能是由于网络请求、文件读取等异步操作需要更长的时间来完成。
  2. 忘记调用resolve或reject:在创建Promise时,你需要明确调用resolvereject函数来改变Promise的状态。如果忘记调用这些函数,Promise将保持在pending状态,并且不会进入resolved(解决)或rejected(拒绝)状态。
  3. 异步操作中出现错误:如果在异步操作过程中发生错误,Promise可能会保持在pending状态,而不会进入resolvedrejected状态。这可能是由于代码错误、网络故障或其他异常情况导致的。
  4. 没有正确处理Promise链:在使用Promise时,需要正确处理Promise链中的所有情况,包括thencatch方法。如果在Promise链中的某个地方没有处理错误或没有返回一个新的Promise实例,可能会导致Promise保持在pending状态。
21.promise里catch和try catch里的catch的区别
  • catch方法用于Promise链中捕获和处理异步操作的错误。
  • try catch语句用于捕获和处理同步代码中的异常。
  • catch方法只能捕获Promise链中的错误,而try catch语句可以捕获同步代码中的各种异常。
  • catch方法通过链式调用添加到Promise链中,而try catch语句是结构化的语法。
22.async,await和promise的区别是什么

async/await提供了更加直观和简洁的语法来处理异步操作,使代码更易读、易写,并且错误处理更加方便。它建立在Promise之上,可以看作是对Promise的一种更高级的封装和语法糖,使异步代码的编写和阅读更加友好。

4.VUE

1.有做过axios的二次封装吗,有什么注意点。会出现重复请求,怎么阻止
  1. 设置基础配置:包括设置基础 URL、请求超时时间、默认请求头等。
  2. 请求拦截器:用于在发送请求之前进行拦截处理,例如添加请求头、认证信息等。
  3. 响应拦截器:用于对响应进行拦截和处理,例如对返回的数据进行统一处理、错误处理等。
  4. 统一错误处理:可以根据响应状态码进行统一的错误处理,例如弹出错误提示、重定向等。
  5. 统一成功处理:对于一些常见的成功响应状态进行统一处理,例如根据返回的状态码进行特定操作。
  6. 添加请求取消机制:支持请求的取消,例如在某些情况下用户主动取消请求或页面跳转时取消请求。
  7. 处理请求参数:对请求参数进行处理,例如序列化、添加默认参数等。
  8. 处理请求结果:对请求结果进行处理,例如转换响应数据格式、解析特定字段等。
  9. 添加身份验证:支持添加身份验证机制,例如在请求头中添加 token、设置登录状态等。
  10. 处理请求异常:对请求异常进行处理,例如网络错误、超时等情况下的处理。
  11. 请求重试机制:支持请求的重试,例如在请求失败时自动进行重试操作。
  12. 多域名支持:支持在不同的环境下使用不同的域名,例如开发环境、测试环境和生产环境。

当用户在短时间内多次点击同一个按钮时,容易出现重复请求的情况。可以通过取消上次请求来避免重复请求。

实现方式:

(1)通过axios.CancelToken.source()创建cancelToken实例;

(2)在请求拦截器中将cancelToken实例赋值给config.cancelToken;

(3)在每次请求前,先取消掉上一次的请求,通过存储上一次请求的cancelToken实例来实现;

(4)在响应拦截器里清空cancelToken实例。

import axios from 'axios';

// 创建axios实例
const service = axios.create({
  baseURL: '/api',
  timeout: 5000
});

// 取消请求
const CancelToken = axios.CancelToken;
let cancel;

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 如果上一次请求还未结束,则取消掉上一次请求
    if (typeof cancel === 'function') {
      cancel('请求取消');
      cancel = undefined;
    }

    // 将cancelToken实例赋值给config.cancelToken
    config.cancelToken = new CancelToken(function executor(c) {
      cancel = c;
    });

    // 设置公共参数
    config.headers['token'] = localStorage.getItem('token');
    return config;
  },
  error => {
    console.log(error);
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  response => {
    // 清空cancelToken实例
    cancel = undefined;
    return response.data;
  },
  error => {
    console.log(error);
    // 清空cancelToken实例
    cancel = undefined;
    return Promise.reject(error);
  }
);

export default service;

  • 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
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
2.移动端适配
  1. 媒体查询(Media Queries): 媒体查询是CSS3中的功能,允许根据设备的特性(如屏幕宽度、高度、分辨率等)应用不同的样式规则。通过使用媒体查询,可以根据不同的设备尺寸和特性调整网页的布局和样式,实现响应式设计。
  2. Viewport: Viewport是指网页在浏览器中的可视区域大小。通过设置Viewport的meta标签,可以控制网页在移动设备上的显示方式,包括设置宽度、缩放、禁用缩放、设置初始缩放比例等。
  3. REM 单位: REM(root em)是相对于根元素(即元素)的字体大小的单位。通过将样式属性的值设置为相对于根元素的REM单位,可以根据设备的字体大小自动调整元素的尺寸。
  4. 百分比布局: 使用百分比布局可以根据父元素的宽度自动调整子元素的大小,实现相对于父元素的自适应布局。
  5. Flexbox 弹性布局: 弹性布局是一种灵活的布局方式,通过设置容器和子元素的属性,可以实现自适应和响应式的布局。
  6. CSS框架: 一些CSS框架(如Bootstrap、Foundation等)提供了移动端适配的解决方案和组件,可以简化开发过程。
  7. JavaScript库和框架: 一些JavaScript库和框架(如React、Vue、Angular等)提供了针对移动端的组件和响应式布局方案,可以更方便地实现移动端适配。
3.说一下vite

Vite是一个基于现代浏览器原生ES模块导入功能的构建工具和开发服务器

Vite通过按需构建模块和即时的热更新实现了快速的冷启动和开发过程中的实时反馈

可以直接在浏览器中加载和运行源代码,无需额外的构建步骤。

vite还提供了零配置的开发服务器和丰富的插件生态系统,原生支持Vue单文件组件3.说一下vite

4.Vite和webpack的区别
  1. 构建速度:vite基于现代浏览器原生支持的 ES6 模块特性,可以仅在需要的时候按需编译。

    与此相比,Webpack则是静态模块打包工具,基于入口文件,通过递归的解析模块之间的依赖关系,将项目中的资源视为模块,并打包成静态资源需要在每次修改后重新编译整个应用程序

    所以,在开发阶段,Vite 的启动速度和模块热更新速度通常都比 webpack 快;在生产阶段,Vite 和 webpack 的构建速度取决于项目的复杂度和配置。

  2. 配置方式:Vite的配置方式更为简单直接,采用基于插件的方式来扩展功能。

    同时,由于Vite的默认配置已经能够满足大部分应用的需求,因此用户通常只需要轻微调整即可。

    而Webpack则需要用户对各种不同的配置选项进行深入理解和配置。

  3. 开箱即用的功能:Vite内置了许多常用的功能,例如HMR(热模块替换)、CSS预处理器、TypeScript 等等。

    而Webpack则需要用户手动安装和配置各种插件来实现这些功能。

  4. 生态系统:Webpack作为构建工具的老牌玩家,其生态系统非常庞大并且稳定,拥有更为成熟的社区和生态圈。

    相比之下,Vite的生态系统较为年轻,但也在快速发展中。

5.TypeScript的数据类型

TypeScript 是 JavaScript 的超集,它在 JavaScript 的基础上增加了类型系统。TypeScript 支持所有 JavaScript 的数据类型,包括:

  • 布尔值 (boolean):表示真或假。
  • 数字 (number):表示整数或浮点数。
  • 字符串 (string):表示文本数据。
  • 数组 (Array):表示一组有序的数据。
  • 元组 (tuple):表示一个已知元素数量和类型的数组。
  • 枚举 (enum):表示一组命名的常数。
  • 任意值 (any):表示任意类型的值。
  • 空值 (void):表示没有任何类型,通常用于函数没有返回值时的返回类型。
  • Null 和 Undefined (nullundefined):表示值缺失或未定义。
  • Never (never):表示永远不会出现的值的类型,通常用于函数永远不会返回或抛出异常时的返回类型。
  • 对象 (object):表示非原始类型,即除 booleannumberstringsymbolnullundefined 之外的类型。

此外,TypeScript 还支持接口(interface)、类(class)、泛型(generic)等高级类型。

需要注意的是,void 类型只能用作函数的返回类型,不能用作变量或参数的类型。如果将一个变量或参数的类型设置为 void,那么它只能被赋值为 undefinednull

6. 使用ts封装了哪些类型

使用interface接口封装了接口返回的数据类型,

还可以定义函数参数类型,以及返回值的类型,帮助我们在编译阶段就找到错误,而不是在代码运行阶段报错

项目中有一些不可变的定死的数组,定义了元组

在 TypeScript 中,可以使用泛型来定义函数、类和接口等数据结构。泛型允许在定义时不指定具体的类型,而是在使用时根据需要指定具体的类型参数。

7.组件通信

props父传子,$emit子传父

  1. 通过 props 传递 在vue3setup语法糖中使用defineProps接受参数
  2. 通过 $emit 触发自定义事件,在vue3setup语法糖中使用defineEmits接受参数
  3. 使用 ref vue3setup语法糖中必须暴露出去父组件才会获取到 使用defineExpose({ childFun, msg })暴露出去参数和方法
  4. EventBus
  5. parent或root
  6. attrs 与 listeners
  7. Provide 与 Inject
  8. Vuex和pinia
8.v-model原理

v-model指令是一个语法糖,是属性绑定和事件的语法糖,v-model 会根据不同的表单元素使用不同的属性并抛出不同的事件:

9.自定义组件上使用v-model

组合式api中需要在v-model后绑定一个参数(v-model:value=“msg”)

<qf-input v-model:value="msg">
  • 1
//这是自定义组件内
在合适的地方导出方法
<input
      type="text"
      :value="value"
      @input="emit('update:value', ($event.target as any)?.value)"
    />
在自定义组件中使用 defineProps(["value"])接收
使用 const emit = defineEmits(["update:value"])导出事件
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
10.vue2和vue3响应原理

vue实例化时,对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。属性被读取或者修改时,vue就能捕获到,并进行相应操作

vue3中通过Proxy(代理):拦截对象中任意属性的变化

Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性进行修改;

vue2中

1.新增属性,或者删除属性,界面不会更新 使用$set

2.直接通过下标修改数组,界面不会自动更新

实现数据的双向绑定,首先要利用Object.defineProperty()对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。

11.vue虚拟dom和diff算法

Vue.js 的虚拟 DOM 是一种将真实的 DOM 树映射到内存中的 JavaScript 对象树。当状态发生变化时,Vue 会通过比较新的虚拟 DOM 树和旧的虚拟 DOM 树来计算出最小化的 DOM 操作,从而尽可能少地更新真实的 DOM。

这个比较过程就是 diff 算法,diff 算法的核心思想是遍历两个树的节点,并进行比较,以确定哪些节点需要被添加、删除或更新。Vue 采用了一种优化的 diff 算法,它假设同级的两个节点具有相似的结构,因此只需要对同级节点进行比较。

在 diff 算法中,每个节点都有一个唯一的 key 属性,用于识别该节点。当新旧节点列表中的顺序不同时,Vue 会使用 key 属性来尽可能地重用已存在的节点,而不是简单地删除并重新创建它们。这可以大大提高性能。

diff算法优化

在vue2中使用的是双端diff算法:是一种同时比较新旧两组节点的两个端点的算法(比头、比尾、头尾比、尾头比)。一般情况下,先找出变更后的头部,再对剩下的进行双端diff。

在vue3中使用的是快速diff算法:它借鉴了文本diff算法的预处理思路,先处理新旧两组节点中相同的前置节点和后置节点。当前置节点和后置节点全部处理完毕后,如果无法通过简单的挂载新节点或者卸载已经不存在的节点来更新,则需要根据节点间的索引关系,构造出一个最长递增子序列。最长递增子序列所指向的节点即为不需要移动的节点。

12.SPA单页面应用理解

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

13.vue组件传值,祖孙传值是不是响应式

Vue组件传值中,如果使用了props和**$emit方法进行传值,则是响应式**的。但是,如果使用了常规的JavaScript对象进行传值,则不是响应式的。

祖孙传值中,如果使用了props和emit方法进行传值,则是响应式的。但是,如果使用了emit方法进行传值,则是响应式的。但是,如果使用了refsparent/parent/children等直接访问祖孙组件的属性或方法,则不是响应式的。

provide和inject是不响应式的。

provide和inject是一种祖先组件向后代组件传递数据的方式,可以实现非父子组件之间的通信。但是,它们传递的数据不是响应式的,也就是说,当provide中的数据发生变化时,并不会自动触发后代组件的更新。

如果需要在provide和inject之间实现响应式的数据传递,可以使用Vue中提供的响应式API,如data、computed、watch等。

14.day.js哪里用到的
  1. 解析时间戳
  2. 格式化日期和时间。
  3. 解析和操作日期和时间。
  4. 时区处理。
  5. 计算时间差和时间间隔。
  6. 显示相对时间或倒计时。
15.vuex如何实现状态管理
  1. 在 state 中定义数据。
  2. 在 mutations 中定义同步函数来修改 state 中的数据。
  3. 在 actions 中定义异步函数来触发 mutations 中的同步函数。
  4. 在 getters 中定义计算属性来获取 state 中派生的数据。

在组件中使用 Vuex 时,通过this. s t o r e ∗ ∗ 来访问 ∗ s t a t e ∗ 中的数据,并通过 ∗ ∗ t h i s . store**来访问*state*中的数据,并通过**this. store来访问state中的数据,并通过this.store.commit() 触发 mutations 中的同步函数,或者通过 this.$store.dispatch() 触发 actions 中的异步函数。

vuex刷新页面数据丢失怎么办

  1. 使用持久化方案:将Vuex中的数据持久化存储到本地存储(如localStorage或sessionStorage)中。在页面加载时,从本地存储中读取数据,并在应用程序初始化时将数据重新写入Vuex。这样可以保证刷新页面后仍然能够获取到之前存储的数据。
  2. 在Vue.js中,有一个常用的Vuex插件用于实现数据持久化,它叫做vuex-persistedstate。这个插件可以将Vuex的状态持久化到本地存储(如localStorage)中,以便在刷新页面后能够保留数据。
16.vue两个路由模式的原理,history模式刷新的时候会不会重新请求后端接口

Hash路由是通过对URL中的hash值进行监听和解析,来实现不同页面之间的切换。当URL中的hash值发生变化时,可以触发相应的路由操作,从而加载不同的组件或页面内容。例如,当URL为http://example.com/#/home时,就可以根据#/home这一部分来加载首页的内容。

相比之下,History路由则是通过HTML5的History API来实现的。它可以在不刷新页面的情况下改变URL,同时还能够记录浏览器访问历史,并支持前进和后退操作。通过History API,开发者可以使用pushState()和replaceState()方法来更新浏览器的URL,并且在URL发生变化时触发路由操作。

总体上来说,Hash路由相对简单、兼容性好,但是URL看起来不太美观,而且可能会被搜索引擎忽略。而History路由则更符合用户的习惯,但是需要浏览器支持HTML5,同时还有可能会遇到一些坑点,需要注意处理。

Hash模式下,URL中带有“#”符号,例如:[http://example.com/#/about。每次路由切换时,URL都会更改,但不会向服务器发送请求http://example.com/#/about每次路由切换时,URL都会更改,但不会向服务器发送请求。

History模式下,URL看起来像普通的URL(例如,http://example.com/about),没有“#”符号。在这种模式下,每次路由切换时,浏览器将向服务器发送HTTP请求以获取新页面内容。

17.父子传值与生命周期的执行顺序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K4Vednsi-1688377006899)(C:\Users\tn\AppData\Roaming\Typora\typora-user-images\image-20230606134738626.png)]

在beforecreate中已经可以使用props了,所以父子传值是在生命周期钩子之前

18.pinia和vuex的区别

​ 1.API 设计

Pinia 的 API 设计更加简单和直观,它使用了类似于 Vuex 的 state、getters、actions 和 mutations 的概念,但是将它们拆分成了不同的 Store 模块。而且 Pinia 还提供了更好的 TypeScript 支持。

Vuex 的 API 设计较为复杂,需要使用对象字面量来定义各种方法,虽然 Vuex 4.0 开始也支持了 TypeScript 类型定义,但是与 Pinia 相比还是稍显笨重。

​ 2.数据响应式

Pinia 使用 Vue.js 3 新特性 reactive 来实现数据响应式;Vuex 则使用了 Vue.js 2.x 中的 Object.defineProperty 实现数据响应式。因此 Pinia 在数据响应式方面可能更加高效。

​ 3.异步操作

Pinia 提供了更加精细的异步操作处理方案,可以更好地支持异步流程,例如使用 await 等待异步操作完成。Vuex 也提供了相应的异步操作方案,但是相对来说略显繁琐。

vuex使用方法

export default {
  mounted() {
    // 访问状态
    const count = this.$store.state.count;
    // 访问派生状态
    const doubleCount = this.$store.getters.doubleCount;
    
    // 提交 mutation
    this.$store.commit('increment');
    
    // 分发 action
    this.$store.dispatch('fetchData');
  }
}
//简写
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState(['count']),
    ...mapGetters(['doubleCount'])
  },
  methods: {
    ...mapMutations(['increment']),
    ...mapActions(['fetchData'])
  }
}
  • 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

pinia使用方法

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { useStore } from './store';

export default {
  setup() {
    const store = useStore();

    return {
      count: store.count,
      doubleCount: store.doubleCount,
      increment: store.increment
    };
  }
};
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
19.VUE路由守卫

路由导航守卫都是在Vue实例生命周期钩子函数之前执行的。

  • router.beforeEach:全局前置守卫。

  • router.beforeResolve:全局解析守卫。

  • router.afterEach:全局后置钩子。

  • beforeEnter守卫

  • //使用方式:
    const router = new VueRouter({
        routes: [
            {
                path: '/foo',
                component: Foo,
                beforeEnter: (to, from, next) => {
                // ...
                }
            }
        ]
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • beforeRouteLeave:在失活的组件里调用离开守卫。

  • beforeRouteUpdate:在重用的组件里调用,比如包含<router-view />的组件。

  • beforeRouteEnter:在进入对应路由的组件创建前调用。

20.$nextTick,里面的name属性是如何使用

name 属性在 $nextTick() 方法中没有特殊的作用,它只是一个普通的属性,用于标识当前回调函数的名称。当多个回调函数添加到队列中时,可以通过这个属性来区分它们。

this.$nextTick(function () {   
console.log('DOM 更新完成')   
*// 获取更新后的 DOM 元素*      
const firstItem = this.$refs['item-0'][0]      console.log(firstItem.innerText) 
}, 'get-updated-element')
'get-updated-element'就是name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
21.Vue数据渲染是同步还是异步

在 Vue.js 中,数据渲染是异步的。这意味着当组件中的数据发生变化时,Vue.js 并不会立即更新 DOM。相反,Vue.js 会在下一个事件循环周期中更新 DOM。

这种异步更新的行为可以提高性能,因为它允许 Vue.js 在同一事件循环周期内,对多个数据变化进行批量处理,从而减少了 DOM 操作次数。但是,需要注意的是,在某些情况下,由于这种异步更新的行为,可能会导致我们看到“过期”或“错误”的 UI。为了解决这个问题,Vue.js 提供了一些 API 来手动触发同步更新。例如,可以使用 $nextTick 方法来等待异步更新完成后再做其它操作。

22.MVC和MVVM模式
  1. MVC模式:MVC是Model-View-Controller的缩写。这个模式将应用程序拆分成三个核心组件:
  • Model:表示应用程序的基础数据结构,定义了数据如何存储、访问和更新。
  • View:负责呈现模型数据,通常是用户界面的一部分。
  • Controller:作为中介,在模型和视图之间建立联系,并处理用户交互事件。

在MVC模式中,每个组件都有自己的职责,彼此独立。这种分离可以简化应用程序的开发和维护,使得更改其中一个组件不会影响到其他组件。

  1. MVVM模式:MVVM是Model-View-ViewModel的缩写。它是MVC模式的变体,通常用于构建现代单页应用程序(SPA)。
  • Model:定义应用程序的基础数据结构,负责处理数据和业务逻辑。
  • View:负责呈现模型数据并处理用户输入。
  • ViewModel:作为View和Model之间的中介,从Model中检索数据并将其按照View需要的格式转换为可供View使用的模型数据。当View中的数据发生更改时,ViewModel将其转换回Model需要的格式并将更改保存到数据存储中。

MVVM模式使得View和Model之间的耦合更加松散,并提供了一个可重用的ViewModel层。这种分离可以使应用程序更容易扩展、测试和维护。

23.vue怎么获取dom元素

Vue 2.x获取DOM

<div ref="myRef"></div>

this.$refs.myRef
  • 1
  • 2
  • 3

Vue 3.0获取单个DOM

<template>
  <div ref="myRef">获取单个DOM元素</div>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    const myRef = ref(null);

    onMounted(() => {
      console.dir(myRef.value);
    });
 
    return {
      myRef
    };
  }
};
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Vue 3.0获取多个DOM(一般用于获取数组)

<template>
  <div>获取多个DOM元素</div>
  <ul>
    <li v-for="(item, index) in arr" :key="index" :ref="setRef">
      {{ item }}
    </li>
  </ul>
</template>

<script>
import { ref, nextTick } from 'vue';

export default {
  setup() {
    const arr = ref([1, 2, 3]);

    // 存储dom数组
    const myRef = ref([]);

    const setRef = (el) => {
      myRef.value.push(el);
    };

    nextTick(() => {
      console.dir(myRef.value);
    });

    return {
      arr,
      setRef
    };
  }
};
</script>
  • 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
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
24.路由怎么传参

https://blog.csdn.net/weixin_47013351/article/details/124224089?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168671871116800227456345%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=168671871116800227456345&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-2-124224089-null-null.142

1.动态路由传参

使用“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,也可以使用props来接收

path:"/test/:id"
1.this.$router.push("/test/123")
2.this.$router.push({
    name:test,
    params:{
        id:"123"
    }
})
3. props:{
        id:{
            type:String,
            default:""
        }
 
    },

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

2 params 传参

params 传参(不显示参数)也可分为 声明式 和 编程式 两种方式,与方式一不同的是,这里是通过路由的别名 name 进行传值的

//路由
{
    path:"/test",
    name:"test",
    component:Test
}
 
//触发方式
this.$router.push({
    name:test,
    params:{
        id:"123"
    }
})
 
//组件接收
this.$route.params.id 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.query传参

query 传参(显示参数)也可分为 声明式 和 编程式 两种方式,但是query传递的参数不能是对象、数组等复杂类型,会出现乱码的情况,如果要传递复杂类型可以将其转化为json类型,接收后在转化回原类型

//路由
{
    path:"/test",
    name:"test",
    component:Test
}
 
//触发方式
//声明式
<router-link :to="{name:'test',query:{id:123}}">进入test</router-link>
//编程式
this.$router.push({
    name:test,
    query:{
        id:"123"
    }
})
 
//组件接收
this.$route.query.id     // 123
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

25.watch和watcheffect的区别

watch
1.watch显式指定依赖数据,依赖数据更新时执行回调函数
2.具有一定的惰性lazy 第一次页面展示的时候不会执行,只有数据变化的时候才会执行(设置immediate: true时可以变为非惰性,页面首次加载就会执行)
3.监视ref定义的响应式数据时可以获取到原值
4.既要指明监视的属性,也要指明监视的回调

watchEffect

1.watchEffect自动收集依赖数据,依赖数据更新时重新执行自身

2.立即执行,没有惰性,页面的首次加载就会执行

3.无法获取到原值,只能得到变化后的值

4.不用指明监视哪个属性,监视的回调中用到哪个属性就监视哪个属性

26.插槽的使用slot

二.项目方面

1.前端登录流程
  1. 用户输入账号和密码,点击登录按钮。
  2. 前端向后端发送登录请求,通常使用 AJAX 或者 Fetch API 发送请求,并带上账号和密码作为参数。
  3. 后端接收到请求后,检查用户账号和密码是否正确,并判断该用户是否有权限访问该系统。
  4. 如果账号和密码正确、有权限访问,则后端将生成一个 token,并将其返回给前端。同时,后端还会在服务器端保存一份 token 信息,以便后面验证用户身份。
  5. 前端接收到 token 后,通常会将其存储在 Cookie 或者 Local Storage 中,以便在用户下一次访问系统时使用。
  6. 用户成功登录后,前端会根据用户的角色和权限动态生成对应的页面和功能,并显示在界面上。
2.短信接口调用

前端封装:

  1. 在阿里云控制台开通短信服务并获取 AccessKeyId 和 AccessKeySecret。

  2. 安装阿里云 SDK 并在前端项目中引入。

  3. 在前端代码中构造 API 调用参数。具体参数以阿里云 API 文档中所列为准。

  4. 进行 API 调用,并处理返回结果。

  5. //调用参数 
    "PhoneNumbers": "手机号码",
      "SignName": "短信签名",
      "TemplateCode": "短信模板编号",
      "TemplateParam": "{ \"code\":\"123456\" }"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

后端封装:

后端定义了一个 /api/sendMsg 的接口,前端需要向该接口传递短信发送的相关参数 phoneNumber 和 templateCode,后端将这些参数构造成短信服务 API 调用所需的参数,然后进行 API 调用,最后将结果返回给前端。

const express = require('express')
const Core = require('@alicloud/pop-core')

const app = express()

// 配置 accessKeyId 和 accessKeySecret
const client = new Core({
  accessKeyId: '<accessKeyId>',
  accessKeySecret: '<accessKeySecret>',
  endpoint: 'https://dysmsapi.aliyuncs.com',
  apiVersion: '2017-05-25'
})

// 定义发送短信的接口
app.get('/api/sendMsg', (req, res) => {
  // 获取前端传递的参数
  const params = req.query

  // 构造 API 调用参数
  const smsParams = {
    "PhoneNumbers": params.phoneNumber,
    "SignName": "短信签名",
    "TemplateCode": params.templateCode,
    "TemplateParam": "{ \"code\":\"123456\" }"
  }

  const requestOption = {
    method: 'POST'
  }

  // 进行 API 调用,并将结果返回给前端
  client.request('SendSms', smsParams, requestOption).then((result) => {
    console.log(result)
    res.send(result)
  }, (err) => {
    console.error(err)
    res.status(500).send(err)
  })
})

// 启动服务器
app.listen(3000, () => {
  console.log('Server is running at http://localhost:3000')
})
  • 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
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
3.企业微信接口调用

前端设置最低存储阈值,通过定时轮询监控库存变化,当低于时,触发逻辑通过调用企业微信api发送应用消息给相关人员,先要获取access-token,(需要企业ID,企业发送消息的应用密钥),然后携带token,应用ID和报警信息等

1.先要获取临时access-token(需要企业ID,企业发送消息的应用密钥)

2.携带token,应用ID和报警信息,调用发送应用消息的接口.

4.权限控制怎么做的

1.首先,在用户登录的时候,通过发送后端API请求,从后端获取当前用户的角色的权限信息**[“inspec”, “familyPartyRecord”]**,并在前端保存到本地,一般采用localStorage或pinia中保存,在后续的权限管理中用到。

2.前端定义一张带权限的全部菜单路由表,通过和当前登录用户的角色权限对比过滤获取有权限的路由,我通常采用JSON格式构建这样的树形路由表,

3.使用组件递归的方式渲染菜单栏,根据获取到的有权限的路由菜单,使用addRoute()方法动态添加路由

在路由中设置roles来决定当前组件该角色能否访问

//路由表
{
      key: "/storeManage",
      label: "门店管理",
     roles: ["inspec", "familyPartyRecord"], //权限  
      children: [
        {
          key: "/storeManage/company",
          label: "餐饮单位列表",
          roles: ["inspec"],
          component: import("./../pages/company/company.vue"),
        },
        {
          key: "/storeManage/culturalAuditorium",
          label: "家宴中心",
          roles: ["familyPartyRecord"],
          component: import(
            "./../pages/culturalAuditorium/culturalAuditorium.vue"
          ),
        },
      ],
    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

组件递归的使用方式:把要递归的部分单独封装成一个组件,vue 的组件递归,其本质是组价自引用,把对应的子集传入当前要递归的组件,并且正常接收,注意:在使用的时候传入初始数据

3.你如何实现基于路由和组件的访问控制?

基于路由的访问控制:我通常采用路由导航守卫的方式,根据当前登录用户的权限信息进行鉴权,如果当前用户没有访问该路由的权限则跳转到无权限访问页面。

基于组件的访问控制:我通常会在组件中进行权限控制,如通过v-if或v-show,根据当前登录用户的权限信息动态隐藏或显示组件。

按钮级的权限

  1. 定义权限列表:首先,你需要定义一个权限列表,其中包含所有可访问的按钮级权限。每个权限可以使用唯一的标识符或名称来表示。

    const buttonPermissions = {
      create: 'inspec',
      edit: 'familyPartyRecord',
      delete: 'familyPartyRecord'
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
  2. 从后端获取当前用户的角色的权限信息**[“inspec”, “familyPartyRecord”]**

  3. 在组件中检查权限:在需要进行权限控制的组件中,你可以使用用户权限来检查是否具有特定按钮级权限,并根据权限来显示或隐藏按钮

    <template>
      <div>
        <button v-if="hasPermission(buttonPermissions.create)" @click="createItem">Create</button>
        <button v-if="hasPermission(buttonPermissions.edit)" @click="editItem">Edit</button>
        <button v-if="hasPermission(buttonPermissions.delete)" @click="deleteItem">Delete</button>
      </div>
    </template>
    
    <script>
    import { buttonPermissions } from '@/constants';
    
    export default {
      data() {
        return {
          buttonPermissions
        };
      },
      methods: {
        hasPermission(permission) {
          return this.userPermissions.includes(permission);
        },
      },
      computed: {
        userPermissions() {
          // 假设从 Vuex 或其他地方获取用户权限
          return this.$store.state.user.permissions;
        }
      }
    };
    </script>
    
    • 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
    • 28
    • 29
    • 30

    我们使用 v-if 指令根据用户权限来判断是否显示按钮。hasPermission 方法用于检查用户是否具有特定的按钮级权限。根据用户的权限列表和定义的权限列表,我们可以动态地显示或隐藏按钮。

5.表格导出怎么实现

利用xlsx插件,封装一个导出方法,在项目中使用的table组件起一个id名,获取到表格数据,XLSX.writeFile方法导出

6.深拷贝项目中怎么使用的

当你使用浅拷贝(shallow copy)时,只会复制对象的引用而不是对象本身,这意味着如果你修改了副本,原始对象也会随之改变。这可能会导致一些潜在的问题和错误。

以下是一些适合使用深拷贝的情况:

  1. 当你需要对一个对象及其所有嵌套属性进行修改,而不希望影响原始对象时,使用深拷贝。
  2. 当你需要在多个地方使用同样的对象,但是每个位置都需要独立的修改时,使用深拷贝。
  3. 当你需要将一个对象传递给某个函数或方法,并且你不想让该函数或方法对原始对象做任何更改时,使用深拷贝。
  4. 当你需要创建一个完全独立的对象,该对象与原始对象没有任何关系时,使用深拷贝。

总之,如果你需要创建一个不会影响原始对象的完全独立的副本,那么你应该使用深拷贝。

7.常用的AntDsign组件,elementUI组件有什么

layout布局组件,间距组件a-space,面包屑组件,分页组件,表单组件及里面的输入框等组件,表格table组件

全局提示组件,以及他的骨架屏组件Skeleton

8.你在状态管理工具里除了存token还存过什么

在项目中存储过当前角色的路由信息,通过这个路由信息去渲染左侧菜单栏选项

9.项目难点

1.el-table表格中碰到字段缺失排序会乱

原因:后端传回的数据缺失的字段没有相应的属性,table里的比较方法比较undefined,数字比较undefined是false

解决方法: 手动处理数据,给没有的属性设置为null

2.难点:在组件挂载需要判断地址能否匹配上路由。如果匹配不上,就跳转到首页

在 mounted 中,router 的初始化还没有完成,所以取到的是一个初始默认

解决办法是使用onReady()方法,现在用isReady()替代onReady()

this.$router.onReady(() => {
      // console.log(this.$route.path);
      this.activeIndex = this.$route.path;
    });
  • 1
  • 2
  • 3
  • 4
10.你在项目中有什么亮点的地方
11.echarts地图怎么使用
12.前端导致白屏的原因

1.JS问题
常用框架Vue React Angular都是依靠JS进行驱动, 并且单页面的应用html也是依靠JS生成,在渲染页面的时候需要加载很大的JS文件( app.js 和vendor.js ),在JS解析加载完成之前无法展示页面,从而导致了白屏(当网速不佳的时候也会产生一定程度的白屏)

2.浏览器兼容问题
vue代码在ie中显示白屏

3.URL 网址无效或者含有中文字符

4.缓存导致 参考
vue项目打包后,在非首次线上替换dist文件时,某些手机/浏览器在之后首次打开页面,可能出现白屏情况

原因:在用户端会默认缓存index.html入口文件,而由于vue打包生成的css/js都是哈希值,跟上次的文件名都不同,因此会出现找不到css/js的情况,导致白屏的产生。在服务端更新包之后,由于旧的文件被删除,而index.html所链接的路径依然是旧文件路径,因此会找不到文件,从而白屏。

5.页面报错

三.业务场景题

1.场景 - 在一堆数字中如何找出三个数字相加等于100

使用递归

function findThreeNumbers(arr) {
  arr.sort((a, b) => a - b);
  const result = [];
  const helper = (index, sum, combination) => {
    if (combination.length === 3 && sum === 100) {
      result.push(combination);
      return;
    }
    for (let i = index; i < arr.length; i++) {
      helper(i + 1, sum + arr[i], [...combination, arr[i]]);
    }
  };
  helper(0, 0, []);
  return result;
}

const numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90];
console.log(findThreeNumbers(numbers)); // [[10, 30, 60]]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
2.场景 - 用户同时请求了三个接口,但是此时他的token过期了,每次请求都提示一个弹框,一次请求了三个 就会造成屏幕闪烁,如何解决这个问题?
  1. 首先,在请求拦截器中判断token是否过期。
  2. 如果过期,则判断当前是否可以显示弹框。
  3. 如果可以,则弹出提示框,并将变量设置为false。
  4. 如果不可以,则不弹出提示框。
  5. 在定时器中,将变量设置为true。
let showMsg = true;

axios.interceptors.request.use(
  (config) => {
    if (tokenExpired()) {
      if (showMsg) {
        alert('Token已过期,请重新登录!');
        showMsg = false;
        setTimeout(() => {
          showMsg = true;
        }, 2000);
      }
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
3.场景 - 有一个树结构,用代码将树的叶子结点转换成一维数组,说说实现思路,除了用递归,那还有其他方法吗,用递归需要注意什么
//递归实现
function getLeafNodes(root) {
  if (root === null) {
    return [];
  }

  if (root.left === null && root.right === null) {
    return [root.value];
  }

  const left = getLeafNodes(root.left);
  const right = getLeafNodes(root.right);

  return left.concat(right);
}
//使用递归实现时需要注意以下事项:

/*1.在递归函数中需要判断当前节点是否为null,因此需要在递归调用前进行判断。
  2.在递归过程中需要传递当前节点,因此函数参数中需要包含当前节点。
  3.在对左右子树进行递归遍历时,需要注意先左后右的顺序,否则可能导致叶子节点顺序出错
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

另外,我们也可以使用栈来模拟递归过程。具体做法是:首先将根节点入栈,然后循环取出栈顶元素,判断该节点是否为叶子节点,如果是则将其值加入到数组中,否则将其右子节点和左子节点分别入栈。这种方法的复杂度与递归相同,但需要手动维护一个栈来模拟递归过程,相对较为繁琐。

使用深度优先搜索(DFS)或广度优先搜索(BFS)来实现。

使用 DFS 时,可以使用来存储遍历过程中的节点。从根节点开始,每次将当前节点的子节点压入栈中,然后取出栈顶元素并判断是否为叶子节点。如果是,则将其添加到结果数组中;否则,继续遍历其子节点。重复这个过程,直到栈为空。

使用 BFS 时,可以使用队列来存储遍历过程中的节点。从根节点开始,每次将当前节点的子节点加入队列中,然后取出队首元素并判断是否为叶子节点。如果是,则将其添加到结果数组中;否则,继续遍历其子节点。重复这个过程,直到队列为空。

4.场景 - 两个div,分为a,b,使用定位使a在上,b在下的重叠,他们不是父子结点和兄弟节点,他们都定义的点击事件,怎么跳过在上面的a的事件,去输出b的事件

1.可以使用z-index,

2.层级不让改变的话,可以给上面的div添加一个css属性(主要回答这个)

pointer-events: none;
 */\* 元素永远不会成为鼠标事件的target。*

​    *但是,当其后代元素的属性指定其他值时,鼠标事件可以指向后代元素,*

​    *在这种情况下,鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器。*

​    *pointer-events \*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5.场景 - 两个数组找交集

1.使用filter和include
function intersection(arr1, arr2) {
  return arr1.filter((x) => arr2.includes(x));
}

// 示例输入和输出
console.log(intersection([1, 2, 3], [2, 3, 4])); // 输出 [2, 3]
2.使用双循环判断
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
5.文件上传怎么做

1.在 Vue 中实现文件上传可以使用 <input type="file"> 元素和相关事件来处理。以下是一个基本的文件上传示例:

<template>
  <div>
    <input type="file" @change="handleFileChange">
    <button @click="uploadFile">Upload</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      file: null  // 存储选中的文件
    };
  },
  methods: {
    handleFileChange(event) {
      this.file = event.target.files[0];  // 获取选择的文件
    },
    uploadFile() {
      if (this.file) {
        const formData = new FormData();  // 创建 FormData 对象
        formData.append('file', this.file);  // 将文件添加到 FormData 中,'file' 是后端接收文件的字段名

        // 发送文件上传请求
        // 这里可以使用 Axios 或其他 HTTP 请求库来发送请求
        // 示例中使用 fetch 方法发送请求
        fetch('/upload', {
          method: 'POST',
          body: formData
        })
          .then(response => {
            // 处理上传成功的响应
          })
          .catch(error => {
            // 处理上传失败的错误
          });
      }
    }
  }
};
</script>

//使用axios发送请求
 // 创建 FormData 对象
  const formData = new FormData();
  formData.append('file', file); // 将文件添加到 FormData 中,'file' 是后端接收文件的字段名

  // 发送文件上传请求
  axios.post('/upload', formData, {
    headers: {
      'Content-Type': 'multipart/form-data' // 设置请求头的 Content-Type
    }
  })
    .then(response => {
      // 处理上传成功的响应
    })
    .catch(error => {
      // 处理上传失败的错误
    });
});
在上述示例中,我们通过监听 <input type="file"> 元素的 change 事件来获取用户选择的文件,并将其存储在组件的 file 数据中。然后,当点击上传按钮时,我们创建一个 FormData 对象,并将选中的文件添加到 FormData 中。最后,使用 HTTP 请求库发送 POST 请求,将 FormData 作为请求体发送到后端的上传接口。

请注意,这只是一个基本的文件上传示例,实际的实现可能会根据具体的需求和后端接口的要求进行调整。
  • 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
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

四.GIT方面

git命令

  1. 配置相关:
    • git config:设置或查看 Git 的配置信息。
  2. 仓库创建与克隆:
    • git init:在当前目录初始化一个新的 Git 仓库。
    • git clone:克隆(下载)一个远程仓库到本地。
  3. 基本操作:
    • git add:将文件添加到暂存区。
    • git status:查看当前仓库的状态。
    • git commit:将暂存区的改动提交到本地仓库。
    • git diff:查看工作区与暂存区或本地仓库的差异。
    • git restore:撤销工作区的修改。
    • git rm:从工作区和版本控制中删除文件。
  4. 分支操作:
    • git branch:管理分支(列出、创建、删除)。
    • git checkout:切换分支或还原文件。
    • git merge:将分支的改动合并到当前分支。
    • git rebase:将当前分支的改动在其他分支上进行变基。
  5. 版本控制历史:
    • git log:查看提交历史记录。
    • git blame:逐行追踪文件的改动。
    • git tag:管理标签(创建、删除、列出)。
  6. 远程操作:
    • git remote:管理远程仓库。
    • git fetch:从远程仓库拉取改动。
    • git pull:从远程仓库拉取改动并合并到当前分支。
    • git push:将本地分支的改动推送到远程仓库。
  7. 协作与合并:
    • git clone:克隆远程仓库到本地。
    • git fetch:从远程仓库拉取改动。
    • git pull:从远程仓库拉取改动并合并到当前分支。
    • git push:将本地分支的改动推送到远程仓库。
  8. 撤销与回退:
    • git revert:撤销指定的提交。
    • git reset:将 HEAD 指针重置到指定的提交。
    • git cherry-pick:选择性地应用其他分支的提交。
  9. 其他工具与辅助命令:
    • git stash:暂存当前工作区的修改。
    • git clean:清理工作区中未追踪的文件。
    • git bisect:二分查找引入错误的提交。
1.你在a分支改东西,突然又一个紧急bug,需要到b分支修改,a分支改了一半还有报错,这时怎么进入b分支解决bug,a分支代码不提交的话不能切换分支,怎么解决

*存储当前分支下的所有改动git stash,恢复暂存的修改:在命令行中运行 git stash apply,这将恢复之前暂存的修改到 A 分支上。

1.暂存 A 分支上的修改:在命令行中运行 git stash,这将把当前的工作区修改保存起来,并将工作区还原到上一次提交的状态。

2.切换到 B 分支:在命令行中运行 git checkout branchB,切换到 B 分支。

3.修复紧急 bug:在 B 分支上进行紧急 bug 的修复。

4.提交 bug 修复:在 B 分支上进行 bug 修复的提交。

5.切换回 A 分支:在命令行中运行 git checkout branchA,切换回 A 分支。

6.恢复暂存的修改:在命令行中运行 git stash apply,这将恢复之前暂存的修改到 A 分支上。

7.解决冲突(如果有):如果在应用暂存修改时发生冲突,需要解决冲突。Git 会提示冲突的文件,并在文件中显示冲突的内容。手动解决冲突后,运行 git add <file> 将文件标记为已解决。

8.继续 A 分支的修改:继续在 A 分支上完成剩余的修改。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
2.a分支改了东西,b分支上需要a分支的东西,怎么快速同步过去

(git:A中commit提交了四次,B中commit提交了四次,将A中的commit第二次合并到B中,如何实现?)

cherry-pick
这个命令的作用就是把指定的commit,拉到一个新的分支上。

1.确定 A 分支和 B 分支的名称。假设 A 分支为 branchA,B 分支为 branchB。

2.切换到 B 分支:在命令行中运行 git checkout branchB。

3.查找第二次 commit 的 commit ID:运行 git log 或 git log --oneline,找到 A 分支中第二次 commit 的 commit ID。

4.运行 cherry-pick 命令:在命令行中运行 git cherry-pick <commit-id>,将 <commit-id> 替换为第二次 commit 的 commit ID。例如:git cherry-pick abc123。

5.Git 会自动将 A 分支中的第二次 commit 应用到 B 分支中。

6.解决冲突(如果有):如果在 cherry-pick 过程中发生冲突,需要解决冲突。Git 会提示冲突的文件,并在文件中显示冲突的内容。手动解决冲突后,运行 git add <file> 将文件标记为已解决,然后运行 git cherry-pick --continue 继续 cherry-pick 过程。

7.重复步骤 3-6:如果还需要将 A 分支中的其他 commit 合并到 B 分支,可以重复上述步骤,将其他 commit 逐个应用到 B 分支中。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
3.代码冲突怎么办
1.理解冲突:首先要理解冲突的原因,了解哪些部分的代码发生了冲突。冲突通常发生在同时对同一文件的相同位置进行不同修改时。

2.使用版本控制工具:使用版本控制工具(如 Git)提供的命令或图形界面工具,查看冲突的文件和代码。
git status:此命令会显示当前项目的状态,包括冲突文件的列表。冲突的文件会以 "both modified" 或 "both deleted" 的状态显示。

git diff:此命令可以显示冲突文件的具体差异。它会将冲突部分用特殊标记(如 "<<<<<<<", "=======", ">>>>>>>") 包围起来,以示区分。

3.解决冲突:手动编辑冲突的文件,解决冲突部分的代码。在冲突标记(通常是 "<<<<<<<", "=======", ">>>>>>>") 之间,根据需要选择保留、修改或删除代码。确保最终的代码能够正确合并冲突的修改。

4.添加解决冲突的代码:完成解决冲突后,将修改后的代码添加到暂存区(或提交到版本控制系统)。

5.测试:在解决冲突后,进行测试以确保代码仍然正常运行,并且没有引入新的问题。

6.注意:在解决冲突之前,建议先备份冲突的文件或工作目录,以防意外情况发生。

7.在多人协作开发时,及时与团队成员沟通、协调并遵守良好的版本控制流程,可以帮助减少代码冲突的发生。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

五.接口方面

1.http请求有哪些
  1. GET:用于从服务器获取资源。GET 请求会将请求参数附加在 URL 的查询字符串中,并通过 URL 发送给服务器。它是幂等的,多次发送相同的 GET 请求会返回相同的结果。
  2. POST:用于向服务器提交数据,并创建新的资源。POST 请求将请求参数包含在请求主体中发送给服务器,而不是通过 URL 传递。它不是幂等的,多次发送相同的 POST 请求会创建多个资源。
  3. PUT:用于向服务器更新资源。PUT 请求将请求参数包含在请求主体中发送给服务器,并指示服务器使用请求中的数据替换指定的资源。
  4. DELETE:用于删除服务器上的资源。DELETE 请求指示服务器删除指定的资源。
  5. PATCH:用于向服务器部分更新资源。PATCH 请求将请求参数包含在请求主体中发送给服务器,并指示服务器对指定的资源进行局部更新。
  6. OPTIONS:用于获取服务器支持的请求方法列表,即查询服务器对指定资源支持的 HTTP 请求方式。

除了上述常见的 HTTP 请求方式,还有一些其他的请求方式,如 HEAD、TRACE、CONNECT 等,它们在特定的场景下有着特殊的用途。

在前端开发中,可以使用 JavaScript 中的 XMLHttpRequest 对象或现代的 fetch API 来发送各种类型的 HTTP 请求。同时,许多前端框架和库也提供了封装的函数或方法来方便地进行 HTTP 请求操作。

2.前端传json和传formdata有什么区别

前端传输数据时,可以选择使用 JSON 或 FormData 两种格式,它们在数据结构和用途上有一些区别:

JSON

  • 格式:JSON 是一种轻量级的数据交换格式,以键值对的形式表示数据,使用 JavaScript 对象表示数据结构。
  • 用途:JSON 适用于结构化的数据传输和存储,常用于前后端之间的数据交互,如 API 请求和响应的数据格式。
  • 优点:易于读写、解析和处理,支持复杂的数据结构和嵌套对象,适合处理结构化的数据。
  • 限制:不适合直接上传二进制文件,需要使用其他方法(如 Base64 编码)将二进制数据转换为字符串。

FormData

  • 格式:FormData 是一种用于构建表单数据的格式,可以将数据序列化为键值对的形式。
  • 用途:FormData 主要用于发送表单数据和上传文件。它可以方便地构建包含文本字段、文件字段等的表单数据,并以 multipart/form-data 格式发送到服务器。
  • 优点:支持上传文件和二进制数据,能够正确处理文件上传的请求,适合用于包含文件字段的表单提交。
  • 限制:相对于 JSON,FormData 的数据结构相对简单,不支持嵌套对象和复杂的数据结构。

选择使用 JSON 还是 FormData 取决于具体的需求和场景:

  • 如果需要发送结构化的数据、进行 API 请求和响应,通常使用 JSON 格式更合适。
  • 如果需要上传文件或使用表单提交数据,特别是包含文件字段的情况,使用 FormData 格式更适合。

需要注意的是,在实际开发中,后端服务器通常会对接收的请求数据格式进行处理和解析,需要根据后端的要求和接口定义来选择使用合适的数据格式。

3.PUT和POST的区别
  1. 语义区别:
    • POST(创建):用于在服务器上创建新的资源。每次使用POST请求,服务器会在请求的URI下创建一个新的资源,并返回该资源的标识符。
    • PUT(更新):用于向服务器更新已存在的资源。通过PUT请求,客户端发送的数据会替换服务器上指定URI下的资源。
  2. 数据处理:
    • POST:通常用于向服务器提交表单数据、上传文件等。数据被包含在请求的主体部分,并在服务器端进行处理。服务器可以对数据进行解析、验证、处理后返回结果。
    • PUT:通常用于完整替换服务器上的资源。客户端发送的数据应该是完整的、包含所有要更新的字段和值的数据。服务器会使用请求的URI指定的资源标识符,将数据直接替换服务器上的资源。
  3. 幂等性:
    • POST:POST请求不是幂等的,即多次发送相同的POST请求可能会在服务器上创建多个相同的资源。每次请求都会引起一些副作用。
    • PUT:PUT请求是幂等的,多次发送相同的PUT请求不会导致资源的多次更新。每次请求都会覆盖原始资源的内容,不会引起副作用。

综上所述,POST用于创建资源,通常用于表单提交、文件上传等场景,而PUT用于更新资源,需要客户端提供完整的资源数据进行替换。在实际应用中,你可以根据具体的需求和语义选择使用POST还是PUT方法来发送请求。

4.双token

1、登录时,拿到accessToken和refreshToken,并存起来
2、请求接口时,带着accessToken去请求
3、如果accessToken过期失效了,后端会返回401
4、401时,前端会使用refreshToken去请求后端再给一个有效的accessToken
5、重新拿到有效的accessToken后,将刚刚的请求重新发起
6、重复1/2/3/4/5

六.微信小程序,uniapp

1.登录流程
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/830852
推荐阅读
相关标签
  

闽ICP备14008679号