2025前端社招最新面试题汇总- 场景题篇
1. pdf预览
// 1.href // 2.window window.open('a.pdf','_blank')
3. 使用 PDF.js
PDF.js是一个由 Mozilla 开发的开源库,它使用 HTML5 Canvas 来渲染 PDF 文件。PDF.js 提供了广泛的 API 来实现 PDF 的加载、渲染、缩放、打印等功能。
// 初始化PDF.js pdfjsLib.getDocument("/path/to/your/document.pdf").promise.then(function (pdfDoc) { // 获取第一页 pdfDoc.getPage(1).then(function (page) { // 设置视口和比例 var scale = 1.5; var viewport = page.getViewport({ scale: scale }); // 准备用于渲染的Canvas var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); canvas.height = viewport.height; canvas.width = viewport.width; // 将Canvas添加到DOM中 document.getElementById("pdf-container").appendChild(canvas); // 通过Canvas渲染PDF页面 var renderContext = { canvasContext: ctx, viewport: viewport, }; page.render(renderContext); }); });
使用第三方服务
也可以使用第三方服务如 Google Docs Viewer 来预览 PDF。这种方法的优点是容易实现,但依赖于外部服务。
其中,将http://path.to/your/document.pdf替换为你的 PDF 文件的真实 URL。
2. 日志监控问题:可有办法将请求的调用源码地址包括代码行数也上报上去?
2.1.1. 源码映射(Source Maps)
SourceMap 主要用于调试目的,让开发者能够在压缩或转译后的代码中追踪到原始代码。
webpack中 配置 devtool: 'source-map'后,
在编译过程中,会生成一个 .map 文件,一般用于代码调试和错误监控。
- 包含了源代码、编译后的代码、以及它们之间的映射关系。
- 编译后的文件通常会在文件末尾添加一个注释,指向 SourceMap文件的位置。
-
- // # sourceMappingURL=example.js.map
- 当在浏览器开发者工具调试时,浏览器会读取这行注释并加载对应的 SourceMap 文件
报错时,点击跳转。即使运行的是编译后的代码,也能够追溯到原始源代码的具体位置,而不是处理经过转换或压缩后的代码,从而提高了调试效率。
2.1.2. 自定义错误日志逻辑
使用try .. catch 自定义报错逻辑,便于错误的追踪。
3. 用户线上问题的解决
首先看是否是突然性大量用户受到影响,如果是可能是上线新功能影响,则应该立即回退,降低影响范围
然后再处理问题。复现问题-判断是前端还是后端问题。
- 浏览器缓存
- 插件影响
- 网络问题
- 浏览器版本问题
- 问题解决后需要进行复盘。
4. 大文件上传
- 前端上传大文件时使用 file.slice 将文件切片,并发上传多个切片(有标号, Blob 对象),最后发送一个合并的请求通知
- 使用formData 上传文件
-
- 将分块后的 Blob 对象封装到FormData中,以便通过 HTTP 请求发送。FormData对象提供了一种简单的方式来构造一个包含表单数据的对象,并且可以直接作为fetch或axios请求的body参数。
- 服务端合并切片
-
- 切片上传可以将上传成功的切片通过localstorage保存,再 继续上传失败的内容
- 服务端接收切片并存储,收到合并请求后使用流将切片合并到最终文件
- 原生 XMLHttpRequest 的 upload.onprogress 对切片上传进度的监听
- 使用 Vue 计算属性根据每个切片的进度算出整个文件的上传进度
upload // 切片大小 // the chunk size const SIZE = 10 * 1024 * 1024; export default { data: () => ({ container: { file: null }, data: [] }), methods: { handleFileChange(e) { const [file] = e.target.files; if (!file) return; Object.assign(this.$data, this.$options.data()); this.container.file = file; }, // 生成文件切片 + createFileChunk(file, size = SIZE) { + const fileChunkList = []; + let cur = 0; + while (cur { + const formData = new FormData(); + formData.append("chunk", chunk); + formData.append("hash", hash); + formData.append("filename", this.container.file.name); + return { formData }; + }) + .map(({ formData }) => + this.request({ + url: "http://localhost:3000", + data: formData + }) + ); + // 并发请求 + await Promise.all(requestList); + }, + async handleUpload() { + if (!this.container.file) return; + const fileChunkList = this.createFileChunk(this.container.file); + this.data = fileChunkList.map(({ file },index) => ({ + chunk: file, + // 文件名 + 数组下标 + hash: this.container.file.name + "-" + index + })); + await this.uploadChunks(); + } // 合并切片 + await this.mergeRequest(); }, + async mergeRequest() { + await this.request({ + url: "http://localhost:3000/merge", + headers: { + "content-type": "application/json" + }, + data: JSON.stringify({ + filename: this.container.file.name + }) + }); + }, } };
5. 文本点开收起展开
Document 这是一段可能很长的文本,我们希望在一开始时只显示部分,点击“展开”按钮后显示全部内容,再次点击则“收起”文本。 展开 const button = document.getElementById('toggleButton'); button.addEventListener('click', () => { const container = document.getElementById('textContainer'); if (button.textContent === '展开') { button.textContent = '收起'; container.style.whiteSpace = 'normal' } else { button.textContent = '展开'; container.style.whiteSpace = 'nowrap' } }) .text-overflow { width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
6. 富文本划线获取
document.addEventListener('mouseup',(e) => { const selection = window.getSelection() if(selection) { const res = selection.toString(); console.log(res) } })
7. 鼠标拖拽
实现鼠标拖拽功能通常涉及到监听和处理鼠标事件,比如:mousedown、mousemove和mouseup事件。
展开 const button = document.getElementById("toggleButton") button.style.cursor = 'pointer' button.style.position = 'absolute' let dist = { x: 0, y: 0 } let isdraggable = false; button.addEventListener('mousedown', (e) => { isdraggable = true; dist.x = e.pageX - button.offsetLeft; dist.y = e.pageY - button.offsetTop; }) button.addEventListener('mousemove', (e) => { if (isdraggable) { button.style.left = e.pageX - dist.x + 'px' button.style.top = e.pageY - dist.y + 'px' } }) button.addEventListener('mouseup', (e) => { if (isdraggable) { isdraggable = false; dist.x = 0 dist.y = 0 } })
8. 要统计全站每一个静态资源(如图片、JS 脚本、CSS 样式表等)的加载耗时
- 使用 PerformanceObserver : 创建一个 PerformanceObserver 实例来监听资源加载事件,能够实时收集性能数据,而且对性能影响较小。
- 过滤静态资源类型: 通过检查 initiatorType 属性,筛选出静态资源(例如 img、script、css 等)的加载事件。
- 计算和展示耗时: 对每个静态资源的加载耗时进行计算并展示。资源的耗时可以通过 duration 属性直接获取。
// 创建性能观察者实例来监听资源加载事件 const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); for (const entry of entries) { // 过滤静态资源类型 if (["img", "script", "css", "link"].includes(entry.initiatorType)) { console.log(`资源 ${entry.name} 类型 ${entry.initiatorType} 耗时:${entry.duration.toFixed(2)} 毫秒`); } } }); // 开始观察 Resource Timing 类型的性能条目 observer.observe({ entryTypes: ["resource"] });
9. 如何防止前端接口重复发送
1、提交按钮点击后增加loading, 防止重复点击
2、节流或防抖
3、使用缓存
对于一些数据不经常变化的请求,例如用户信息、配置数据等,可以将请求的结果缓存起来。下一次请求相同的资源时,先从缓存中读取数据,如果缓存有效,则无需再发起新的网络请求。
10. 一次性渲染十万条数据
10.1. 全部渲染-卡死
这种方法虽然实现起来简单直接,但由于它在一个循环中创建并添加了所有列表项至DOM树,因此在执行过程中,浏览器需要等待JavaScript完全执行完毕才能开始渲染页面。当数据量非常大(例如本例中的100,000个列表项)时,这种大量的DOM操作会导致浏览器的渲染队列积压大量工作,从而引发页面的回流与重绘,浏览器无法进行任何渲染操作,导致了所谓的“阻塞”渲染。
10.2. setTimeout分批渲染 或 requestAnimationFrame
为了避免一次性操作引起浏览器卡顿,我们可以使用setTimeout将创建和添加操作分散到多个时间点,每次只渲染一部分数据。
let ul=document.getElementById('container'); const total=100000 let once= 20 let page=total/once let index=0 function loop(curTotal,curIndex){ let pageCount=Math.min(once,curTotal) setTimeout(()=>{ for(let i=0;i{ item.text }} { let target = document.querySelector('.' + entry.target.className + 'Li') if (entry.isIntersecting) { // 出现在检测区域内 document.querySelectorAll('li').forEach(el => { if(el.classList.contains('active')){ el.classList.remove('active') } }) if (!target.classList.contains('active')) { target.classList.add('active') } } }) }, { threshold: 1 }) document.querySelectorAll('div').forEach(el => { observer.observe(el) })
13. 退出浏览器之间, 发送积压的埋点数据请求
- fecth的 keepalive属性
- navigator.sendBeacon()
navigator.sendBeacon() 方法允许你在浏览器会话结束时异步地向服务器发送小量数据。这个方法的设计初衷就是为了解决上述问题。sendBeacon() 在大多数现代浏览器中得到支持,并且其异步特性意味着它不会阻塞页面卸载或影响用户体验。
window.addEventListener("beforeunload", function (event) { var data = { /* 收集的埋点数据 */ }; var beaconUrl = "https://yourserver.com/path"; // 你的服务器接收端点 navigator.sendBeacon(beaconUrl, JSON.stringify(data)); });
fetch() API 的 keepalive 选项是另一个选择。这个选项允许你发送一个保持存活状态的请求,即使用户已经离开页面。但是,需要注意的是,使用 keepalive 选项发送的请求有大小限制(大约为 64KB)。
window.addEventListener("beforeunload", function (event) { var data = { /* 收集的埋点数据 */ }; var beaconUrl = "https://yourserver.com/path"; // 你的服务器接收端点 fetch(beaconUrl, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json", }, keepalive: true, // 保持请求存活 }); });
14. 代码打印
const { a = 1, b = 2, c = 3 } = { a: '', b: undefined, c: null }; // 只有设置为undefined的时候或者没有这个属性的时候才使用默认值 console.log(a, b, c); // 2, null // 考察运符号优先级和 加法 // const result = undefined || (1 + undefined) || 2; // undefined转换成数字是NaN, 1+NaN = NaN // const result = undefined || NaN || 2; // 最终输出2 const result = undefined || 1 + undefined || 2; console.log(result);
15. 多核处理任务
多核环境下的性能优化需求和JavaScript特性,可采用Web Workers结合时间分片技术实现非阻塞定时任务处理。以下是基于JavaScript类的实现方案
// worker.js self.onMessage = (data) => { const start = Date.now() while (Date.now() - start this.#handleResult(data, worker) worker.task = null; this.workersPool.push(worker) } } addTask(input) { return new Promise((resolve, reject) => { const task = { id: Date.now(), input, resolve, } this.taskQueue.push(task) this.#run() }) } #run() { if (!this.taskQueue.length) return; const availWorker = this.workersPool.find(item => !item.busy) if (!availWorker) return; availWorker.busy = true; const task = this.taskQueue.shift(); availWorker.task = task; availWorker.postMessage({ id: task.id, input: task.input }) } #handleResult(data, worker) { worker.busy = false; worker.task.resolve(data); worker.task = null; this.#run() } } // 使用实例 const processor = new MultTask(3) setInterval(() => { processor.addTask(Math.random()).then(res => console.log(res)) }, 40)
16. 浏览器环境下幂级计算的优化方案
快速幂运算 + webworker 结合
快速幂算法
通过二进制分解指数,将计算复杂度从 O(n) 优化至 O(log n),减少乘法次数。例如计算 a15 时,分解为 a8×a4×a2×a1,仅需 4 次乘法而非 14 次
// worker.js // // 分解为子问题计算,递归 function fast(a, n) { if (n === 0) return 1; if(n { const res = fastPower(a,n) self.postmessage(res) } // main const worker = new Worker(./worker.js) worker.postmessage(2,15) worker.onmessage = (res) => { console.log(res) }
- 切片上传可以将上传成功的切片通过localstorage保存,再 继续上传失败的内容
-
- 服务端合并切片
- 将分块后的 Blob 对象封装到FormData中,以便通过 HTTP 请求发送。FormData对象提供了一种简单的方式来构造一个包含表单数据的对象,并且可以直接作为fetch或axios请求的body参数。
-
- 当在浏览器开发者工具调试时,浏览器会读取这行注释并加载对应的 SourceMap 文件
- // # sourceMappingURL=example.js.map
-