前端面试题(三)

一、once用原生js如何生效

在原生 JavaScript 中,可以使用 addEventListener 的第三个参数 { once: true },事件触发一次后浏览器会自动移除监听器。

const button = document.querySelector('#btn');

button.addEventListener('click', () => {
  console.log('只会执行一次');
}, {
  once: true
});

如果是 Lodash 的 once 函数,可以通过闭包保存一个状态,第一次调用时执行原函数,后续调用直接返回第一次的结果:

function once(fn) {
  let called = false;
  let result;

  return function (...args) {
    if (!called) {
      called = true;
      result = fn.apply(this, args);
    }

    return result;
  };
}

const init = once(() => {
  console.log('只执行一次');
});

init(); // 执行
init(); // 不再执行

二、获取div在屏幕上的位置

在 Vue 中通过 ref 获取 DOM 元素,然后调用原生的 getBoundingClientRect() 获取元素相对于视口的位置和尺寸。getBoundingClientRect() 返回的是元素相对于当前浏览器视口的位置:

// this.$refs.box.getBoundingClientRect(); vue2的写法
const rect = boxRef.value.getBoundingClientRect(); rect.top; // 元素顶部距离视口顶部 rect.left; // 元素左侧距离视口左侧 rect.right; // 元素右侧距离视口左侧 rect.bottom; // 元素底部距离视口顶部 rect.width; // 元素宽度 rect.height; // 元素高度

如果需要获取元素相对于整个文档的位置,需要加上页面滚动距离

const rect = boxRef.value.getBoundingClientRect();

const top = rect.top + window.scrollY;
const left = rect.left + window.scrollX;

console.log(top, left);

三、css 变量如何声明和使用

CSS 变量也叫自定义属性,使用 --变量名 声明,使用 var(--变量名) 读取。通常会声明在 :root 中,这样可以全局使用。

:root {
  --primary-color: #409eff;
  --font-size: 16px;
  --border-radius: 4px;
}

.button {
  color: white;
  background-color: var(--primary-color);
  font-size: var(--font-size);
  border-radius: var(--border-radius);
}

也可以声明局部变量:

.card {
  --card-padding: 20px;
  padding: var(--card-padding);
}

CSS 变量具有继承性,子元素可以使用父元素声明的变量:

.parent {
  --text-color: red;
}

.child {
  color: var(--text-color);
}

可以设置备用值:

// 如果 --title-color 没有定义,就会使用 #333
.title
{ color: var(--title-color, #333); }

JavaScript 也可以读取和修改 CSS 变量:

const root = document.documentElement;

// 修改
root.style.setProperty('--primary-color', 'green');

// 读取
const color = getComputedStyle(root)
  .getPropertyValue('--primary-color')
  .trim();

console.log(color);

在 Vue 中也可以动态绑定:

<template>
  <div class="box" :style="{ '--box-color': color }">
    内容
  </div>
</template>

<script setup>
import { ref } from 'vue';

const color = ref('skyblue');
</script>

<style>
.box {
  color: var(--box-color);
}
</style>

四、fetch下载文件

使用 fetch 下载文件时,先发起请求,再通过 response.blob() 将响应转换成二进制对象,最后创建一个临时 <a> 标签,设置 hrefdownload 属性,模拟点击下载。

async function downloadFile(url, filename) {
  const response = await fetch(url, {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${token}`
    },
    credentials: 'include'
  });

  if (!response.ok) {
    throw new Error('文件下载失败');
  }

  const blob = await response.blob();
  const urlObject = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = urlObject;
  a.download = filename;
  a.click();

  URL.revokeObjectURL(urlObject);
}

如果接口通过响应头返回文件名,可以读取 Content-Disposition

const disposition = response.headers.get('Content-Disposition');

let filename = 'download';

const match = disposition?.match(/filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i);

if (match) {
  filename = decodeURIComponent(match[1]);
}

需要注意几个点:

  1. fetch 遇到 404500 时不会自动进入 catch,所以要手动判断 response.ok
  2. 文件下载要使用 response.blob(),JSON 数据则使用 response.json()
  3. URL.createObjectURL(blob) 创建的是临时对象 URL,下载完成后应调用 URL.revokeObjectURL() 释放资源。
  4. 如果是跨域请求,服务端需要配置 CORS;如果要读取文件名响应头,还需要暴露请求头:
Access-Control-Expose-Headers: Content-Disposition

一句话总结:

fetch 下载文件的核心流程是:fetch 请求、判断 response.ok、调用 blob()、创建对象 URL、通过 <a download> 模拟点击,最后释放对象 URL。

posted @ 2026-08-20 10:55  携手度余生  阅读(6)  评论(0)    收藏  举报