刷刷题22

230 阅读7分钟

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) ‌,从而实现以下核心优势:


二、具体优势

  1. 智能依赖追踪
    Proxy 能捕捉对象属性的访问与修改行为,自动建立组件与数据的依赖关系。当依赖数据变化时,‌仅触发关联组件的更新‌,避免无效渲染,提升性能‌。
    示例:  深层嵌套对象 obj.a.b.c 的修改会被自动追踪,触发对应视图更新‌。

  2. 深层嵌套对象的自动响应式转换
    访问对象任意层级的属性时,Proxy 会自动将未转换的普通对象属性转换为响应式对象,开发者无需手动处理深层嵌套结构‌1。
    示例:  首次访问 obj.x.y 时,Vue 会自动将 y 转换为响应式属性‌。

  3. 动态属性的响应式支持
    与 Vue 2 的 Object.defineProperty 不同,Proxy 支持直接为对象 ‌添加新属性‌,且新增属性默认具备响应性,无需 Vue.set 等额外操作‌。

  4. 性能优化

    • 缓存机制‌:多次对同一对象调用 reactive() 会返回同一 Proxy 实例,避免重复代理的开销‌5。
    • 细粒度更新‌:仅追踪实际被访问的属性,减少不必要的依赖收集‌。
  5. 引用关系维持与解包
    Proxy 能自动处理对象内部嵌套的 ref 引用,维持其响应式连接。例如:

    jsCopy Code
    const count = ref(0);
    const obj = reactive({ count }); 
    // 直接通过 obj.count 访问,无需 .value
    
  6. 类型约束与安全隔离
    Proxy 代理与原始对象严格隔离(proxy !== raw),强制开发者通过代理操作数据,确保响应式系统的行为一致性‌。
    注意:  直接修改原始对象不会触发视图更新‌。


三、与 ref() 的对比

特性‌**reactive()** ‌‌**ref()** ‌
适用数据类型仅对象/数组任意类型(基本类型+对象)
返回值Proxy 对象含 .value 的 Ref 对象
深层响应性自动处理嵌套属性需手动处理嵌套对象

总结

Vue 3 通过 reactive() 返回 Proxy 代理,实现了 ‌细粒度依赖追踪‌、‌动态属性支持‌ 和 ‌深层响应式转换‌ 等核心功能‌12。这种设计在提升性能的同时,降低了开发者处理复杂数据结构的成本,是 Vue 响应式系统的基石。

生命周期

一、整体对比

特性Vue 2Vue 3说明
钩子命名选项式(如 created函数式(如 onCreatedVue 3 组合式 API 需显式导入钩子函数 ‌
创建阶段合并beforeCreate 和 created 分开合并到 setup() 中Vue 3 的 setup() 替代了这两个钩子 ‌
销毁阶段重命名beforeDestroydestroyedonBeforeUnmountonUnmounted更贴合“卸载”行为语义 ‌
新增调试钩子onRenderTrackedonRenderTriggered用于追踪数据变化和渲染流程 ‌

proxy 可以拦截数组变化吗

一、Proxy 对数组操作的拦截能力

  1. 原生数组方法触发更新
    Proxy 可拦截 pushpopsplice 等原生数组方法,无需像 Vue 2 那样重写数组方法即可自动触发依赖更新 ‌13。

  2. 索引修改与属性增减

    • 通过索引修改数组元素(如 arr = 1)可直接被 Proxy 拦截 ‌。
    • 动态添加或删除数组元素(如 arr.length = 0)也能被捕获 ‌。
  3. 嵌套数组的深度监听
    Proxy 支持递归代理嵌套数组,确保深层元素变化也能触发响应式更新 ‌。


二、与 Vue 2 的对比(Object.defineProperty 的局限性)

能力Vue 3(Proxy)Vue 2(Object.defineProperty)
拦截原生数组方法✅ 直接拦截(无需改写方法) ‌❌ 需重写数组方法(如 pushpop
检测索引赋值✅ 支持(如 arr = 1) ‌❌ 不支持
动态增删数组元素✅ 支持(如 arr.length = 0) ‌❌ 需 Vue.set/Vue.delete 辅助

三、实现原理

  1. 依赖收集与触发

    • Proxy 拦截器‌:在 get 中收集依赖,在 setdeleteProperty 等操作中触发更新 ‌。
    • 数组方法代理‌:调用原生数组方法时,Proxy 会自动触发更新逻辑(如 push 后通知视图渲染) ‌。
  2. 性能优化
    Proxy 的拦截范围覆盖数组所有操作,避免了 Vue 2 中因重写方法导致的性能损耗和代码冗余 ‌。


总结

Proxy 能够全面拦截数组的‌索引修改、原生方法调用、动态属性增删‌等操作,解决了 Vue 2 中响应式数组的局限性 ‌。这种设计简化了开发逻辑,同时提升了性能与代码可维护性。

前端两个 dom 元素是可以拖拽的, 要实现两个 dom 之间的连接线,如何实现

一、核心实现思路

  1. 元素定位与坐标计算
    通过 offsetLeftoffsetTop 或 getBoundingClientRect() 获取拖拽元素的实时位置,计算连线起点与终点坐标(如中心点或自定义锚点)‌。

  2. 动态绘制连线
    使用 ‌SVG‌、‌Canvas‌ 或 ‌CSS 绝对定位元素‌(如 <div>)绘制连接线,并根据元素位置动态调整连线的位置、长度和角度‌。

  3. 拖拽与连线更新
    监听元素的拖拽事件(如 mousedownmousemovemouseup),在拖拽过程中实时更新连线坐标‌。

    二、具体实现步骤(以 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();

三、优化与扩展方案

  1. 使用库简化开发
    采用 ‌jsPlumb‌ 或 ‌LeaderLine‌ 等库,可快速实现拖拽连线、锚点自定义和动态样式调整‌。

  2. 性能优化

    • 节流更新‌:对 mousemove 事件使用节流函数(如 requestAnimationFrame),避免高频重绘‌4。
    • 局部重绘‌:仅更新受影响的连线,而非全量渲染。
  3. 自定义连线样式
    支持箭头、虚线、渐变等效果(通过 SVG 的 marker-endstroke-dasharray 属性)‌。


总结

通过 ‌原生 JS 监听拖拽事件 + SVG 动态绘制路径‌ 或 ‌现成库(如 jsPlumb) ‌,可实现可拖拽 DOM 元素的动态连接线。核心要点包括:

  • 实时计算元素坐标‌;
  • 动态更新连线位置‌;
  • 优化性能与交互体验‌。