computed 和 普通函数有何区别
一、缓存机制
- computed
根据依赖的响应式数据自动缓存计算结果,依赖值未变化时直接返回缓存结果。
例如:同一模板多次调用{{ totalMoney }}仅触发一次计算。 - 普通函数
每次调用都会重新执行逻辑,无缓存机制。例如模板中多次调用{{ getTotal() }}会重复触发计算。
二、响应式依赖处理
- computed
自动追踪依赖的响应式变量(如data中的属性),依赖变化时自动重新计算。
例如:当price或quantity变化时,total自动更新。 - 普通函数
不会自动追踪依赖,需手动触发执行(如通过事件或生命周期钩子),无法自动响应依赖变化。
三、调用方式
- computed
作为属性直接使用,无需括号。例如{{ total }}。 - 普通函数
必须通过括号调用。例如{{ getTotal() }}。
四、性能差异
- computed
适合复杂计算或高频读取场景,依赖不变时减少计算开销。
例如:渲染列表时多次使用同一计算结果。 - 普通函数
适合需要频繁变动或副作用(如异步操作)的场景,但可能引发性能问题。
五、数据修改限制
- computed
默认只读(仅有getter),如需修改需显式定义setter。 - 普通函数
无此限制,可直接修改数据或执行任意逻辑34。
Vue 中 reactive() 返回原始对象 Proxy 的核心原因与优势
一、实现响应式系统的核心机制
reactive() 通过返回原始对象的 Proxy 代理(而非直接修改原始对象),建立响应式系统的底层依赖追踪与更新触发机制。Proxy 的拦截能力使 Vue 能精确控制对象属性的 读取(get) 和 修改(set) ,从而实现以下核心优势:
二、具体优势
-
智能依赖追踪
Proxy 能捕捉对象属性的访问与修改行为,自动建立组件与数据的依赖关系。当依赖数据变化时,仅触发关联组件的更新,避免无效渲染,提升性能。
示例: 深层嵌套对象obj.a.b.c的修改会被自动追踪,触发对应视图更新。 -
深层嵌套对象的自动响应式转换
访问对象任意层级的属性时,Proxy 会自动将未转换的普通对象属性转换为响应式对象,开发者无需手动处理深层嵌套结构1。
示例: 首次访问obj.x.y时,Vue 会自动将y转换为响应式属性。 -
动态属性的响应式支持
与 Vue 2 的Object.defineProperty不同,Proxy 支持直接为对象 添加新属性,且新增属性默认具备响应性,无需Vue.set等额外操作。 -
性能优化
- 缓存机制:多次对同一对象调用
reactive()会返回同一 Proxy 实例,避免重复代理的开销5。 - 细粒度更新:仅追踪实际被访问的属性,减少不必要的依赖收集。
- 缓存机制:多次对同一对象调用
-
引用关系维持与解包
Proxy 能自动处理对象内部嵌套的ref引用,维持其响应式连接。例如:jsCopy Code const count = ref(0); const obj = reactive({ count }); // 直接通过 obj.count 访问,无需 .value -
类型约束与安全隔离
Proxy 代理与原始对象严格隔离(proxy !== raw),强制开发者通过代理操作数据,确保响应式系统的行为一致性。
注意: 直接修改原始对象不会触发视图更新。
三、与 ref() 的对比
| 特性 | **reactive()** | **ref()** |
|---|---|---|
| 适用数据类型 | 仅对象/数组 | 任意类型(基本类型+对象) |
| 返回值 | Proxy 对象 | 含 .value 的 Ref 对象 |
| 深层响应性 | 自动处理嵌套属性 | 需手动处理嵌套对象 |
总结
Vue 3 通过 reactive() 返回 Proxy 代理,实现了 细粒度依赖追踪、动态属性支持 和 深层响应式转换 等核心功能12。这种设计在提升性能的同时,降低了开发者处理复杂数据结构的成本,是 Vue 响应式系统的基石。
生命周期
一、整体对比
| 特性 | Vue 2 | Vue 3 | 说明 |
|---|---|---|---|
| 钩子命名 | 选项式(如 created) | 函数式(如 onCreated) | Vue 3 组合式 API 需显式导入钩子函数 |
| 创建阶段合并 | beforeCreate 和 created 分开 | 合并到 setup() 中 | Vue 3 的 setup() 替代了这两个钩子 |
| 销毁阶段重命名 | beforeDestroy、destroyed | onBeforeUnmount、onUnmounted | 更贴合“卸载”行为语义 |
| 新增调试钩子 | 无 | onRenderTracked、onRenderTriggered | 用于追踪数据变化和渲染流程 |
proxy 可以拦截数组变化吗
一、Proxy 对数组操作的拦截能力
-
原生数组方法触发更新
Proxy 可拦截push、pop、splice等原生数组方法,无需像 Vue 2 那样重写数组方法即可自动触发依赖更新 13。 -
索引修改与属性增减
- 通过索引修改数组元素(如
arr = 1)可直接被 Proxy 拦截 。 - 动态添加或删除数组元素(如
arr.length = 0)也能被捕获 。
- 通过索引修改数组元素(如
-
嵌套数组的深度监听
Proxy 支持递归代理嵌套数组,确保深层元素变化也能触发响应式更新 。
二、与 Vue 2 的对比(Object.defineProperty 的局限性)
| 能力 | Vue 3(Proxy) | Vue 2(Object.defineProperty) |
|---|---|---|
| 拦截原生数组方法 | ✅ 直接拦截(无需改写方法) | ❌ 需重写数组方法(如 push、pop) |
| 检测索引赋值 | ✅ 支持(如 arr = 1) | ❌ 不支持 |
| 动态增删数组元素 | ✅ 支持(如 arr.length = 0) | ❌ 需 Vue.set/Vue.delete 辅助 |
三、实现原理
-
依赖收集与触发
- Proxy 拦截器:在
get中收集依赖,在set、deleteProperty等操作中触发更新 。 - 数组方法代理:调用原生数组方法时,Proxy 会自动触发更新逻辑(如
push后通知视图渲染) 。
- Proxy 拦截器:在
-
性能优化
Proxy 的拦截范围覆盖数组所有操作,避免了 Vue 2 中因重写方法导致的性能损耗和代码冗余 。
总结
Proxy 能够全面拦截数组的索引修改、原生方法调用、动态属性增删等操作,解决了 Vue 2 中响应式数组的局限性 。这种设计简化了开发逻辑,同时提升了性能与代码可维护性。
前端两个 dom 元素是可以拖拽的, 要实现两个 dom 之间的连接线,如何实现
一、核心实现思路
-
元素定位与坐标计算
通过offsetLeft、offsetTop或getBoundingClientRect()获取拖拽元素的实时位置,计算连线起点与终点坐标(如中心点或自定义锚点)。 -
动态绘制连线
使用 SVG、Canvas 或 CSS 绝对定位元素(如<div>)绘制连接线,并根据元素位置动态调整连线的位置、长度和角度。 -
拖拽与连线更新
监听元素的拖拽事件(如mousedown、mousemove、mouseup),在拖拽过程中实时更新连线坐标。二、具体实现步骤(以 SVG + 原生 JS 为例)
1. 元素拖拽功能
<!-- 可拖拽元素 -->
<div class="draggable" id="element1">元素A</div>
<div class="draggable" id="element2">元素B</div>
<!-- SVG 画布容器 -->
<svg id="lineCanvas"></svg>
.draggable {
position: absolute;
width: 100px;
height: 100px;
background: #3498db;
cursor: move;
user-select: none;
}
2. 实现拖拽与坐标更新
let isDragging = false;
let currentElement = null;
// 拖拽事件绑定
document.querySelectorAll('.draggable').forEach(element => {
element.addEventListener('mousedown', startDrag);
});
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', endDrag);
function startDrag(e) {
isDragging = true;
currentElement = e.target;
}
function drag(e) {
if (!isDragging) return;
// 更新元素位置
currentElement.style.left = e.clientX - 50 + 'px'; // 中心点对齐
currentElement.style.top = e.clientY - 50 + 'px';
updateLine(); // 同步更新连线
}
function endDrag() {
isDragging = false;
}
3. 动态绘制连线
// 获取元素坐标(中心点)
function getElementCenter(element) {
const rect = element.getBoundingClientRect();
return {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
}
// 更新连线路径
function updateLine() {
const svg = document.getElementById('lineCanvas');
const elem1 = document.getElementById('element1');
const elem2 = document.getElementById('element2');
const pos1 = getElementCenter(elem1);
const pos2 = getElementCenter(elem2);
// 清除旧连线,绘制新连线
svg.innerHTML = '';
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', pos1.x);
line.setAttribute('y1', pos1.y);
line.setAttribute('x2', pos2.x);
line.setAttribute('y2', pos2.y);
line.setAttribute('stroke', '#e74c3c');
line.setAttribute('stroke-width', '2');
svg.appendChild(line);
}
// 初始化首次连线
updateLine();
三、优化与扩展方案
-
使用库简化开发
采用 jsPlumb 或 LeaderLine 等库,可快速实现拖拽连线、锚点自定义和动态样式调整。 -
性能优化
- 节流更新:对
mousemove事件使用节流函数(如requestAnimationFrame),避免高频重绘4。 - 局部重绘:仅更新受影响的连线,而非全量渲染。
- 节流更新:对
-
自定义连线样式
支持箭头、虚线、渐变等效果(通过 SVG 的marker-end、stroke-dasharray属性)。
总结
通过 原生 JS 监听拖拽事件 + SVG 动态绘制路径 或 现成库(如 jsPlumb) ,可实现可拖拽 DOM 元素的动态连接线。核心要点包括:
- 实时计算元素坐标;
- 动态更新连线位置;
- 优化性能与交互体验。