前端实践问题

拖拽过程中,如果原本的元素消失了,onDrop还能触发么?具体表现是什么?

即使原始元素在拖拽过程中被移除了,只要拖拽操作未被中断,onDrop 事件仍然可以触发

  1. 拖拽数据独立存储
    拖拽操作一旦开始(dragstart),浏览器会将拖拽数据存储在独立的 DataTransfer 对象中。即使原始元素被移除,已存储的拖拽数据依然有效

  2. 视觉反馈与 DOM 解耦
    拖拽过程中显示的「拖拽预览图像」是浏览器生成的副本,与原始元素的 DOM 状态无关。即使原始元素消失,预览图像通常仍会正常显示。

具体表现

1. onDrop 正常触发

  • 如果拖拽操作成功完成(用户释放鼠标在有效的拖放目标上),onDrop 仍会触发。

  • 可以通过 event.dataTransfer.getData() 正常获取拖拽数据。

  • 示例代码

     
    // 拖拽源元素
    source.addEventListener('dragstart', (e) => {
      e.dataTransfer.setData('text/plain', 'Hello World');
      e.target.remove(); // 立即移除原始元素
    });
    
    // 放置目标
    target.addEventListener('drop', (e) => {
      e.preventDefault();
      console.log(e.dataTransfer.getData('text/plain')); // 输出 "Hello World"
    });

2. dragend 事件可能异常

  • 如果原始元素被移除,其 dragend 事件可能无法触发,或触发时因元素不存在而导致关联逻辑出错。

  • 示例问题

     
    source.addEventListener('dragend', () => {
      console.log('拖拽结束'); // 可能不会执行
    });

3. 意外中断拖拽操作

  • 如果在 dragstart 中直接移除元素,某些浏览器可能因渲染更新而意外终止拖拽操作,导致 onDrop 无法触发。

  • 解决方案:使用异步移除(如 setTimeout)确保拖拽操作初始化完成:
source.addEventListener('dragstart', (e) => {
  e.dataTransfer.setData('text/plain', 'Hello World');
  setTimeout(() => e.target.remove(), 0); // 异步移除
});

总结

  • onDrop 触发条件:仅取决于用户是否在有效的目标上释放鼠标,与原始元素是否存在无关。

  • 风险点dragend 事件可能不可靠,且直接移除元素可能导致浏览器行为不一致。

  • 最佳实践:如需移除原始元素,建议在 dragend 或 drop 事件中处理,而非 dragstart

如何改变拖拽预览图?如何让拖拽预览图有圆角?

一、核心方法:setDragImage()

通过 dragstart 事件的 dataTransfer.setDragImage() 方法,可直接指定拖拽预览图

代码示例

element.addEventListener('dragstart', (e) => {
  // 1. 创建一个自定义预览元素
  const preview = document.createElement('div');
  preview.textContent = "拖拽我";
  preview.style.cssText = `
    width: 100px;
    height: 40px;
    background: #2196F3;
    color: white;
    border-radius: 8px;  // 关键:圆角样式
    display: flex;
    align-items: center;
    justify-content: center;
  `;

  // 2. 将元素临时添加到 DOM 中(部分浏览器需要渲染才能捕获图像)
  document.body.appendChild(preview);
  
  // 3. 设置为拖拽预览图,并指定光标偏移位置(居中)
  e.dataTransfer.setDragImage(preview, preview.offsetWidth / 2, preview.offsetHeight / 2);

  // 4. 立即移除临时元素(可选)
  setTimeout(() => document.body.removeChild(preview), 0);
});

二、实现圆角的注意事项

1. 必须确保元素已渲染

  • 部分浏览器(如 Firefox)要求预览元素必须已插入 DOM 并完成渲染,否则无法生成图像。

  • 解决方案:临时添加到 DOM 后立即移除。

2. 使用 Canvas 生成复杂预览

若需要更复杂的图形(如带阴影的圆角矩形),可通过 Canvas 生成图像:

const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 40;
const ctx = canvas.getContext('2d');

// 绘制圆角矩形
ctx.beginPath();
ctx.roundRect(0, 0, 100, 40, 8);  // 圆角半径 8px
ctx.fillStyle = '#2196F3';
ctx.fill();

// 设置为拖拽预览
e.dataTransfer.setDragImage(canvas, 50, 20);  // 偏移到中心

三、浏览器兼容性

方法 Chrome Firefox Safari
setDragImage()
Canvas 作为预览图
未渲染元素的 setDragImage

四、完整示例(含圆角和动画)

<style>
  .drag-item {
    width: 120px;
    padding: 12px;
    background: #4CAF50;
    color: white;
    cursor: grab;
  }
</style>

<div class="drag-item" draggable="true">拖拽我</div>

<script>
  document.querySelector('.drag-item').addEventListener('dragstart', (e) => {
    // 创建预览元素
    const preview = document.createElement('div');
    preview.textContent = "正在拖拽...";
    preview.style.cssText = `
      width: 120px;
      padding: 12px;
      background: #4CAF50;
      color: white;
      border-radius: 8px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.2);
      opacity: 0.8;
    `;

    // 临时插入 DOM
    document.body.appendChild(preview);
    e.dataTransfer.setDragImage(preview, 60, 20); // 居中偏移
    setTimeout(() => preview.remove(), 0);
  });
</script>

五、高级技巧

1. 动态预览内容

preview.innerHTML = `
  <div style="display: flex; align-items: center;">
    <img src="icon.png" width="24">
    <span>${e.target.textContent}</span>
  </div>
`;

2. 隐藏默认预览

e.dataTransfer.setDragImage(new Image(), 0, 0);  // 设置透明图像

通过 setDragImage() 结合 CSS 或 Canvas,可以完全控制拖拽预览图的样式,包括圆角、阴影等复杂效果。

如何让 echart 的内容跟随容器大小而变化?onResize 的时候要怎么做?如果有可伸缩侧边栏之类的,导致容器因为其他原因发生了改变,应该用什么事件监听? 

核心方法:resize() + 尺寸监听

ECharts 实例通过 resize() 方法重新计算尺寸,需在容器尺寸变化时手动调用。


一、基础场景:窗口缩放

监听浏览器窗口的 resize 事件:

const chart = echarts.init(document.getElementById('chart-container'));

// 监听窗口变化
window.addEventListener('resize', () => {
  chart.resize();
});

二、进阶场景:容器尺寸动态变化(如侧边栏伸缩)

1. 使用 ResizeObserver(推荐)

  • 直接监听容器尺寸变化,无需依赖父级元素事件

  • 代码示例:

    const container = document.getElementById('chart-container');
    const chart = echarts.init(container);
    
    const resizeObserver = new ResizeObserver(() => {
      chart.resize();
    });
    
    resizeObserver.observe(container); // 开始监听
    
    // 组件卸载时销毁(重要!)
    // Vue: onBeforeUnmount(() => resizeObserver.disconnect())
    // React: useEffect(() => () => resizeObserver.disconnect(), [])
  • 兼容性处理(旧版浏览器):

    npm install resize-observer-polyfill
     
    import ResizeObserver from 'resize-observer-polyfill';

2. 框架特定方案(如侧边栏回调)

  • 若使用 Element UI/ Ant Design 等组件库,在侧边栏展开/折叠的回调中触发:

    // Element UI 侧边栏折叠事件
    <el-menu @collapse="handleCollapse"> → 
    handleCollapse() {
      this.$nextTick(() => this.chart.resize());
    }

三、性能优化

1. 防抖处理(高频变化场景)

let resizeTimer;
const resizeObserver = new ResizeObserver(() => {
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(() => chart.resize(), 100);
});

2. 容器动画同步

.chart-container {
  transition: width 0.3s; /* 动画持续期间持续触发 ResizeObserver */
}

四、完整示例(Vue3 + TypeScript)

<template>
  <div ref="chartContainer" class="chart"></div>
</template>

<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from 'vue';
import * as echarts from 'echarts';
import ResizeObserver from 'resize-observer-polyfill';

const chartContainer = ref<HTMLElement>();
let chart: echarts.ECharts;
let resizeObserver: ResizeObserver;

onMounted(() => {
  chart = echarts.init(chartContainer.value!);
  chart.setOption({ /* 配置项 */ });

  // 监听容器变化
  resizeObserver = new ResizeObserver(() => chart.resize());
  resizeObserver.observe(chartContainer.value!);
});

onBeforeUnmount(() => {
  resizeObserver?.disconnect();
  chart?.dispose();
});
</script>

五、常见问题排查

现象 解决方案
图表未响应侧边栏变化 确认监听的是图表容器而非父元素
出现空白间隙 检查容器 CSS 是否设置 width: 100%
内存泄漏 确保组件卸载时调用 disconnect()

通过 ResizeObserver + resize() 的组合,可精准响应任意原因导致的容器尺寸变化,无需依赖具体 UI 框架。

如果是 toc 产品,后端为 index.html 设置了 1 h 的 max age,请问从你重新构建代码发布,大概多久之后,所有用户都可以看到新界面?如果用户访问出现了白屏,是什么原因?

问题一:代码发布后用户看到新界面的时间

核心时间范围:最长 1 小时,但存在变量

  1. 理论最长时间
    由于 index.html 设置了 max-age=3600(1小时),未主动刷新页面的用户需等待缓存过期后才会获取新版本

    • 用户首次访问时间点不同:若用户恰好在发布前 5 分钟访问过,需等待 55 分钟才能更新。

  2. 实际可能更短

    • 强制刷新(Ctrl+F5):用户手动刷新会跳过缓存,立即获取新版本。

    • 文件名哈希策略:若 JS/CSS 文件使用哈希指纹(如 app.a1b2c3.js),新版本会触发浏览器重新下载资源,即使 index.html 仍被缓存,也可能部分更新。

    • CDN 缓存行为:若 CDN 配置的缓存时间短于 1 小时,用户可能提前获取新版本。

问题二:用户访问白屏的可能原因

核心原因排查清单

原因分类 具体场景 解决方案
缓存冲突 旧版 index.html 引用了已被删除或重命名的资源文件(如未哈希的 JS/CSS) 使用内容哈希文件名,确保新旧资源共存
资源加载失败 新版本资源未正确部署到服务器,导致 404 错误 检查构建产物是否完整上传,验证 CDN/服务器路径
代码执行错误 新版 JS 中存在语法错误或依赖兼容性问题(如浏览器不支持 ES6+ 语法) 启用 Babel 转译,添加错误监控(如 Sentry)捕获运行时错误
路由配置问题 单页应用(SPA)路由未正确配置,导致空白路由匹配 检查路由兜底设置(如 404 重定向到首页)
Service Worker 旧版 Service Worker 强制缓存了过时资源 更新 Service Worker 版本并触发立即激活(如 self.skipWaiting()

最佳实践建议

  1. 缓存策略优化

    • 对 index.html 设置较短缓存(如 max-age=300),或使用 no-cache 配合 ETag 验证。

    • 静态资源(JS/CSS/图片):设置长期缓存(如 max-age=31536000)并添加哈希指纹。

  2. 部署流程增强

    # 示例:使用 Webpack 生成带哈希的文件名
    output: {
      filename: '[name].[contenthash].js',
      chunkFilename: '[name].[contenthash].chunk.js'
    }
    • 先上传新资源,再更新 index.html,避免出现中间状态。

  3. 监控与回滚

    • 部署后实时监控错误率(如通过 APM 工具)。

    • 准备快速回滚方案(如保留前一次构建产物)。

  4. 用户提示机制

     
    // 检测版本更新并提示用户刷新
    if (navigator.serviceWorker) {
      navigator.serviceWorker.addEventListener('controllerchange', () => {
        window.location.reload();
      });
    }

示例:强制缓存失效方案

通过修改 index.html 的 URL 路径(如添加版本号)绕过缓存:

# Nginx 配置示例
location / {
  if ($request_uri ~* "index\.html$") {
    add_header Cache-Control "no-cache, must-revalidate";
  }
}

或使用查询参数(不推荐,部分 CDN 会忽略):

<script src="/app.js?v=20231001"></script>

toc 产品大量使用 cdn,请问 cdn 的定价大概多少?针对这样的定价策略,前端应该进行什么样的优化?前端应用中?上传贵还是下载贵?上传快还是下载快?

一、CDN 定价模型(以主流厂商为例)

CDN 的定价通常由 流量、请求次数、存储 和 增值功能 四部分构成,以下为典型价格范围(以中国大陆和全球主流 CDN 服务商为例):

计费项 价格范围 示例场景
流量费用 中国大陆: 0.1~0.3 元/GB
海外: 0.05~~~~~~~~~~~~~~~~~~~0.2 元/GB
用户下载 1GB JS/CSS/图片资源,约消耗 0.2 元(按国内均价)
HTTP 请求次数 - 0.01~0.1 元/万次 100 万次图片请求 ≈ 5~10 元
存储费用 - 0.1~0.3 元/GB/月 存储 100GB 静态资源 ≈ 10~30 元/月
增值服务 - DDoS 防护: 100~500 元/月
- 全站加速(动态请求): 0.3~~~~~~~~~~~~~~~~~~~~~~1 元/GB
动态 API 加速 100GB ≈ 30~100 元

主流厂商参考

  • 阿里云/腾讯云:流量阶梯计价(用量越大单价越低)

  • Cloudflare:免费套餐含基础防护,Pro 套餐 20 美元/月(不限流量)

  • AWS CloudFront:按区域定价(北美 0.085 美元/GB,亚洲 0.14 美元/GB)


二、前端优化策略(降低成本 + 提升性能)

针对 CDN 定价模型,前端需重点优化 流量消耗 和 请求次数

1. 减少流量消耗

方法 效果 实施示例
资源压缩 减少 50%~70% 体积 使用 Brotli(最高压缩率)替代 Gzip,Webpack 配置 compression-webpack-plugin
图片优化 WebP 比 JPEG 节省 30%+ <picture> 标签兼容性兜底:
<source srcset="img.webp" type="image/webp">
代码拆分(Code Splitting) 按需加载减少初始下载量 Vue/React 使用动态导入:() => import('./Component')
Tree Shaking 移除未使用代码 Webpack/Rollup 默认支持,确保 sideEffects: false

2. 降低请求次数

方法 效果 实施示例
HTTP/2 多路复用 单连接并行传输,减少队头阻塞 Nginx 配置 listen 443 http2
资源合并 减少小文件请求 合并图标为 SVG Sprite(如 icon-sprite.svg#home
合理缓存策略 减少重复请求 静态资源设置 Cache-Control: public, max-age=31536000, immutable
CDN 边缘计算 请求在边缘节点处理(如压缩、重定向) Cloudflare Workers 实现 A/B 测试分流

3. 监控与分析

工具 用途
Google Lighthouse 性能评分 + 优化建议
CDN 自带监控 分析流量热点、异常请求(如 404 资源)
Sentry 捕获前端错误,定位资源加载失败问题

三、上传 vs 下载:成本与速度对比

1. 成本对比

方向 定价逻辑 成本
上传(写入CDN) 通常免费或极低费用(厂商希望吸引内容注入) (接近0)
下载(用户访问) 主要收费项(占 CDN 厂商带宽成本的核心) (主要支出)

2. 速度对比

方向 速度表现
上传 依赖本地网络上行带宽(家用宽带通常 10~50Mbps),但可通过 分片上传 或 CDN 就近接入点 优化
下载 通过 CDN 全球加速节点,用户从最近节点获取资源,速度更快(通常 100Mbps+)

四、完整优化案例

场景:一个全球化的电商网站,图片资源占比 70%,JS/CSS 资源频繁更新。

优化步骤

图片处理

  • 使用 sharp 库批量转换为 WebP 格式

  • 响应式图片:srcset 按设备分辨率分发

<img src="product.jpg" 
     srcset="product-400.webp 400w, product-800.webp 800w"
     sizes="(max-width: 600px) 400px, 800px">
  1. 代码优化

    • 启用 Brotli 压缩(Nginx 配置 brotli on;

    • 非核心代码动态加载:

      // 点击时加载支付模块
      button.addEventListener('click', () => {
        import('./payment.js').then(module => module.process());
      });
  2. 缓存策略

    • 静态资源:Cache-Control: public, max-age=31536000, immutable

    • HTML 文件:Cache-Control: no-cache(通过 ETag 验证更新)


五、成本估算(示例)

假设某应用月消耗:

  • 流量:100TB(下载)

  • 请求次数:10 亿次

  • 存储:1TB

费用计算(以阿里云为例)

  • 流量费:100,000 GB × 0.18 元/GB ≈ 18,000 元

  • 请求费:10 亿次 ÷ 10,000 × 0.06 元 ≈ 6,000 元

  • 存储费:1TB × 0.15 元/GB ≈ 150 元

  • 总成本 ≈ 24,150 元/月

优化后(预计节省 40%+)

  • WebP 图片减少 30% 流量 → 流量费降至 12,600 元

  • Brotli 压缩减少 20% 请求量 → 请求费降至 4,800 元

  • 总成本 ≈ 17,550 元/月


通过前端优化可直接降低 CDN 的核心成本项(流量和请求),同时提升用户体验,形成双赢局面。

 图片设置协商缓存后,浏览器会整体缓存,视频能设置协商缓存么?视频的http返回内容与图片有什么区别?如何降低视频展示的成本?

一、视频能否设置协商缓存?

可以,视频与图片的缓存机制原理相同,均可通过 HTTP 缓存头实现协商缓存。

配置示例(Nginx)

location /videos/ {
  # 设置强缓存(可选)
  add_header Cache-Control "public, max-age=3600";
  
  # 协商缓存:通过 Last-Modified/ETag 验证
  if_modified_since before;
  etag on;
}

协商缓存流程

  1. 首次请求:返回 200 OK + Last-Modified/ETag

  2. 再次请求:浏览器携带 If-Modified-Since 或 If-None-Match

  3. 资源未修改:服务器返回 304 Not Modified,浏览器使用本地缓存


二、视频与图片的 HTTP 响应差异

核心区别点

特征 图片(如 JPEG) 视频(如 MP4)
状态码 通常 200 OK 可能 206 Partial Content(范围请求)
Content-Type image/jpeg video/mp4
Accept-Ranges 通常无(小文件不分片) bytes(支持分段加载)
Content-Range bytes 0-999/2000(指示当前传输的字节范围)
缓存行为 完整缓存 可能分片缓存(取决于是否启用范围请求)

示例视频请求响应头

HTTP/1.1 206 Partial Content
Content-Type: video/mp4
Accept-Ranges: bytes
Content-Range: bytes 0-1048575/5242880
Content-Length: 1048576
Cache-Control: public, max-age=3600
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

三、降低视频展示成本的几种策略

1. 启用 HTTP 范围请求(Range Requests)

  • 原理:允许用户仅加载观看的部分视频片段,减少无效流量消耗。

  • 实现:确保服务器支持 Accept-Ranges: bytes(Nginx 默认开启)。

2. 转码为高效编码格式

  • 推荐格式:H.265(HEVC)比 H.264 节省 50% 带宽,AV1 更优但兼容性差。

  • 工具示例

    ffmpeg -i input.mp4 -c:v libx265 -crf 28 output-hevc.mp4

3. 自适应码率(ABR)

  • 技术方案:使用 HLS 或 DASH 协议,根据网络状况动态切换分辨率。

  • 实现步骤

    1. 将视频切片(如 10s/段)并生成多码率版本

    2. 通过 .m3u8(HLS)或 .mpd(DASH)描述文件管理播放

4. CDN 分层存储

  • 策略

    • 热数据:使用 SSD 边缘节点加速

    • 冷数据:存储到低价对象存储(如 AWS S3 Glacier)

ts 开发下,interface和对象类型声明可不可以用来声明数组和函数?如果声明函数,函数名可不可以重复?函数名如果重复,意味着什么?

一、TypeScript 中 interface 和类型声明的能力

1. 声明数组

  • interface 方式

    interface NumberArray {
      [index: number]: number; // 索引签名定义数组
    }
    const arr: NumberArray = [1, 2, 3];
  • 类型别名方式

    type StringArray = string[];
    const arr: StringArray = ["a", "b"];

2. 声明函数

  • interface 定义函数类型

    interface SearchFunc {
      (source: string, keyword: string): boolean;
    }
    const mySearch: SearchFunc = (src, kw) => src.includes(kw);
  • 类型别名定义函数

    type AddFunction = (a: number, b: number) => number;
    const add: AddFunction = (x, y) => x + y;

二、函数名重复问题

1. 函数重载(允许同名)

TypeScript 支持函数重载,但需满足以下条件:

  • 多个签名声明 + 一个实现

    // 重载签名
    function greet(name: string): string;
    function greet(age: number): string;
    
    // 实现签名(需兼容所有重载)
    function greet(input: string | number): string {
      return typeof input === "string" 
        ? `Hello, ${input}` 
        : `You are ${input} years old`;
    }
  • 输出表现

    greet("Alice");    // OK: "Hello, Alice"
    greet(25);         // OK: "You are 25 years old"
    greet(true);       // Error: 没有匹配的重载

2. 重复函数名的本质

  • 编译后结果:JavaScript 没有重载机制,最终只会保留一个函数实现。

  • 类型安全:重载仅在类型检查阶段生效,确保调用时参数合法。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2025-02-16 18:28  Yang9710  阅读(91)  评论(0)    收藏  举报