CSS
HTML 语义化标签
HTML5 引入了很多语义化标签,用于更好地描述文档结构,如:
<header>: 页眉,通常包含导航、标题等内容。<nav>: 导航区域。<main>: 主要内容区域。<article>: 独立内容块。<aside>: 侧边栏。<footer>: 页脚。
优势:
- 提高可访问性。
- 增强 SEO 。
- 使代码更易读。
<link> 标签和 @import 标签的区别
<link>标签: 在 HTML 中引用外部样式表,优先加载,支持并行下载。@import规则: 在 CSS 文件中导入其他样式表,依赖样式文件加载顺序,加载较慢,兼容性较差。
html
<link rel="stylesheet" href="styles.css">
css
@import url("styles.css");
css3新特性
- 选择器
- 边框
- border-radius:创建圆角边框
- box-shadow:为元素添加阴影
- border-image:使用图片来绘制边框
- 背景
background-clip、background-origin、background-size和background-break- 四、transition 过渡 transition: CSS属性,花费时间,效果曲线(默认ease),延迟时间(默认0)
- 五、transform 转换
transform-origin:转换元素的位置(围绕那个点进行转换),默认值为(x,y,z):(50%,50%,0) - 六、animation 动画
- 七、渐变
flex弹性布局、Grid栅格布局:Grid布局即网格布局
回流(重排)与重绘
回流:是指当元素的尺寸、位置、或其他属性发生变化时,浏览器需要重新计算元素的几何属性并重新布局
触发回流的情况:
- 添加或删除可见的 DOM 元素
- 元素的尺寸、边距、边框、填充或显示样式发生变化
- 浏览器窗口大小发生变化(响应式设计)
- 内容变化,例如输入框内文字的输入
- 获取某些属性的值,例如
offsetHeight、offsetWidth、clientHeight、clientWidth等
重绘(Repaint)
重绘是指当元素的外观(例如颜色、背景)发生变化,但不影响布局时,浏览器会重新绘制元素的外观。与回流相比,重绘的代价较小,但仍然会影响性能
触发重绘的情况:
- 颜色、背景、阴影等样式变化
- 可见性变化(
visibility属性变化但不涉及display: none)
性能优化建议
- 批量修改样式:避免逐条修改样式,使用 CSS 类或
style属性的批量设置。 - 避免频繁读取导致回流的属性:将可能导致回流的属性值缓存起来,避免在循环中多次读取。
- 动画优化:尽量使用
transform和opacity来做动画,这些属性只会触发合成层的更新,而不会触发回流和重绘。 - CSS3 动画和 GPU 加速:使用 CSS3 动画和启用 GPU 加速可以显著提高动画的性能。
magin塌陷问题如何解决
形成原因:两个相邻的块级元素的外边距(margin)重叠在一起时,导致它们之间的间距小于预期 解决办法:
- 给父级 添加
overflow: hidden, 触发父元素生成一个新的块级格式化上下文(BFC),从而避免外边距的塌陷 - 使用
padding或border, 在父元素或其中一个子元素上添加padding或border。 - 父元素使用
display: inline-block或display: flex - 子元素使用
float - 使用
position属性, 将子元素设置为position: absolute;或position: relative;,也可以避免外边距的塌陷。 - 避免上/下外边距相邻
BFC的特性
- BFC 是页面上的一个独立容器,容器里面的子元素不会影响外面的元素。
- BFC 内部的块级盒会在垂直方向上一个接一个排列
- 同一 BFC 下的相邻块级元素可能发生外边距折叠,创建新的 BFC 可以避免外边距折叠
- 每个元素的外边距盒(
margin box)的左边与包含块边框盒(border box)的左边相接触(从右向左的格式的话,则相反),即使存在浮动 - 浮动盒的区域不会和 BFC 重叠
- 计算 BFC 的高度时,浮动元素也会参与计算
BFC 块格式化上下文
触发方式
- 根元素,即
<html> - 浮动元素:
float值为left、right overflow值不为visible,即为auto、scroll、hiddendisplay值为inline-block、table-cell、table-caption、table、inline-table、flex、inline-flex、grid、inline-grid- 绝对定位元素:
position值为absolute、fixed
浏览器解析渲染机制
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上
文本的溢出
单行文本溢出省略
- text-overflow:规定当文本溢出时,显示省略符号来代表被修剪的文本
- white-space:设置文字在一行显示,不能换行
- overflow:文字长度超出限定宽度,则隐藏超出的内容
多行文本溢出省略
-
伪元素 + 定位
- position: relative:为伪元素绝对定位
- overflow: hidden:文本溢出限定的宽度就隐藏内容)
- position: absolute:给省略号绝对定位
- line-height: 20px:结合元素高度,高度固定的情况下,设定行高, 控制显示行数
- height: 40px:设定当前元素高度
- ::after {} :设置省略号样式
-
基于行数截断
单行文本两端对齐
- 方法一:添加一行 我们可以新增一行,使该行文本不是最后一行
- text-align-last 该属性定义的是一段文本中最后一行在被强制换行之前的对齐规则。
怎么让小于16px的文字支持
- 使用缩放比例:可以使用 CSS 的
transform属性来缩放文本元素以达到小于 12px 的效果。例如,使用transform: scale(0.8)将文本缩放为 80% 的原始大小。请注意,这可能会导致文本外观变得模糊或失真。 - 使用 zoom: 将容器或文本元素的 zoom 属性设置为小于 1 的值
- 使用 -webkit-text-size-adjust: 将容器或文本元素的 -webkit-text-size-adjust 属性设置为 "none" 或 "auto"
- 图片或图标字体
- 使用
SVG
画一条 0.5px 的线
在高分辨率屏幕上实现 0.5px 的线可以通过以下方式:
- 使用
transform: scale(0.5)对 1px 的线进行缩放。 - 在移动设备中,可以使用
border和transform结合。
.line {
border-top: 1px solid black;
transform: scaleY(0.5);
}
如何隐藏一个元素
- 完全隐藏且不占空间: 使用
display: none;Js 还是可以访问该元素并改变它的样式或属性 - 隐藏但仍占空间: 使用
visibility: hidden; - 透明但仍占空间仍响应交互: 使用
opacity: 0;(可结合pointer-events: none;禁用元素的鼠标事件阻止事件) - 隐藏到屏幕外: 使用
position: absolute; left: -9999px;常用于图像轮播、无障碍访问(如屏幕阅读器)等场景 - 裁剪隐藏: 使用
clip-path: inset(50%); - 将元素的宽高设置为
0,并隐藏其内容。 使用height: 0; width: 0; overflow: hidden;
防抖与节流
防抖原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
适用场景:
- 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
- 搜索框联想场景:防止联想发送请求,只发送最后一次输入
节流原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
适用场景
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器resize
使用时间戳实现
- 使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
三栏布局实现方法
三栏布局是网页布局中常见的结构,左右两侧为固定宽度,中间部分自适应。常用的实现方法有:
-
浮动法:
<div class="left"></div> <div class="middle"></div> <div class="right"></div>.left { float: left; width: 200px; } .right { float: right; width: 200px; } .middle { margin: 0 200px; } -
Flexbox布局:
.container { display: flex; } .left, .right { width: 200px; } .middle { flex: 1; } -
Grid布局:
.container { display: grid; grid-template-columns: 200px 1fr 200px; }
calc() 方法
calc() 是 CSS 中的一个函数,可以动态计算长度、百分比和其他值。
css
.container {
width: calc(100% - 200px);
}
特点:
- 可以在 CSS 中进行加、减、乘、除运算。
- 常用于响应式设计中,以实现动态调整元素尺寸。
渐进增强
渐进增强(Progressive Enhancement)是一种 Web 开发策略,先为所有浏览器提供基本功能,然后为支持更高级功能的浏览器添加增强体验。
特点:
- 保证了在低端设备或旧版浏览器中的可访问性。
- 在现代浏览器中提供更好的用户体验。
CSS 盒子布局(Box Model)
CSS 盒模型是每个 HTML 元素在网页上占据空间的方式。包括:
- 内容区域(content)
- 内边距(padding)
- 边框(border)
- 外边距(margin)
.element {
width: 200px; /* content width */
padding: 10px;
border: 1px solid black;
margin: 10px;
}
特点:
- 标准盒模型:
width指内容区域的宽度。 - IE盒模型:
width包含内容、内边距和边框。
HTML5 新特性
HTML5 引入了很多新特性,如:
- 语义化标签:
<header>,<footer>,<article>,<section> - 表单增强:
<input type="email">,<input type="date"> - 多媒体元素:
<audio>,<video> - API: 离线存储(localStorage)、Geolocation、WebSocket
CSS 选择器优先级
CSS 优先级决定了哪个样式会应用于元素。优先级规则如下:
- 内联样式:
1000 - ID 选择器:
100 - 类、伪类、属性选择器:
10 - 元素选择器、伪元素选择器:
1
计算方式:
/* ID: 1个100, 类: 1个10, 元素: 1个1 */
#header .nav li a { /* 优先级: 111 */ }
CSS 属性的继承
CSS 中有些属性是可以继承的,如 color, font-size, line-height 等;而 padding, margin, border 等属性则不会被继承。
css
.parent {
color: red;
}
.child {
/* 继承自父元素 */
}
position 的值
position 属性用于定位元素。可选值包括:
static: 默认值,元素按照正常文档流进行定位。relative: 相对定位,元素相对于其正常位置进行偏移。absolute: 绝对定位,元素相对于最近的非static祖先元素定位。fixed: 固定定位,元素相对于视口进行定位。sticky: 粘性定位,元素在滚动到指定位置后变为固定定位。
浮动是什么,会引起什么
浮动(float)用于将元素从正常文档流中移出,使其左右浮动。
特点:
- 浮动元素会导致父容器高度塌陷。
- 浮动的元素会影响后续元素的布局,可能需要清除浮动。
清除浮动:
css
.clearfix::after {
content: "";
display: block;
clear: both;
}
Less 和 Sass 区别
Less 和 Sass 都是 CSS 预处理器,区别在于:
- 语法: Less 更接近 CSS,而 Sass 有缩进式和 SCSS 两种语法,SCSS 与 CSS 更接近。
- 编译方式: Less 可以直接在浏览器中编译,而 Sass 需要借助工具编译。
- 特性支持: Sass 提供了更多功能,如嵌套规则、变量、混合、继承和控制指令(
@if,@for)。
响应式原理方案
响应式设计通过 CSS 媒体查询 (@media) 实现,根据不同的屏幕尺寸或设备类型调整布局。
方案:
- 流式布局: 使用百分比来定义宽度,使布局在不同分辨率下自动调整。
- 弹性盒布局(Flexbox) 和 网格布局(Grid) : 提供了强大的响应式设计能力。
- 媒体查询: 根据设备宽度或其他属性应用不同的样式。
css
@media screen and (max-width: 600px) {
.container {
flex-direction: column;
}
}
px,rpx,vw,vh,rem,em 的区别
px: 像素,绝对单位,基于显示设备的分辨率。rpx: 响应像素,小程序中特有单位,自动适应屏幕宽度。vw: 视口宽度的 1%,适用于响应式布局。vh: 视口高度的 1%。rem: 相对于根元素(html)的字体大小。em: 相对于父元素的字体大小。
应用场景:
px用于精确控制尺寸。rem和em用于响应式字体和元素尺寸。vw和vh用于响应式布局。
scope 是怎么做的样式隔离的
Vue 组件的 <style> 标签上添加 scoped 属性时,Vue 会自动将该样式限定在当前组件的范围内,从而防止样式冲突和不必要的样式泄漏。
实现原理:
- 生成唯一的作用域 ID: (如
data-v-f3f3eg9)。这个 ID 是随机的 - 添加作用域 ID 到模板元素:在编译组件模板的过程中,将这个作用域 ID 作为自定义属性添加到组件模板的所有元素上。例如,如果作用域 ID 是
data-v-f3f3eg9,那么在该组件模板的所有元素上都会添加一个属性data-v-f3f3eg9。 - 修改 CSS 选择器: Vue 会自动转换其选择器,使其仅匹配带有对应作用域 ID 的元素。这是通过在 CSS 选择器的末尾添加相应的作用域 ID 属性选择器来实现的。例如,如果 CSS 规则是
.button { color: red; },并且作用域 ID 是data-v-f3f3eg9,那么该规则会被转换成.button[data-v-f3f3eg9] { color: red; }。
JS
let cosnt var的区别
- let const 有快级作用域,var没有
- var存在变量提升 let const不存在
- var可以重复声明,会覆盖之前的 let const不存在 使用
const声明的对象可修改其属性,但不能重新赋值这个对象本身。如果重新赋值,将会抛出TypeError。 - var不存在暂时性死区 let const声明变量前不可以用
- const要申明变量 var let不需要
变量提升和函数提升
-
变量提升:
var声明的变量在声明之前可以访问,但值为undefined。console.log(a); // undefined var a = 10; -
函数提升:函数声明在代码运行前被提升,可以在声明之前调用。
console.log(foo()); // 可以正常调用 function foo() { return "bar"; }
JS数据类型有哪些?区别?
基本数据类型(7 种)
- Number: 所有数字(整数和浮点数),如
42、3.14。特殊值 NaNInfinity和-Infinity:表示无穷大。 - BigInt:表示任意大的整数n b
- String:字符串
- Boolean
- Symbol: 唯一且不可变的值,常用于对象属性的唯一标识符
- Undefined: 变量被声明但没有赋值时
- Null: 空值
引用数据类型
Object: 对象是键值对的集合,可以表示复杂的数据结构。
- 普通对象:如
{name: "John", age: 30}。 - 数组(Array) :一种特殊的对象,用于存储有序列表,如
[1, 2, 3]。 - 函数(Function) :函数也是一种对象,存储可以执行的代码块。
- 日期(Date) :表示日期和时间的对象,如
new Date()。 - 正则表达式(RegExp) :用于匹配字符串的模式,如
/abc/。
区别:
原始类型:Number、BigInt、String、Boolean、Symbol、Undefined、Null,它们存储的是值,存储在栈内存中,比较时是值比较,不可变。
引用类型:Object,它们存储的是引用地址,存储在堆内存中,比较时是引用比较,可变。
什么是堆与栈
是两种用于管理内存的区域
栈:用于存储原始类型和局部变量,具有较小的内存空间和快速的访问速度。栈中存储的是实际的值,变量生命周期结束后自动释放。
堆:用于存储引用类型,存储较大的对象和数组,动态分配内存。堆内存管理依赖垃圾回收机制,访问速度较慢。
伪数组和数组的区别
数组是 JavaScript 内置的对象,具有完整的数组方法和属性。
伪数组是一个对象,具有 length 属性和通过索引访问元素的能力,但不具备数组的方法。可以通过 Array.from()、扩展运算符等方式将伪数组转换为真正的数组以使用数组的方法。
如何判断object为空
可以通过检查对象是否有自己的属性来判断对象是否为空。
-
使用
Object.keys:const isEmpty = (obj) => Object.keys(obj).length === 0; -
使用
for...in循环:javascript const isEmpty = (obj) => { for (let key in obj) { if (obj.hasOwnProperty(key)) return false; } return true; }; -
使用
JSON.stringify()方法 将对象转换为 JSON 字符串,然后判断字符串是否为"{}"
如何判断对象相等
-
浅比较: 检查对象引用是否相同或属性值是否相等(不考虑嵌套对象)。
function isEqual(obj1, obj2) { return obj1 === obj2; } -
深比较: 比较对象的所有属性值,包括嵌套对象。
function isDeepEqual(obj1, obj2) { return JSON.stringify(obj1) === JSON.stringify(obj2); }
null 和 undefined 区别
null: 表示“无”或“空”值,通常由程序员显式赋值。 undefined: 表示未定义,通常是变量声明后未初始化或属性不存在。
js的类型转换机制
常见的类型转换有:
-
强制转换(显示转换)
- Number()
- parseInt()
- String()
- Boolean()
- Number()
-
自动转换(隐式转换)
- 比较运算(
==、!=、>、<)、if、while需要布尔值地方 - 算术运算(
+、-、*、/、%)
- 比较运算(
== 与 ===的区别
== 运算符在比较两个值时,会先进行类型转换(如果类型不同),然后再进行比较。这种类型转换有时被称为“类型强制转换”(type coercion)。 === 运算符在比较两个值时,不会进行类型转换,只有在类型相同且值相等的情况下,才会返回 true。这种比较被称为“严格相等”或“全等”。
垃圾回收机制
js引擎(如 V8)有自动的垃圾回收机制,用于管理堆内存。当某个对象不再被引用时,垃圾回收器会自动释放其占用的内存,防止内存泄漏。
垃圾回收的核心机制包括:
- 标记-清除算法:垃圾回收器会标记那些仍在使用的对象,并清除那些未被引用的对象。
- 引用计数:通过跟踪对象的引用次数,当引用数为 0 时,对象将被回收。
内存泄漏
内存泄漏是指程序分配的内存由于某种原因未能释放,从而导致内存使用不断增加的问题。随着时间的推移,内存泄漏会导致可用内存减少,最终可能导致程序崩溃或系统性能下降。
内存泄漏的常见原因有哪些?
- 全局变量:未正确使用
var、let或const声明的变量会成为全局变量,导致内存无法释放。 - 闭包:闭包中未正确释放的变量会导致内存泄漏。
- 定时器和回调:未清除的
setInterval或setTimeout回调函数会导致内存泄漏。 - DOM引用:未正确移除的 DOM 元素或事件监听器会导致内存泄漏。
- 缓存:不合理的缓存策略会导致内存泄漏。
如何检测内存泄漏?
浏览器开发者工具 Allocation instrumentation on timeline 分析快照或时间轴,查找未释放的对象
什么是内存,作用是什么
内存是计算机中用于存储数据和指令的硬件。其作用包括:
- 存储数据: 保持程序运行时的数据状态。
- 快速访问: 提供快速读写操作,相比于硬盘存储更快。
闭包
因为作用域链的存在,可以在一个内层函数中访问到其外层函数的作用域
使用场景
- 创建私有变量
- 延长变量的生命周期 计数器、延迟调用、回调就是还是创建私有变量和延长变量的生命周期
为什么要使用闭包:局部变量无法共享和长久的保存,而全局变量可能造成变量污染。 闭包可以读取函数内部的局部变量,且不会被垃圾回收机制回收,可以长期保存。
什么是作用域链
是 js 中用于查找变量和函数的一种机制。每个 js 函数都会创建一个作用域链。
工作原理:当执行一个函数时,引擎会首先在函数的局部作用域中查找变量。如果没有找到,则会沿着作用域链向上查找,依次查找所有嵌套的外部作用域,直到全局作用域。如果仍未找到该变量,则会抛出引用错误。
- 全局作用域:最外层的作用域,在浏览器中通常是
window对象。在全局作用域中声明的变量可以在任何地方访问。 - 函数作用域:使用
var声明的变量具有函数作用域,每个函数都会创建自己的作用域。函数内部声明的变量只能在该函数内部访问。 - 块级作用域:由
let和const关键字引入,作用域被限制在块级(例如{}内)。 - 作用域链:当查找变量时,JavaScript引擎首先查找当前作用域,如果没有找到,则沿着作用域链向上查找,直到找到变量或到达全局作用域。如果仍然没有找到变量,则抛出错误。
什么是原型?
原型是每个JavaScript对象的一个属性,称为 prototype,它是一个对象,包含了该对象的所有共享属性和方法。通过原型,JavaScript对象可以实现继承和共享属性和方法。
什么是原型链?
原型链是JavaScript中实现继承的一种机制。当访问一个对象的属性时,如果该属性在对象自身上不存在,JavaScript会沿着该对象的 prototype 链向上查找,直到找到该属性或到达链的顶端(即 Object.prototype 的 prototype,它是 null)。这种层层查找的机制称为原型链。
JS继承的方法有哪些?优缺点?
JS继承的方法有以下几种:原型链继承、构造函数继承、组合继承、原型式继承和寄生式继承,寄生组合式继承,ES6 Class实现继承。
-
原型链继承:将父类的实例作为子类的原型,从而实现对父类属性和方法的继承。
- 优:写法方便简洁。
- 缺:不能传递参数和共享所有继承的属性和方法,当一个发生改变另外一个随之改变。
-
构造函数继承:在子类的构造函数中调用父类的构造函数,使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上,从而实现对父类实例属性的继承。
- 优点:解决了原型链继承不能传参的问题和父类的原型共享的问题。
- 缺点:方法都在构造函数中定义,因此无法实现函数复用。
-
组合继承:将原型链继承和构造函数继承结合起来,既可以实现对父类原型属性和方法的继承,又可以实现对父类实例属性的继承。
- 优点: 解决了原型链继承和构造函数继承造成的影响。
- 缺点: 无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
-
原型式继承:通过创建一个临时构造函数来实现对父类的属性和方法的继承。
- 优点:不需要单独创建构造函数。
- 缺点:属性中包含的引用值始终会在相关对象间共享。
-
寄生式继承:在原型式继承的基础上,通过在临时构造函数中添加方法和属性,从而实现对父类的继承。
- 优点:写法简单,不需要单独创建构造函数。
- 缺点:通过寄生式继承给对象添加函数会导致函数难以重用。
-
寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
- 优点:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变。
- 缺点:复杂。
-
ES6 Class实现继承:ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面 (Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法加到 this 上面 (所以必须先调用super方法),然后再用子类的构造函数修改 this。需要注意的是,class 关键字只是原型的语法糖,JS继承仍然是基于原型实现的。 优点:语法简单易懂,操作更方便。缺点:并不是所有的浏览器都支持 class 关键字 lass Person。
js经常使用的数组方法
push在数组末尾添加一个或多个元素,并返回新数组的长度。pop移除并返回数组末尾的元素。slice(): 从数组中提取指定位置的元素,返回一个新的数组,不会修改原始数据。splice(): 从指定位置删除或替换元素,可修改原始数组。concat(): 合并两个或更多数组,并返回新的合并后的数组,不会修改原始数组。unshift(): 在数组开头添加一个或多个元素,并返回新数组的长度。shift(): 移除并返回数组开头的元素。indexOf(): 查找指定元素在数组中的索引,如果不存在则返回-1。lastIndexOf(): 从数组末尾开始查找指定元素在数组中的索引,如果不存在则返回-1。includes(): 检查数组是否包含指定元素,返回一个布尔值。join(): 将数组中的所有元素转为字符串,并使用指定的分隔符连接它们。reverse(): 颠倒数组中元素的顺序,会修改原始数组。sort(): 对数组中的元素进行排序,默认按照字母顺序排序,会修改原始数组。filter(): 创建一个新数组,其中包含符合条件的所有元素。map(): 创建一个新数组,其中包含对原始数组中的每个元素进行操作后的结果。reduce(): 将数组中的元素进行累积操作,返回一个单一的值。forEach(): 对数组中的每个元素执行提供的函数。- 修改原数组:push、pop、shift、unshift、splice、reverse、sort、fill。
- 不修改原数组:slice、concat、indexOf、lastIndexOf、join、toString、filter、every、some、forEach、map、find、findIndex。
js经常使用的字符串方法
除了常用+以及${}进行字符串拼接之外
-
concat 用于将一个或多个字符串拼接成一个新字符串
-
删的有 slice() - substr() - substring()
-
改
- trim()、trimLeft()、trimRight() 删除前、后或前后所有空格符,再返回新的字符串
- toLowerCase()、 toUpperCase() 大小写转化
- repeat() 接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
- padEnd() 复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
-
查
- indexOf() 从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )
- includes() 从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
- chatAt() 返回给定索引位置的字符,由传给方法的整数参数指定
-
转换方法
- split 把字符串按照指定的分割符,拆分成数组中的每一项 str = "12+23+34" let arr = str.split("+") // [12,23,34]
-
正则表达式
- replace() 接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)
- search() 接收一个参数,可以是一个正则表达式字符串,也可以是一个
RegExp对象,找到则返回匹配索引,否则返回 -1 - match() 接收一个参数,可以是一个正则表达式字符串,也可以是一个
RegExp对象,返回数组
数组去重方法
- 双重 for 循环:这是一个最笨的方法。
- 对象属性 key:利用对象属性名 key 不可以重复这一特点,如果对象中不存在,就 push 进空数组。
- for 循环 + indexOf:主要是利用 indexOf 的特性,查找元素返回下标,找不到就返回-1。-1就 push 进空数组。
- for 循环 + sort 排序:利用数组的 sort 排序方法去重,如果第 i 项 和 i-1 项不一致,就 push 进空数组。
- filter + indexOf:利用 filter 过滤配合 indexOf 查找元素,判断返回元素下标和过滤数组的 index 是否相等。
- Set:Es6 中新增了数据类型 Set,Set 的最大一个特点就是数据不重复,可以用作数组去重。new Set 方法,返回是一个类数组,需要结合扩展运算符...,转成真实数组。
- set + Array.from:Set 去重结合 Array.from 转成真实数组。Array.from(new Set(原数组))
- for 循环 + includes:includes 用来判断一个数组是否包含一个指定的值,是就返回 true,否则返回 false。判断空数组是否包含原数组的某个元素,不包含就 push 进空数组。
- reduce + includes:利用 reduce 遍历结合 includes去重。
set map的区别
set是集合的数据结构,map是一种叫字典的数据结构
set是es6新增的数据结构,类似于数组,但成员的值是唯一的,没有重复的值
Set本身是一个构造函数,用来生成 Set 数据结构 Set的实例关于增删改查的方法:
- add() 添加某个值,返回
Set结构本身 - delete() 删除某个值,返回一个布尔值,表示删除是否成功
- has() 返回一个布尔值,判断该值是否为
Set的成员 - clear() 清除所有成员,没有返回值
Set实例遍历的方法有如下: - keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
Map
- size 属性
size属性返回 Map 结构的成员总数。 - set()
- get()
get方法读取key对应的键值,如果找不到key,返回undefined - has()
has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中 - delete()
delete方法删除某个键,返回true。如果删除失败,返回false - clear()
遍历
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回所有成员的遍历器
- forEach():遍历 Map 的所有成员
new 一个箭头函数
箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。
new操作符的实现步骤如下:
1、创建一个空的js对象(即{});
2、为步骤1新创建的对象添加属性proto,将该属性链接至构造函数的原型对象 ;
3、将步骤1新创建的对象作为this的上下文 ;
4、如果该函数没有返回对象,则返回this。
所以,上面的第二、三步,箭头函数都是没有办法执行的。
Object.defineProperty与Proxy的区别
在 Vue2.x 的版本中,双向绑定是基于 Object.defineProperty 方式实现的。而 Vue3.x 版本中,使用了 ES6 中的 Proxy 代理的方式实现。
-
语法差异:
Object.defineProperty是 ES5 中提供的一种 API,用于定义对象的属性。Proxy是 ES6 中提供的一种新的代理对象,用于定义对象的自定义行为。
-
支持程度:
Object.defineProperty只能监视对象的属性,需要遍历对象的每个属性进行定义。Proxy则可以监视整个对象,并且可以监视对象的所有属性以及对象的方法调用。
-
性能:
-
由于
Object.defineProperty需要遍历对象的每个属性进行定义,当对象属性较多时,性能会受到影响。Proxy的性能通常比Object.defineProperty更好,因为它可以一次性监视整个对象,并且可以直接拦截对对象的访问、赋值、删除等操作,而无需遍历属性。
-
API 和用法:
- 使用
Object.defineProperty需要手动为对象的每个属性定义 getter 和 setter 方法。 - 使用
Proxy则可以通过提供一个处理程序对象,来定义对象的自定义行为,例如拦截属性的获取、赋值、删除等操作。
- 使用
使用 Object.defineProperty 会产生:不能监听数组的变化
在 Vue2.x 中解决数组监听的方法是将能够改变原数组的方法进行重写实现(比如:push、 pop、shift、unshift、splice、sort、reverse)
Proxy 针对的整个对象,Object.defineProperty 针对单个属性,这就解决了 需要对对象进行深度递归(支持嵌套的复杂对象劫持)实现对每个属性劫持的问题
箭头函数与普通函数的区别
- 语法更加简洁、清晰
- 箭头函数不会创建自己的this(重要!!深入理解!!)没有自己的this,它会捕获自己在定义时(注意,是定义时,不是调用时)所处的外层执行环境的this,并继承这个this值。
- 箭头函数继承而来的this指向永远不变(重要!!深入理解!!)
- .call()/.apply()/.bind()无法改变箭头函数中this的指向
- 箭头函数不能作为构造函数使用
- 箭头函数没有自己的arguments
- 箭头函数没有原型prototype
- 箭头函数不能用作Generator函数,不能使用yeild关键字
bind、apply、call 区别
bind:
- 返回一个新的函数,而不是立即调用。
- 第一个参数是要绑定的
this值,后续参数是预设的参数。 - 语法:
const newFunc = func.bind(thisArg, arg1, arg2, ...) - 新函数可以在后续的时间被调用。
apply:
- 立即调用函数。
- 第一个参数是要绑定的
this值,第二个参数是一个数组或类数组对象,包含传递给函数的参数。以数组形式參數 - 语法:
func.apply(thisArg, [argsArray])
call:
- 立即调用函数。
- 第一个参数是要绑定的
this值,传递参数时逐个传递。 - 语法:
func.call(thisArg, arg1, arg2, ...)
深拷贝
深拷贝开辟一个新的栈,两个对象的属性完全相同,但对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性 常见的深拷贝方式有:
- _.cloneDeep()
- jQuery.extend()
- JSON.stringify()
- 手写循环递归
浅拷贝
-
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝 修改会相互影响
-
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址,新旧对象还是共享同一块内存,修改对象属性会影响原对象 方法:
-
Object.assignArray.prototype.slice(),Array.prototype.concat()- 解构赋值
-
this 关键字在不同作用域中的表现
- 全局作用域 this指向的全局对象 window
- 函数作用域 在非严格模式下,函数中的
this也指向全局对象;在严格模式下,this是undefined。 - 对象方法
this指向调用该方法的对象 - 箭头函数:箭头函数没有自己的
this,它会捕获定义时所在作用域的this。
this的对象
在绝大多数情况下,函数的调用方式决定了 this 的值 下面几种情况:
- 默认绑定 全局环境
this指向window - 隐式绑定 函数还可以作为某个对象的方法调用,这时
this就指这个上级对象this永远指向的是最后调用它的对象 - new绑定 此时
this指向这个实例对象 - 显示绑定
apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
for循环和forEach有什么区别?⭐⭐
- for 循环的 return 是终止循环 forEach 是返回参数。
- for 循环实际上是可以使用 break 和 continue 去终止循环的,但是 forEach 不行。
- forEach 不支持在循环中添加删除操作。
- for 多数时候都可以使用,一般我们需要知道循环次数。而 forEach 更适合于集合对象的遍历和操作。
数组迭代的方法有哪些? ⭐
- for。
- for in:可以遍历对象, 首个行参是 key。
- for of:只能遍历数组,首个行参是 value:。
- forEach:for的增强版,特殊简化版,不支持在循环中添加删除操作。
- while :先判断后执行。
- do while:先执行后判断。
- some:所有返回值都为真则返回真,否则返回假。 every:反之。
JS模块化有哪些?
commonjs、es6、amd、cmd
JS的几种具体异常类型(报错)
- SyntaxError:语法错误
- ReferenceError:引用错误
- RangeError:范围错误
- typeError:类型错误
- URLError:与 url 相关参数不正确
- EvalError:全局函数 eval 执行错误
在创建对象的时候, new class 和 new function 可有什么区别
- 语法和语义:
class提供了一种清晰、模块化的方式来定义构造函数和原型方法。通过class关键字声明类使得代码更加直观易懂。 - 继承:使用
class语法,可以通过extends关键字更加简洁地实现继承。而在传统的函数式继承中,需要手动设置原型链。 - 严格模式:使用
class语法定义的类的方法自动运行在严格模式下("use strict"),而传统的构造函数则需要手动声明。 - 构造函数和原型方法的声明:
class语法使得构造函数和原型方法的声明更加直观和组织化,而在传统的构造函数中,需要分别设置构造函数的属性和其原型的方法。
Js执行机制
执行机制通过执行上下文、任务队列和事件循环来管理代码的执行顺序,确保异步操作的有效处理
几个概念:
1.执行上下文(Execution Context)
- 全局执行上下文:代码在全局环境中执行时创建,只有一个。
- 函数执行上下文:每当一个函数被调用时,都会创建一个新的执行上下文。
2. 任务队列(Task Queue)
- JavaScript是单线程的,所有的代码在一个主线程中执行。
- 任务队列分为宏任务和微任务。
3. 事件循环(Event Loop)
- 事件循环是JavaScript处理异步操作的机制。
- 它不断检查任务队列,执行宏任务(如
setTimeout、setInterval等),然后执行微任务(如Promise的回调)。
4. 微任务与宏任务
- 宏任务:包括整体代码、
setTimeout、setInterval等。 - 微任务:包括
Promise的回调、MutationObserver等。
执行顺序
- 执行全局代码,创建全局执行上下文。
- 将宏任务放入宏任务队列。
- 执行微任务队列中的所有微任务,直到队列为空。
- 从宏任务队列中取出一个宏任务执行。
- 重复步骤3和4,直到所有任务完成。
异步相关
解释异步和同步的区别:
同步:操作按顺序执行,一个操作完成后才能执行下一个。
异步:操作可以并行执行,一个操作不会阻塞其他操作,执行完毕后通过回调或其他机制通知主线程。
异步执行的核心机制
JavaScript 是单线程的,依赖事件循环 (Event Loop) 来管理同步和异步任务。异步操作通常会被放入任务队列(宏任务或微任务),当主线程空闲时,事件循环会从任务队列中取出任务执行。
JS 事件循环机制 (Event Loop)
是运行时如何处理异步操作、事件和回调的核心机制
事件循环检查调用栈是否为空,如果为空,则从消息队列中取出消息并执行。主要用于处理异步操作和事件
-
执行栈: 所有的代码都在一个执行栈中,执行同步代码。
-
任务队列: 是一个队列结构,存放着待执行的异步任务回调函数。当异步操作完成后,对应的回调函数会被放入任务队列中
根据异步任务的类型,任务队列可以分为两类:
- 宏任务队列: 包含
setTimeout、setInterval、setImmediate、I/O 操作、UI 渲染等任务。 - 微任务队列: 包含
Promise的then、catch、finally回调等任务。
- 宏任务队列: 包含
-
事件循环在执行同步任务后,会检查微任务队列并执行所有微任务,然后再执行宏任务队列中的第一个任务。
Promise
是异步编程的一种解决方案
一、状态
- pending 进行中
- fulfilled 成功
- rejected 已失败
二、用法
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”reject函数的作用是,将Promise对象的状态从“未完成”变为“失败
三、实例方法
- then() 实例状态发生改变时的回调函数,第一个参数是
resolved状态的回调函数,第二个参数是rejected状态的回调函数.then方法返回的是一个新的Promise实例 - catch() 用于指定发生错误时的回调函数,catch 方法可以链式调用,而不需要在每次调用 then 方法时都传递第二个参数。
- finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
Promise构造函数存在以下方法:
- all() 用于将多个
Promise实例,包装成一个新的Promise实例 通过all()实现多个请求合并在一起,汇总所有请求结果, - race() 同样是将多个 Promise 实例,包装成一个新的 Promise 实例
- allSettled() 法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
- resolve() 将现有对象转为
Promise对象 - reject()
- try()
如何让Promise.all在抛出异常错误依然有效
在处理多个并发请求时,我们一般会用Promise.all()方法。 romise.all 中任何一个 promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用。
方案一:
在promise.all队列中,使用map过滤每一个promise任务,其中任意一个报错后,return一个返回值,确保promise能正常执行走到.then中。
var p1 = new Promise((resolve, reject) => { resolve('p1'); });
var p2 = new Promise((resolve, reject) => { resolve('p2'); });
Promise.all([p1, p2].map(p => p.catch(e => '出错后返回的值' )))
.then(values => {
console.log(values);
}).catch(err => {
console.log(err); })
方案二:
使用 Promise.allSettled 替代 Promise.all()。 它会等待所有 promise 都被处理完(无论是 fulfilled 还是 rejected)
async 和 await 的工作原理是什么?它们如何与 Promise 交互
async与await是用来处理异步操作的语法糖,使得异步代码看起来更像同步代码。
async:标记一个函数为异步函数,返回一个 Promise。await:暂停异步函数的执行,等待 Promise 完成,并返回结果。
什么是微任务(microtask)和宏任务(macrotask)?Promise 在其中扮演什么角色
微任务和宏任务是 JS 事件循环的重要组成部分
微任务: 包括
- setTimeout 和 setInterval 定时器
- DOM 事件处理程序
- AJAX 请求的回调函数
- script 标签的加载和执行操作等
- 每次事件循环会执行一个宏任务队列中的一个任务,然后检查微任务队列。
宏任务: 包括
- Promise 的 then 方法和 catch 方法
- async/await 中的 await 表达式
- MutationObserver 监听器
- 微任务会在当前宏任务执行完后立即执行,并且会在进入下一个宏任务之前执行完所有的微任务。具有更高的执行优先级 执行顺序是:
- 执行一个宏任务 ->
- 执行所有的微任务 ->
- 渲染页面(如果需要) ->
- 再执行下一个宏任务。 Promise 的回调函数会被添加到微任务队列中,因此会在当前宏任务结束后立即执行,而不会等待下一个宏任务。
事件循环Event Loop的工作原理
js是单线程得 ,通过事件循环Event Loop来处理异步操作。原理如下
- 执行栈:代码从上到下执行,所有的执行上下文压入到行栈中。
- 宏任务队列:步任务(如
setTimeout、setInterval、I/O 操作)完成后会将回调函数放入宏任务队列。 - 微任务队列:微任务(如
Promise回调、process.nextTick)会将回调函数放入微任务队列
步骤
- 检查执行栈是否为空。如果为空,则从宏任务队列中取出第一个任务并执行。
- 在当前宏任务执行完后,检查并执行所有微任务队列中的任务。
- 执行完所有微任务后,如果需要,进行 UI 渲染。
- 然后开始下一轮循环。
事件对象?
- event:事件对象。
- currentTarget:绑定的事件。
- target:触发的事件。
- eventPhase:返回当前触发的阶段(捕获阶段1,事件派发阶段2,冒泡阶段3)
- type:返回当前event对象的事件名。
事件三要素
- 事件目标(Target) : 触发事件的元素。
- 事件类型(Type) : 事件的种类,如
click,keypress。 - 事件处理函数(Handler) : 响应事件的回调函数。
DOM 事件模型
DOM 事件模型的确是浏览器处理和响应用户交互事件的机制,分为捕获阶段、目标阶段、冒泡阶段。
- 捕获阶段:事件从文档的最顶层(如
window、document)开始,一直向下传播到目标元素。你可以在这个阶段拦截事件。 - 目标阶段:事件到达目标元素,并在目标元素上执行相应的事件处理程序。
- 冒泡阶段:事件从目标元素向上传播,逐级经过其祖先元素,直到到达
window或document。在这个阶段,也可以处理事件。
事件冒泡 / 事件捕获 / 事件委托
事件冒泡
事件冒泡 是指事件从触发的元素(目标元素)向其祖先元素逐级向上传播。例如,如果点击一个按钮,事件会从按钮传递到父级的 div、再到 body,最后到达 document。冒泡的默认行为使得你可以在祖先元素上统一处理事件。
事件捕获
事件捕获 是事件从最顶层元素(如 window、document)开始逐级向目标元素传递的过程。通过设置 addEventListener 的第三个参数为 true,可以让事件在捕获阶段被监听。
element.addEventListener('click', handler, true);// 捕获阶段element.addEventListener('click', handler, false);// 冒泡阶段(默认)
事件委托
事件委托 是一种将事件监听器绑定在父级或祖先元素上,以处理其子元素的事件的技术。它利用事件冒泡的机制,从而不需要在每个子元素上都绑定事件监听器。事件委托可以显著提高性能,尤其是在动态生成的或数量较多的子元素中。
Module
模块,可以进行代码抽象,代码封装,代码复用,依赖管理 模块功能主要由两个命令构成:
export:用于规定模块的对外接口import:用于输入其他模块提供的功能
因此,需要一种将JavaScript程序模块化的机制
- CommonJs (典型代表:node.js早期),是一套
Javascript模块规范,用于服务端 - AMD (典型代表:require.js) 异步模块定义,采用异步方式加载模块,都定义在一个回调函数中,等到模块加载完成之后,这个回调函数才会运行
- CMD (典型代表:sea.js)
执行上下文 执行栈
执行上下文是一种对Javascript代码执行环境的抽象概念 执行上下文的类型分为三种:
- 全局执行上下文:只有一个,浏览器中的全局对象就是
window对象,this指向这个全局对象 - 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
- Eval 函数执行上下文: 指的是运行在
eval函数中的代码,很少用而且不建议使用
执行上下文的生命周期包括三个阶段:创建阶段(确定 this 的值,组件被创建) → 执行阶段(执行变量赋值、代码执行) → 回收阶段(执行上下文出栈等待虚拟机回收执行上下文)
执行栈
也叫调用栈,当Javascript引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中
怎么判断一个页面是刷新还是重新打开
- 使用
sessionStorage是一个用于存储在一个会话中数据的对象,当页面被重新打开时,sessionStorage会被清空;但在页面刷新时,数据会保留。 - 使用页面生命周期 API
visibilitychange - 使用浏览器会话恢复机制
performance.navigation.type值来判断页面的加载类型 - 在 Vue 中使用
created钩子和sessionStorage。 - 在 Vue 中使用 Vuex 状态管理。
- 在 React 中使用
useEffect钩子和useState。 - 在 React 中使用 Context 和
useReducer或useState管理全局状态。
0.1 + 0.2 不等于 0.3 这是什么原因,要怎么解决
- 将浮点数乘以一个适当的倍数转换为整数进行计算,计算完成后再除以这个倍数转换回浮点数: let num1 = 0.1 * 10;
- 使用第三方库,如
decimal.js,它提供了更精确的十进制运算
vue
什么是虚拟DOM
虚拟 DOM,实际上它只是一层对真实DOM的抽象
vue2.0 diff算法
diff 算法是一种通过同层的树节点进行比较的高效算法 有两个特点:
- 比较只会在同层级进行, 不会跨层级比较
- 在diff比较的过程中,循环从两边向中间比较,深度优先,同层比较
React/Vue2 项目中 key 的作用
它们有助于优化和正确处理 DOM 元素的更新。
- 唯一标识元素 属性同样用于标识列表中的唯一元素,确保在更新虚拟 DOM 时能够正确识别和更新元素
- 提升性能更高效地处理列表的更新,避免不必要的重渲染。
- 强制重新渲染:在某些情况下,如果希望强制某个元素重新渲染,可以通过改变
key的值来实现
vue2/vue3的生命周期一起
| Vue 2 | Vue 3 | 描述 |
|---|---|---|
beforeCreate | setup() | 初始化组件时调用,Vue 3 中 setup 替代。 |
created | setup() | 完成实例初始化后调用,Vue 3 中 setup 替代。 |
beforeMount | onBeforeMount | 在挂载之前调用。 |
mounted | onMounted | 挂载完成后调用。 |
beforeUpdate | onBeforeUpdate | 数据更新之前调用。 |
updated | onUpdated | 数据更新后调用。 |
beforeDestroy | onBeforeUnmount | 组件销毁前调用,Vue 3 中改为 onBeforeUnmount。 |
destroyed | onUnmounted | 组件销毁后调用,Vue 3 中改为 onUnmounted。 |
activated | onActivated | keep-alive 组件激活时调用。 |
Vue2 与 Vue3 生命周期的区别
在 Vue2 中,生命周期方法绑定在 this 上,所有的逻辑需要在类式组件结构中进行定义。
在 Vue3 中,使用 setup 函数可以直接定义生命周期方法,同时可以与其他 setup 中的逻辑合并在一起,消除了代码的分散性。
我们通常会用 onMounted 钩子在组件挂载后发送异步请求,获取数据并更新组件状态。
这是因为onMounted钩子在组件挂载到DOM后调用,而发送异步请求通常需要确保组件已经挂载,以便正确地操作DOM或者更新组件的状态。
Vue 初始化做了什么
Vue 初始化时,会做以下操作:
- 解析模板:将模板转换为渲染函数。
- 初始化响应式数据:通过
Object.defineProperty(Vue 2) 或Proxy(Vue 3) 为数据对象的属性创建 getter 和 setter。 - 调用生命周期钩子:如
beforeCreate、created。 - 挂载组件:将组件挂载到 DOM 上。
vue2父子组件的生命周期 按照“先父后子”的顺序进行h
父组件创建阶段
- 父组件
beforeCreate钩子:在实例初始化之后 - 父组件
created钩子:建完成后立即调用例,已完成数据观测、属性和方法的初始化,但尚未挂载 DOM。 - 父组件
beforeMount钩子:在挂载开始之前被调用,相关的 render 函数首次被调用。
子组件创建阶段
- 子组件
beforeCreate钩子 - 子组件
created钩子 - 子组件
beforeMount钩子:在子组件挂载开始之前调用。 - 子组件
mounted钩子:子组件挂载到 DOM 上后立即调用。
父组件挂载阶段
- 父组件
mounted钩子:父组件挂载到 DOM 上后立即调用。
更新阶段
在更新阶段,如果父组件的数据变化会触发以下生命周期钩子
- 父组件
beforeUpdate钩子:在数据变化导致的虚拟 DOM 重新渲染之前调用。 - 子组件
beforeUpdate钩子:在子组件的数据变化或父组件重新渲染之前调用。 - 子组件
updated钩子:子组件的虚拟 DOM 重新渲染并应用到实际 DOM 后调用。 - 父组件
updated钩子:父组件的虚拟 DOM 重新渲染并应用到实际 DOM 后调用。
销毁阶段
当父组件或子组件被销毁时,生命周期钩子按以下顺序调用
- 父组件
beforeDestroy钩子:在实例销毁之前调用。这一步,实例仍然完全可用。 - 子组件
beforeDestroy钩子:在子组件实例销毁之前调用。 - 子组件
destroyed钩子:子组件实例销毁后调用。 - 父组件
destroyed钩子:父组件实例销毁后调用。
双向数据绑定的原理
是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
- 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
- compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- Watcher订阅者是Observer和Compile之间通信的桥梁
双向数据绑定的原理
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
- 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
- compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
- MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
MVVM、MVC、MVP的区别
- MVC 通过分离 Model(数据)、View{页面} 和 Controller(q桥梁)
- MVVM Model(代表数据模型,数据和业务逻辑)、View代表UI视、ViewModel(负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作)
- MVP 与 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。
v-if、v-show区别
使用 v-if 时,元素会根据条件动态添加或移除 DOM 节点,在每次条件变化时对 DOM 进行 动态渲染 或 销毁。开销较大,适合用于条件变化不频繁的场景。
使用 v-show 时,元素始终在 DOM 中,只是通过 CSS(display: none 隐藏,display: block 显示) 控制显示隐藏,性能开销较小,适合频繁切换显示状态的场景。
v-if、v-show、v-html 的原理
- v-if会调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染;
- v-show会生成vnode,render的时候也会渲染成真实节点,只是在render过程中会在节点的属性中修改show属性值,也就是常说的display;
- v-html会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。
v-for指令中的key属性有什么作用?为什么要使用它?
v-for指令中的key属性用于给每个迭代项设置一个唯一的标识符
帮助Vue.js跟踪每个节点的身份,以便在数据发生变化时高效地更新DOM。使用key属性可以避免出现错误的节点更新或重新排序的问题。
Vue 怎么做权限管理
通过 Vue Router 的导航守卫 (beforeEach) 来进行权限控制,判断用户的角色或登录状态是否有权访问某个路由。可以结合 Vuex 或后端返回的用户权限数据来做进一步管理。
hash 模式和 history 模式区别
- Hash 模式:URL 中会有
#,例如http://example.com/#/home。#后的部分不会被发送到服务器,适用于单页应用。 - History 模式:使用 HTML5 的
pushStateAPI,URL 没有#,例如http://example.com/home。需要服务器配置,否则刷新页面时会出现 404 错误
Vue 项目脚手架
Vue 项目通常使用 Vue CLI 来初始化项目,它提供了项目模板、热重载、自动化配置等功能。
-
特性:
- 支持 Babel 和 TypeScript
- 支持 PWA
- 预设和插件管理
- 内置的 Vue Router 和 Vuex 集成
vue2.0不能检查数组的变化,怎么解决
Vue2.0对于响应式数据的实现有一些不足:
- 无法检测数组/对象的新增
- 无法检测通过索引改变数组的操作。
原因:
vue检测数据的变动是通过Object.defineProperty实现的,所以无法监听数组的添加操作,因为在构造函数中就已经为所有属性做了这个检测绑定操作。
解决方案:
(出于对性能原因的考虑,没有去实现它。而不是不能实现。)
数组 可以通过 Vue.set 或 splice 来更新数组,从而触发视图更新。
- this.$set(array, index, data)
this.dataArr = this.originArr
this.$set(this.dataArr, 0, {data: '修改第一个值'}) console.log(this.dataArr)
console.log(this.originArr) //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果
- splice 因为splice会被监听有响应式,而splice又可以做到增删改。
- 利用临时变量进行中转
vue如何监听对象或者数组某个属性的变化
-
this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value)
-
调用以下几个数组的方法
- splice()、 push()、pop()、shift()、unshift()、sort()、reverse()
对象
- this.$set(obj, key ,value) - 可实现增、改
- watch时添加
deep:true深度监听,只能监听到属性值的变化,新增、删除属性无法监听
Vue 修饰符
.stop:阻止事件冒泡。
.prevent:阻止默认事件。
.self:只有在事件来自元素本身时触发。
.capture:在捕获阶段触发事件。
.once:事件只触发一次。
.passive:提高性能,优化滚动事件的监听。
Computed 和 Watch 的区别
- Computed支持缓存,只有依赖的数据发生了变化(计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。),才会重新计算,Watch不支持缓存,数据变化时,它就会触发相应的操作
- Computed不支持异步,存在异步操作时,无法监听数据的变化,Watch支持异步监听
- Watch监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作函数有两个的参数:
- immediate:组件加载立即触发回调函数
- deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。
data为什么是一个函数而不是对象
- Js中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。在vue中想要复用组件,那就需要每个组件都有自己的数据,组件之间数据才不会相互干扰。
- 如以函数返回值的形式定义,当每次复用组件的时候,就会返回一个新的data,每个组件都有自己的私有数据空间,就不能互相扰互相影响
NextTick
官方回复:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM 意思:Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新
使用场景:在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()
- 第一个参数为:回调函数(可以获取最近的
DOM结构) - 第二个参数为:执行函数上下文
Vue 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决?
数据已经成功添加,但是视图并未刷新 此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局 api $set()
Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
不会立即同步执行重新渲染,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。 同一个watcher被多次触发,只会被推入到队列中一次
vue2 组件通信:
-
props / $emit
父组件通过
props向子组件传递数据,子组件通过$emit和父组件通信 -
eventBus事件总线(
$emit / $on)兄弟组件传值 -
依赖注入(provide / inject)
-
ref / $refs
-
children
子组件可以直接改变父组件的数据吗?
不可以 为了维护父子组件的单向数据流 不建议这么做,Vue 提倡单向数据流 会在浏览器的控制台中发出警告。 只能通过 $emit** 派发一个自定义事件,父组件接收到后,由父组件修改。**
slot(插槽)
通过插槽去更好地复用组件和对其做定制化处理 slot可以
///////////////////////////////////////////////////////////////////////////////////分来以下三种:
- 默认插槽
- 具名插槽 带name的名字的插槽
- 作用域插槽 子组件在作用域上绑定属性来将子组件的信息传给父组件使用
Keep-alive 是什么
keep-alive是vue中的内置组件(组件缓存),能在组件切换过程中将状态保留在内存中,防止重复渲染DOM keep-alive有以下三个属性:
include- 字符串或正则表达式。只有名称匹配的组件会被缓存exclude- 字符串或正则表达式。任何名称匹配的组件都不会被缓存max- 数字。最多可以缓存多少组件实例
原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。
assets和static的区别
相同点: 都是放静态文件的地方 不相同点: assets在项目打包的时候,它生成的资源文件会放在static里面跟着 index.html 一同上传至服务器。
delete和Vue.delete删除数组的区别
delete只是被删除的元素变成了empty/undefined其他的元素的键值还是不变。Vue.delete直接删除了数组 也改变了数组的键值。
默认事件修饰符
.stop调用event.stopPropagation()阻止事件冒泡。.prevent调用event.preventDefault()阻止默认事件行为。.capture使用事件捕获模式添加事件监听器,而不是冒泡模式。.self仅当事件是从事件绑定的元素本身触发时才触发回调。.once事件只触发一次,之后移除事件监听器。.passive以{ passive: true }模式添加监听器,表示不会调用preventDefault()。
Vue.js中的路由导航守卫有哪些?它们的执行顺序是怎样的?
答案:Vue.js中的路由导航守卫包括全局前置守卫、全局解析守卫、全局后置守卫、路由独享守卫和组件内守卫。它们的执行顺序如下:
全局前置守卫(beforeEach) 路由独享守卫(beforeEnter) 解析守卫(beforeResolve) 组件内守卫(beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave) 全局后置守卫(afterEach)
vue编译阶段有什么优化
1. 代码分割
- 移除无用代码:Vue 在编译模板时,会移除不必要的代码,如未使用的指令和组件。确保模板代码简洁且不含多余的逻辑,可以帮助编译器生成更高效的渲染函数。
- 按需加载:通过
import()动态导入组件,Vue CLI 会自动将其打包成独立的 chunk。这种按需加载的方式可以显著减少首屏加载时间。
2. Tree Shaking
- 移除无用代码:在生产环境下,Vue CLI 默认启用 Tree Shaking,以移除未使用的模块和代码。确保代码结构符合 ES Module 标准,使得 Tree Shaking 可以有效工作。
3. Minification(代码压缩)
- 移除无用代码:在生产环境下,Vue CLI 默认启用 Tree Shaking,以移除未使用的模块和代码。确保代码结构符合 ES Module 标准,使得 Tree Shaking 可以有效工作。
4. Minification(代码压缩)
- 压缩代码:Vue CLI 在生产模式下会默认使用 Terser 等工具压缩代码。压缩不仅减少了文件大小,还可以提高加载速度。
- 移除 console 和 debugger:通过配置 Terser,可以在生产构建时自动移除
console.log和debugger语句,避免不必要的输出干扰用户体验。
5. 环境变量和模式
- 使用正确的模式:Vue CLI 提供了不同的模式(如开发模式、生产模式),确保在编译时使用正确的模式。生产模式下会启用各种优化策略,如 Tree Shaking 和 Minification。
- 配置环境变量:通过
.env文件配置环境变量,可以控制在不同模式下的代码行为。例如,在生产环境中禁用开发模式下的一些调试工具。
6. 使用 vue-loader 的优化配置
- 预编译模板:
vue-loader可以将 Vue 组件中的模板部分直接编译成渲染函数,减少运行时的编译开销。 - 开启缓存:在
vue-loader配置中启用缓存(如cache-loader),可以加速二次编译,特别是在开发过程中。
7. 组件懒加载
- 路由懒加载:使用 Vue Router 时,通过动态导入组件的方式实现路由懒加载,只在需要时才加载相应的组件,减少首屏加载时间。
- 非关键组件懒加载:对于一些非关键的组件,如模态框、弹出层等,可以在需要时才加载,从而减少初始加载的体积。
8. 生产模式下的性能优化
- 删除 Vue 开发工具提示:确保在生产环境中删除 Vue 的开发工具提示信息,可以通过
Vue.config.productionTip = false设置。 - 关闭开发模式的错误检测:生产环境中,关闭 Vue 的运行时错误检测,如
Vue.config.devtools = false,以减少开销。
VUE3相关
虚拟DOM其实就是用一个JS对象描述一个DOM节点,实际上对真实 DOM 的一层抽象。最终可以通过一系列操作使这棵树映射到真实环境上。
相当于在js与DOM之间做了一个缓存,利用patch(diff算法)对比新旧虚拟DOM记录到一个对象中按需更新, 最后创建真实的DOM
Vue3中的Composition API是什么?它与Options API有什么区别?
Composition API是Vue.js 3中引入的一种新的组织组件逻辑的方式。它允许开发者通过函数的方式组织和重用逻辑,而不是通过选项对象。相比之下,Options API是Vue.js 2中常用的组织组件逻辑的方式,通过选项对象中的属性来定义组件的数据、方法等。
Vue 3 的 Diff 算法
Vue 3 中的 Diff 算法通过静态标记优化和块节点 (Block Tree) 生成提高了渲染性能,尽可能减少虚拟 DOM 的重新渲染,且处理复杂列表时采用了 双端 Diff 算法。
Vue 3 最长递增子序列
Vue 3 的 Diff 算法在处理列表元素的移动时使用了 最长递增子序列 (LIS) 算法。它可以帮助 Vue 更高效地找到不需要移动的 DOM 元素,从而减少 DOM 操作。
ref 和 reactive 区别
ref:处理基本类型和 DOM 引用,适用于单个值的响应式。通过.value属性来访问或修改其内部值。reactive:处理对象和数组,适合复杂的响应式数据结构。
vue3组件通信:
- 父子组件通信:使用
props和emit。 - 祖孙组件通信:使用
provide和inject。父组件中使用provide提供数据,然后在子组件中使用inject注入这些数据 - 全局状态管理:使用 Vuex 或 Pinia。
Vue2 组件通信:
-
父子组件通信:使用
props和$emit。 -
父组件通过
props向子组件传递数据。- 子组件通过
$emit触发自定义事件,向父组件传递数据。
- 子组件通过
-
兄弟组件通信:使用事件总线eventBus或 Vuex。
-
事件总线:创建一个空的 Vue 实例作为事件中心,兄弟组件通过它来通信。
- Vuex:使用全局状态管理,兄弟组件通过 Vuex 存储和获取数据。
-
祖孙组件通信:使用
provide和inject。- 祖先组件通过
provide提供数据,后代组件通过inject注入数据
- 祖先组件通过
provide和inject是否支持响应式数据?
默认情况下,provide和inject不支持响应式数据
需要在provide中提供一个响应式数据,可以使用ref或reactive将数据包装起来。
然后在inject中使用toRefs或toRef将数据解构出来,以获取响应式的引用。
vue3.0性能提升主要在那些方面
-
更快的渲染 响应式系统:
- 3.0 使用了 ES6 的
Proxy代替了 Vue 2 中的Object.defineProperty实现响应式数据。Proxy 可以直接拦截对象的变化,并且支持对动态属性和数组的更细粒度的监听,减少了性能开销。 编译优化也做了处理
- 3.0 使用了 ES6 的
-
更小的包体积:
- ree-shaking 支持,打包时去除未使用的代码,减小最终打包体积。
- 按需引入特性,模块化设计,减小包体积
-
改进的虚拟 DOM
- 静态提升:从而在渲染时减少不必要的计算。
-
diff算法优化(在
diff算法中相比vue2增加了静态标记,其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较) -
更好的 TypeScript 支持
-
事件监听缓存
-
SSR优化
vue3相比vue2增加了那些
- 性能优化: Vue 3 通过重写虚拟 DOM、编译器和响应式系统等核心部分,Proxy
- Composition API: Vue 3 引入了 Composition API,使得组件的逻辑更加模块化和可复用。Composition API 使用函数式的方式组织组件逻辑,提高了代码的可读性和维护性。
- Teleport 组件: 引入了 Teleport 组件,用于将组件的 DOM 结构移动到指定的目标节点下,以解决传统的模态框等组件嵌套在组件树中的问题。
- Fragment 支持: 支持了 Fragment,允许组件返回多个根节点,简化了组件的编写和布局。
- 全局 API 重构: 对全局 API 进行了重构和简化,例如
Vue.component()、Vue.directive()等全局 API 被替换为app.component()、app.directive()等实例方法。 - 更好的 TypeScript 支持
- 更小的包体积
- Reactivity API: 引入了响应式 API,使得可以更灵活地创建和操作响应式数据,包括
reactive、ref、toRefs等。
Object.defineProperty与Proxy的区别
在 Vue2.x 的版本中,双向绑定是基于 Object.defineProperty 方式实现的。而 Vue3.x 版本中,使用了 ES6 中的 Proxy 代理的方式实现。
-
语法差异:
Object.defineProperty是 ES5 中提供的一种 API,用于定义对象的属性。Proxy是 ES6 中提供的一种新的代理对象,用于定义对象的自定义行为。
-
支持程度:
Object.defineProperty只能监视对象的属性,需要遍历对象的每个属性进行定义。Proxy则可以监视整个对象,并且可以监视对象的所有属性以及对象的方法调用。
-
性能:
- 由于
Object.defineProperty需要遍历对象的每个属性进行定义,当对象属性较多时,性能会受到影响。 Proxy的性能通常比Object.defineProperty更好,因为它可以一次性监视整个对象,并且可以直接拦截对对象的访问、赋值、删除等操作,而无需遍历属性。
- 由于
-
API 和用法:
- 使用
Object.defineProperty需要手动为对象的每个属性定义 getter 和 setter 方法。 - 使用
Proxy则可以通过提供一个处理程序对象,来定义对象的自定义行为,例如拦截属性的获取、赋值、删除等操作。
- 使用
使用 Object.defineProperty 会产生:不能监听数组的变化 , 在 Vue2.x 中解决数组监听的方法是将能够改变原数组的方法进行重写实现(比如:push、 pop、shift、unshift、splice、sort、reverse)
Proxy 针对的整个对象,Object.defineProperty 针对单个属性,这就解决了 需要对对象进行深度递归(支持嵌套的复杂对象劫持)实现对每个属性劫持的问题
proxy 能够监听到对象中的对象的引用吗?
Proxy 默认只能监听最外层对象的属性变化,要监听对象内部嵌套对象的引用变化,需要递归地为每个嵌套对象创建代理。
如果你想要监听一个嵌套对象内部的变化(例如,对象的属性或者数组的元素),那么你需要单独为这个嵌套对象也创建一个 Proxy 实例
vue2 / vue3 核心 diff 算法区别
区别主要是为了提高性能、减少不必要的 DOM 更新,以及更好地支持响应式数据。
Virtual DOM 的处理
- vue2 双向指针遍历的算法 比较算法会逐级比较新旧虚拟 DOM 树的节点,然后更新需要更新的节点。
- Vue 3.x 使用了完全重写的虚拟 DOM 实现,使用了经过优化的单向遍历算法,并且引入了静态提升(Static Hoisting)和模板编译优化(Template Compilation Optimizations)。这些优化使得 Vue 3.x 在处理 Virtual DOM 时更加高效,提高了渲染性能。
对列表更新的优化
- 2中对列表进行更新时,通常采用的是基于索引的方法,会对列表中的每一项进行 diff 操作,性能较差,特别是当列表中包含大量数据时。
- 3中引入了针对列表的新的 diff 策略,称为 Fragments 和 Text Nodes,以及优化后的 Children Patching 策略,对列表进行更新时的性能有了显著的提升。这些优化使得 Vue 3.x 在处理包含大量数据的列表时,渲染性能更好。
Vue3为什么比Vue2快
- 响应式系统优化 基于Proxy实现的响应式机制,这种机制为开发人员提供更加高效的API,也减少了一些运行时代码。
- 编译优化 Vue3的编译器对代码进行了优化,包括减少了部分注释、空白符和其他非必要字符的编译,同时也对编译后的代码进行了懒加载优化。
- 更快的虚拟DOM 对虚拟DOM进行了优化,使用了跟React类似的Fiber算法,这样可以更加高效地更新DOM节点,提高性能。
- Composition API 供逻辑组合和重用的方法来提升代码的可读性和重用性
Vue3如何实现响应式?
- 使用
Proxy对象来实现数据的响应式。Proxy对象允许你拦截对象上的各种操作,包括读取、赋值、删除等,从而实现对数据的监听和劫持。 - 当创建 Vue 组件时,Vue 会将组件的
data对象转换为响应式对象。这些响应式对象由Proxy对象包裹,当访问响应式对象的属性时,Proxy对象会触发相应的拦截器方法。 - 在访问响应式对象的属性时,Vue 3 会自动追踪属性的依赖关系,并将当前组件的更新函数与属性建立关联。
- 当修改响应式对象的属性时,
Proxy对象会触发相应的拦截器方法,从而通知 Vue 更新相关组件的视图。Vue 3 使用了一种称为“触发器”(Trigger)的机制来管理更新,保证更新是异步且批量进行的,从而提高了性能和效率。
watch和watchEffect的区别?
相同点: 都是监听器,watchEffect 是一个副作用函数
不同
watch:既要指明监视的数据源,也要指明监视的回调。- 而
watchEffect可以自动监听数据源作为依赖。不用指明监视哪个数据,监视的回调中用到哪个数据 watch可以访问改变之前和之后的值,watchEffect只能获取改变后的值。watch运行的时候不会立即执行,值改变后才会执行,而watchEffect运行后可立即执行。这一点可以通过watch的配置项immediate改变。watch与vue2.x中watch配置功能一致,但也有两个小坑
侦听器在什么情况下是需要清理副作用的
清理副作用主要指的是在一个响应式侦听器(例如:watch 或 watchEffect)中,当侦听的响应式状态(或侦听的回调函数)重新执行之前或组件销毁时,移除或停止之前创建的资源,以避免内存泄漏、性能问题或意外的行为。
- 使用定时器时: 要在回调函数下一次执行之前清理这个定时器清除。
- 在组件卸载后后 清除订阅外部或异步资源时:
- 响应式引用发生变化时
Vue.js 3中的Suspense是什么?它的作用是什么?
vue3 里面 <script setup> 作用是啥
是一种新的语法糖,用于简化组件的编写和提高开发体验
- 更简洁的代码: 直接在
<script>标签内使用 Composition API - 更好的类型推断: TS
- 易于使用 Composition API 特性
- 简化 Props 和 Emits 定义
- 性能优化
3.x 中 app.config 有哪些应用配置?
app.config.errorHandler
- 作用:为未捕获的异常定义一个全局的处理函数。这在集中处理组件渲染或观察者(watchers)中的异常时非常有用。
- 示例:
javascript 代码解读复制代码app.config.errorHandler = (err, instance, info) => {
// 处理错误
};
app.config.warnHandler
- 作用:为 Vue 运行时警告定义一个全局的处理函数,允许你在开发过程中自定义处理警告的方式。
- 示例:
javascript 代码解读复制代码app.config.warnHandler = (msg, instance, trace) => {
// 处理警告
};
app.config.performance
- 作用:开启性能追踪。.在开发模式下启用,能够测量和追踪组件的初始化、编译时间等性能指标。
- 示例:
javascript
代码解读
复制代码app.config.performance = true;
app.config.compilerOptions
- 作用:允许自定义编译器选项,如模板中的自定义指令等。这对于更细致地控制模板的编译过程很有帮助。
- 示例:
javascript 代码解读复制代码app.config.compilerOptions = {
// 编译器配置
};
app.config.globalProperties
- 作用:定义全局可用的属性。这在 Vue 2 中通过
Vue.prototype实现,Vue 3 中通过app.config.globalProperties实现。 - 示例:
javascript 代码解读复制代码app.config.globalProperties.$utils = {
// 一些全局方法或属性
};
app.config.optionMergeStrategies
-
作用:自定义选项的合并策略。允许你为自定义选项指定如何合并父子选项。
-
示例
:
javascript 代码解读复制代码app.config.optionMergeStrategies.myOption = (parent, child) => { // 合并策略 };
app.config.idPrefix
- 作用:配置此应用中通过 useId() 生成的所有 ID 的前缀。由 3.5+ 版本引入。
- 示例:
javascript 代码解读复制代码app.config.idPrefix = "custom-";
// 在组件中:
const id1 = useId(); // 'my-app:0'
const id2 = useId(); // 'my-app:1'
app.config.throwUnhandledErrorInProduction
- 作用:强制在生产模式下抛出未处理的错误。 由 3.5+ 版本引入。
React
vue与react的区别
-
vue是用于构建用户界面的渐进式框架 , React 是一个用于构建用户界面的 JavaScript 库
-
数据绑定
- vue是使用的双向数据绑定,在模板中,数据和视图是双向绑定的,数据变化会自动更新视图,视图变化也会自动更新数据。
- react提倡的是单向数据流,数据通过组件的 props 从上往下流动,视图变化需要通过事件处理函数来触发数据更新。
-
响应式系统
- vue2
Object.defineProperty实现响应式数据追踪,而 Vue 3 则使用了 Proxy 进行更高效的响应式系统实现。Vue 的响应式系统会自动追踪依赖,并在数据变化时触发视图更新。 - React 通过状态(state)管理和钩子(hooks)来实现响应式。当组件的状态改变时,会触发重新渲染,从而更新视图。React 本身不提供内置的响应式系统,而是通过重新渲染组件来实现。
- vue2
-
组件生命周期
-
vue主要的生命周期钩子包括:
beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy和destroyed。React 类组件的生命周期方法包括:
componentDidMount、componentDidUpdate、componentWillUnmount等。React 函数组件使用
useEffect钩子来管理副作用,相当于componentDidMount、componentDidUpdate和componentWillUnmount的组合。
-
-
模板Template和 JSX
-
状态管理 vuex redex
-
性能优化
- Vue 通过虚拟 DOM 和响应式系统进行性能优化。Vue 3 引入了编译时优化、静态提升和 Patch Flag 等技术,使得渲染和更新更加高效。
- React 通过虚拟 DOM 和 Diff 算法优化性能。React 提供了
shouldComponentUpdate、React.memo和useMemo等方法来控制组件更新。React 还引入了 Fiber 架构,以提高复杂应用的渲染性能。
什么是React?它的核心概念是什么?
React是一个用于构建用户界面的JavaScript库。它的核心概念是组件化和声明式编程。React将用户界面拆分为独立的可重用组件,并使用声明式语法描述组件的状态和UI的关系,使得构建复杂的UI变得简单和可维护。
React的设计思想
- 组件化
- 数据驱动视图
- 虚拟DOM
JSX是什么,它和JS有什么区别
- JSX 是一种语法糖,使得在 JavaScript 中编写用户界面变得更加直观和简洁。
JSX与JS的区别:
- 语法差异 :JSX 允许你在 JavaScript 中编写类似 HTML 的标签语法。JS 只能使用字符串或函数来生成 DOM 结构
- 编译过程: JSX 代码不能直接在浏览器中运行,需要通过 Babel 等工具进行转译,将 JSX 语法转化为纯 JavaScript 代码。JS直接在浏览器中运行。
- 表达能力 在 JSX 中,可以直接使用 JavaScript 表达式,并用
{}包裹。JS 需要使用字符串连接或者其他方法来实现同样的效果。 - 更直观的模板语法
为什么React自定义组件首字母要大写
由于 JSX 语法的规定和 React 内部的处理机制决定的,
React 在处理 JSX 时,会将大写字母开头的标签解析为组件构造函数或类,而小写字母开头的标签被解析为 HTML 原生元素直接映射到 DOM 元素上 如 <div>、<span> 等。
什么是React组件?它们有哪两种类型(函数组件, 类组件)?
React组件是构建用户界面的独立单元。React组件有两种类型:
函数组件: 使用函数来定义组件,接收props作为参数,并返回一个React元素。
类组件: 使用ES6类来定义组件,继承自React.Component类,通过render方法返回一个React元素。
函数组件,类组件区别
- 类组件需要声明constructor,函数组件不需要
- 类组件需要手动绑定this,函数组件不需要
- 类组件有生命周期钩子,函数组件没有
- 类组件可以定义并维护自己的state,属于有状态组件,函数组件是无状态组件
- 类组件需要继承class,函数组件不需要
- 类组件使用的是面向对象的方法,封装:组件属性和方法都封装在组件内部 继承:通过extends React.Component继承;函数组件使用的是函数式编程思想
组件生命周期方法
React 类组件有一系列生命周期方法,如 componentDidMount、componentDidUpdate 和 componentWillUnmount,用于在组件的不同阶段执行代码。函数组件通过 Hooks(如 useEffect)来实现类似的功能。
什么是状态(state)和属性(props)?它们之间有什么区别?
答案:
- 状态(state)是组件自身管理的数据,可以通过setState方法来更新。
- 属性(props)是从父组件传递给子组件的数据,子组件无法直接修改props,只能通过父组件的更新来改变props。
区别:
- 状态(state)是组件内部的数据,可以在组件中自由修改和管理。 可变
- 属性(props)是从父组件传递给子组件的数据,子组件无法直接修改,只能接收和使用。不可变
为什么不直接更新 state 呢 ?
如果试图直接更新 state ,则不会重新渲染组件。
需要使用setState()方法来更新 state。它调度对组件state对象的更新。当state改变时,组件通过重新渲染来响应:
什么是React生命周期方法?列举一些常用的生命周期方法。
React生命周期方法是在组件不同阶段执行的特定方法。以下是一些常用的React生命周期方法:
挂载、更新、卸载
componentDidMount:组件挂载后立即调用。 componentDidUpdate:组件更新后调用。 componentWillUnmount:组件卸载前调用。 shouldComponentUpdate:决定组件是否需要重新渲染。
getDerivedStateFromProps:根据props的变化来更新状态。
简述React的生命周期
React 的生命周期方法是指在组件的不同阶段(挂载、更新、卸载)触发的特定方法 组件的生命周期可以分为挂载、更新、卸载阶
1.挂载阶段
constructor:在组件实例化时调用,用于初始化状态和绑定事件处理程序。static getDerivedStateFromProps:在组件实例化或接收新属性时调用,用于从属性派生状态。render:返回要渲染的 JSX 结构。componentDidMount:在组件首次渲染并挂载到 DOM 后调用,用于执行副作用(如数据获取、订阅)。
2. 更新(Updating)阶段
static getDerivedStateFromProps:每次组件接收新属性时调用,用于从属性派生状态。shouldComponentUpdate:在组件接收到新属性或状态时调用,用于决定组件是否需要重新渲染。返回true或false。render:返回要渲染的 JSX 结构。getSnapshotBeforeUpdate:在最新的渲染输出提交到 DOM 之前调用,用于读取最新的 DOM 数据。componentDidUpdate:在组件的更新已同步到 DOM 后调用,用于执行副作用(如 DOM 操作、数据获取)。
3. 卸载(Unmounting)阶段
componentWillUnmount:在组件从 DOM 中移除之前调用,用于清理副作用(如清除计时器、取消订阅
4. 错误处理(Error Handling)阶段
static getDerivedStateFromError:在渲染期间抛出错误后调用,用于更新状态以显示错误边界。componentDidCatch:在组件树中的某个组件抛出错误后调用,用于执行副作用(如日志记录)。
React 18 引入了一些新的 API 和特性来支持并发渲染:
startTransition:用于标记非紧急更新。React 将优先处理紧急更新(如输入)而延迟非紧急更新。useTransition:用于管理并发 UI 状态,提供isPending状态和startTransition函数。useDeferredValue:允许你推迟更新以保持界面响应。Suspense:扩展以支持更多用例,比如数据加载。Automatic Batching:在 React 18 中,更新将自动批处理,即使是在异步操作中。
如何创建 refs
React.createRef()适用于类组件。 或 useRef 钩子 适用于函数组件。
使用 React.createRef()
import React from 'react';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef(); // 创建 ref
}
componentDidMount() {
// 访问 DOM 节点
this.myRef.current.focus();
}
render() {
return <input ref={this.myRef} type="text" />;
}
}
使用 useRef 钩子
import React, { useRef, useEffect } from 'react';
const MyComponent = () => {
const myRef = useRef(null); // 创建 ref
useEffect(() => {
// 访问 DOM 节点
myRef.current.focus();
}, []);
return <input ref={myRef} type="text" />;
};
React 中 refs 干嘛用的?
Refs 用于直接访问和操作 DOM 元素或 React 组件实例
- 访问 DOM 元素:可以用来获取输入框的值、聚焦某个元素等。
- 管理动画:可以直接控制动画的开始和结束。
- 集成第三方库:在使用非 React 库时,可以通过
refs直接与 DOM 交互。 - 保持组件状态:在某些情况下,可以用
refs来存储不需要引起重新渲染的状态
import React, { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus(); // 直接访问 DOM 元素
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
使用hooks的好处是什么?
- 简化代码结构,减少类组件的复杂性
- 以前使用类组件时,开发者需要管理
this关键字,并将状态和生命周期方法分开。Hooks 让你在 函数组件 中使用状态和副作用,代码结构更简单、更直观。 - 不需要再写类组件,只需要写函数组件即可,这使得组件更加简洁易懂。
- 重用状态逻辑,简化代码维护
- 以前使用类组件时,重用状态逻辑是通过高阶组件(HOC)或 render props 实现的,逻辑复杂且容易混淆。而 Hooks 允许通过自定义 Hooks 来轻松提取和重用状态逻辑。
- 可以将逻辑封装成自定义 Hook,实现逻辑的分离与复用。
- 更好地管理副作用
useEffectHook 替代了类组件中的componentDidMount、componentDidUpdate和componentWillUnmount,统一管理副作用。它允许在函数组件中声明式地处理副作用,例如数据获取、订阅或手动 DOM 操作。- 更容易地处理副作用的清理逻辑,避免内存泄漏或多次执行问题。
- 避免生命周期函数中的重复代码
- 在类组件中,某些逻辑可能会在
componentDidMount和componentDidUpdate中重复。而useEffect可以让你将这些逻辑整合到同一个 Hook 中,避免代码重复。
- 减少
this指向的困扰
- 类组件中经常需要处理
this的指向问题(例如绑定事件处理器)。Hooks 完全消除了this的使用,所有状态和副作用都可以通过函数和闭包来处理,从而减少因为this绑定错误带来的问题。
什么是React Hooks?它们的作用是什么?和解决什么样的问题?
React Hooks是React 16.8版本引入的一种特性,用于在函数组件中使用状态和其他React特性。
Hooks提供了一种无需编写类组件的方式来管理状态和处理副作用,使得函数组件具有类组件的能力。
解决的问题:
- 状态逻辑复用:在类组件中,复用状态逻辑通常需要使用高阶组件或 render props,这可能导致组件嵌套复杂。Hooks 提供了一种无需修改组件结构即可复用状态逻辑的方法。
- 类组件的复杂性:类组件可能会因为生命周期方法的分散而变得复杂。Hooks 允许在函数组件中使用状态和其他 React 特性,使代码更简洁。
- 更好的组织代码:通过 Hooks,可以将相关的状态逻辑组织在一起,而不是分散在不同的生命周期方法中。
常用Hooks
useState是一个用于在函数组件中声明状态变量的 Hook。useEffect是一个用于在函数组件中处理副作用的 Hook,例如数据获取、订阅或手动更改 DOM。useContext用于在函数组件中订阅上下文。它接受一个 context 对象(React.createContext的返回值)并返回当前上下文的值useReducer是一种替代useState的 Hook,用于在复杂状态逻辑时处理状态。它接受一个 reducer 函数和初始状态,返回当前状态和 dispatch 方法。useCallback返回一个 memoized 回调函数,它仅在依赖项发生变化时才会更新。常用于优化子组件的性能。useMemo返回一个 memoized 值,它仅在依赖项发生变化时才会重新计算。常用于性能优化以避免在每次渲染时进行昂贵的计算。useRef返回一个可变的 ref 对象,其.current属性初始化为传入的参数(初始值)。useRef可以用于访问 DOM 节点或存储任何可变值。useLayoutEffect与useEffect类似,但它在所有 DOM 变更之后同步调用。适用于需要读取布局并同步重新渲染的情况。useTransition用于创建一个过渡状态,让用户界面在过渡期间保持响应。它主要用于处理 UI 状态切换时的性能优化useDeferredValue用于延迟更新某些状态,帮助保持用户界面的响应性useId用于生成唯一的 ID,通常用于可访问性属性。useReducer是一种替代useState的 Hook,用于在复杂状态逻辑时处理状态。它接受一个 reducer 函数和初始状态,返回当前状态和 dispatch 方法。
React 的上下文(Context)
上下文提供了一种在组件树中传递数据的方法,而无需手动通过每层组件传递 props。常用于全局状态管理,如主题、用户信息等。
SetState是同步还是异步的
setState 在 React 中的行为可以是同步的,也可以是异步的,这取决于上下文。SetState是异步的
- 异步情况下:在合成事件处理程序、生命周期方法、钩子函数中,
setState是异步的,React 会批量处理这些更新以优化性能。 - 同步情况下:在原生事件处理程序或
setTimeout等回调函数中,setState可能是同步的,但在 React 18 中,许多这些情况也会被自动批处理。
useEffect和useLayoutEffect区别
用于在函数组件中处理副作用。它们的主要区别在于副作用的触发时机和执行上下文。
useEffect 是一个 React Hook,用于在组件渲染到屏幕后执行副作用。常见的副作用包括数据获取、订阅和手动更改 DOM 等。
useLayoutEffect 是另一个 React Hook,它的执行时机在所有的 DOM 变更之后,同步触发副作用。在浏览器绘制之前执行,因此会阻塞页面的渲染。
区别
- 异步执行:
useEffect在浏览器完成布局和绘制后异步调用。这意味着它不会阻塞浏览器更新屏幕。 - 不会阻塞浏览器绘制:因为它是异步的,所以不会影响用户界面的渲染速度。
- 执行时机:默认情况下,
useEffect会在组件首次渲染后和每次更新后执行。
- 同步执行:
useLayoutEffect在所有 DOM 变更后立即同步调用。这使得它适合需要在浏览器绘制前立即读取布局信息并同步更新 DOM 的操作。 - 会阻塞浏览器绘制:因为它是同步执行的,所以会阻塞用户界面的渲染,直到副作用完成。
- 执行时机:
useLayoutEffect在 DOM 变更后、浏览器绘制前同步执行。
使用场景:
useEffect 适用于那些不需要同步阻塞 DOM 渲染的副作用操作,比如:
- 数据获取
- 订阅/取消订阅
- 更改状态或执行副作用,不直接影响布局
useLayoutEffect 适用于那些需要同步处理 DOM 的副作用操作,比如:
- 读取布局和测量 DOM 元素的尺寸或位置
- 同步改变 DOM,确保在浏览器绘制前生效
技术组件和业务组件的划分
- 技术组件:关注于实现特定的技术功能,如 UI 库组件。
- 业务组件:与具体业务逻辑相关,通常组合多个技术组件来实现业务需求。
HOC高阶组件和hooks的区别
高阶组件是一个函数,它接受一个组件并返回一个新的组件。用于重用组件逻辑的设计模式。
Hooks 是 React 16.8 引入的一个特性,允许你在函数组件中使用状态和其他 React 特性。Hooks 是函数,它们在组件的渲染过程中调用,以使用 React 的功能。
代码可读性和简洁性:
- Hooks:使函数组件变得更强大,更简洁、易读。通过 Hooks,可以将状态和副作用逻辑分离和重用。
- HOC:可能导致嵌套和层级过深,特别是多个 HOC 嵌套使用时,代码变得难以理解和维护。
组件层级:
- Hooks:不会增加组件树的层级,因此不会导致性能问题或调试困难。
- HOC:会增加组件树的层级,可能影响性能和增加调试复杂度。
Redux 解决的问题
Redux 是一个状态管理库,解决了 React 应用中状态管理复杂的问题。它提供了一个集中式的状态存储,使得状态管理更可预测和可调试。
Redux工作原理
Redux 是一个用于管理应用状态的 js 库
- 单一状态树(Single Source of Truth) :
- 状态是只读的(State is Read-Only) :
- 使用纯函数来执行修改
核心
- Store(存储):一个全局状态管理对象
- Action(动作) 改变状态的唯一方式是dispatch action
- Reducer(处理器):一个纯函数,根据旧state和props更新新state
- Dispatch(分发)
数据如何在React组件中流动
在 React 中,数据的流动是单向的,这意味着数据只能从父组件传递到子组件。 父组件向子组件通信
- props传递,利用React单向数据流的思想,通过props传递
子组件向父组件通信
- 回调函数 : 父组件向子组件传递一个函数,通过函数回调,拿到子组件传过来的值
- Ref 兄弟组件通信:就是通过父组件中转数据的,子组件a传递给父组件,父组件再传递给子组件b 父组件向后代组件通信
- Context
- Redux 或其他状态管理库,ref,useRef,
类组件中,为什么修改状态要使用 setState 而不是用 this.state.xxx = xxx
setState 是 React 处理状态更新的核心工具,它确保状态的 不可变性、批量更新、合并新旧状态,并 触发重新渲染。
直接修改 this.state 绕过了 React 的更新机制,可能导致错误或性能问题,因此应该避免。
-
状态的不可变性
- React 中的状态应该是 不可变的。直接修改
this.state会绕过 React 的状态管理机制,导致 React 无法检测到状态的变化。setState是 React 提供的用于更新状态的唯一安全方法,能够保证状态的正确更新和组件的重新渲染。
- React 中的状态应该是 不可变的。直接修改
-
状态更新是异步
- 可能会将多个
setState调用批量处理为一个更新,以优化性能。因为setState是异步的,所以这意呀着在调用setState之后立即读取this.state可能不会返回预期的值。如果直接修改this.state,则无法利用 React 的异步更新和批量处理机制。
- 可能会将多个
-
组件重新渲染
setState方法不仅更新状态,而且还告诉 React 该组件及其子组件需要重新渲染,以反映状态的变化。直接修改this.state不会触发组件的重新渲染,因此即使状态发生了变化,用户界面也不会更新。
-
可预测的状态变更
- 使用
setState方法可以确保所有状态更新都有一个清晰、可预测的流程。这使得调试和理解组件的行为变得更加容易。同时,setState还提供了一个回调函数,只有在状态更新和组件重新渲染完成后,这个回调函数才会被执行,这样就可以安全地操作更新后的状态
- 使用
-
合并状态更新
- 合并 新的状态与旧的状态对象,只更新提供的新属性,而不会替换整个状态对象。如果直接修改
this.state,你可能会不小心覆盖掉已有的状态,导致数据丢失。setState能确保只修改你想改变的部分状态。
- 合并 新的状态与旧的状态对象,只更新提供的新属性,而不会替换整个状态对象。如果直接修改
什么是控制组件与非控制组件 区别 使用场景
区别: 控制组件和非控制组件的区别主要在于如何管理数据。
控制组件: 是指其表单数据由 React 组件的状态(state)控制。每当用户输入数据时,组件的状态会更新,表单的值总是与状态保持同步。输入值和组件状态同步 更细粒度的控制
-
使用场景:
- 需要实时验证用户输入。
- 需要根据用户输入动态更新其他组件或状态。
- 需要在提交表单时处理数据(如格式化、清理等)。
非控制组件: 是指其表单数据不由 React 组件的状态控制,而是直接通过 DOM 元素的引用(refs)来访问。数据的管理不在 React 的状态中。
-
使用场景:
- 简单的表单,不需要实时验证或复杂的状态管理。
- 需要与第三方库集成,且这些库不支持 React 的状态管理。
- 需要快速实现原型或简单功能。
React.createElement 相关
React.createElement 方法用于创建一个 React 元素,返回值 DOM 节点的对象,React 会根据这个对象来渲染 UI。
React.createElement(type, props, ...children)
type: 元素的类型(如字符串表示的 HTML 标签或 React 组件)。props: 传递给元素的属性。children: 子元素,可以是其他元素或文本
为什么要使用 React.createElement
- 使用
React.createElement可以避免 JSX 的编译过程,适用于动态生成元素或在不支持 JSX 的环境中。
StrictMode相关
React 的StrictMode是一种辅助组件,可以帮助咱们编写更好的 react 组件,可以使用<StrictMode />包装一组组件,并且可以帮咱们以下检查:
- 验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告。
- 验证是否使用的已经废弃的方法,如果有,会在控制台给出警告。
- 通过识别潜在的风险预防一些副作用。
React性能优化手段
- 避免不必要的重渲染,
React.memo,帮助函数组件进行性能优化,通过对比前后 props 的变化,来决定是否需要重新渲染组件。 - 使用
useMemo和useCallback - 懒加载组件
React.lazy和Suspense实现组件的懒加载,可以在需要时才加载组件,减少初始加载时间。 - 分割代码,Webpack,将代码拆分成多个块,按需加载,减少初始加载时间。
- v-for使用正确的key
- 使用虚拟列表:长列表,使用虚拟列表
- 优化图片和资源加载, 使用
srcset和sizes - 使用服务端渲染(SSR)和静态站点生成(SSG),使用 Next.js 等框架实现服务端渲染(SSR)和静态站点生成(SSG),提高首屏渲染速度和 SEO。
React事件机制
React 的事件机制通过合成事件提供了跨浏览器的兼容性和一致性,并通过事件委托提高了性能,就是基于浏览器的事件机制实现了一套自身的事件机制,它符合W3C规范,包括事件触发、事件冒泡、事件捕获、事件合成和事件派发等
-
事件触发: 事件是通过 JSX 属性来绑定的。React 事件的命名采用 camelCase(驼峰命名法),例如
onClick、onChange。 -
事件冒泡、事件捕获: 了解事件冒泡和事件捕获之前,首先需要了解事件传播(Event Propagation)。事件传播分为三个阶段:
-
事件捕获阶段(Capturing Phase) :事件从文档的根节点开始向目标节点传播。
-
目标阶段(Target Phase) :事件到达目标节点。
-
事件冒泡阶段(Bubbling Phase) :事件从目标节点开始向上级节点传播,直到到达文档的根节点。
- 事件捕获(Event Capturing) :事件从最顶层的节点(例如
document)向下传播,直到目标元素。在这个阶段,父元素有机会在事件到达目标元素之前拦截并处理事件。捕获阶段的事件处理程序可以通过在 React 中使用onEventNameCapture形式的属性来指定。 - 事件冒泡(Event Bubbling) :指事件从目标元素开始向上级元素传播,直到到达最顶层的节点(例如
document)。在这个阶段,目标元素和其每个祖先元素都有机会处理事件。冒泡阶段的事件处理程序是默认的事件处理方式,可以通过onEventName形式的属性来指定。
- 事件捕获(Event Capturing) :事件从最顶层的节点(例如
-
-
事件合成:将事件从目标元素传播到事件监听器的过程。 React 使用合成事件对象来封装原生事件,提供跨浏览器兼容性和性能优化。
-
事件派发:是一种将事件处理程序添加到其父元素而不是每个子元素的方法。通过将所有事件处理程序绑定到一个公共的祖先元素,通过事件委托机制,React 将事件处理程序集中在根节点上,实现高效的事件处理。
React怎么阻止事件冒泡
- 阻止合成事件的冒泡用e.stopPropagation()
- 阻止合成事件和最外层document事件冒泡,使用e.nativeEvent.stopImmediatePropogation()
- 阻止合成事件和除了最外层document事件冒泡,通过判断e.target避免
React 如何实现 MVVM
React 通过组件的状态(Model)和 JSX(View)实现了 MVVM 模式。组件的状态变化会自动触发视图更新。
什么是React的Fiber架构?它解决了什么问题?
是React 16 中的架构改进,解决了传统 React 中的性能瓶颈
将渲染任务切分为可中断的工作单元,并使用优先级调度机制,提高了 React 的灵活性和性能
Fiber 的工作方式:
- 核心采用的是“工作单元(Work Units) ”机制
- 时间切片,会把大的渲染任务切分成多个小任务,每个小任务在一定时间内完成。当任务时间超出时,Fiber 会暂停当前任务,并允许浏览器执行其他高优先级任务。
- 任务优先级:React 可以根据任务的优先级来决定哪个任务应该最早执行,例如用户输入或动画更新可能具有更高的优先级。
- 可中断渲染:传统的 React 渲染是不可中断的,导致页面可能会出现明显的卡顿现象。Fiber 使渲染过程变得可中断,从而解决了渲染阻塞的问题。
总结: Fiber 架构是 React 16 中的核心改进,旨在解决传统 React 中的性能瓶颈。通过将渲染任务切分为可中断的小任务(称为“fiber”节点)并使用任务优先级调度机制,Fiber 提高了 React 的灵活性和渲染性能。
其核心是通过“时间切片(Time Slicing) ”的方式,将大的渲染任务分割成多个小任务。在每个任务执行过程中,React 会检查是否有剩余时间,如果任务耗时过长,React 会暂停当前任务,允许浏览器处理其他高优先级的操作(如用户输入或动画更新)。通过这种机制,Fiber 可以优先处理重要的任务,确保页面不会因为长时间的渲染阻塞而导致明显的卡顿。
传统的 React 渲染是同步、不可中断的,容易在长时间任务时出现卡顿现象。而 Fiber 架构通过可中断渲染,让 React 可以更流畅地响应用户操作,提升用户体验。
Diff 算法
React 的 Diff 算法用于比较虚拟 DOM 树的变化,以最小化实际 DOM 操作。它通过 key 属性优化列表渲染。
Fiber 架构 Fiber 是 React 16 引入的新架构,旨在提高渲染性能。它允许 React 将渲染工作分成可中断的任务,以提高响应速度。
Key 在渲染时的作用 Key 是 React 用于识别哪些列表项发生变化的标识符。使用稳定的 key 可以提高渲染性能,避免不必要的组件卸载和重建。
为什么 react 组件, 都必须要申明一个 import React from 'react';
关键词:babel 编译 react
JSX 是无法直接运行在浏览器环境。@babel/plugin-transform-react-jsx 来转换语法,使之能够在浏览器或任何 JavaScript 环境中执行。
在组件中使用 JSX 时,JSX 语法最终会被 Babel 编译成使用React.createElement方法的 JavaScript 代码 解释需要调用
计算机网络/浏览器
HTTP 和 HTTPS 的区别
- HTTP:(超文本传输协议)明文传输,数据不加密 端口 80
- HTTPS:使用 SSL/TLS 加密数据,提供安全性 端口 443
SSL/TLS工作原理
在客户端和服务器之间创建加密通道,确保数据在传输过程中不会被窃听或篡改。会数字证书来验证服务器的身份,确保数据在传输过程中未被修改。
HTTPS 加密过程
- 客户端请求:浏览器请求 HTTPS 页面。
- 服务器响应:服务器返回证书。
- 证书验证:客户端验证证书的合法性。
- 密钥交换:使用非对称加密交换对称密钥。
- 加密通信:使用对称密钥加密数据传输。
HTTP1.0, 1.1, 2.0
-
HTTP 1.0
- 单次请求-响应:每次请求都需要重新建立一个 TCP 连接。由于建立和关闭连接的开销大,这种方式效率较低。
- 缺少 Host 字段:HTTP 1.0 并没有
Host字段,意味着一台服务器只能处理一个域名的请求。这在虚拟主机普遍使用的情况下带来了局限性。
HTTP 1.1
- 持久连接:
Connection: keep-alive允许 TCP 连接在多次请求之间保持打开,避免了反复创建和销毁连接的开销,大大提升了性能。 - 流水线:HTTP 1.1 支持请求流水线化(Pipelining),可以在等待响应的同时发出多个请求,但因为响应必须按顺序返回,仍然存在队头阻塞问题。
- 缓存控制:
Cache-Control取代了 HTTP 1.0 的Expires,提供了更细粒度的缓存控制,允许客户端和服务器更高效地管理缓存内容。 - 虚拟主机支持:通过
Host字段,HTTP 1.1 支持同一个 IP 地址处理多个不同域名的请求。
HTTP 2.0
- 二进制分帧:HTTP 2.0 的核心改进是将 HTTP 的文本协议转换为二进制格式,这样能更高效地解析和传输数据。每个请求被分解为若干个帧(Frame),通过流 ID 来区分帧属于哪个请求。
- 多路复用:HTTP 2.0 允许一个 TCP 连接中多个请求和响应同时传输,克服了 HTTP 1.1 中的队头阻塞问题。每个请求有自己的流 ID,所以即使请求和响应是交错的,也不会混淆。
- 头部压缩:HTTP 2.0 引入了 HPACK 压缩算法,用于压缩重复的 HTTP 头部,减少了带宽消耗,提升了效率。特别是在移动网络环境下,这种压缩可以显著降低传输延迟。
- 服务器推送:服务器可以在客户端明确请求之前,主动向客户端发送资源。这种机制常用于提前加载可能需要的资源,如 CSS、JS 文件,以减少等待时间。
总结
- HTTP 1.0 主要问题在于每个请求都要建立新的 TCP 连接,带来了极大的开销。
- HTTP 1.1 通过持久连接和流水线请求缓解了部分性能问题,但仍然受制于队头阻塞。
- HTTP 2.0 则通过二进制分帧、多路复用和头部压缩大幅提升了性能,解决了队头阻塞问题,并引入了服务端推送,进一步提升用户体验。
HTTP状态码
-
1xx 服务端收到请求,但需要客户端再发一个请求以进行后续动作
-
2xx 服务端收到请求并处理成功
-
3xx 临时请求,需要进一步细化
-
4xx 客户端发起请求出现了问题
-
5xx 服务端处理请求出现了问题
3次🤝
-
第一次:客户端向服务端发送一个不包含数据报文的请求,请求报文中SYN标识为1
-
第二次:服务端接收到请求后同意建立连接,向客户端返回报文,SYN和ACK标识都为1
-
第三次:客户端收到服务端同意的报文后,再次发送请求,请求报文中ACK标识为1
4次👋
-
第一次:客户端向服务端发送请求关闭连接的报文,报文中FIN标识为1
-
第二次:服务端收到关闭请求后,进入半关闭状态,返回给客户端报文,报文中包含FIN和ACK
-
第三次:客户端收到服务端将关闭的消息后,关闭客户端的TCP连接,并向服务端发送ACK的请求
-
第四次:服务端收到客户端ACK之后,关闭服务端的TCP连接
GET与POST的区别
-
参数传递方式:
- GET:参数通过 URL 查询字符串进行传递。数据附加在 URL 之后,以
?分隔。 - POST:参数通过 HTTP 请求体(Request Body)进行传递。数据不会显示在 URL 中。
- GET:参数通过 URL 查询字符串进行传递。数据附加在 URL 之后,以
-
安全性:
- GET:因为参数暴露在 URL 中,所以不适合传递敏感信息(如密码)。URL 可以被缓存、书签保存和记录在浏览器历史中
- POST:相对安全一些,因为数据包含在请求体中。
-
请求的幂等性和安全性:
- GET:是幂等的,意思是多次请求同一资源不会有副作用。GET 请求一般是安全的,因为它主要用于获取数据,而不修改服务器上的资源。
- POST:不是幂等的,因为多次相同的 POST 请求可能导致服务器上的数据发生变化。POST 请求通常用于提交数据或进行某种操作,所以可能对服务器产生副作用。
-
数据长度限制:
- GET:由于数据包含在 URL 中,存在长度限制(通常是 2048 个字符左右,取决于浏览器和服务器)。因此,GET 不适合传递大数据量。
- POST:没有明确的长度限制,服务器端的限制通常由配置决定,所以适合传输大数据量。
-
刷新和后退:
- GET:浏览器刷新和后退时,通常会重新发送 GET 请求,不会有弹窗提示。
- POST:浏览器刷新和后退时,会弹出提示框询问用户是否重新提交表单数据。
-
应用场景的区别:
-
GET:
- 适用于查询操作,如检索数据、获取资源内容等。
- 常用于获取不敏感的数据,如网页资源、图像、视频等。
- 可以缓存请求结果,因为GET请求通常不改变服务器状态。
-
POST:
- 适用于提交数据、上传文件、提交表单、发送复杂数据等。
- 用于创建、修改或删除服务器资源。
- 请求结果通常不会被缓存,因为POST请求可能会对服务器产生副作用。
-
什么是缓存?在前端中如何使用缓存来提高性能?
缓存是将数据或资源存储在临时存储中,以便在后续请求中重复使用,从而提高性能和减少网络流量。
- HTTP缓存:通过设置适当的缓存头(如Cache-Control和Expires)来指示浏览器缓存响应。
- 资源缓存:使用文件指纹或版本号来重命名静态资源文件,以便在文件内容变化时使浏览器重新下载。
- 数据缓存:使用内存缓存、
- 浏览器本地存储(如localStorage)或服务端缓存(如Redis)来存储数据,避免重复请求。
强缓存与协商缓存的区别
-
强缓存(也称为本地缓存或浏览器缓存)是指浏览器可以直接使用缓存中的资源,而无需向服务器发送请求。即使用户断网,浏览器也能加载缓存的资源,通过
Expires或Cache-Control控制。 -
协商缓存(也称为对比缓存)是指浏览器向服务器发送请求并验证缓存的资源是否有效。如果资源未改变,服务器会告知浏览器继续使用缓存资源;如果资源改变,服务器则返回新的资源
-
请求次数:
- 强缓存:不发起请求,直接从缓存中获取资源。
- 协商缓存:发起请求,向服务器验证资源是否过期。
-
缓存控制:
- 强缓存:通过
Expires或Cache-Control控制。 - 协商缓存:通过
Last-Modified/If-Modified-Since或ETag/If-None-Match控制。
- 强缓存:通过
-
效率:
- 强缓存:效率更高,因为没有与服务器的通信开销。
- 协商缓存:有与服务器的通信开销,但比下载完整资源更节省带宽。
实际项目有设置过强缓存吗
静态资源:如图片、CSS、JavaScript 文件等,通常会设置较长的缓存时间,以减少服务器负担和提高页面加载速度。
API 响应:对于一些不常变化的API数据,可以设置强缓存,以减少重复请求。例如,某些配置数据或静态列表。
CDN 加速:在使用CDN时,强缓存可以帮助加速内容分发,减少源站点的请求。
实现长缓存
- 文件名哈希:使用
[contenthash]生成文件名,确保文件内容变化时 URL 也变化。 - 缓存策略:配置服务器或 CDN,使用
Cache-Control和ETag头。
列表无线滚动方案
- 虚拟滚动:只渲染可视区域的列表项,使用库如
react-virtualized。 - 懒加载:动态加载数据,减少初始加载时间。
域名发散
通过使用多个子域名(如 img1.example.com,img2.example.com)来增加浏览器的并发请求数。
域名收敛
减少域名数量,利用 HTTP/2 的多路复用特性,减少 DNS 查询和连接开销。
Cookie 解决了什么
- 状态管理:如会话状态、用户偏好。
- 个性化设置:如主题、语言。
- 跟踪:如分析用户行为。
Cookie 和 Session
- Cookie:存储在客户端,适合保存少量数据。
- Session:存储在服务器,适合保存敏感数据。
TCP 和 UDP
-
TCP:
- 面向连接,可靠传输。
- 有序传输,流量控制。
- 适用于文件传输、网页浏览。
-
UDP:
- 无连接,不保证可靠性。
- 低延迟,适用于实时应用。
- 适用于视频流、在线游戏。
什么是跨域,如何解决
-
跨域:浏览器出于安全限制,阻止不同源的请求。
-
解决方法:
- CORS(跨域资源共享)。
- JSONP(仅支持 GET 请求)。
- 代理服务器。
XSS 攻击
- 定义:攻击者在网页中注入恶意脚本。
- 防御:输入验证、输出编码、使用 CSP(内容安全策略)。
SQL 注入
- 定义:攻击者通过输入恶意 SQL 代码操控数据库。
- 防御:使用参数化查询、ORM、输入验证。
DDOS 攻击
- 定义:分布式拒绝服务攻击,通过大量请求使服务器瘫痪。
- 防御:流量监控、使用 CDN、负载均衡。
CSRF 攻击
- 定义:攻击者诱导用户在已认证的会话中执行未授权操作。
- 防御:使用 CSRF 令牌、验证 Referer 头。
封装 Axios
- 目的:简化 HTTP 请求,统一请求配置,处理错误。
- 实现:创建实例,设置默认配置,添加请求/响应拦截器。
请求发送两次原因
-
可能原因:
- 浏览器预请求(如 OPTIONS 请求)。
- 页面重定向。
- 代码逻辑错误。
计算机网络体系结构
- 7层结构:应用层 -> 表示层 -> 会话层 -> 传输层 -> 网络层 -> 数据链路层 -> 物理层
- 5层结构:应用层 -> 传输层 -> 网络层 -> 数据链路层 -> 物理层
- TCP/IP结构:应用层 -> 传输层 -> 网络层 -> 网络接口层
- TCP
- 面向连接的,可靠的数据传输服务
- 每次数据传输都需要先建立连接
- 一对一的单点数据传输
- 提供可靠的数据传输,无差错,不重复,不丢失且按序
- 面向字节流
- UDP
- 提供无连接的,尽最大可能的数据传输服务
- 无连接,不保证可靠的交付
- 面向报文
- 没有拥塞控制,即使网络阻塞依然不影响发送速率
- 支持一对一、一对多、多对多传输
- HTTPS
- SSL协议 + HTTP协议
- 优点
- 数据完整性:内容传输会进行完整性校验
- 数据加密性:内容经过对称加密,每一个链接生成唯一密钥
- 身份认证:第三方无法伪造客户端(服务端)的身份
- 加密方式
- 对称加密:加解密使用同一个密钥
- 非对称加密:使用公钥进行加密,使用私钥进行解密
- 对称加密 + 非对称加密:先通过对称加密来加密报文,再通过非对称加密来加密对称加密的密钥
- 数字签名
-
- 客户端通过Hash函数提取报文中的部分摘要,并使用私钥进行加密生成签名,将加密后的签名和报文一起发送给服务端
-
- 服务端使用客户端的公钥对加密后的签名进行解密,并将解密后的摘要和从报文中使用相同Hash函数提取的摘要进行对比
-
- 若摘要对比相同,则认为该报文内容未被篡改
- 数字证书
- 数字证书由CA机构颁发,并且会在系统和浏览器中预装根证书
- 域名系统(DNS)
- 域名系统包含:根域名、顶级域名、二级域名、三级域名、四级域名等
- 每一级域名都包含一个域名服务器,域名服务器一共有四类:顶级域名服务器、根域名服务器、权威域名服务器、本地域名服务器
- DNS查询有两种方式:迭代查询和递归查询(具体过程查看浏览器模块)
- 域名解析包含:A记录、MX记录、CNAME记录、TXT记录、NS记录
- CDN
- 内容分发网络,在距离用户更近的地方设置缓存服务器,当用户访问资源时,由该缓存服务器返回资源,从而提升响应速度
- 当用户访问CDN地址时,请求会先打到CDN负载均衡服务器上,再由该服务器选择一台CDN缓存服务器去请求资源
- CDN的缓存除了受缓存时间到期自动刷新外,也支持用户手动刷新
- CDN的好处除了提升响应速度,还包含隐藏源服务器IP、分散请求压力等
事件循环
- JS是单线程的,JS中的异步实际上为JS给浏览器或者V8引擎发送命令,由其他线程去执行对应操作
- JS在执行代码时,会将同步代码依次放入到执行栈中,当遇到异步任务时,则会通知其他线程去执行异步任务,并将异步任务的回调放入到一个队列中,当栈中的同步代码执行完成后,便会从异步队列中依次取出异步任务执行
- 异步队列中异步任务执行顺序为,始终优先执行微任务,只有队列中没有微任务时才会执行宏任务
- 异步队列中没有新的任务时一次事件循环结束,下一次会继续从同步代码开始执行
- 宏任务和微任务
- 任务队列中的任务包含了宏任务和微任务,微任务会比宏任务先执行
- 常见的宏任务包含:setTimeout、setInterval、setImmediate
- 常见的微任务有:promise.then、promise.catch、process.nextTick、new MutationObserver()
- 宏任务需要外部线程的支持,微任务不需要,只是单纯的晚执行
- 浏览器的渲染视图是在微任务执行完成之后,宏任务执行之前
- NodeJS中的事件队列执行顺序为:timer阶段 -> pending阶段 ->poll阶段 -> check阶段 -> close阶段,因为定时器会比I/O请求等先执行
Web Worker 优化方案
- 独立线程:将计算密集型任务移到 Web Worker,避免阻塞主线程。
- 数据传输:使用
Transferable Objects传输数据,减少复制开销。
延迟加载方式
- 动态导入:使用
import()实现模块的按需加载。 - Intersection Observer:用于懒加载图片和组件。
图片懒加载和预加载
- 懒加载:使用
loading="lazy"属性或Intersection Observer。 - 预加载:使用
<link rel="preload">提前加载关键资源。
大量图片优化方案
- 压缩:使用工具如
imagemin压缩图片。 - 格式优化:使用 WebP 等现代格式。
- 响应式图片:使用
srcset和sizes提供不同分辨率的图片。
CDN 加速访问的原理
CDN(内容分发网络)通过在全球多个节点缓存内容,减少用户与服务器之间的物理距离,从而加快访问速度。
浏览器渲染过程/工作原理
- 解析 HTML:生成 DOM 树。
- 解析 CSS:生成 CSSOM 树。
- 构建渲染树:结合 DOM 和 CSSOM。
- 布局:计算每个节点的位置和大小。
- 绘制:将渲染树转换为屏幕上的像素。
输入 URL 到页面加载过程
- DNS 解析:将域名转换为 IP 地址。
- TCP 连接:通过三次握手建立连接。
- 发送 HTTP 请求:请求页面资源。
- 服务器响应:返回 HTML、CSS、JS 等资源。
- 浏览器渲染:解析和渲染页面。
如何防止前端页面重复请求
- 使用锁或标志位
- 使用防抖(Debounce)和节流(Throttle)
- 使用缓存结果 在请求数据时,可以先检查缓存,如果缓存中已有数据,则直接使用缓存数据,而不是发送新的请求。
前端项目里面 怎么中断请求
使用 AbortController 中断 fetch 请求
在 Axios 中,可以使用 CancelToken 来取消请求
XMLHttpRequest 可以直接调用 abort() 方法。
处理组件卸载时的请求中断
前端性能怎么做检测
一、前端性能检测
-
使用浏览器开发者工具
-
Performance 面板:通过浏览器(如 Chrome)的开发者工具中的 Performance 面板,可以记录和分析页面加载和渲染过程。主要可以查看:
- 加载时间(Load Time):包括 DOMContentLoaded 和 Load 事件触发时间。
- 帧率(FPS):显示页面的渲染帧率,帮助分析动画或滚动的流畅度。
- 关键操作的耗时:如脚本执行、样式计算、布局和渲染。
-
Lighthouse:这是 Chrome 开发者工具中的一个自动化工具,可以生成关于页面性能、可访问性、SEO 等方面的报告,并提供改进建议。
-
Network 面板:用于查看网络请求的详细信息,包括请求时间、数据传输量、缓存命中率等,帮助检测页面资源的加载情况。
-
-
在线工具
- Google PageSpeed Insights:提供页面的整体性能评分以及优化建议,涵盖了移动端和桌面端的建议。
- WebPageTest:可以进行详细的页面加载分析,提供瀑布图、缓存分析等信息,并能够从不同地点和设备模拟页面加载情况。
-
前端性能监控工具
- Sentry、New Relic、Datadog 等前端监控工具可以实时监控应用性能,跟踪页面加载时间、错误和资源使用情况。
-
自定义指标
- Performance API:通过 JavaScript 的 Performance API 可以手动测量页面的关键性能指标,如 First Contentful Paint (FCP),Time to Interactive (TTI),DOM 完成时间等。
- 自定义埋点:可以在代码中添加自定义埋点,跟踪特定的用户交互或页面加载过程中的关键步骤。
二、前端性能优化
-
减少页面加载时间
- 图片优化:使用合适的图片格式(如 WebP),压缩图片大小,懒加载不在首屏显示的图片资源。
- 减少 HTTP 请求:合并 CSS 和 JS 文件,使用 CSS Sprites 技术合并图标,减少页面中的请求数量。
- 使用 CDN:将静态资源放置在 CDN 上,缩短用户与资源服务器的物理距离。
- 压缩和最小化资源:压缩 CSS、JavaScript 文件,删除不必要的注释和空白。启用 gzip 或 Brotli 压缩来减小传输数据的大小。
-
优化资源加载
- 异步加载资源:使用
async或defer属性异步加载非关键 JavaScript,防止阻塞页面渲染。 - 预加载关键资源:通过
<link rel="preload">预加载关键资源,如字体、CSS 文件。 - 延迟加载非关键内容:懒加载图片和非首屏内容,延迟加载不立即需要的 JS 文件。
- 异步加载资源:使用
-
提高页面渲染效率
- 减少重排和重绘:减少 DOM 操作,避免频繁的样式变更导致重排(Reflow)和重绘(Repaint)。使用
classList操作类名而非直接操作样式属性。 - CSS 优化:使用简洁、高效的选择器,避免使用性能消耗大的通配符选择器或深层嵌套选择器。
- 使用 CSS 动画:将动画效果尽量使用 CSS 实现,并开启硬件加速,避免 JavaScript 操作 DOM 带来的性能开销。
- 减少重排和重绘:减少 DOM 操作,避免频繁的样式变更导致重排(Reflow)和重绘(Repaint)。使用
-
优化 JavaScript 执行
- 减少不必要的 JavaScript 执行:移除未使用的代码,减少 Polyfill 的使用,使用现代浏览器原生支持的特性。
- 懒加载 JavaScript:对不必要的 JavaScript 进行懒加载或按需加载,减少首屏 JavaScript 的体积。
- 防抖与节流:对于高频率触发的事件(如滚动、输入),使用防抖(Debounce)或节流(Throttle)来控制函数的执行频率。
-
优化内存使用
- 减少内存泄漏:避免不必要的全局变量,及时清理不再需要的对象和事件监听器。
- 优化数据处理:对于大数据的处理,考虑使用 Web Worker,将计算任务放在后台线程,避免主线程阻塞。
-
提升体验的优化
- 优化首屏时间:确保用户尽快看到内容,通过骨架屏(Skeleton Screen)或渐进增强技术来优化用户体验。
- 优先级队列:对于需要即时响应的用户交互操作,优先处理相关代码,延迟处理低优先级的操作。
什么是首屏加载
首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容
关于计算首屏时间
利用performance.timing提供的数据
// 方案一: document.addEventListener('DOMContentLoaded', (event) => {
console.log('first contentful painting');
});
// 方案二:
performance.getEntriesByName("first-contentful-paint")[0].ntTiming的实例,结构如下: // performance.getEntriesByName("first-contentful-paint")[0] // 会返回一个 PerformancePaintTiming的实例,结构如下: { name: "first-contentful-paint", entryType: "paint", startTime: 507.80000002123415, duration: 0, };
二、加载慢的原因:
- 网络延时问题
- 资源文件体积是否过大
- 资源是否重复发送请求去加载了
- 加载脚本的时候,渲染内容堵塞了
三、解决方案
- 减小入口文件积
- 静态资源本地缓存
- UI框架按需加载
- 图片资源的压缩
- 组件重复打包
- 开启GZip压缩
- 使用SSR
Vite的原理
基于esbuild与Rollup,依靠浏览器自身ESM编译功能, 实现极致开发体验的新一代构建工具! 其原理主要基于两个核心概念:ES Modules 和服务器端渲染(SSR)。
- ES Modules: Vite 利用了现代浏览器对 ES Modules 的原生支持。在开发模式下,Vite 将每个
.vue、.js、.ts、.jsx、.tsx等文件作为一个独立的模块,以 ES Module 的形式直接在浏览器中运行,无需预先构建为静态资源文件。这种方式极大地提高了开发过程中的速度。 - 服务器端渲染(SSR): Vite 在开发模式下运行一个轻量级的服务器,它会拦截对模块的请求并动态地编译和返回相应的模块内容。当浏览器请求一个模块时,Vite 会检查模块依赖关系并将其编译成 JavaScript,然后将结果返回给浏览器。由于 Vite 采用了 SSR 的方式,因此无需像传统的打包工具那样提前将所有模块编译成静态资源文件,而是按需编译、按需返回,从而实现了快速的开发环境。
Webpack 性能优化方案
- 代码分割:使用
SplitChunksPlugin分割代码,减少单个文件的大小。 - Tree Shaking:移除未使用的代码,确保使用 ES6 模块。
- 缓存:使用
cache-loader和hard-source-webpack-plugin提高构建速度。 - 压缩:使用
TerserPlugin压缩 JavaScript,css-minimizer-webpack-plugin压缩 CSS。 - 减少解析时间:通过
resolve.alias和resolve.extensions优化模块解析。
关于其他
ECharts 十万级+ 数据渲染性能优化方案实现方法
在我的一个实际项目中,有一个应用场景是:用户要通过时间范围选择框,查询最近半年的数据量,进而通过 ECharts 折线图一次性渲染大概十万+以上的数据量。可想而知,数据量大带来的用户体验效果会非常不好,接口请求回来的数据量大概有几万条数据,
方案:数据分段渲染
ECharts dataZoom 组件常用于区域缩放,从而让用户能自由关注细节的数据信息,或者概览数据整体。为了能让 ECharts 避免一次性渲染的数据量过大,因此可以考虑使用 dataZoom 的区域缩放属性实现首次渲染 ECharts 图表时就进行区域渲染,减少整体渲染带来的性能消耗。 减少一次性加载大数据量带来的性能压力,实现更加流畅的大规模数据可视化展示。
2. 实现步骤
dataZoom 组件提供了几个属性,利用这几个属性可以控制图表渲染时的性能问题,如下所示:
start: 数据窗口范围的起始百分比。范围是:0 ~ 100。表示 0% ~ 100%。end: 数据窗口范围的结束百分比。范围是:0 ~ 100。minSpan: 用于限制窗口大小的最小值(百分比值),取值范围是 0 ~ 100。maxSpan: 用于限制窗口大小的最大值(百分比值),取值范围是 0 ~ 100。
具体方案是使用 start 和 end 控制 ECharts 图表初次渲染时滑块所处的位置以及数据窗口范围,使用 minSpan 和 maxSpan 用于限制窗口大小的最小值和最大值,最终限制的图表的可视区域显示范围,如下代码所示:
方案:降采样策略 那就是降采样策略 series-line.sampling,通过配置sampling采样参数可以告诉 ECharts 按照哪一种采样策略,可以有效的优化图表的绘制效率。
2. 实现步骤
sampling 属性提供了几个可选值,配置不同的值可以有效的优化图表的绘制效率,如下所示:
sampling 的可选值有以下几个:
lttb: 采用Largest-Triangle-Three-Bucket算法,可以最大程度保证采样后线条的趋势,形状和极值。average: 取过滤点的平均值min: 取过滤点的最小值max: 取过滤点的最大值minmax: 取过滤点绝对值的最大极值 (从 v5.5.0 开始支持)sum: 取过滤点的和
具体方案是配置 series 的 sampling,最终表示使用的是 ECharts 的哪一种采样策略,ECharts 内部机制实现优化策略:
缺点
- 并不是展示的所有点,会删除一些无用的点,保证渲染性能
- 最大程度保证采样后线条的趋势,形状和极值,但是某些情况下,极值有偏差,测试中发现
其他方案:
- 虚拟滚动
- 硬件加速
- 优化数据结构,精简数据返回字段,降低数据包大
- 开启 gzip 压缩,加快海量数据下载速度
- 数据聚合:对于特别密集的数据点,使用聚合算法在源头对数据降采样,进行数据聚合,减少渲染的数据点数量。
- 数据过滤:数据中存在一些无关的信息或数据噪音,服务端对数据进行过滤,只需要保留有用的数据即可,剔除无效的数据。
实时数据更新 前端怎么做
- WebSocket 向通信协议,允许服务器在有新数据时主动推送到客户端,而无需客户端不断轮询
通过前端实现闪购场景下的秒杀?
实现秒杀的总体流程:
- 页面加载:页面预加载秒杀商品信息,并在秒杀按钮上显示倒计时。 静态资源优化 / 秒杀按钮预热
- 实时库存更新:通过 WebSocket、SSE 或轮询实时获取商品库存变化,更新页面展示。
- 秒杀触发:秒杀倒计时结束后,用户点击秒杀按钮,触发秒杀请求。前端通过防抖机制避免重复提交。 请求防抖和请求限流
- 请求发送与响应:发送秒杀请求时附带签名或加密信息,确保数据安全。后端处理成功后返回结果,前端更新页面状态。
- 反馈展示:根据秒杀成功或失败的结果,给出对应的提示。
优化方案
-
请求防抖和请求限流
-
秒杀请求的处理
-
使用 CDN 和缓存机制
- 保秒杀页面的静态资源(HTML、CSS、JavaScript)通过 CDN 分发,加快用户加载速度。
- 合理设置缓存策略:在秒杀过程中,通过
localStorage或sessionStorage缓存一些不需要频繁更新的数据
-
用户反馈与体验
- 加载与占位符设计
- 失败重试制
-
安全防护
- 限流与防刷单:对于前端发出的秒杀请求,配合后端限流机制,对同一用户、同一 IP 进行限流,避免短时间内大量请求。
- 验证码机制
62401884
Electron使用 hotkeys 优化统一快捷键,确保不与现有的快捷键冲突,同时处理 Mac 和 Windows 的快捷键兼容性,并支持用户自定 义快捷键。 有什么难点
Mac 上使用 Command+C 复制,而 Windows 上使用 Ctrl+C。
Mac 上快捷键通常会使用 Option 键,Windows 上使用 Alt 键。
可以通过 process.platform 检查当前系统,并为 Mac 和 Windows 设置不同的按键。
对于 Mac 系统,可能会使用 Command 替代 Ctrl。
对于 Windows 系统,可能会优先使用 Ctrl 或 Alt 键。
用户自定义快捷键
- 用户配置保存与动态更新:用户自定义快捷键的功能涉及用户界面的设计、配置文件的存储与解析,以及在应用运行时动态更新快捷键的绑定。
- 配置文件管理:为了支持用户自定义,前端需要提供一个交互界面让用户配置自己的快捷键,后端则需要将这些配置存储在文件或数据库中(如
localStorage、JSON文件等)。 - 快捷键冲突检测:在用户自定义快捷键时,需要检测新设置的快捷键是否与现有的快捷键冲突,并提供友好的冲突提示和修改建议。
在用户修改快捷键时,确保使用 globalShortcut.unregister 解绑旧的快捷键,再使用 globalShortcut.register 重新注册新快捷键。
确保在窗口失去焦点或应用关闭时,正确解除全局快捷键的注册,避免内存泄漏或键位冲突。
解决方案示例流程:
-
检查平台并动态设置快捷键:通过
process.platform来确定是 Mac 还是 Windows,并分别设置不同的默认快捷键。 -
检测快捷键冲突:在快捷键注册前,查询已有系统和应用快捷键,确保自定义快捷键不冲突。如果检测到冲突,提供其他备选组合。
-
支持用户自定义快捷键:
- 在前端提供设置界面,允许用户选择自定义快捷键。
- 将用户选择的快捷键存储到配置文件或数据库中,并在应用启动时动态加载并注册。
-
确保跨平台兼容性:使用
CmdOrCtrl、Alt、Shift等键在不同平台上注册合适的快捷键。 -
动态更新快捷键:用户更改快捷键时,解除旧快捷键的绑定,并重新注册新的快捷键。
倒计时抢购功能的实时性和性能优化
-
前端倒计时同步服务器时间 服务器返回的时间戳
-
服务器推送实时更新,使用WebSocket等实时通讯技术
-
节流与防抖
- 了应对用户频繁点击“抢购”按钮,节流和防抖技术可以有效限制请求频率,避免服务器在短时间内承受过多请求压力。
- 节流可以确保在指定的时间间隔内只发送一次请求,而防抖技术则在一段时间内只处理最后一次点击。
-
队列处理机制: 针对同时发起的抢购请求,可以使用服务器队列来逐一处理,以避免因过多并发请求导致的资源争抢问题。用户的请求可以被放入队列并按顺序处理,确保公平性。
-
预防重复提交和按钮状态控制: 前端通过 按钮禁用(在一次点击后按钮禁用一段时间)或者显示 加载动画 来阻止用户重复点击。
-
缓存与CDN加速: 倒计时页面涉及静态资源缓存使用CDN
-
接口缓存优化:与抢购无关的接口数据缓存,减少不必要的服务器请求,优化整体性能。
-
异步请求优化: 处理好 请求超时 和 错误恢复
-
并发请求优化: 可以通过队列或并行处理减少不必要的重复请求
-
前端监控与埋点: 通过埋点技术,监控用户在抢购过程中的操作,实时获取数据以便优化性能。以使用如 Sentry、Google Analytics 等前端监控工具
CSS 扫光的原理
从左到右的、无限循环的位移动画
位移动画可以选择transform或者改变background-position都行。
扫光,需要绘制一条斜向上45deg的线性渐变
文本扫光 实现方式
- 由于扫光在文本内部,需要将这个渐变作为文本的颜色。文本渐变色,可以用
backgrond-clip:text来实现,通过裁剪背景,使其只显示在文本的区域内。即,背景将只影响文本的填充,而不影响其他部分 - 需要将当前文本颜色设置透明,建议通过
-webkit-text-fill-color: transparent来设置,这样可以保留文本原有颜色,好处是其他地方,比如background-color可以直接使用原有文本颜色currentColor - 接下来使用动画效果 让扫光动起来 使用background-position
<!DOCTYPE html>
<html lang="en">
<h1 class="shark-txt">1112</h1>
</html>
<style>
h1 {
font-size: 60px;
font-family: "RZGFDHDHJ";
font-weight: normal;
color: #c8bfd4;
}
.shark-txt {
-webkit-text-fill-color: transparent;
/* 设置文本的填充颜色为透明 */
background: linear-gradient(45deg,
rgba(255, 255, 255, 0) 40%,
rgba(255, 255, 255, 0.7),
rgba(255, 255, 255, 0) 60%) -100% / 50%
no-repeat
currentColor;
-webkit-background-clip: text;
animation: shark-txt 5s infinite;
}
@keyframes shark-txt {
form {
background-position: -100%;
}
to {
background-position: 200%;
}
}
</style>
难点:
- 在设置线性渐变作为文本的背景得时候 在设置变的方向为 45 度角,渐变开始是完全透明的白色到,然后在 40% 的位置变成 70% 不透明的白色,在 到60% 的位置再次变回透明的白色,形成一个类似光晕的效果。
- 设置完这些后 发现动画没有效果 通过排查发现
- 这是因为背景默认尺寸是
100%,根据背景偏移百分比的计算规则,当背景尺寸等于容器尺寸时,百分比完全失效,即使设置background-position: 100%或0%,因为背景图已经充满了容器,所以看不出明显的位移变化 - 所以我修改了背景图的尺寸 background-size: 50% 背景尺寸缩小后,偏移才有效
background-position: -100%:通过设置负偏移量,使得背景图开始于容器的左侧不可见区域。然后动画可以逐渐将背景移动到可见区域,产生“扫光”的效果。
0% :背景图的左(或上)边缘对齐容器的左(或上)边缘。
100% :背景图的右(或下)边缘对齐容器的右(或下)边缘。
50% :背景图居中,图像的中点(50%)与容器的中点对齐。
卡片容器扫光
- 普通容器的扫光效果需要借助伪元素实现,因为如果使用背景会被容器内的元素覆盖
- 普通容器的扫光动画可以直接用
transfrom实现
容器内的扫光动效,通常是在一个圆角矩形的容器里
不能直接用背景渐变了,因为会被容器内的其他元素覆盖。所以我们需要创建一个伪元素,然后通过改变伪元素的位移来实现扫光动画了。
<!DOCTYPE html>
<html lang="en">
<div class="shark-wrap card">
<img src="https://imgservices-1252317822.image.myqcloud.com/coco/b11272023/ececa9a5.7y0amw.jpg">
</div>
</div>
</html>
<style>
.card {
width: 300px;
border-radius: 8px;
background-color: #FFE8A3;
}
.card img {
display: block;
width: 100%;
height: 100px;
}
.shark-wrap::after {
content: '';
position: absolute;
inset: -20%;
background: linear-gradient(45deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0) 60%);
animation: shark-wrap 2s infinite;
transform: translateX(-100%);
}
.shark-wrap {
flex-shrink: 0;
position: relative;
overflow: hidden;
}
@keyframes shark-wrap {
to {
transform: translateX(100%);
}
}
</style>
不规则图片扫光
- 不规则图片的扫光效果无法直接根据形状裁剪
- 借助
CSS mask可以根据图片本身裁剪掉扫光多余部分
CSS mask遮罩
<div class="shark-wrap" style="-webkit-mask: url(https://imgservices-1252317822.image.myqcloud.com/coco/s09252023/3af9e8de.00uqxe.png) 0 0/100%">
<img class="logo" src="https://imgservices-1252317822.image.myqcloud.com/coco/s09252023/3af9e8de.00uqxe.png">
</div>
-webkit-mask 这是一个用于创建遮罩效果的 CSS 属性。它允许你使用图像作为遮罩,从而控制该元素的可见部分。
mask-image: url('path/to/mask-image.png');
0 0/100% :这部分表示遮罩图像的位置和大小。
0 0:表示遮罩图像的左上角与容器的左上角对齐。 mask-position X 轴和 Y 轴 遮罩图像的左上角与容器的左上角对齐。/100%:表示遮罩图像的大小为容器的 100%(即完全覆盖容器)。 mask-size
mask-mode 属性的默认值是 alpha。这意味着遮罩图像的透明度(alpha 通道)将用于确定如何显示遮罩效果。
属性说明:
alpha:使用遮罩图像的透明度来决定可见性。透明部分将使底层内容可见,而不透明部分将遮盖底层内容。luminance:使用遮罩图像的亮度值来决定可见性。较亮的区域会更加可见,而较暗的区域会更不明显。
应用上线后, 怎么通知用户刷新当前页面?
静态资源更新、页面版本更新、服务端推送