2025前端社招最新面试题汇总- 场景题篇

1. pdf预览

javascript 复制代码
// 1.href
<a href='a.pdf' target="_blank">

// 2.window
window.open('a.pdf','_blank')
  1. 使用 PDF.js

PDF.js是一个由 Mozilla 开发的开源库,它使用 HTML5 Canvas 来渲染 PDF 文件。PDF.js 提供了广泛的 API 来实现 PDF 的加载、渲染、缩放、打印等功能。

xml 复制代码
<!-- 引入pdf.js和pdf.worker.js -->
<script src="/path/to/pdf.js"></script>
<script src="/path/to/pdf.worker.js"></script>

<!-- PDF容器 -->
<div id="pdf-container"></div>

<script>
  // 初始化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);
    });
  });
</script>

使用第三方服务

也可以使用第三方服务如 Google Docs Viewer 来预览 PDF。这种方法的优点是容易实现,但依赖于外部服务。

ini 复制代码
<iframe
  src="http://docs.google.com/gview?url=http://path.to/your/document.pdf&embedded=true"
  style="width:600px; height:500px;"
  frameborder="0"
></iframe>

其中,将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对象提供了一种简单的方式来构造一个包含表单数据的对象,并且可以直接作为fetchaxios请求的body参数。
  • 服务端合并切片
    • 切片上传可以将上传成功的切片通过localstorage保存,再 继续上传失败的内容
  • 服务端接收切片并存储,收到合并请求后使用流将切片合并到最终文件
  • 原生 XMLHttpRequest 的 upload.onprogress 对切片上传进度的监听
  • 使用 Vue 计算属性根据每个切片的进度算出整个文件的上传进度
xml 复制代码
<template>
   <div>
    <input type="file" @change="handleFileChange" />
    <el-button @click="handleUpload">upload</el-button>
  </div>
</template>
​
<script>
// 切片大小
// 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 < file.size) {
+        fileChunkList.push({ file: file.slice(cur, cur + size) });
+        cur += size;
+      }
+      return fileChunkList;
+    },
+   // 上传切片
+    async uploadChunks() {
+      const requestList = this.data
+        .map(({ chunk,hash }) => {
+          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
+        })
+      });
+    },    
  }
};
</script>

5. 文本点开收起展开

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="textContainer" class="text-overflow">
        这是一段可能很长的文本,我们希望在一开始时只显示部分,点击"展开"按钮后显示全部内容,再次点击则"收起"文本。
    </div>
    <button id="toggleButton">展开</button>
    <script>
        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'
            }

        })
    </script>
    <style>
        .text-overflow {
            width: 300px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap
        }
    </style>
</body>

</html>

6. 富文本划线获取

javascript 复制代码
document.addEventListener('mouseup',(e) => {
  const selection = window.getSelection()
  if(selection) {
    const res = selection.toString();
    console.log(res)
  }
})

7. 鼠标拖拽

实现鼠标拖拽功能通常涉及到监听和处理鼠标事件,比如:mousedownmousemovemouseup事件。

ini 复制代码
<button id="toggleButton">展开</button>
    <script>
        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
            }
        })
    </script>

8. 要统计全站每一个静态资源(如图片、JS 脚本、CSS 样式表等)的加载耗时

  • 使用 PerformanceObserver 创建一个 PerformanceObserver 实例来监听资源加载事件,能够实时收集性能数据,而且对性能影响较小。
  • 过滤静态资源类型: 通过检查 initiatorType 属性,筛选出静态资源(例如 imgscriptcss 等)的加载事件。
  • 计算和展示耗时: 对每个静态资源的加载耗时进行计算并展示。资源的耗时可以通过 duration 属性直接获取。
javascript 复制代码
// 创建性能观察者实例来监听资源加载事件
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将创建和添加操作分散到多个时间点,每次只渲染一部分数据。

ini 复制代码
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<pageCount;i++){
      let li=document.createElement('li');
      li.innerText=curIndex+i+':'(Math.random()*total)
      ul.appendChild(li)
    }
    loop(curTotal-pageCount,curIndex+pageCount)
  })
}
loop(total,index)

这里就是把浏览器渲染时的压力分摊给了js引擎js引擎是单线程工作的,先执行同步,异步,然后浏览器渲染,再宏任务,这里就很好的利用了这一点,把渲染的任务分批执行,减轻了浏览器一次要渲染大量数据造成的渲染"阻塞",也很好的解决了数据过多时可能造成页面卡顿或白屏的问题,

使用 requestAnimationFrame 替代 setTimeout,将数据拆分为每帧处理 20-50 条,避免主线程阻塞。相比 setTimeout,帧率更稳定且与浏览器渲染周期同步‌

10.3. 分页实现渲染

10.4. 虚拟列表

虚拟列表其实是按需显示的一种实现,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。

xml 复制代码
<template>
  <div ref="listWrapper" class="list-wrapper" @scroll="handleScroll">
    <div class="visible-items" :style="{ transform: `translateY(${startIndex * itemHeight}px)` }">
      <div v-for="item in visibleItems" :key="item.id" class="list-item">
        {{ item.text }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [/* 假设这里是你的大数据列表 */],
      itemHeight: 50, // 假设每个列表项的高度是固定的
      startIndex: 0, // 当前可视区域的起始索引
      visibleCount: 10 // 可视区域内同时显示的列表项数量
    };
  },
  computed: {
    endIndex() {
      return Math.min(this.startIndex + this.visibleCount, this.items.length);
    },
    visibleItems() {
      return this.items.slice(this.startIndex, this.endIndex);
    }
  },
  methods: {
    handleScroll() {
      const scrollTop = this.$refs.listWrapper.scrollTop;
      this.startIndex = Math.floor(scrollTop / this.itemHeight);
    }
  }
};
</script>

<style scoped>
.list-wrapper {
  height: 300px; /* 设定滚动容器的高度 */
  overflow-y: auto; /* 允许垂直滚动 */
  position: relative;
}
.list-item {
  height: 50px; /* 与data中的itemHeight保持一致 */
  /* 其他样式 */
}
</style>

11. 如何判断用户设备

  • 用户代理字符串包含了浏览器类型、版本、操作系统等信息,可以通过分析这些信息来大致判断用户的设备类型。navigator.userAgent 属性用于获取用户代理字符串。

  • 使用window.innerWidth 检测视口宽度

12. IntersectionObserver + scrollIntoView 实现电梯导航

juejin.cn/post/739998...

电梯导航也被称为锚点导航

  • 当点击锚点元素时,页面内相应标记的元素滚动到视口。 scrollIntoView
  • 页面内元素滚动时相应锚点也会高亮。IntersectionObserver
javascript 复制代码
// 点击锚点跳转
let rightBox = document.querySelector('.rightBox')
  rightBox.addEventListener('click', function (e) {
    let target = e.target || e.srcElement;
    if (target && !target.classList.contains('rightBox')) {
      document.querySelector('.' + target.className.replace('Li', '')).scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      })
    }
  }, false)
// 页面滚动时,对应锚点样式改变
// 也可以使用计算el.offsetTop < window.innerHeight + document.documentElement.scrollTop判断
// 是否进入视口
let observer = new IntersectionObserver(function (entries) {
  entries.forEach(entry => {
    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() 在大多数现代浏览器中得到支持,并且其异步特性意味着它不会阻塞页面卸载或影响用户体验。

javascript 复制代码
window.addEventListener("beforeunload", function (event) {
  var data = {
    /* 收集的埋点数据 */
  };
  var beaconUrl = "https://yourserver.com/path"; // 你的服务器接收端点

  navigator.sendBeacon(beaconUrl, JSON.stringify(data));
});

fetch() API 的 keepalive 选项是另一个选择。这个选项允许你发送一个保持存活状态的请求,即使用户已经离开页面。但是,需要注意的是,使用 keepalive 选项发送的请求有大小限制(大约为 64KB)。

less 复制代码
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. 代码打印

ini 复制代码
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类的实现方案

ini 复制代码
 // worker.js
self.onMessage = (data) => {
    const start = Date.now()
    while (Date.now() - start < 100) {
        continue
    }
    self.postMessage(data)
}
// main

class MultTask {
    constructor(concurrency = 4) {
        this.workersPool = [];
        this.taskQueue = [];
        for (let i = 0; i < concurrency; i++) {
            const worker = new Worker('worker.js');
            worker.onmessage = (data) => 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 次‌

scss 复制代码
// worker.js
// // 分解为子问题计算,递归
function fast(a, n) {
  if (n === 0) return 1;

  if(n < 0) return 1 / fast(a, -n);
  
  const half = fast(a, Math.floor(n / 2));
  return n % 2 === 0 ? half * half : half * half * a
}

self.onmessage = (a,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)
}
相关推荐
Johnstons几秒前
AnaTraf:深度解析网络性能分析(NPM)
前端·网络·安全·web安全·npm·网络流量监控·网络流量分析
whatever who cares26 分钟前
CSS3 伪元素(Pseudo-elements)大全
前端·css·css3
若愚679229 分钟前
前端取经路——性能优化:唐僧的九道心经
前端·性能优化
积极向上的龙1 小时前
首屏优化,webpack插件用于给html中js自动添加异步加载属性
javascript·webpack·html
Bl_a_ck1 小时前
开发环境(Development Environment)
开发语言·前端·javascript·typescript·ecmascript
田本初2 小时前
使用vite重构vue-cli的vue3项目
前端·vue.js·重构
ai产品老杨2 小时前
AI赋能安全生产,推进数智化转型的智慧油站开源了。
前端·javascript·vue.js·人工智能·ecmascript
帮帮志2 小时前
vue实现与后台springboot传递数据【传值/取值 Axios 】
前端·vue.js·spring boot
xixingzhe22 小时前
Nginx 配置多个监听端口
服务器·前端·nginx
程序员Bears2 小时前
从零打造个人博客静态页面与TodoList应用:前端开发实战指南
java·javascript·css·html5