目录
19.Promise用来解决什么问题?async/await用来解决什么问题?
18.v-for中的key是什么?为什么不建议使用索引值作为key值?
2.如果正在进行项目的某一部分工作,但是想转到其他分支进行工作又不想提交当前代码,该如何操作?
9.参加过社团、学生会吗?具体的工作内容是什么呢?担任过领导层面吗?
CSS
1.CSS的布局有哪些?
- 正常布局流:指在不对页面进行任何布局控制时,浏览器默认的HTML布局方式
- display属性:css中实现页面布局的主要方法,正常流中的所有内容都有一个默认的display值,此属性允许我们修改默认的显示方式
- 弹性盒子(Flexbox):用于创建横向或是纵向的一维页面布局
- 网格(Grid)布局:用于同时在两个维度上把元素按行和列排列整齐
- 浮动(float):把一个元素“浮动”起来,会改变该元素本身和在正常布局流中跟随它的其他元素行为。这一元素会浮动到左侧或右侧,并且从正常布局流中移除,这时候其他的周围内容就会在这个被设置浮动的元素周围环绕
- 定位(position):能够让我们把一个元素从它原本在正常布局流中应该在的位置移动到另一个位置。有如下几种方式:
- 静态定位(Static positioning):每个元素默认的属性,它表示将元素放在文档布局流的默认位置
- 相对定位(Relative positioning):允许我们相对于元素在正常的文档流中的位置移动它
- 绝对定位(Absolute positioning):将元素完全从页面的正常布局流中移出,在创建复杂布局效果时非常有用(如通过标签显示和隐藏的内容面板,或者通过按钮控制滑动到屏幕中的信息面板)
- 固定定位(Fixed positioning):与绝对定位类似,但是它是将一个元素相对浏览器视口固定,而非相对另外一个元素
- 粘性定位(Sticky positioning):是一种新的定位方式,它会让元素先保持position: static一样的定位,当它的相对视口位置达到某一个预设值时,它就会像position: fixed一样定位
- CSS表格布局:一种传统的、不灵活的、标记繁重的、难以调试的、语义上错误的方法,通常会被用于兼容一些不支持Flexbox和Grid的浏览器
- 多列布局:多列布局模组给了我们一种把内容按列排序的方式
2.flex怎么实现左右布局。
/* html */
<div class="container">
<div class="left">左侧内容</div>
<div class="right">右侧内容</div>
</div>
/* css */
.container {
display: flex; /* 1.启动Flex布局 */
flex-direction: row; /* 2.实现左右布局 row为水平排列 row-reverse为反向水平排列也适用 */
align-items: strech; /* 3.使子元素填充垂直方向的可用高度 实现高度相等 */
}
.left {
width: 300px; /* 4.设置左侧宽度 */
}
.right {
flex: 1; /* 5.分配剩余宽度 */
}
3.CSS常用单位。
- 绝对单位
- px:图像显示的基本单元
- 相对单位
- em:相对于父元素的font-size来计算的,在没有任何CSS规则的前提下,1em的长度是16px,em单位设置了font-size属性后,会逐级向上相乘
- rem:相对于根元素<html>的font-size来计算的,不用像em一样使用级联的方式来计算尺寸
- vw:视窗宽度的百分比(1vw代表视窗的宽度为1%)
- vh:视窗高度的百分比
- vmin:当前vw和vh中较小的一个值
- vmax:当前vw和vh中较大的一个值
- %:相对于父元素的尺寸来计算的
4.CSS里什么可以被继承?
- 字体系列属性:font、font-family、font-weight、font-size、font-style
- 文本系列属性:
- 内联元素:color、line-height、word-spacing、letter-spacing、text-transform
- 块级元素:text-indent、text-align
- 元素可见性:visibility
- 表格布局属性:caption-side、border-collapse、border-spacing、empty-cells、table-layout
- 列表布局属性:list-style
- 生成内容属性:quotes
- 光标属性:cursor
- 页面样式属性:page、page-break-inside、windows、orphans
- 声音样式属性:speak、speech-rate、volume、voice-family、pitch、stress、elevation
5.关于回流重绘。
- 回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置
- 如何触发:
- 添加或删除可见的DOM元素
- 元素的尺寸发生变化(包括外边距、内边距、边框大小、高度和宽度等)
- 元素的位置发生变化
- 内容发送变化,如文本变化或图片被另一个不同尺寸的图片所替代
- 页面一开始渲染的时候(无可避免)
- 浏览器的窗口尺寸变化
- 获取一些特定属性的值(包括需要通过即使计算得到的属性:offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight等)
- 如何避免:
- 通过改变元素的class类名设定元素样式
- 避免设置多项内联样式
- 应用元素的动画,使用position属性的fixed值或absolute值,尽可能地使元素脱离文档流,从而减少对其他元素的影响
- 避免使用table布局,table中每个元素的大小以及内容的改动都会导致整个table重新计算
- 使用CSS3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘
- 避免使用CSS的JavaScript表达式
- 如何触发:
- 重绘:当计算好盒模型的位置、大小及其他属性后,浏览器会根据每个盒子的特性进行绘制
- 如何触发:
- 触发回流(触发回流一定会触发重绘)
- 颜色的修改
- 文本方向的修改
- 阴影的修改
- 如何触发:
6.flex布局有哪些属性?
<div class="container">
<div class="item1">1</div>
<div class="item2">2</div>
</div>
- display:flex:作用于container上,用于实现flex布局
- flex:作用于items上,items占container总容量的份数
- flex-direction:决定主轴排列方向
- row(默认):主轴为水平方向,起点在左端
- row-reverse:主轴为水平方向,起点在右端
- column:主轴为垂直方向,起点在上沿
- column-reverse:主轴为垂直方向,起点在下沿
- flex-wrap:决定当一排items排不下时,该如何排布
- nowrap:不换行,按宽度比例排在一行
- wrap:换行,正序排列
- wrap-reverse:换行,倒序排列
- justify-content:items在主轴上的对齐方式
- flex-start(默认):左对齐
- flex-end:右对齐
- center:居中对齐
- space-between:两端对齐,每个item之间间隔相等
- space-around:每个item两侧间隔相等,即item与边框间隔为items之间间隔的二分之一
- space-evenly:每个item之间的间隔相等
- align-items:items在交叉轴上的对齐方式
- stretch(默认):如果item未设置高度或设为auto,将占满整个容器的高度
- flex-start:上沿对齐
- flex-end:下沿对齐
- center:居中对齐
- baseline:以盒子里第一行文字的基线对齐
- align-self:允许item和其他items具有不一样的对齐方式,可覆盖align-items
- align-content:多行items在交叉轴的对齐方式,如果只有一行不生效,即必须搭配flex-wrap使用
- stretch(默认):轴线占满整个交叉轴
- flex-start:上对齐
- flex-end:下对齐
- center:居中对齐
- space-between:与交叉轴两端对齐,轴线之间的间隔平均分配
- space-around:每根轴线两侧的间隔相等
- space-evenly:items均匀分布
- order:定义items的排列顺序,数值越小越靠前,默认值为0
- flex-grow:决定了items占据剩余空间的能力
- flex-shrink:决定了当container空间不足时,items缩小的比例,默认值为1
- gap:items之间的最小间隙,如果间隙以某种方式变大,那么间隙只有在该间隙最终变小的情况下生效
- row-gap:主轴方向的最小间隙
- column-gap:交叉轴方向的最小间隙
7.CSS层叠样式权重值规则。
- 最近的祖先样式比其他祖先样式优先级高
- 直接样式比祖先样式优先级高
- 内联样式 > ID选择器样式 > 类选择器样式 = 属性选择器样式 = 伪类选择器样式 > 标签选择器样式 = 伪元素选择器样式
- 计算选择符中ID选择器的个数(a),再计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c),按照a、b、c的顺序依次比较大小,大的则优先级高,相等则比较下一个,若最后两个选择符中a、b、c都相等,则按照就近原则来判断。选择符如下:
- 后代选择符:.father .child {}
- 子选择符:.father > .child {}
- 相邻选择符:.bro1 + .bro2 {}
- 属性后插有!important的属性拥有最高优先级,如果同时插有!important,则再利用规则3、4判断优先级
8.伪类和伪元素的区别。
- 伪类本质上是给元素创建多一个类名,多个类名多个效果
- 伪元素本质上是创建了一个有内容的虚拟容器,也就是多了一个DOM节点(元素)
- CSS3中伪类和伪元素的语法不同,伪元素一般是::,伪类一般是:
- 可以同时使用多个伪类(中间不要有空格),但只能同时使用一个伪元素
9.盒子模型有哪些?
- W3C标准的盒子模型(标准盒模型)
- width指content的宽度,height指content的高度
- 盒子大小 = content + border + padding + margin
- IE标准的盒子模型(怪异盒模型)
- width指content、border和padding的总宽度,height指content、border和padding的总高度
- 盒子大小 = width + margin
10.元素的居中定位。
- 绝对定位 + transform反向偏移
// html
<div class="main">1</div>
// css
.main {
position: absolute;
left: 50%; // 起始在body中,横向距左50%的位置
right: 50%; // 其实在body中,纵向距上50%的位置
transform: tanslate(-50%, -50%); // 水平、垂直都居中
}
- 弹性盒子
// html
<div class="container">
<div class="item">1</div>
</div>
// css
.container {
display: flex;
justify-content: center; // 水平居中
align-items: center; // 垂直居中
}
11.如何选择列表中某些子元素进行单独样式设计?
- :nth-child(n):选择第n个子元素,n从1开始计数
- :nth-child(odd)/:nth-child(even):选择子元素中奇/偶数位的,n从1开始计数
- :nth-child(an+b):n从0开始计数
12.CSS怎么设置元素的隐藏。
- 通过opacity设置元素的不透明度,元素仍保留在页面上
.element {
opacity: 0;
}
// 或者配合过滤器属性一起使用
.element {
filter: opacity(0);
}
- 通过visibility控制元素的可见性,元素仍保留在页面上
.element {
visibility: hidden;
}
- 通过display隐藏元素,可以有效地从文档流中删除该元素,使其就像在DOM中从未存在过一样
.element {
display: none;
}
13.块级元素和行内元素的区别。
块级元素
- 各个块级元素独占一行,默认垂直向下排列
- 高度、宽度、margin即pdding都是可控的,设置有效且有边距效果,宽度未设置时默认为100%
- 块级元素中可以包含块级元素和行内元素
行内元素
- 行内元素和其他行内元素都会在一条水平线上排列
- 高度、宽度是不可控的,设置无效,由内容决定
- 根据标签语义化的理念,行内元素最好只包含行内元素,不包含块级元素
JavaScript
1.ES6新特性。
- let和const:
- let:用于定义变量,只能在块作用域内访问,不能声明同名变量,不能跨函数访问
- const:用于定义常量,使用时必须初始化,不能声明同名变量,不存在变量提升,只能在块作用域内访问,不能修改(如果定义的是对象,其属性值可修改),不能跨函数访问
- 新的基本数据类型symbol和bigInt
- symbol:独一无二的且不可变(解决全局变量冲突、内部变量覆盖)
- bigInt:表示任意精度的整数,支持安全的存储和操作大数
- 函数方面:
- 函数默认值:若函数没有传参,参数值为默认值
- 箭头函数:更加简洁,但是内部没有arguments和prototype,所以不能用new调用,且内部this永远指向父作用域的this
- 数组操作:
- fill():用给定值填充数组
- find():遍历数组,找到第一个符合条件的元素并返回该元素,若未找到返回undefined
- findIndex():类似于find(),只不过返回的是该元素下标,未找到返回-1
- Array.from():将类数组或伪数组转换成数组,要求类数组的属性必须为数值型或者字符串型的数字,并且必须有length属性,没有length属性将生成空数组
- flat():将嵌套数组拉平一次或多次,默认为一次,使用Infinity可全部拉平
- flatMap():map()和flat()的组合,先通过map()返回生成的新数组,再用flat()拉平一次
- includes():看数组中是否存在某元素,存在返回true否则返回false
- 模板字符串
- 通过“${}”来界定,“``”来完成
- 字符串方法:
- includes():判断字符串是否包含参数字符,返回boolean值
- startsWith():判断字符串是否以参数字符串开头,返回boolean值
- endsWith():判断字符串是否以参数字符串结尾,返回boolean值
- repeat():按指定次数返回一个新字符串
- padStart():按给定长度从前补全字符串(没有给定参数默认为空格),返回新字符串
- padEnd():按给定长度从后补全字符串(没有给定参数默认为空格),返回新字符串
- 解构表达式
- 数组解构:
-
let[a,b,c] = [1,2,3]; console.log(a,b,c); // 1 2 3 let[a,b,c] = [1,2]; console.log(a,b,c); // 1 undefined 2 let[a,,b] = [1,2,3]; console.log(a,b); // 1 3 let[a,...b] = [1,2,3]; comsole.log(a,b); // 1 [2,3]
- 对象解构:
-
let obj = { name: "ren", age: 18, sex: "female" }; let { name, age, sex } = obj; console.log({ name, age, sex }); // "ren" 18 "female" let { name: myName, age: myAge, sex: mySex } = obj; console.log({ myName, myAge, mySex }); // "ren" 18 "female"
-
对象方面:
-
Map:存储键可以是任意值,提供size属性
-
Object.is():判断两个值是否为同一个值
-
Object.assign():将对象合并,返回目的对象(相同属性会被覆盖)
-
Object.keys():返回对象的所有属性
-
Object.values():返回对象的所有属性值
-
Object.entries():返回多个数组(属性+属性值)
-
-
class:创建类和继承(子类必须在构造函数constructor里调用super()),本质上还是一个函数
-
模块化
-
通过import关键字导入模块
-
通过export和export default关键字导出模块
-
优点:
-
防止命名冲突
-
复用性强
-
-
-
promise:异步编程的一种解决方法,构造函数是同步执行的,then方法是异步执行的
-
状态(状态一旦改变不能再变):pending(初始状态)、fulfilled(成功状态)、rejected(失败状态)
-
只能由pending->fulfilled || pending->rejected
-
-
方法:
-
then():函数回调执行,用于接收请求接口返回的数据,有两个参数,一个用于处理Promise成功时的回调函数,另一个用于处理Promise拒绝时的回调函数,返回值也是Promise对象
-
catch():处理在Promise拒绝时的回调函数,返回值为Promise对象,同时也用来捕获代码异常和出错
-
finally():与Promise状态无关,不接收任何参数,也没有返回值,无论如何都会执行
-
resolve()(静态方法):用于创建一个成功状态的Promise对象
-
reject()(静态方法):用于创建一个失败状态的Promise对象
-
race()(静态方法):接收一个Promise数组,当任何一个Promise对象状态被确定时,立即执行回调函数
-
any()(静态方法):接收一个Promise数组,只要数组内有一个Promise对象执行成功就会返回已经成功执行的Promise结果,若全部为rejected状态,则会在最后的Promise对象执行完后,全部的promise返回到异常函数中
-
all()(静态方法):接收一个Promise数组,当数组内全部Promise对象没有出现rejected状态,则会在全部resolve成功后执行回调,一旦发现存在一个是rejected状态,会立即执行异常函数
-
allSettled()(静态方法):接收一个Promise数组,等所有的Promise对象状态都确定后(不管成功还是拒绝),才执行回调函数,并返回一个对象数组{[state: xx, value/reson(成功是value,失败为reson): xx ]}
-
-
-
proxy:用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
-
方法:
-
Proxy.revocable():创建一个可撤销的Proxy对象
-
-
语法:const p = new Proxy(target, handler)
-
target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
-
handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为
-
-
-
运算符
-
...:扩展运算符,常用于拷贝和合并
-
?.:可选运算符
-
:::函数绑定运算符
-
2.数组的操作有哪些?
# ES5及ES5以前
- push():在数组最后一位添加一个或多个参数,返回新数组长度
- pop():删除数组最后一位并返回该元素
- unshift():类似于push(),只不过是在数组第一位进行操作
- shift():类似于pop(),只不过是在数组第一位进行操作
- forEach():遍历数组,执行回调函数
- map():与forEach()类似,但可以返回结果
- filter():遍历数组,筛选出所有满足条件的元素,返回新数组
- reduce():遍历数组,回调函数的每次结果作为下一次的初始值pre,返回最终结果
- reduceRight():类似于reduce(),只不过是从右到左
- every():遍历数组,若全部元素满足条件返回true,否则返回false
- some():类似与every(),但只需要存在一个元素满足条件就返回true
- valueOf():返回数组原始值,一般情况下返回数组本身
- indexOf():返回某元素在数组中第一次出现的位置,不存在返回-1
- lastIndexOf():类似于indexOf(),只不过返回的是最后一次出现的位置
- sort():对数组进行排序,无参排序根据的是Unicode码点,不稳定
- reverse():对数组进行反转
- splice():对数组进行添加、删除和替换操作,第一个参数为开始操作的位置下标,第二个参数为需要删除的元素个数,后面的参数为需要添加的元素
- slice():返回对数组进行裁切后的新数组,不改变原数组,区间为左闭右开
- concat():将多个数组进行拼接并返回新数组,不改变原数组
- join():根据给定字符将数组转换为字符串,无参默认字符为“,”
- toString():将数组转换为字符串,类似于join()无参情况
# ES6
- find():遍历数组,找到第一个符合条件的元素并返回该元素,若未找到返回undefined
- findIndex():类似于find(),只不过返回的是该元素下标,未找到返回-1
- includes():看数组是否存在某元素,存在返回true否则返回false
- Array.from():将类数组或伪数组转换成数组,要求类数组的属性必须为数值型或者字符串型的数字,并且必须有length属性,没有length属性将生成空数组
- flat():将嵌套数组拉平一次或多次,默认为一次,使用Infinity参数可全部拉平
- flatMap():map()和flat()的组合,先通过map()返回生成的新数组,再用flat()拉平一次
- fill():用给定值填充数组
3.闭包的概念,为什么要用闭包?
闭包是指有权访问另一个函数作用域的变量的函数。
闭包函数是指声明在一个函数中的函数。
- 特点:
- 让外部访问函数内部变量成为可能
- 局部变量会常驻在内存中
- 可以避免使用全局变量,防止全局变量污染
- 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
4.防抖和节流。
防抖和节流都是常见的前端优化性能方案。
- 防抖:多次触发,只执行最后一次
// 可以利用lodash库的debounce方法实现防抖
// _.debounce(func,[wait = 0],[option=]) 返回值:防抖新函数
// 1.func:原函数
// 2.wait:防抖延迟时间
// 3.option:其他配置(可选)
// ---------- 手写debounce -----------
function debounce(func,wait = 0) {
let timeId;
// 返回防抖新函数,因为要返回this所以不使用箭头函数
return function(...args) {
clearTimeout(timeId); // 清除上一个定时器
timeId = setTimeout(function() {
const _this = this;
func.aplly(_this,args); // 通过apply指定this并以数组形式传参
},wait);
};
}
- 节流:在规定时间内,只执行一次
// 可以使用lodash库中的throttle方法实现节流
// _.throttle(func,[wait = 0],[option=]) 返回值:节流新函数
// 1.func:原函数
// 2.wait:节流时间
// 3.option:其他配置 options.leading——节流开始前是否调用,默认为true
// ---------- 自己实现throttle ----------
function throttle(func,wait = 0) {
let timeId;
return function(...args) {
// 如果已经存在定时器,返回
if(timeId !== undefined) {
return;
}
const _this = this;
timeId = setTimeOut(() => {
func.apply(_this,args);
timeId = undefined; // 执行完毕,id设置为空
},wait);
};
}
5.如何确认this。
ps:在全局环境或函数顶部声明'use strict'即可进入严格模式
- 全局环境:不管是严格模式还是非严格模式,this都指向全局对象(Window)
- 函数内部:
- 直接调用:严格模式下this指向undefined,非严格模式下this指向全局对象(Window)
- 对象调用:不管是严格模式还是非严格模式,this都指向调用者
6.基本数据类型。
ES5及以前:undefined、null、boolean、string、number、object
ES6新增:symbol、bigInt
7.怎么判断arr是一个数组。
- arr instanceof Array; // true
- Object.prototype.toString.call(arr); // true
- (arr).constructor === Array; // true
- Array.isArray(arr); // true
- Array.prototype.getPrototype(arr); // true
- Object.getPrototypeOf(arr) === Array.prototype; // true
- arr._proto_ === Array.prototype; // true
8.关于typeOf。
const person = {
name: 'xfs',
age: 18
};
console.log(typeof 1); // number
console.log(typeof NaN); // number
console.log(typeof '1'); // string
console.log(typeof undefined); // undefined
console.log(typeof true); // boolean
console.log(typeof null); // object
console.log(typeof []); // object
console.log(typeof person); // object
9.如何判断一个数是正整数。
- num > 0 && Number.isInteger(num); // ES6提供
- num > 0 && ((num | 0) === num); // 效率高
- num > 0 && num % 1 === 0 && tyoeof num === 'number'; // num % 1的时候,非数值型会自动进行类型转换从而导致判断结果也为true(如‘true’会自动转换为1),所以需要判断num是否为数值型
- num > 0 && Math.floor(num) === num; // 向下取整后还是自己本身
- num > 0 && parseInt(num,10) === num; // 超过检测范围的数会不在检测,会返回首个数字
10.异步编程。
- Promise
axios('url1').then(res1 => {
return axios('url2');
}).then(res2 => {
return axios('url3');
}).then(res3 => {
// ...
});
- Generator
function* myGenerator() {
yeild axios('url1');
yeild axios('url2');
yeild axios('url3');
// ...
}
- async
async function myAsync() {
const res1 = await axios('url1');
const res2 = await axios('url2');
const res3 = await axios('url3');
// ...
}
11.异步任务核心api。
- queueMicrotask
console.log(1);
queueMicrotask(() => {
console.log(2);
});
console.log(3); // 输出:1 3 2
- MutationObserver
console.log(1);
// 创建观察者
const obs = new MutationObserver(() => {
console.log(2);
});
// 创建元素并添加监听
const divNode = document.creatElement('div');
/*
* 参数1:观察的dom节点
* 参数2:观察的选项(childList观察字节点改变)
*/
obs.observe(divNode,{ childList: true });
// 修改元素内容
divNode.innerText = 'xfs666';
console.log(3); // 输出:1 3 2
- setTimeout
console.log(1);
setTimeout(() => {
console.log(2);
},1000);
console.log(3); // 输出:1 3 2
12.前端拦截器。
- 路由守卫
import { createRouter, createWebHashHistory } from "vue-router";
// 路由
const router = createRouter({
history: createWebHashHistory(),
route: [
// 登陆界面
{
path: '/login'
name: 'Login',
component: () => import("../views/Login.vue"),
},
// 主界面
{
path: '/main',
name: 'Main',
meta: { requireAuth: true },
component: () => import("../views/Main.vue"),
},
]
});
/* 路由守卫(这里主要用来验证登录)
* 1.to:即将前往的目的路由
* 2.from:目前所在路由
* 3.next:执行resolve钩子的函数
*/
router.beforeEach((to, from, next) => {
const isAuthenticated = !!localStorage.getItem('user');
// 路由需要登录但用户未认证,跳转至登录界面
if(to.meta.requiresAuth && !isAuthenticated) {
next({ name: 'Login' });
} else { // 路由不需要登录认证,直接放行
next();
}
});
- Axios中的http拦截器
const services = axios.create({
baseUrl: "http://" + ipUrl; // ipUrl:后端的ip地址
});
// 请求拦截器:发送请求前触发
services.interceptors.request.use(config => {
// 发送请求要做的事
// 在这里可以添加一些请求头的逻辑,如配置token等
return config;
}, error => {
// 处理发送请求失败
return Promise.reject(error);
});
// 响应拦截器:得到响应后触发
services.interceptors.response.use(response => {
// 对获取的响应数据进行处理
// 在这可以根据实际后台的响应值去进行判断,如登录失效跳转到登录页等
return response.data;
}, error => {
// 处理获取响应失败
return Promise.reject(error);
})
13.浅拷贝和深拷贝的区别和实现。
- 浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存
- 通过Object.assign实现
// 如果对象属性存在引用类型或对象,那么对于这个对象而言是浅拷贝
let oldObj = {
'name': 'xfs',
'grade': {
'english': 80,
'math': 90
}
};
let newObj = Object.assign({}, oldObj);
- 深拷贝:复制并创建一个一模一样的对象,不共享内存
- 通过Object.assign实现
-
// 对于Object.assign()而言,如果对象属性值为简单类型,那么就是深拷贝 let oldObj = { 'name': 'xfs', 'grade': 80 }; let newObj = Object.assign({}, oldObj);
- 通过JSON对象实现
-
function deepCopy(oldObj) { let obj = JSON.stringfy(oldObj); let newObj = JSON.parse(obj); return newObj; }
- 利用jQuery的extend方法
-
/* deep:是否深拷贝 * target:目标对象 * obj:objN可选,第一个以及第N个被合并的对象 */ $.extend([deep], target, obj1, [objN]) let oldObj = [0, 1, 2]; let newObj = $.extend(true, [], oldObj);
- 通过concat()实现数组拷贝
-
concat(arr1, arr2, ...) var oldArr = [0, 1, 2]; var newArr = [].concat(oldArr);
- 通过slice()实现数组拷贝
-
slice(start, end) var oldArr = [0, 1, 2]; var newArr = oldArr.slice(0); // [0, 1, 2]
- 通过lodash库的cloneDeep()实现
-
// value为要拷贝的值 _.cloneDeep(value) var oldObj = [ { 'a': 1 }, { 'b': 2 } ]; var newObj = _.cloneDeep(oldObj);
- 通过扩展运算符...实现
-
var oldArr = [0, 1, 2]; var newArr = [...oldArr];
- 通过递归的方式封装实现深拷贝函数
-
function deepCopy(newObj, oldObj) { for(var i in oldObj) { // 1.获取属性值 var item = oldObj[i]; // 2.判断是否是数组 if(item instanceof Array) { newObj[i] = []; deepCopy(newObj[i], item); } else if(item instanceof Object) { // 3.判断是否是对象 newObj[i] = {}; deepCopy(newObj[i], item); } else { // 4.简单数据类型直接赋值 newObj[i] = item; } } }
14.任务队列。
任务队列是JS引擎中的一种机制,它负责处理异步任务和事件。在任务队列中,JS引擎将任务分为两类:宏任务和微任务。
- 宏任务:
- 同步代码:在主线程上执行的同步代码
- setTimeout/setInterval:通过定时器设置的异步任务
- script标签:通过script标签引入的脚本
- 微任务:
- Promise:通过Promise设置的异步任务
- async/await:在异步函数中通过await关键字等待的异步任务
- MutationObserver:用于观察DOM树变化的异步任务
- 执行顺序:
- 执行同步任务
- 执行微任务队列中的所有微任务
- 执行宏任务队列中的所有宏任务
- 执行渲染更新
- 重复1~4步骤
- 区别:
- 执行顺序:每次事件循环迭代结束时,只执行一次宏任务,而在执行完宏任务后,如果有微任务存在,会立即执行所有的微任务,因此,微任务会比宏任务更快地执行
- 执行时机:宏任务的执行时机比较固定,例如定时器、异步函数等,而微任务的执行时机取决于其所在的任务队列是否被清空,如果一个微任务队列中还有任务,那么这些任务会在下一个宏任务执行前全被执行
15.变量作用域及其优先级。
作用域,即变量和函数生效的区域。
- 全局作用域:变量在全局从文件开头到结尾一直有效即全局变量
- 函数作用域:局部变量
- 块级作用域:用{}定义的变量,在代码块内有效
- 函数的声明作用域(函数原型):变量只在声明的开始到声明参数的结束
16.变量提升的优缺点。
变量提升,即变量的声明会被提升到当前作用域的顶部。
- 优点:
- 容错率更高:在JS代码执行过程中,先赋值再声明这种错误的写法是可以正确执行的
- 提高性能:由于变量提升,JS代码执行前的语法检查和编译只会执行一次,而且会为函数生成预编译代码,那么当代码没有改变时,会直接使用预编译中的变量和函数,所以当代码执行时就不需要进行编译了
- 缺点:
var text = "xfs";
function f() {
// 本来想输出外层xfs 但是由于变量提升 函数内部的text声明会提升在函数顶部
// 所以最后输出是undefined
console.log(text);
if(false) {
var text = "yyds";
}
}
17.原型链。
原型:函数(或构造函数)身上才有prototpe(原型,是一个对象),而其他任何通过构造函数实例化出来的对象(不包括null、undefined)身上都有_proto_,实例化对象的_proto_就是构造函数的prototype(===关系),undefined和null既没有prototype也没有_proto_,因为它两既不是函数也不是函数实例化出来的对象
原型链:当在实例化对象中访问一个属性时,首先会在该对象的内部(自身属性)寻找,如果找不到,则向其_proto_指向的原型中寻找,如果仍然找不到,则继续向原型中的_proto_指向的上一级原型寻找,直到找到或者到Object.prototype._proto_为止(值为null),这种链状过程即为原型链。
原型链的作用:
- 避免了代码冗余,公用的属性和方法可以放到原型对象中,这样,通过该构造函数实例化的所有对象都可以使用该对象的构造函数中的属性和方法
- 减少了内存占用
原型链的应用场景:
- 通过Object.prototype.toString.call()进行类型判断
- 在vue项目中,通常会将对象公共属性放在vue原型上,或者将使用插件挂载到vue原型上
18.let和const分别解决了什么问题?
- let存在块作用域的概念,解决变量穿透问题
for(var i = 0;i < 5;i ++) {
console.log(i); // 0 1 2 3 4
}
console.log(i); // 5 出现变量穿透
for(let i = 0;i < 5;i ++) {
console.log(i); // 0 1 2 3 4
}
console.log(i); // Uncaught ReferenceError: index is not deined
- const定义常量,解决var定义的值可以改变的问题
19.Promise用来解决什么问题?async/await用来解决什么问题?
Promise
- 回调地狱,代码难以维护,常常是第一个函数的输出是第二个函数的输入这种现象
- Promise可以支持多个并发的请求,获取并发请求的作用
async/await
- 相较于Promise冗长的.then()、.catch()链式调用,async/await使得代码更像同步流程,错误处理更直观,代码可读性也更佳
TypeScript
1.ref怎么声明一个不确定类型的变量?
// 根据输入参数推导类型 Ref<String>
const a = ref('');
// 通过泛型显示约束 Ref<String[]>
const b = ref<String[]>([]);
// 声明类型 Ref<String[]>
const c: Ref<String[]> = ref([]);
// 如果不确定类型
type Ref<T = any> = {
value: T
};
function ref<T>(value: T): Ref<T>;
const count1 = ref(2);
const count2 = ref("2");
console.log(typeof count1.value); // number
console.log(typeof count2.value); // string
2.表单校验怎么写?
<template>
// 利用el-from,绑定ref和校验规则rules
<el-form ref="formRef" :model="form" :rules="rules">
<el-form-item label="用户名">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" />
</el-form-item>
<el-button @click="toSubmit">提交</el-button>
</el-form>
</template>
<script setup lang="ts">
import{ FormInstance, FormRules } from 'element-plus';
import{ ref, reactive } from 'vue';
const formRef = ref<FormInstance>();
const form = reactive({
username: '',
password: ''
});
// 定义校验规则
const rules = reactive<FormRules>({
username: [
/**
* 1.required: true —— 表示必填
* 2.message —— 如果为空就提示message中的信息
* 3.trigger —— 如果表单项是el-select,trigger为'change'
**/
{ required: true, message: '必填', trigger: 'blur' },
// 自定义规则
{ asyncValidator: validateCheck, trigger: 'blur' }
],
password: [
{ required: true, message: '必填', trigger: 'blur' }
]
});
/**
* 通过asyncValidator自定义校验规则
* function(source, [options], callback): Promise
* 1.source —— 要验证的对象(必选)
* 2.[options] —— 验证处理选项对象(可选)
* 3.callback —— 验证完成调用的回调函数(可选)
**/
const validateCheck = async(rule: any, value: any, callback: any) => {
if(value === '') {
callback(new Error('用户名不得为空!'));
} else {
callback();
}
}
// 点击提交按钮触发
async function toSubmit() {
await formRef.value?.validate((valid) => {
if(valid) {
// 如果校验通过,执行相应的动作
}
})
}
</script>
3.泛型的作用是?
泛型是对Java语言的类型系统的一种扩展,以支持创建可按类型进行参数化的类。作用如下:
- 类型安全(主要目标)
- 消除强制类型转换
- 潜在的性能收益
- 更好的代码复用性
4.interface和type的区别。
- type可以做到但是interface做不到的
// 1.可以声明基本类型
type name = string;
// 2.可以声明联合类型
type user = string | number;
// 3.可以声明元组类型
type user = [number, string];
// 4.可以利用typeof操作符进行声明
type user = typeof somObj;
- type做不到的但是interface可以做到的
// 可以重复声明且不会报错
interface user {
name: string
}
interface user {
age: number
}
// 会合并为
interface user {
name: string,
age: number
}
- type通过&实现继承,而interface通过extends实现继承
Vue
1.Vue有哪些指令,作用分别是什么?
- v-for:动态渲染,遍历数组
- v-show:显示内容
- v-if:显示与隐藏
- v-on:绑定一个点击函数
- v-model:多用于表单元素实现双向数据绑定
- v-bind:单向数据绑定
- v-text:渲染数据但不解析标签
- v-html:渲染数据同时可以解析标签
- {{}}(插值表达式):渲染数据但不解析标签
2.v-if和v-show的区别。
- v-if通过控制dom节点的存在与否来控制元素显影,而v-show通过设置dom元素的display样式(block为显示,none为隐藏)
- v-if切换先销毁再重建内部监听事件和子组件,而v-show只是简单的css切换
- v-if在切换方面更消耗性能,而v-show在初始渲染更消耗性能
3.Vue常用的修饰符,应用场景有哪些?
修饰符是一种用于限定类型以及类型成员的声明的符号,能够处理许多DOM事件的细节。
- 表单修饰符:在填写表单时,对input和v-model的一些修饰
- .lazy:填写完信息光标离开标签后,才将输入值赋予value
- .trim:自动过滤用户输入的首空格字符,中间的空格字符不会被过滤
- .number:自动将用户输入值转化为数值型,如果这个值无法被parseFloat解析,会返回原来的值
- 事件修饰符:对事件捕获以及目标进行处理
- .stop:阻止事件冒泡
- .prevent:阻止事件的默认行为
- .self:将事件绑定在自身身上,相当于阻止事件冒泡
- .once:绑定事件只触发一次
- .capture:使事件触发从包含这个元素的顶层开始往下触发
- .passive:相当于给onscroll事件添加了.lazy修饰符,防止在监听元素滚动事件时,由于一直触发onscroll事件使得网页变卡(不能和.prevent一起使用,.prevent会被忽略)
- .native:让组件能够监听根元素的原生事件(操作普通HTML标签会令事件失效)
- 鼠标按键修饰符:主要就是针对左、右和中键点击
- .left:左键点击
- .right:右键点击
- .middle:中键点击
- 键值修饰符:修饰键盘事件(onkeyup、onkeydown)的
- .keyCode:监听特定键盘按下,分为以下两类:
- 普通键:enter、tab、delete、space、esc、up、...
- 系统修饰键:ctrl、alt、meta、shift、...
- .keyCode:监听特定键盘按下,分为以下两类:
- v-bind修饰符:
- .sync:对props进行一个双向绑定
- .prop:设置自定义标签,避免暴露数据,防止污染HTML结构
- .camel:将命名变为驼峰命名法
4.Vue2和Vue3的区别。
- 架构的设计和实现:Vue2通常采用大型monolithic系统架构模式,该模式开发难度大、难以维护,而Vue3更倾向于使用API+TypeScript的方式进行架构设计和实现,代码更简洁、结构更清晰,便于组件化和模块化管理
- 响应式系统:Vue3采用Proxy代理对象替代了Vue2的Object.defineProperty方法,带来了更好的性能和响应式能力
- 性能:Vue3通过减少虚拟DOM更新的次数和更高效的渲染,相较于Vue2有更快的速度和更低的内存
- 编辑器:Vue2使用基于字符串的模板编译器,而Vue3引入了基于编译器的静态分析,将模板编译为更高级的渲染函数,能够生成更优化的代码、减少运行时的开销、提高性能表现并且减小了打包体积
- 工具链:Vue3在开发者工具方面提供了更强大的调试和性能分析工具,使得开发者可以更方便地调试和优化应用程序
- 组件实例的挂载:Vue2需要通过手动调用$mount()方法来完成,而Vue3在默认情况下组件实例会自动挂载到与其定义位置相匹配的DOM元素上
- 新特性和增强功能:Vue3引入了Composition API(组合型API),相对于Vue2的Option API(选项型API)而言,提供了更灵活的组件组合方式,使得代码更具可读性和可维护性
- TypeScript支持:Vue3对TypeScript的支持更加友好,提供了更全面、准确的类型定义
5.关于Vite。
Vite是一个面向现代浏览器,基于ECMA标准的ES-Module实现的一个更轻更快的web应用开发工具。
Vite的特点:
- 快速的冷启动
- 及时的模块热更新
- 真正的按需编译
6.Vue的渲染过程。
- 解析模板:Vue首先会解析模板,并生成一个抽象语法树(AST)
- 生成渲染函数:Vue对AST进行静态分析,生成一个渲染函数(是一个JavaScript函数),该函数用于生成Virtual DOM树
- 执行渲染函数:当组件的状态发生变化时,Vue会重新执行渲染函数,生成一个新的Virtual DOM树
- 对比新旧Virtual DOM树:Vue会通过Diff算法对比新旧Virtual DOM树的差异,找出要更新的部分
- 更新DOM:Vue会根据差异通过patch函数更新真实的DOM树
7.讲解一下MVVM?
MVVM是Model-View-ViewModel的简写,是一种设计模式,用于构建用户界面,通过将用户界面的开发和业务逻辑的开发分离开来,使得代码更加模块化、可维护性更高,简化前端开发过程。
View(视图层)通过ViewModel的DOM Listeners将事件绑定到Model上,Model(数据模型层)则通过Data Binding来管理View中的数据,而ViewModel(视图模型层)通过双向绑定将View和Model连接起来。
8.Vue中VM的实现原理是什么?
- 先通过Object.defineProperty()来做数据劫持和响应式
- 再通过Vue中的render函数将模板转换为虚拟DOM
- 最后通过updateComponent方法将虚拟DOM渲染成HTML
9.Vue3组件之间传值的方法有哪些?
- 父传子:通过props
// 父组件,通过动态绑定方式
<template>
<div>
<Child :name="name" @click="FatherMethod"></Child>
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import { ref } from 'vue';
const name = ref('xfs');
const FatherMethod = () => {
console.log("父组件方法");
};
</script>
// 子组件
<template>
<div>
<button @click="ChildMethod">子组件</button>
</div>
</template>
<script setup lang="ts">
// 子组件接收
const props = defineProps(["name"]);
const childName = props.name;
const emits = defineEmits(["FatherMethod"]);
const ChildMethod = () => {
emits("FatherMethod");
}
</script>
- 子传父:通过emits
// 子组件
<template>
<div>
<button>子组件</button>
</div>
</template>
<script setup lang="ts">
const emits = defineEmits(["name"]);
function age() {
emit('age',18);
};
const ChildMethod = () => {
console.log("子组件方法");
};
// 暴露出去
defineExpose({
ChildMethod
});
</script>
// 父组件
<template>
<div>
<button @click="FatherMethod">父组件</button>
<Child ref="Childs">{{ name }}</Child>
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import { ref } from 'vue';
// 声明子组件
const Childs: any = ref(null);
const FatherMethod = () => {
Childs.value.ChildMethod();
}
</script>
- 依赖注入:使用provide与inject可以实现祖孙组件之间的传值,避免组件过深导致的props传值困难同时又没必要使用状态管理。
// 父组件
import { provide } from 'vue';
provide('name', 'xfs');
// 子组件
import { inject } from 'vue';
const name = inject('name');
- 浏览器缓存:通过sessionStorage
// 组件1
sessionStorage.setItem("user", JSON.stringify(user));
// 组件2
const user = JSON.parse(sessionStorage.getItem("user"));
- 使用事件总线:通过mitt
// mitt的index.ts
import mitt from "mitt";
const emitter = mitt();
export default emitter;
// 组件1
<template>
<div>
{{ count }}
<ChildPage />
</div>
</template>
<script setup>
import ChildPage from "./ChildPage.vue";
import emitter from "@/mitt/index";
import { ref } from "vue";
const count = ref(0);
emitter.on("changeCount", (counter) => {
count.value = counter;
});
</script>
// 组件2
<template>
<div>
<button @click="changeCount">按钮</button>
</div>
</template>
<script>
import emitter from "../mitt/index";
let a = 0;
const changeCount = () => {
a ++;
emitter.emit("changeCount", a);
};
</script>
- 使用pinia
// store的index.ts
import { defineStore } from "pinia";
import { ref } from "vue";
export const useCounterStore = defineStore("counter", () => {
const count = ref(0);
const changeCount = () => {
count.value ++;
};
return { count, changeCount };
});
// 组件1
<template>
<div>
{{ count }}
<Child />
</div>
</template>
<script lang="ts" setup>
import Child from "./Child.vue";
import { useCounterStore } from "../store/index";
import { storeToRefs } from "pinia";
const store = useCounterStore();
const { count } = storeToRefs(store);
</script>
// 组件2
<template>
<div>
<button @click="changeCount">按钮</button>
</div>
</template>
<script lang="ts" setup>
import { useCounterStore } fron "../store/index";
const store = useCounterStore();
const { changeCount } = store;
</script>
- 通过Vuex
// vuex的index.ts
import { createStore } from "vuex";
export default createStore({
state: {
number: 0,
},
getters: {},
mutations: {
changeNumber(state, payload) {
state.number = payload;
},
},
actions: {},
modules: {},
});
// 组件1
<template>
<div>
{{ a }}
<Child />
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { useStore } from "vuex";
import Child from "./Child.vue";
const store = useStore();
const a = computed(() => store.state.number);
</script>
// 组件2
<template>
<div>
<button> @click="update">按钮</button>
</div>
</template>
<script setup lang="ts">
import { useStore } from "vuex";
const store = useStore();
let a = 0;
const update = () => {
a ++;
store.commit("changeNumber", a);
};
</script>
- 父子组件进行双向绑定:通过v-model
// 父组件
<template>
<div>
{{ a }}
<Child v-model="a" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import Child from './Child.vue';
const a = ref<number>();
</script>
// 子组件
<template>
<div>
<button @click="update">按钮</button>
</div>
</template>
<script setup lang="ts">
import { defineModel } from 'vue';
const a = defineModel({
type: Number,
default: 0,
});
const update = () => {
a.value += 1;
};
</script>
10.关于Vuex。
Vuex是专门为Vue.js应用程序开发的状态管理模式,采用集中式管理应用到各个组件,从而解决多组之间的数据通信。
Vuex的五个部分:
- state:存储公共数据(类似组件中的data)
- mutations:改变数据的方法集合(类似methods)
- getters:利用现有的状态进行计算得到新的数据——派生(类似computed)
- actions:异步操作
- modules:模块化拆分
11.关于Webpack,Webpack打包太慢怎么办?
Webpack是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
有以下几种方法提升打包速度:
- 升级Webpack版本
- 多线程/多实例构建
- 按需打包
- 充分利用缓存
- 使用DLL
- 配置resolve.modules配置项
- 使用Tree Shaking
- 压缩代码
12.图片懒加载。
图片懒加载即当图片位于用户可视区之外时,默认不加载,等到进入了可显范围时,再加载显示图片。
// main.js
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyLoad);
const loadimage = require('./assets/loading.gif');
const errorimage = require('./assets/error.gif');
Vue.user(VueLazyLoad, {
preLoad: 1.3, // 预压高度比例
error: errorimage, // 加载失败时的图片路径
loading: loadimage, // 加载时的图片路径
attempt: 1, // 尝试计数
listenEvents: ['scroll'], // 想要监听的事件
});
// 把项目中img标签里面的:src属性改成v-lazy
<img v-lazy="'/static/img/' + item.image" alt="">
13.Vue的生命周期。
- beforeCreate:组件实例被创建之初,执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
- Created:组件实例已经完全创建,初始化完毕,各种数据可以使用,常用于异步数据获取
- beforeMount:组件挂载之前,未执行渲染、更新,DOM未创建
- Mounted:组件挂载到实例上去之后,初始化结束,DOM已创建,可用于获取访问数据和DOM元素
- beforeUpdate:组件数据发生变化,更新之前,可用于获取更新前各种状态
- Updated:组件数据更新之后,所有状态已是最新
- beforeDestroy:组件实例销毁之前,可用于一些定时器或订阅的取消
- destoryed:组件实例销毁之后,作用同上
- activated:keep-alive缓存的组件激活时调用
- deactivated:keep-alive缓存的组件停用时调用
- errorCaptured:捕获一个来自子孙组件的错误时被调用
14.关于keep-alive。
<keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,与<transition>相似,是一个抽象组件,自身不会渲染一个DOM元素,也不会出现在组件的父组件链中。
对于被<keep-alive>包裹的组件:
- 第一次进入:created --> mounted --> activated
- 离开:deactivated
- 再次进入:activated
属性:
- include:只有名称匹配的组件会被缓存
- exclude:任何名称匹配的组件都不会被缓存
- max:最多可以缓存的组件实例个数,一旦数字达到了,在新的实例被创建之前,已缓存组件中最久没有被访问的组件会被销毁
- meta:
export defualt[
{
path: '/',
name: 'home',
components: Home,
meta: { keepAlive: true } // 需要被缓存的组件
},
{
path: '/book',
name: 'book',
components: Book,
meta: { keepAlive: false } // 不需要被缓存的组件
},
]
15.关于虚拟DOM。
- 定义:Virtual DOM是一层对真实DOM的抽象
- 作用:减少JavaScript操作真实DOM带来的性能消耗,抽象了原本的渲染过程,实现了跨平台的能力
16.Vue中的响应式原理。
- Vue2:
- 数据观测:当Vue实例创建时,它会遍历data对象的所有属性,并使用Object.defineProperty将每个属性转换为响应式,这个过程由Observer类完成
- 依赖收集:当模板渲染或计算属性时,Vue会追踪哪些数据被访问了,这通过Dep类和Watcher类完成,Watcher会在读取数据时将自身添加到数据的依赖列表里
- 数据变更通知:当数据被修改时,对应的Watcher会收到通知,并触发视图更新
- Vue3:
- 数据包装:在Vue3中,响应式数据不再是直接修改的原生对象,而是通过reactive函数包装后的代理对象,这个代理对象使用Proxy创建,可以拦截所有的读取和写入操作
- 读取操作的追踪:当访问响应式数据的属性时,Proxy的get方法会被调用,Vue的响应式系统会记录下这次读取操作,并将其与当前的副作用函数(effect)关联起来
- 写入操作的追踪:当修改响应式数据的属性时,Proxy的set方法会被调用,Vue的响应式系统会检查哪些副作用函数依赖于这个属性,并将它们标记为需要更新
- 触发更新:当执行到被标记为需要更新的副作用函数时,Vue的调度器会确保它们重新执行,从而触发视图的更新,这个过程通常是异步的,以提高性能
17.computed和watch的区别。
区别:
- computed支持缓存,当其依赖的属性值发生变化时,计算属性会重新计算,否则直接使用缓存中的属性值,watch不支持缓存,当对应属性值发生变化时相应执行
- computed不支持异步,有异步操作时无法监听数据变化,watch支持异步操作
- computed第一次加载时就监听,watch默认第一次加载时不监听
- computed是计算属性,watch是监听(监听data中的数据变化)
- computed中的函数必须调用return,watch不是
应用场景:
- computed:一个数据受到多个数据影响,如购物车商品结算
- watch:一个数据影响多个数据(数据变化响应、执行异步操作或高性能消耗的操作,watch最佳),如搜索数据
18.v-for中的key是什么?为什么不建议使用索引值作为key值?
key是给每一个vnode的唯一id,也是diff算法的一种优化策略,可以根据key更准确、更快地找到对应的vnode节点。
尽量不要使用索引值index作为key值,一定要用唯一标识的值,如id等。因为若用数组索引index作为key,当向数组中指定位置插入一个新元素后,会重新更新index索引,对应的虚拟DOM的key值全部更新,与没加key一样会做不必要的更新。因此index虽然可以解决key不冲突的问题,但是不能解决复用的情况,如果是静态数据就没问题。
小程序
1.关于生命周期。
当小程序启动后,首先完成小程序的onLaunch和onShow,然后完成页面的onLoad、onShow和onReady;小程序切换至后台时,先触发页面的onHide,再触发小程序的onHide;小程序切换至前台时,先触发小程序的onShow,再触发页面的onShow。
- onLaunch:当小程序初始化完成时,会触发onLaunch(全局触发一次)
- onShow:小程序启动或从后台进入前台时,触发onShow
- onHide:小程序隐藏或从前台进入后台时,触发onHide
- onReady:页面初次渲染完成
- onLoad:页面加载
- onUnLoad:页面卸载
- onError:当小程序发生脚本错误,或者api调用失败时,会触发onError并带上错误信息
计算机网络
1.http和https的区别。
http(超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议,被用于在web浏览器和网站服务器之间传递信息。
https(超文本传输安全协议)是一种通过计算机网络进行安全通信的传输协议,https经由http进行通信,但是利用ssl/tls来加密数据包。
区别:
- http明文传输,数据都是未加密的,安全性较差,https数据传输过程是加密的,安全性较好
- 使用https协议需要到CA(数字证书认证机构)申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy和GlobalSign等
- http页面响应速度比https快,主要是因为http使用TCP三次握手建立连接,客户端和服务器需要交换3个包,而https除了TCP的三个包,还要加上ssl握手需要的9个包,所以一共是12个包
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
- https其实就是构建在SSL/TLS上的http协议,所以,要比较https比http更耗费服务器资源
2.http状态码。
- 1xx(信息性状态码):表示接收的请求正在处理
- 2xx(成功状态码):表示请求正常处理完毕
- 3xx(重定向状态码):需要后续操作才能完成这一请求
- 4xx(客户端错误状态码):表示请求包含语法错误和无法完成
- 5xx(服务器错误状态码):服务器在处理请求的过程中发生了错误
3.http1.0、http2.0、http3.0的区别。
- http1.0
- 非持久连接:每个请求/响应都需要建立一个新的连接,每次请求都要进行三次握手和关闭连接的过程
- 不支持请求/响应的头部压缩:每个请求/响应的头部信息都需要完整地发送
- 不支持管道化:客户端无法同时发送多个请求,必须等待前一个请求响应后才能发送下一个请求
- http2.0
- 二进制分帧:将数据分割为更小的帧,通过多路复用在同一个连接上并行发送和接收多个帧,提高了传输的效率
- 头部压缩:使用HPACK算法对请求/响应的头部进行压缩,减少了数据传输的大小
- 服务器推送:服务器可以主动向客户端推送资源,减少了客户端发起请求的次数
- 优先级和依赖:可以指定请求的优先级和依赖关系,优化资源的加载顺序
- http3.0
- 使用QUIC作为传输协议:http3.0基于QUIC(Quick UDP Internet Connections)协议,取代了TCP作为传输层协议
- 改进的拥塞控制:http3.0使用了更先进的拥塞控制算法,提高了网络的稳定性和性能
- 减少握手延迟:QUIC协议通过0-RTT(零往返时间)握手和快速握手恢复,减少了握手的延迟
- 抗丢包能力:QUIC协议具有更好的丢包恢复能力,可以更快地恢复丢失的数据包
Git版本控制
1.git常用的命令有哪些?
- git clone:拷贝一个远程仓库,也就是下载一个项目
- git push:上传远程代码并合并
- git add:添加文件到暂存区
- git commit:提交暂存区到本地仓库
- git checkout:分支切换
- git pull:下载远程代码并合并
2.如果正在进行项目的某一部分工作,但是想转到其他分支进行工作又不想提交当前代码,该如何操作?
- git stash:暂时将未提交的变化移除
- git stash pop:取出存储中最后存入的工作状态进行回复,会删除储藏
- git stash list:查看所有储藏中的工作
- git stash apply <储藏的名字>:取出储藏中对应的工作状态进行恢复,不会删除储藏
- git stash clear:清空所有储藏中的工作
- git stash drop <储藏的名称>:删除对应的某个储藏
数据结构与算法
1.了解哪些数据结构?Vue中哪里使用到了?
数据结构:
- 数组
- 栈
- 队列
- 链表
- 树
- 图
Vue中主要在路由跳转中使用到了栈:
- this.$router.push():跳转到指定URL,向history栈添加一个新的记录,点击后退会返回上一个页面
- this.$touter.replace():跳转到指定URL,替换history栈最后一个记录,点击后退会返回上上一个页面
- this.$router.go():类似于window.history.go(n),向前或向后跳转n个页面,n可正(向后跳转)可负(向前跳转)
设计模式
1.了解哪些设计模式?
- 发布订阅模式
- 观察者模式
- 单例模式
- 工厂模式
- 装饰器模式
MySQL
1.char和varchar的区别是什么?
- char有固定的长度,而varchar属于可变长的字符类型
- 存储容量不同:char最多只能存放的字符个数为255,和编码无关,而varchar最多能存放65532个字符,varchar的最大有效长度由最大行大小和使用的字符集来确定,整体最大长度是65532个字符
其他