赞
踩
面试官您好,我叫xx,我本科毕业于xxxxxxxxxx专业,上一份工作是在成都做前端开发工程师,擅长使用vue框架进行开发项目,vue2,vue3都在项目中使用过
我本人的性格比较的稳重,爱好晨跑目前是离职状态
上一个项目是一个家政类型的管理项目,这个项目使用vue3+ts,配合AntDsign组件库,
和其他插件进行开发,这个项目以下几个模块构成
客户管理帮助我们管理客户信息,在客户信息管理中我们还能对客户进行短信的消息推广,这里我们调用了一个阿里云短信服务的api.
职工管理是用来记录职工信息以及客户对员工的评价、
订单管理负责管理预约订单、一次性保洁订单、包月家务、
财务管理用于统计收入和支出,并记录工人工资发放等
以及其他的系统设置,各模块数据的展示等
想来一线城市追求一个更好的发展,我相信自己的技能和潜力,所以我决定主动寻找一个更具挑战性的环境,能够让我不断成长和发展。
优点是学习能力强,对于新的事物有较强的接受能力,在面对前端中新出现的技术或者新特性时能够快速了解学习,并且上手,工作方面做事情有条理性,计划性,能够明确每天的任务,按时完成项目任务
面对很多人进行演说的时候会紧张,但我已经意识到了这个问题,并且在逐步改善自己,如果有幸进入贵公司,我也尝试主动参与公司内部的演讲机会,以提升我的经验和自信心。
基本工资(7000)+项目奖金+通勤餐补
半年一次绩效考核
50人左右
上家公司技术团队的规模在20人左右
我们项目组有7个人,1前端,3后端,组长负责统筹,测试一个,ui一个
xxxxxxxxxxxxxxxx
住在馨美佳苑
目前我手上有一个offer,但是我更倾向加入咱们公司,因为不管是发展前景还是岗位方向技术的匹配度方面,咱们公司都是我最理想的选择。
前端---->java---->全栈----->项目负责人
1.可以介绍一下当前正在进行的项目吗?
2.办公地点在哪
3.面试流程的下一步是什么?大概需要多长时间?
4.技术团队的规模
1.语义化标签如
2.表单控件和属性增多如日期选择器,颜色选择器,placeholder、
required、
autofocus等
3.多媒体支持和音视频播放,MediaSource、
Stream API
4.canvas2D绘图,实现复杂图形动画效果
5.提供storage本地存储
6.提供webSocket通信,实现浏览器与服务器之间的实时双向通信
在需要可拖动的元素上设置draggable
属性为true
作用:后台线程中执行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('消息来自主线程');
4.worker.js中可以通过self.onmessage
事件监听来自主线程的消息,并通过self.postMessage()
方法将结果发送回主线程。
`self.onmessage = function(event) {`
`var message = event.data;`
`// 处理接收到的消息`
`// 发送结果回主线程`
`self.postMessage('Hello from Web Worker!');`
`};`
5.终止Web Worker:worker.terminate();
Web Socket是HTML5中提供的一种用于实现双向通信的协议和API。它允许在客户端和服务器之间建立持久的、全双工的通信连接,可以通过这个连接实时地发送和接收数据。
1.连接方式不同
Web Socket建立的是一种持久连接,通过握手过程建立连接后,可以在连接保持打开的状态下进行实时的双向通信
而HTTP是一种无状态的协议,每次请求-响应后就会关闭连接,需要重新建立连接来发送下一个请求
2.通信效率
由于Web Socket建立的是长连接,可以避免每次通信都需要重新建立连接的开销,减少了通信的延迟和数据传输的额外负担。
HTTP在每次请求-响应时都需要进行握手、建立连接和断开连接等操作,相对较慢。
3.服务器推送
Web Socket允许服务器主动向客户端推送数据,可以实时更新数据并及时通知客户端。
而HTTP是基于请求-响应模式的,客户端需要不断发送请求来获取最新数据。
总结:
Web Socket在实现实时双向通信方面更加高效和实用,特别适合需要实时性的应用场景,如实时数据更新、多人协作和即时聊天等。
而HTTP更适用于传统的请求-响应模式,用于获取静态或动态内容的场景。
使用方法
说一下websocket,断网了怎么处理websocket;
心跳检测,去检查这个链接是否成功
SEO(搜索引擎优化)是通过优化网站内容和结构,提高网站在搜索引擎中的排名和曝光度的过程。
HTML5中引入的语义化标签,旨在提高网站的SEO效果,还需要考虑其他因素,如关键字优化、网站速度优化、友好的URL结构等。
1.使用CSS媒体查询(Media Queries)
2.弹性网格布局(Flexible Grid Layout)
3.CSS网格布局(CSS Grid Layout
4.视口(Viewport)设置
HTTP是不加密的传输协议,
HTTPS是基于HTTP协议的安全版本,通过使用SSL(Secure Sockets Layer)或TLS(Transport Layer Security)协议对传输的数据进行加密和身份验证。
CSS中用来描述元素布局和渲染的概念。它将每个HTML元素视为一个矩形的盒子,这个盒子由四个部分组成:内容区域(content)、内边距(padding)、边框(border)和外边距(margin)
标准盒模型(content-box):
总宽度 = width + padding+ border+ margin
总高度 = height + padding+ border+ margin
IE盒模型(border-box):内间距和边框被包括在宽度中
总宽度 = width + margin
总高度 = height + margin
BFC(块级格式化上下文)是 CSS 中的一个概念,它是页面中一个独立的渲染区域,具有自己的布局规则。BFC 可以用来解决一些布局问题,例如清除浮动、防止 margin 重叠等。
BFC 的特性包括:
高度塌陷的原因是浮动元素或绝对定位元素脱离了文档流,导致父元素无法正确计算其高度。结果是父元素的高度变为零,从而影响了页面布局。
1.
.clearfix::after {
content: "";
display: table;
clear: both;
}
2.使用 overflow: auto 或 overflow: hidden 清除浮动:
DNS解析:浏览器会将URL中的域名发送给DNS服务器进行解析,以获取与该域名相对应的IP地址。DNS服务器返回IP地址信息给浏览器。
建立TCP连接:浏览器使用HTTP协议通过socket API建立与Web服务器的TCP连接。
发送HTTP请求:浏览器向Web服务器发送HTTP请求,其中包括请求方法(GET, POST, PUT等)、请求头部(例如Accept-Language、Cookies等)和请求主体(仅适用于某些请求方法,如POST)。
接收HTTP响应:Web服务器接收到请求,并向浏览器返回HTTP响应,其中包括状态码(比如200 OK, 404 Not Found)、响应头部(例如Content-Type、Set-Cookie等)和响应主体(通常为HTML、CSS、JavaScript等)。
处理响应内容:浏览器根据响应头部中的Content-Type字段确定响应主体的类型,并将其传递给渲染引擎进行渲染。如果响应主体是HTML文档,则渲染引擎会根据HTML标记和CSS样式表将其转换为可视化的页面。同时,浏览器会执行页面中嵌入的JavaScript代码,以实现一些动态交互效果。
显示页面:渲染引擎完成页面渲染后,将其呈现到浏览器窗口中显示给用户。
1.输入URL
2.访问hosts解析,如果没有解析访问DNS解析
3.TCP握手
4.HTTP请求
5.HTTP响应返回数据
6.浏览器解析并渲染页面
浏览器缓存是一种机制,允许浏览器在第一次请求资源时将其保存在本地,以便在未来的请求中可以直接使用本地的缓存副本,而不必重新从服务器下载。这样可以大大减少请求的数量和响应时间,提高用户体验和网站性能。
(1).Cache-Control:这个头用来控制缓存的行为,可以设置值为max-age,表示缓存的最大时间。
(2).Expires:这个头用来设置过期时间,它指定了资源的到期时间,之后就需要重新请求服务器。
(3).Last-Modified / If-Modified-Since:这两个头用于检查资源是否已经被修改。
(4).ETag / If-None-Match:这两个头也用于检查资源是否已经被修改。
通过使用这些HTTP头,服务器可以控制浏览器缓存的行为,从而提高网站的性能和用户体验。同时,开发者也可以根据需要调整这些头的设置,以便更好地控制缓存。
CSS盒模型是指在网页布局中,每个元素都被看作一个盒子,它包含了内容区域、内边距、边框和外边距。CSS盒模型的属性包括width、height、padding、border和margin等。
伪类用于选择元素的特定状态或行为,如:hover、:active、:nth-child等。
伪元素则用于创建并操作元素的特定部分,如::before、::after等。
主要区别在于伪类选择的是元素的某个状态,而伪元素选择的是元素的某个部分。
CSS中的层叠样式表(CSS cascade)是一种规则,用于解决多个CSS规则之间的冲突。层叠样式表通过选择器的特定性和样式的来源来确定哪个样式将被应用,具有更高特定性和来源优先级的样式将覆盖其他样式。
!important>内联样式>id>
class=属性选择器=伪类选择器>
标签=伪元素>
兄弟=子=后代=*
1.过渡(Transitions):通过过渡效果可以实现元素在状态变化时的平滑过渡。你可以指定属性的变化时间、变化类型(线性、缓动等)和延迟等。例如:
.box {
width: 100px;
height: 100px;
background-color: red;
transition: width 1s ease-in-out;
}
.box:hover {
width: 200px;
}
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;
}
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 像素 */
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 倍缩放元素 */
3.旋转(Rotation):可以通过旋转变换使元素绕其原点旋转。使用 rotate()
函数来指定旋转角度。
transform: rotate(45deg); /* 顺时针旋转元素 45 度 */
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 度 */
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% 时,它将占据尽可能少的空间。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; /* 水平居中 */
}
闭包是指在一个函数内部定义的函数,该内部函数可以访问其外部函数的变量、参数和其他内部函数,即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的作用域。
它可以用于创建私有变量、模块化开发、实现函数柯里化等场景
不会造成变量污染,但可能造成内存泄漏
场景:保护变量,模块化开发,vue3中的setup函数
原型:原型就是一个为对象实例定义了一些公共属性和公共方法的对象模板。
原型链:每个JavaScript对象(除了 null 和 undefined)都有一个内部属性 [[Prototype]]
,它指向该对象的原型。当我们访问对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript会沿着原型链向上查找(这个原型对象也有他的原型),直到找到该属性或方法或者到达原型链的顶端(Object.prototype = null)
1.let const
2.箭头函数()=>{},箭头函数没有this指向
3.模板字符串``
4.解构赋值[]{}
5.class关键字,类
6.import,export,模块化语法
7.map,set(去重),symbol
8.promise,异步编程解决方式,解决了回调地狱问题
9.可选链操作符?
10.async/await
11.展开运算符…
defer,同时下载,等页面加载完后加载js
async,同时下载,如果有js下载好,暂停加载页面,执行js
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]`
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]`
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]`
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);`
// 创建 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();
// 可以在此处继续执行其他操作,而无需等待响应
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 对象的实例)
这是因为 str 是一个字符串字面量(基本数据类型),而不是一个 String 对象。在 JavaScript 中,字符串字面量被自动包装成相应的 String 对象,以便在需要时进行字符串操作。尽管 str 看起来像一个字符串对象,但它实际上是一个基本的原始值。
当使用 instanceof 运算符检查一个对象的类型时,它会检查对象是否是指定类型的实例。由于 str 不是 String 对象的实例,而是一个原始字符串值,因此 str instanceof String 返回 false。 要注意的是,虽然 str 不是 String 对象的实例,但它仍然可以执行字符串的操作和方法,因为 JavaScript 在需要时会自动将其转换为 String 对象。
使用**Object.prototype.toString.call(obj)**进行类型检查
注意,JavaScript 中的变量是无类型的,即变量可以在任何时刻持有任何类型的值。同一个变量可以在不同的上下文中存储不同类型的值。这种动态特性是 JavaScript 的一大特点。
不改变原数组的方法(Non-Mutating Array Methods):
改变原数组的方法(Mutating Array Methods):
方法 | 参数 | 返回值 |
---|---|---|
concat | item1, item2, …, itemN | 新的合并后的数组 |
copyWithin | target, start, end | 修改后的原数组 |
entries | 无 | 包含数组键值对的迭代器对象 |
every | callback(element, index, array) | 布尔值 |
fill | value, start, end | 修改后的原数组 |
filter | callback(element, index, array) | 符合条件的新数组 |
find | callback(element, index, array) | 满足条件的第一个元素 |
findIndex | callback(element, index, array) | 满足条件的第一个元素的索引 |
flat | depth | 展开后的新数组 |
forEach | callback(element, index, array) | 无 |
includes | searchElement, fromIndex | 布尔值 |
indexOf | searchElement, fromIndex | 指定元素的索引 |
join | separator(分隔符,默认为逗号) | 连接后的字符串 |
keys | 无 | 包含数组索引的迭代器对象 |
lastIndexOf | searchElement, fromIndex | 指定元素在数组中最后一次出现的索引 |
map | callback(element, index, array) | 映射后的新数组 |
pop | 无 | 被移除的元素 |
push | element1, element2, … | 新数组的长度 |
reduce | callback(accumulator, currentValue, index, array), initialValue | 累积的结果 |
reduceRight | callback(accumulator, currentValue, index, array), initialValue | 累积的结果 |
reverse | 无 | 反转后的原数组 |
shift | 无 | 被移除的元素 |
slice | start, end | 包含指定元素的新数组 |
some | callback(element, index, array) | 布尔值 |
sort | compareFunction(排序比较函数,可选) | 排序后的原数组 |
splice | start, deleteCount(要删除的元素个数), item1, item2, … | 包含被删除元素的数组 |
toLocaleString | 无 | 数组的字符串表示 |
toString | 无 | 数组的字符串表示 |
unshift | element1, element2, … | 新数组的长度 |
values | 无 | 包含数组值的迭代器对象 |
需要注意的是,改变原数组的方法会直接修改原始数组,而不是创建一个新的数组。如果不想改变原数组,应该使用不改变原数组的方法。
this
关键字用于指代当前执行上下文中的对象
this
:在全局作用域中,this
指代全局对象,在浏览器环境中通常是 window
对象,在 Node.js 环境中是 global
对象。需要注意的是,函数中的 this
是在运行时确定的,而不是在函数定义时确定的。这意味着同一个函数在不同的调用环境下,其 this
的值可能不同。(谁调用,指向谁)
它通过封装异步任务并提供链式调用的方式来解决回调地狱的问题,并提供了更优雅和可读性强的代码编写方式。
promise本身既不是异步也不是同步的,只有.then回调的时候是异步的,promise是微任务,事件轮询机制,,它通过不断从任务队列中取出任务并执行来保证任务的顺序性和异步操作的完成。在 Promise 中,异步操作的回调函数会被放入任务队列中,并在适当的时机被事件循环取出并执行。
Promsie.all([Promsie1,Promsie2,Promsie3])
当所有给定的 promise 都 resolve 时,新的 promise 才会 resolve,并且其结果数组将成为新 promise 的结果。
如果任意一个 promise 被 reject,由 `Promise.all` 返回的 promise 就会立即 reject,并且带有的就是这个 error。
Promise.allSettled([Promsie1,Promsie2,Promsie3])
Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何,都会被返回。结果数组具有:
{status:"fulfilled", value:result} 对于成功的响应,
{status:"rejected", reason:error} 对于 error。
Promise.race([Promsie1,Promsie2,Promsie3])
只等待第一个 settled 的 promise 并获取其结果(或 error),不论成功还是失败
Promise.any([Promsie1,Promsie2,Promsie3])
只等待第一个 fulfilled(成功) 的 promise,如果给出的 promise 都 rejected(失败)
那么返回的 promise 会带有 AggregateError —— 一个特殊的 error 对象,在其 errors 属性中存储着所有 promise error。
执行同步任务:首先执行当前执行栈中的同步任务,直到执行栈为空。
执行一个宏任务,然后执行微任务
执行微任务:检查微任务队列,依次执行队列中的所有微任务。微任务包括 Promise
的回调函数、MutationObserver
的回调函数等。
更新渲染:如果浏览器环境下,会执行渲染操作,更新页面的视图。
执行宏任务:从宏任务队列中选择一个任务执行。宏任务包括 setTimeout
、setInterval
、I/O
操作等。
执行完同步任务后,执行一个宏任务,然后执行一轮微任务,微任务都执行完后,执行一个宏任务,在查看有无微任务,继续执行,直到完毕
在浏览器环境中,script
标签的加载和执行被认为是宏任务。当浏览器解析到 script
标签时,它会将该标签的内容作为脚本代码加载并执行。
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; });
这意味着在map()
方法中,回调函数必须返回一个值,否则新的数组中对应的位置将是undefined
。
map不加{}时可以不使用return
1.压缩和合并文件,压缩和合并CSS、JavaScript和图像等文件,减少网络传输的数据量
2.图片优化:使用适当的图片格式、压缩图片大小、懒加载等技术来减少图片的加载时间和带宽消耗。使用精灵图
3.减少HTTP请求
4.使用CDN:将静态资源部署到内容分发网络(CDN)上,使用户能够从离其最近的服务器获取资源,减少网络延迟,提高访问速度。
应用场景:
应用场景:
页面滚动事件:滚动过程中触发某些操作,节流可以限制操作的频率,减少触发次数,避免过多的计算和渲染操作。
鼠标移动事件:鼠标连续移动时触发某些操作,节流可以限制操作的频率,减少触发次数,提高性能。
区别: 防抖和节流的主要区别在于执行操作的时机。
防抖是等待一段时间后执行最后一次触发的操作
而节流是按照固定的时间间隔执行操作
//浅拷贝:
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 }]
function ClassA(){
this.name = "123"
}
var p = new ClassA();
1、 创建空对象;
var obj = {};
2、 设置新对象obj的constructor属性为构造函数的名称,设置新对象obj的__proto__属性指向构造函数的prototype对象;
obj.proto = ClassA.prototype;
3、 使用新对象调用函数,函数中的this被指向新实例对象:
ClassA.call(obj); //通过 call(),obj能够使用属于另一个对象的方法。
4、 将初始化完毕的obj返回,赋给变量p
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
}
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
}
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
}
}
总结:for in适合遍历对象,for of适合遍历数组。for in遍历的是数组的索引,对象的属性,以及原型链上的属性。
pending
状态,直到操作完成或被拒绝。这可能是由于网络请求、文件读取等异步操作需要更长的时间来完成。resolve
或reject
函数来改变Promise的状态。如果忘记调用这些函数,Promise将保持在pending
状态,并且不会进入resolved
(解决)或rejected
(拒绝)状态。pending
状态,而不会进入resolved
或rejected
状态。这可能是由于代码错误、网络故障或其他异常情况导致的。then
和catch
方法。如果在Promise链中的某个地方没有处理错误或没有返回一个新的Promise实例,可能会导致Promise保持在pending
状态。catch
方法用于Promise链中捕获和处理异步操作的错误。try catch
语句用于捕获和处理同步代码中的异常。catch
方法只能捕获Promise链中的错误,而try catch
语句可以捕获同步代码中的各种异常。catch
方法通过链式调用添加到Promise链中,而try catch
语句是结构化的语法。async/await
提供了更加直观和简洁的语法来处理异步操作,使代码更易读、易写,并且错误处理更加方便。它建立在Promise
之上,可以看作是对Promise
的一种更高级的封装和语法糖,使异步代码的编写和阅读更加友好。
当用户在短时间内多次点击同一个按钮时,容易出现重复请求的情况。可以通过取消上次请求来避免重复请求。
实现方式:
(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;
Vite是一个基于现代浏览器原生ES模块导入功能的构建工具和开发服务器
Vite通过按需构建模块和即时的热更新实现了快速的冷启动和开发过程中的实时反馈
可以直接在浏览器中加载和运行源代码,无需额外的构建步骤。
vite还提供了零配置的开发服务器和丰富的插件生态系统,原生支持Vue单文件组件3.说一下vite
构建速度:vite基于现代浏览器原生支持的 ES6 模块特性,可以仅在需要的时候按需编译。
与此相比,Webpack则是静态模块打包工具,基于入口文件,通过递归的解析模块之间的依赖关系,将项目中的资源视为模块,并打包成静态资源需要在每次修改后重新编译整个应用程序。
所以,在开发阶段,Vite 的启动速度和模块热更新速度通常都比 webpack 快;在生产阶段,Vite 和 webpack 的构建速度取决于项目的复杂度和配置。
配置方式:Vite的配置方式更为简单直接,采用基于插件的方式来扩展功能。
同时,由于Vite的默认配置已经能够满足大部分应用的需求,因此用户通常只需要轻微调整即可。
而Webpack则需要用户对各种不同的配置选项进行深入理解和配置。
开箱即用的功能:Vite内置了许多常用的功能,例如HMR(热模块替换)、CSS预处理器、TypeScript 等等。
而Webpack则需要用户手动安装和配置各种插件来实现这些功能。
生态系统:Webpack作为构建工具的老牌玩家,其生态系统非常庞大并且稳定,拥有更为成熟的社区和生态圈。
相比之下,Vite的生态系统较为年轻,但也在快速发展中。
TypeScript 是 JavaScript 的超集,它在 JavaScript 的基础上增加了类型系统。TypeScript 支持所有 JavaScript 的数据类型,包括:
boolean
):表示真或假。number
):表示整数或浮点数。string
):表示文本数据。Array
):表示一组有序的数据。tuple
):表示一个已知元素数量和类型的数组。enum
):表示一组命名的常数。any
):表示任意类型的值。void
):表示没有任何类型,通常用于函数没有返回值时的返回类型。null
和 undefined
):表示值缺失或未定义。never
):表示永远不会出现的值的类型,通常用于函数永远不会返回或抛出异常时的返回类型。object
):表示非原始类型,即除 boolean
、number
、string
、symbol
、null
或 undefined
之外的类型。此外,TypeScript 还支持接口(interface
)、类(class
)、泛型(generic
)等高级类型。
需要注意的是,void
类型只能用作函数的返回类型,不能用作变量或参数的类型。如果将一个变量或参数的类型设置为 void
,那么它只能被赋值为 undefined
或 null
。
使用interface接口封装了接口返回的数据类型,
还可以定义函数参数类型,以及返回值的类型,帮助我们在编译阶段就找到错误,而不是在代码运行阶段报错
项目中有一些不可变的定死的数组,定义了元组
在 TypeScript 中,可以使用泛型来定义函数、类和接口等数据结构。泛型允许在定义时不指定具体的类型,而是在使用时根据需要指定具体的类型参数。
props父传子,$emit子传父
v-model指令是一个语法糖,是属性绑定和事件的语法糖,v-model 会根据不同的表单元素使用不同的属性并抛出不同的事件:
组合式api中需要在v-model后绑定一个参数(v-model:value=“msg”)
<qf-input v-model:value="msg">
//这是自定义组件内
在合适的地方导出方法
<input
type="text"
:value="value"
@input="emit('update:value', ($event.target as any)?.value)"
/>
在自定义组件中使用 defineProps(["value"])接收
使用 const emit = defineEmits(["update:value"])导出事件
vue实例化时,对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。属性被读取或者修改时,vue就能捕获到,并进行相应操作
vue3中通过Proxy(代理):拦截对象中任意属性的变化
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性进行修改;
vue2中
1.新增属性,或者删除属性,界面不会更新 使用$set
2.直接通过下标修改数组,界面不会自动更新
实现数据的双向绑定,首先要利用Object.defineProperty()对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。
Vue.js 的虚拟 DOM 是一种将真实的 DOM 树映射到内存中的 JavaScript 对象树。当状态发生变化时,Vue 会通过比较新的虚拟 DOM 树和旧的虚拟 DOM 树来计算出最小化的 DOM 操作,从而尽可能少地更新真实的 DOM。
这个比较过程就是 diff 算法,diff 算法的核心思想是遍历两个树的节点,并进行比较,以确定哪些节点需要被添加、删除或更新。Vue 采用了一种优化的 diff 算法,它假设同级的两个节点具有相似的结构,因此只需要对同级节点进行比较。
在 diff 算法中,每个节点都有一个唯一的 key 属性,用于识别该节点。当新旧节点列表中的顺序不同时,Vue 会使用 key 属性来尽可能地重用已存在的节点,而不是简单地删除并重新创建它们。这可以大大提高性能。
在vue2中使用的是双端diff算法:是一种同时比较新旧两组节点的两个端点的算法(比头、比尾、头尾比、尾头比)。一般情况下,先找出变更后的头部,再对剩下的进行双端diff。
在vue3中使用的是快速diff算法:它借鉴了文本diff算法的预处理思路,先处理新旧两组节点中相同的前置节点和后置节点。当前置节点和后置节点全部处理完毕后,如果无法通过简单的挂载新节点或者卸载已经不存在的节点来更新,则需要根据节点间的索引关系,构造出一个最长递增子序列。最长递增子序列所指向的节点即为不需要移动的节点。
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
Vue组件传值中,如果使用了props和**$emit方法进行传值,则是响应式**的。但是,如果使用了常规的JavaScript对象进行传值,则不是响应式的。
祖孙传值中,如果使用了props和emit方法进行传值,则是响应式的。但是,如果使用了emit方法进行传值,则是响应式的。但是,如果使用了refs或parent/parent/children等直接访问祖孙组件的属性或方法,则不是响应式的。
provide和inject是不响应式的。
provide和inject是一种祖先组件向后代组件传递数据的方式,可以实现非父子组件之间的通信。但是,它们传递的数据不是响应式的,也就是说,当provide中的数据发生变化时,并不会自动触发后代组件的更新。
如果需要在provide和inject之间实现响应式的数据传递,可以使用Vue中提供的响应式API,如data、computed、watch等。
在组件中使用 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刷新页面数据丢失怎么办
vuex-persistedstate
。这个插件可以将Vuex的状态持久化到本地存储(如localStorage)中,以便在刷新页面后能够保留数据。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请求以获取新页面内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K4Vednsi-1688377006899)(C:\Users\tn\AppData\Roaming\Typora\typora-user-images\image-20230606134738626.png)]
在beforecreate中已经可以使用props了,所以父子传值是在生命周期钩子之前
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'])
}
}
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>
路由导航守卫都是在Vue实例生命周期钩子函数之前执行的。
router.beforeEach
:全局前置守卫。
router.beforeResolve
:全局解析守卫。
router.afterEach
:全局后置钩子。
beforeEnter
守卫
//使用方式:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
beforeRouteLeave
:在失活的组件里调用离开守卫。
beforeRouteUpdate
:在重用的组件里调用,比如包含<router-view />
的组件。
beforeRouteEnter
:在进入对应路由的组件创建前调用。
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
在 Vue.js 中,数据渲染是异步的。这意味着当组件中的数据发生变化时,Vue.js 并不会立即更新 DOM。相反,Vue.js 会在下一个事件循环周期中更新 DOM。
这种异步更新的行为可以提高性能,因为它允许 Vue.js 在同一事件循环周期内,对多个数据变化进行批量处理,从而减少了 DOM 操作次数。但是,需要注意的是,在某些情况下,由于这种异步更新的行为,可能会导致我们看到“过期”或“错误”的 UI。为了解决这个问题,Vue.js 提供了一些 API 来手动触发同步更新。例如,可以使用 $nextTick
方法来等待异步更新完成后再做其它操作。
在MVC模式中,每个组件都有自己的职责,彼此独立。这种分离可以简化应用程序的开发和维护,使得更改其中一个组件不会影响到其他组件。
MVVM模式使得View和Model之间的耦合更加松散,并提供了一个可重用的ViewModel层。这种分离可以使应用程序更容易扩展、测试和维护。
Vue 2.x获取DOM
<div ref="myRef"></div>
this.$refs.myRef
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>
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>
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:""
}
},
2 params
传参
params
传参(不显示参数)也可分为 声明式 和 编程式 两种方式,与方式一不同的是,这里是通过路由的别名 name
进行传值的
//路由
{
path:"/test",
name:"test",
component:Test
}
//触发方式
this.$router.push({
name:test,
params:{
id:"123"
}
})
//组件接收
this.$route.params.id
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
25.watch和watcheffect的区别
watch
1.watch显式指定依赖数据,依赖数据更新时执行回调函数
2.具有一定的惰性lazy 第一次页面展示的时候不会执行,只有数据变化的时候才会执行(设置immediate: true时可以变为非惰性,页面首次加载就会执行)
3.监视ref定义的响应式数据时可以获取到原值
4.既要指明监视的属性,也要指明监视的回调
watchEffect
1.watchEffect
自动收集依赖数据,依赖数据更新时重新执行自身
2.立即执行,没有惰性,页面的首次加载就会执行
3.无法获取到原值,只能得到变化后的值
4.不用指明监视哪个属性,监视的回调中用到哪个属性就监视哪个属性
前端封装:
在阿里云控制台开通短信服务并获取 AccessKeyId 和 AccessKeySecret。
安装阿里云 SDK 并在前端项目中引入。
在前端代码中构造 API 调用参数。具体参数以阿里云 API 文档中所列为准。
进行 API 调用,并处理返回结果。
//调用参数
"PhoneNumbers": "手机号码",
"SignName": "短信签名",
"TemplateCode": "短信模板编号",
"TemplateParam": "{ \"code\":\"123456\" }"
}
后端封装:
后端定义了一个 /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')
})
前端设置最低存储阈值,通过定时轮询监控库存变化,当低于时,触发逻辑通过调用企业微信api发送应用消息给相关人员,先要获取access-token,(需要企业ID,企业发送消息的应用密钥),然后携带token,应用ID和报警信息等
1.先要获取临时access-token(需要企业ID,企业发送消息的应用密钥)
2.携带token,应用ID和报警信息,调用发送应用消息的接口.
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"
),
},
],
},
组件递归的使用方式:把要递归的部分单独封装成一个组件,vue 的组件递归,其本质是组价自引用,把对应的子集传入当前要递归的组件,并且正常接收,注意:在使用的时候传入初始数据
3.你如何实现基于路由和组件的访问控制?
基于路由的访问控制:我通常采用路由导航守卫的方式,根据当前登录用户的权限信息进行鉴权,如果当前用户没有访问该路由的权限则跳转到无权限访问页面。
基于组件的访问控制:我通常会在组件中进行权限控制,如通过v-if或v-show,根据当前登录用户的权限信息动态隐藏或显示组件。
按钮级的权限
定义权限列表:首先,你需要定义一个权限列表,其中包含所有可访问的按钮级权限。每个权限可以使用唯一的标识符或名称来表示。
const buttonPermissions = {
create: 'inspec',
edit: 'familyPartyRecord',
delete: 'familyPartyRecord'
};
从后端获取当前用户的角色的权限信息**[“inspec”, “familyPartyRecord”]**
在组件中检查权限:在需要进行权限控制的组件中,你可以使用用户权限来检查是否具有特定按钮级权限,并根据权限来显示或隐藏按钮
<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>
我们使用 v-if
指令根据用户权限来判断是否显示按钮。hasPermission
方法用于检查用户是否具有特定的按钮级权限。根据用户的权限列表和定义的权限列表,我们可以动态地显示或隐藏按钮。
利用xlsx插件,封装一个导出方法,在项目中使用的table组件起一个id名,获取到表格数据,XLSX.writeFile方法导出
当你使用浅拷贝(shallow copy)时,只会复制对象的引用而不是对象本身,这意味着如果你修改了副本,原始对象也会随之改变。这可能会导致一些潜在的问题和错误。
以下是一些适合使用深拷贝的情况:
总之,如果你需要创建一个不会影响原始对象的完全独立的副本,那么你应该使用深拷贝。
layout布局组件,间距组件a-space,面包屑组件,分页组件,表单组件及里面的输入框等组件,表格table组件
全局提示组件,以及他的骨架屏组件Skeleton
在项目中存储过当前角色的路由信息,通过这个路由信息去渲染左侧菜单栏选项
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.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.页面报错
使用递归
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]]
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);
}
);
//递归实现
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.在对左右子树进行递归遍历时,需要注意先左后右的顺序,否则可能导致叶子节点顺序出错
*/
另外,我们也可以使用栈来模拟递归过程。具体做法是:首先将根节点入栈,然后循环取出栈顶元素,判断该节点是否为叶子节点,如果是则将其值加入到数组中,否则将其右子节点和左子节点分别入栈。这种方法的复杂度与递归相同,但需要手动维护一个栈来模拟递归过程,相对较为繁琐。
使用深度优先搜索(DFS)或广度优先搜索(BFS)来实现。
使用 DFS 时,可以使用栈来存储遍历过程中的节点。从根节点开始,每次将当前节点的子节点压入栈中,然后取出栈顶元素并判断是否为叶子节点。如果是,则将其添加到结果数组中;否则,继续遍历其子节点。重复这个过程,直到栈为空。
使用 BFS 时,可以使用队列来存储遍历过程中的节点。从根节点开始,每次将当前节点的子节点加入队列中,然后取出队首元素并判断是否为叶子节点。如果是,则将其添加到结果数组中;否则,继续遍历其子节点。重复这个过程,直到队列为空。
1.可以使用z-index,
2.层级不让改变的话,可以给上面的div添加一个css属性(主要回答这个)
pointer-events: none;
*/\* 元素永远不会成为鼠标事件的target。*
*但是,当其后代元素的属性指定其他值时,鼠标事件可以指向后代元素,*
*在这种情况下,鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器。*
*pointer-events \*/
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.在 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 作为请求体发送到后端的上传接口。
请注意,这只是一个基本的文件上传示例,实际的实现可能会根据具体的需求和后端接口的要求进行调整。
git config
:设置或查看 Git 的配置信息。git init
:在当前目录初始化一个新的 Git 仓库。git clone
:克隆(下载)一个远程仓库到本地。git add
:将文件添加到暂存区。git status
:查看当前仓库的状态。git commit
:将暂存区的改动提交到本地仓库。git diff
:查看工作区与暂存区或本地仓库的差异。git restore
:撤销工作区的修改。git rm
:从工作区和版本控制中删除文件。git branch
:管理分支(列出、创建、删除)。git checkout
:切换分支或还原文件。git merge
:将分支的改动合并到当前分支。git rebase
:将当前分支的改动在其他分支上进行变基。git log
:查看提交历史记录。git blame
:逐行追踪文件的改动。git tag
:管理标签(创建、删除、列出)。git remote
:管理远程仓库。git fetch
:从远程仓库拉取改动。git pull
:从远程仓库拉取改动并合并到当前分支。git push
:将本地分支的改动推送到远程仓库。git clone
:克隆远程仓库到本地。git fetch
:从远程仓库拉取改动。git pull
:从远程仓库拉取改动并合并到当前分支。git push
:将本地分支的改动推送到远程仓库。git revert
:撤销指定的提交。git reset
:将 HEAD 指针重置到指定的提交。git cherry-pick
:选择性地应用其他分支的提交。git stash
:暂存当前工作区的修改。git clean
:清理工作区中未追踪的文件。git bisect
:二分查找引入错误的提交。*存储当前分支下的所有改动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 分支上完成剩余的修改。
(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.使用版本控制工具:使用版本控制工具(如 Git)提供的命令或图形界面工具,查看冲突的文件和代码。
git status:此命令会显示当前项目的状态,包括冲突文件的列表。冲突的文件会以 "both modified" 或 "both deleted" 的状态显示。
git diff:此命令可以显示冲突文件的具体差异。它会将冲突部分用特殊标记(如 "<<<<<<<", "=======", ">>>>>>>") 包围起来,以示区分。
3.解决冲突:手动编辑冲突的文件,解决冲突部分的代码。在冲突标记(通常是 "<<<<<<<", "=======", ">>>>>>>") 之间,根据需要选择保留、修改或删除代码。确保最终的代码能够正确合并冲突的修改。
4.添加解决冲突的代码:完成解决冲突后,将修改后的代码添加到暂存区(或提交到版本控制系统)。
5.测试:在解决冲突后,进行测试以确保代码仍然正常运行,并且没有引入新的问题。
6.注意:在解决冲突之前,建议先备份冲突的文件或工作目录,以防意外情况发生。
7.在多人协作开发时,及时与团队成员沟通、协调并遵守良好的版本控制流程,可以帮助减少代码冲突的发生。
除了上述常见的 HTTP 请求方式,还有一些其他的请求方式,如 HEAD、TRACE、CONNECT 等,它们在特定的场景下有着特殊的用途。
在前端开发中,可以使用 JavaScript 中的 XMLHttpRequest
对象或现代的 fetch
API 来发送各种类型的 HTTP 请求。同时,许多前端框架和库也提供了封装的函数或方法来方便地进行 HTTP 请求操作。
前端传输数据时,可以选择使用 JSON 或 FormData 两种格式,它们在数据结构和用途上有一些区别:
JSON:
FormData:
选择使用 JSON 还是 FormData 取决于具体的需求和场景:
需要注意的是,在实际开发中,后端服务器通常会对接收的请求数据格式进行处理和解析,需要根据后端的要求和接口定义来选择使用合适的数据格式。
综上所述,POST用于创建资源,通常用于表单提交、文件上传等场景,而PUT用于更新资源,需要客户端提供完整的资源数据进行替换。在实际应用中,你可以根据具体的需求和语义选择使用POST还是PUT方法来发送请求。
1、登录时,拿到accessToken和refreshToken,并存起来
2、请求接口时,带着accessToken去请求
3、如果accessToken过期失效了,后端会返回401
4、401时,前端会使用refreshToken去请求后端再给一个有效的accessToken
5、重新拿到有效的accessToken后,将刚刚的请求重新发起
6、重复1/2/3/4/5
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。