前端面试相关问题集合

浏览器相关

  • url输入会发生什么?

  1. 浏览器解析URL,查找DNS解析IP
  2. 建立TCP(http)/TLS(https)连接
  3. 发起http请求,服务器返回http响应(HTML,状态码)
  4. 浏览器解析HTML构建DOM,下载CSS和JS
  5. 构建Render Tree, 布局和绘制
  6. 加载外部资源,运行JS,事件绑定,页面加载完成触发DOMContentLoaded和load事件
  • tcp三次握手,为什么是三次?

 tcp是面向连接的协议,通信双方在正式数据传输前,需要通过三次握手来建立连接,保证双方都可以收发数据

 *SYN和ACK都是Tcp协议中头部的标志位,syn表示发起连接的同步序列号,ack表示确认号有效

1、第一次握手:客户端发送 SYN 报文,表示「我要连接」,并发送一个初始序列号 x

2、第二次握手:服务端收到 SYN,回复 SYN+ACK 表示「我收到了,也想连接你」,并发送自己初始序列号 y

3、第三次握手:客户端收到 SYN+ACK,回复 ACK 表示「我收到了,连接建立完毕」

如果只两次握手,某些旧连接包可能引发服务端状态错误(如“半开连接”)

三次握手可以确保:客户端和服务端都确认了对方的“接收能力”

浏览器有哪些优化策略?

首先可以通过Performance Api 或者是chrome devTools中的performance和lightHouse进行页面性能的分析,看FSP,LCP、TTFB、TTI等关键指标,lightHouse的报告会有具体的优化建议

1、网络层面优化: 

缓存:

使用http强缓存/协商缓存(通过在nginx或者nodejs配置cache control)

对构建后的静态资源如img和css等使用缓存(强缓存/协商缓存)下图是在nginx上部署的项目的缓存,会根据dist文件夹内的hash命名值判断是不是文件发生变化:

DNS优化: 

使用DNS预解析

<!-- 为 CDN 资源提前做 DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">
<link rel="dns-prefetch" href="//api.example.com">

减少不同域名的解析,把图片、静态资源、API尽量合并到统一域名,比如通过前端proxy代理到主站或者CDN统一域名,nginx部署统一子域名;

CDN:使用静态资源CDN加速 

2、渲染层面的优化

图片优化:            

  • 图片优化为svg、webP格式,首屏以外的图片使用懒加载给img标签添加loading="lazy"

代码执行层(JS)

  • Webpack的Tree-shaking/按需引入组件
    • Vite默认开启的,只能是基于ES Module的shaking
    • // ✅ 按需引入组件(Tree Shaking 生效)
      import { ElButton, ElMessage } from 'element-plus'
  • 减少全局变量,闭包引用
  • 防抖节流

 渲染性能优化

  • 减少重排重绘
    • 减少 DOM 操作次数,使用 DocumentFragment 批量插入、缓存 layout 信息(获取的offsetTop、offsetLeft之类的)、使用 class 操作样式、visibility 替代 display
  • 使用CSS3动画代替JS动画
  • 简化DOM树

Http协议相关问题

 1.HTTP和HTTPS的区别

2.HTTP的常见码有哪些?

3.什么是HTTP缓存?强缓存vs协商缓存? 

HTTP 缓存通过 HTTP 协议的缓存头控制资源的存储和复用,分为:

  • 缓存头
    • Cache-Control: max-age(缓存有效期)、no-cache(验证后使用)、no-store(不缓存)。
    • ETag:资源唯一标识,验证资源是否变更。
    • Last-Modified:资源最后修改时间,与 If-Modified-Since 配合。

缓存大小:浏览器通常分配几十 MB 到 1GB

 

http缓存的应用:

1、静态资源缓存

不常变更的静态文件,如css, js库,图片等

2、api响应缓存

一些在一定时间内不会变化的数据,如一小时一更新的天气数据等

websocket相关问题

 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,常用的方法有以下:

// 创建WebSocket连接
const socket = new WebSocket('wss://example.com/chat');

// 连接打开事件
socket.onopen = function(event) {
  console.log('连接已建立');
  socket.send('Hello Server!');
};

// 接收消息事件
socket.onmessage = function(event) {
  console.log('收到消息: ', event.data);
};

// 错误处理
socket.onerror = function(error) {
  console.error('WebSocket错误: ', error);
};

// 连接关闭事件
socket.onclose = function(event) {
  console.log('连接已关闭', event.code, event.reason);
};

// 发送消息
function sendMessage(message) {
  if (socket.readyState === WebSocket.OPEN) {
    socket.send(message);
  }
}

websoket和http的区别:

什么是SSR?

前端的本地存储有哪些?

1.localStorage

  • 保存时间永久,除非手动清除
  • 通过键值对存储,大概5MB左右
  • 访问范围:同一域名(origin)下所有页面共享。
  • 常见的使用范围:适合存储需要在页面刷新或关闭后保留的持久化数据,比如地图的底图选择,用户的界面设置
// 存储
localStorage.setItem('key', 'value');

// 读取
const data = localStorage.getItem('key');

// 删除
localStorage.removeItem('key');

// 清空
localStorage.clear();

2. sessionStorage

  • 保存时间,关闭页签或者浏览器就清除
  • 通过键值对存储,大概5MB左右
  • 访问范围:仅限同一标签页,同一域名下不同标签页不共享
  • 通常用于临时的表单内容保存(医生修改内容),敏感用户的一次性登录
sessionStorage.setItem('sessionKey', 'tempValue');

 3.cookie

  • 可设置过期时间,默认会话结束清除
  • 通过字符串保存,约4kb,每次http请求都会携带
  • 同一域名下,视 Path 和 Domain 设置,可跨页面甚至服务器访问。
  • 常用于存储用户登录状态,跟踪用户操作行为习惯(医生修改记录)
// 设置
document.cookie = "username=John; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/";

// 读取
const cookies = document.cookie.split(';');

 前端攻击有哪些?应该如何防护?

1、XSS (Cross-Site Scripting)

XSS 是一种攻击方式,攻击者通过将恶意脚本(如 JavaScript)注入到 Web 页面中,当其他用户访问该页面时,恶意脚本在用户浏览器中执行,可能导致数据窃取、页面篡改或恶意操作。

  • 攻击者将恶意脚本注入到目标网站(如输入框、URL 参数、数据库存储的内容)。
  • 当受害者的浏览器渲染页面时,恶意脚本被执行,可能访问用户的 Cookie、Session、DOM 或发起恶意请求。
  • XSS 利用了 Web 应用对用户输入未正确过滤或转义的漏洞。

XSS的防护有哪些?

  • 对用户的输入进行验证(表单,url参数等)使用库如 sanitize-html(Web)
  • 用户输出到渲染的时候使用转义,比如vue的{{}}和v-bind已经实现了转义,谨慎使用v-html
  • 配置http的csp头,限制同源脚本的执行,示例:Content-Security-Policy: script-src 'self' https://trusted.com

2、CSRF(cross site request fogery)

CSRF 是一种攻击方式,攻击者诱导用户在已登录的网站上执行非预期的操作,利用用户的认证状态(如 Cookie)向服务器发送伪造请求。

  • 用户登录目标网站(如银行网站),浏览器保存认证 Cookie。
  • 攻击者诱导用户访问恶意页面(如通过钓鱼链接),该页面自动发送请求到目标网站(如转账请求)。
  • 目标网站因信任用户的 Cookie,执行请求,导致恶意操作。

CSRF的防护方法:

  • CSRF Token:为每个请求生成唯一、不可预测的 token,将token添加到请求头,服务器验证 token 有效性。
  • 同源策略检查:服务器验证请求头,确保来自可信域名
  • 双重cookie验证:服务器设置 Cookie(如 session_id),并要求请求头包含相同值。

3、DDos攻击

DDoS(Distributed Denial of Service)攻击是一种通过大量恶意流量或请求淹没目标服务器、网站或网络资源,使其无法正常响应合法用户请求的攻击方式。其目的是耗尽服务器资源(如带宽、CPU、内存),导致服务不可用。

DDos防护手段:

1、使用csrf token为每一个请求动态生成token,防止伪造请求

2、使用cdn分发网络,分散攻击压力

3、限制单个ip或用户的请求频率

4、ip黑白名单,及时做好流量监控,拉黑已知的恶意ip

什么是跨域,应该如何解决?

跨域是指浏览器在执行前端脚本(如 JavaScript)时,由于同源策略(Same-Origin Policy)限制,禁止从一个源(origin)加载的网页访问另一个源的资源;同源策略要求请求的协议(protocol)、域名(host)和端口(port)必须完全一致,否则视为跨域。

解决跨域分为开发环境和正式环境:

开发环境:通过vite.config.js中设置代理,设置changeOrigin为true,重写路径

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

生产环境:生产环境需要服务器设置CORS允许特定源

// Node.js + Express 示例
const express = require('express');
const app = express();
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:8080'); // 允许特定源
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});
app.get('/api/data', (req, res) => {
  res.json({ data: 'Hello from API' });
});

或者使用Nginx进行代理

 JS/TS/CSS

JS

  • 常用的ES6方法有哪些?

    • 模块化import/export
    • const let的变量声明
      • 避免了变量提升和污染作用域的问题,可以更有效的维护代码可读性
    • 箭头函数
    • Promise
    • Class
      • class是构造函数的语法糖,但是class的声明不会被提升,构造函数会
      • class语法
      • 继承
    • Map和Set
      • Map是一种键值对集合,保持插入顺序,key可以是任意一种类型,常用的方法有:
      • Set是不包含重复值的集合,主要用于存储一组唯一值,常用方法有:
    • 对象和数组的解构赋值
    • 扩展运算符spread和剩余参数Rest
      • Spread把数组/对象展开为独立的元素/属性对
      • Rest把元素/属性对打包成数组/对象

Promise相关问题? 

  •     基本语法

Promise的构造器语法如下:

let promise = new Promise(function(resolve, reject) {
  // executor(生产者代码,“歌手”)
});

其中传入的excutor生产者代码有以下特点:

  1. executor 被自动且立即调用(通过 new Promise)。

  2. executor 接受两个参数:resolve 和 reject。这些函数由 JavaScript 引擎预先定义,因此我们不需要创建它们。我们只需要在准备好(译注:指的是 executor 准备好)时调用其中之一即可。

New Promise构造器返回的promise对象具有state和result内部属性, 一个调用了resolve和reject的promise都会被称为“settled”

  • 消费者:then, catch,finally (实例方法)         

then: 

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

.then 的第一个参数是一个函数,该函数将在 promise resolved 且接收到结果后执行。

.then 的第二个参数也是一个函数,该函数将在 promise rejected 且接收到 error 信息后执行

promise链:

每个对 .then 的调用都会返回了一个新的 promise,因此我们可以在其之上调用下一个 .then

当处理程序返回一个值时,它将成为该 promise 的 result,所以将使用它调用下一个 .then

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

.then(handler) 中所使用的处理程序(handler)可以创建并返回一个 promise。

在这种情况下,其他的处理程序将等待它 settled 后再获得其结果。

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1

  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) { // (**)

  alert(result); // 2

  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) {

  alert(result); // 4

});

catch:

.catch(f) 调用是 .then(null, f) 的完全的模拟,它只是一个简写形式。

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// .catch(f) 与 promise.then(null, f) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"

 finally:

new Promise((resolve, reject) => {
  /* 做一些需要时间的事,之后调用可能会 resolve 也可能会 reject */
})
  // 在 promise 为 settled 时运行,无论成功与否
  .finally(() => stop loading indicator)
  // 所以,加载指示器(loading indicator)始终会在我们继续之前停止
  .then(result => show result, err => show error)
  • Promise相关静态方法:
  • async/await 

函数前面的关键字 async 有两个作用:

  1. 让这个函数总是返回一个 promise。
  2. 允许在该函数内使用 await

Promise 前的关键字 await 使 JavaScript 引擎等待该 promise settle,然后:

  1. 如果有 error,就会抛出异常 —— 就像那里调用了 throw error 一样。
  2. 否则,就返回结果。
  3. 现代浏览器在 modules (es module)里允许顶层的 await

async/await 可以和 Promise.all 一起使用:

let results = await Promise.all([
    fetch(url1),
    fetch(url2),
    ...
])

宏任务与微任务的关系?

JavaScript 是单线程语言,通过**事件循环(Event Loop)**处理异步任务。事件循环将任务分为两类:

  • 宏任务(Macrotask)
    • 包括:setTimeout、setInterval、DOM 事件(如 click)、setImmediate、I/O 操作(如 fetch、wx.request)、UI 渲染等。
    • 宏任务在事件循环的**任务队列(Task Queue)**中排队,每次事件循环处理一个宏任务。
    • 特点:宏任务之间有明显的“时间间隔”,可能涉及浏览器渲染或 I/O。
  • 微任务(Microtask)
    • 包括:Promise.resolve/reject、.then/catch、async/await、MutationObserver 等。
    • 微任务在当前宏任务完成后、渲染前执行,存储在微任务队列(Microtask Queue)
    • 特点:微任务优先级高于宏任务,当前宏任务结束后会清空微任务队列。
事件循环执行顺序
  1. 执行当前宏任务中的同步代码
  2. 清空当前宏任务的微任务队列(执行所有微任务)。
  3. 进行 DOM 渲染(如果需要)。
  4. 从任务队列中取出下一个宏任务,重复上述步骤。

如何理解JS中的this? 

this是在程序运行时得到的,this只有在函数被调用时根据上下文计算得到的,以方法的语法调用函数时, object.method(),调用过程中的 this 值是 object;

在 Vue2 中,this 代表当前组件实例,它使得我们可以访问组件的数据、方法、属性等。需要注意的是,在 data() 中不能访问 this,同时在异步回调或普通函数中 this 可能会改变,推荐使用箭头函数或手动绑定。

1、call,apply,bind的相同点:
都是改变this指向的;

第一个参数都是this要指向的对象;

都可以利用后续参数传参;

2、call,apply,bind的区别:
call和bind的参数是依次传参,一一对应的;

但apply只有两个参数,第二个参数为数组;

call和apply都是对函数进行直接调用,而bind方法返回的仍是一个函数;

讲讲JS中的闭包? 

闭包指的是一个函数可以记住其外部变量并访问这些变量,在js中所有函数都是天生闭包的,所有函数在被创建的时候都会通过[[Environment]] 的隐藏属性,该属性保存了对创建该函数的词法环境的引用;

闭包的两个关键点

  1. 函数嵌套:内部函数引用了外部函数的变量。
  2. 外部作用域保留:即使外部函数执行完毕,其作用域中的变量依然被内部函数保留(未被垃圾回收)。

闭包的核心特点

  • 词法作用域:JavaScript 的作用域是基于代码定义时的位置(静态作用域),而非运行时的位置。
  • 变量生命周期延长:外部函数的变量因被内部函数引用而不会被销毁。

常见的应用有ajax的成功/失败回调,setTImeout()的延时回调等,这些回调函数都能“记住”被定义时的外部的函数作用域的词法环境

实际开发中使用的闭包有debouce方法:

function debounce(fn, delay) {
  let timer = null;  // timer 是闭包中的私有变量
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  }
}

判断js数据类型的方法有哪些?

1、typeof

2、instanceof 判断某个实例是不是属于某个构造函数

value instanceof Constructor

3、Object.prototype.toString.call(value) 

AJAX的原理是什么?

创建 XMLHttpRequest 对象:这是Ajax的核心对象,用于在后台与服务器交换数据而不必重新加载整个页面。

通过 XMLHttpRequest 的 open() 方法建立与服务器的连接,并指定请求的类型(GET、POST等)和URL。

构建请求所需的数据内容,可以是参数或者请求体。然后使用 XMLHttpRequest 的 send() 方法将请求发送给服务器

监听 XMLHttpRequest 的 onreadystatechange 事件,以便在通信状态发生改变时进行处理。通常会检查 readyState 和 status 属性来确定请求的状态。

当 readyState 为 4 且 status 为 200 时,表示请求成功,可以从 XMLHttpRequest 对象的 responseText 或 responseXML 属性中获取服务器返回的数据结果。

使用获取到的数据结果,通过JavaScript操作DOM来更新页面的特定部分。

ESModule和CommonJS的区别?

区别主要在语法(import vs require)、加载方式(异步 vs 同步)和兼容性(ESM 可导入 CJS,但反之不行)。ESM 更适合现代前端,因为支持 tree shaking 和浏览器原生;CJS 适合 Node.js 后端。在项目中,我会优先用 ESM(如 Vue 3 项目),通过 Babel/Webpack 转译兼容旧环境。

TS

TS和JS的区别是什么? 

TS是JS的超集, TS支持编译期错误提示,提升代码可维护性与可靠性,主要有以下区别:

1、类型系统

  • js是动态类型语言,变量类型在运行时确定,可以随时改变变量的类型
  • ts是静态语言,编译时进行类型检查,支持类型注解

2、 编译过程

  • js直接由浏览器或nodejs运行,不需要执行编译
  • ts会先编译为js,编译过程会进行类型检查

3、TS的额外特性

 Interface(接口):

interface User {
  name: string;
  age: number;
  greet(): void;
}

const user: User = {
  name: "Alice",
  age: 30,
  greet() { console.log(`Hello, ${this.name}`); }
};

Type(类型): 

type Point = {
  x: number;
  y: number;
};

type ID = string | number;

type和interface有什么区别? 

1、type常用于定义复杂类型组合,interface常用于定义对象结构 

2、对象类型的继承和拓展不同(交叉):

interface Employee extends Person {
  jobTitle: string;
}

type EmployeeType = PersonType & {
  jobTitle: string;
};

3、interface支持声明合并,但是type会报错

interface Animal {
  name: string;
}

interface Animal {
  age: number;
}

// 合并后:{ name: string; age: number }

 4、type支持联合类型,interface不支持

type Status = 'success' | 'error' | 'loading';

泛型: 

泛型(Generics)是 TypeScript 中非常核心的概念之一,它允许我们在定义函数、接口或类时不预先指定具体类型,而是在使用时再传入类型参数,从而增强代码的灵活性和可复用性。

 泛型定义函数:

const identity = <T>(value: T): T => value;

identity(123);      // 推断为 number
identity('hello');  // 推断为 string

泛型定义接口: 

interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}

const res1: ApiResponse<string> = {
  code: 200,
  data: 'ok',
  message: 'success'
};

const res2: ApiResponse<{ id: number }> = {
  code: 200,
  data: { id: 1 },
  message: 'success'
};

 CSS

什么是盒模型? 

盒模型包括以下四个部分:

 

box-sizing盒模型有两种,content-box和border-box

 content-box的width和height不包括padding和border

border-box的width和height包括padding和border,两种盒模型都不包括margin

讲解一下css3中的transition、transform、animation?

 transform是用于对一个元素进行2D/3D变换的属性,可以通过一系列变换函数(旋转,缩放,平移,倾斜)来改变元素的视觉呈现,且不会影响dom布局,即不会改变元素在文档流中的原始位置,其他元素不会重排,性能较好;

transition是由于属性变化时触发的过渡动画, 基本属性如下:

 animation 是 “关键帧动画”,可以控制更复杂的多步骤动画,且能自动播放、循环播放。

 

什么是BFC? 

BFC(Block Formatting Context,块级格式化上下文)是CSS视觉渲染的一部分,它是Web页面中一个独立的渲染区域,内部元素的布局不会影响外部元素。 核心特征如下:

  1. 隔离性:BFC内部的元素与外部完全隔离,互不影响

  2. 自包含性

    • 自动包含内部所有浮动元素

    • 计算高度时会包含浮动元素的高度

  3. 边距控制:阻止相邻元素的边距折叠

  4. 浮动隔离:BFC区域不会与外部浮动元素重叠

BFC的主要应用:

  • 防止边距重叠(把重叠的元素分别放入BFC容器中
  • 清除浮动(计算浮动元素高度,解决高度塌陷,浮动元素会脱离正常文档流,导致显示不如预期

说一下flex和grid? 

垂直居中有哪些方案?

1、flex

.parent {
    display: flex;
    justify-content: center; 
    align-items: center; 
    height: 100vh; 
}

2、grid

2. 使用grid

.parent {
    display: grid;
    place-items: center; 
    height: 100vh; 
}

3、使用position:absolute

.parent {
    position: relative;
    height: 100vh;
}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%); 
    width: 100px;
    height: 100px;
    background-color: lightblue;
}

重绘和重排是什么?如何避免?

1、重绘

  • 重绘是指浏览器重新绘制页面上元素的外观(如颜色、背景、边框等),但不改变元素的布局(位置或大小)。
  • 重绘的性能开销较低,因为它不涉及重新计算布局,只更新绘制层

触发重绘:颜色变化,透明度,阴影, visibility(hidden/visible)

减少重绘:

使用 will-change 或 CSS 隔离(如 contain: paint)限制重绘区域。

.element { will-change: transform, opacity; }

2、重排

  • 重排是指浏览器重新计算页面上元素的几何属性(如位置、大小、宽高、边距等),并更新 DOM 树的布局。
  • 重排通常伴随着重绘,因为布局变化后,浏览器需要重新绘制受影响的区域。
  • 重排的性能开销较高,因为它涉及 DOM 树的重构和渲染树的重新生成。

触发重排:

  • DOM 结构变化
    • 添加或删除 DOM 节点。
      • 示例:element.appendChild(newNode); 或 element.removeChild(node);
    • 修改 DOM 节点的显示状态(如果影响布局)。
      • 示例:element.style.display = 'none';(从显示到隐藏可能触发重排)。
  • 几何属性变化
    • 修改元素的 width、height、margin、padding、border、position 等。
      • 示例:element.style.width = '100px';
    • 修改 top、left、right、bottom(对于定位元素)。
      • 示例:element.style.top = '50px';

优化重排:

1、批量修改样式,如通过类,避免逐个修改

2、批量添加或删除dom节点,减少dom操作

const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const div = document.createElement('div');
  fragment.appendChild(div);
}
document.body.appendChild(fragment);

3、使用transform代替布局属性,对于动画,使用 transform(如 translate)代替 top、left,因为 transform 通常只触发合成(不触发重排或重绘)。

/* 触发重排 */
.move { top: 50px; }
/* 仅触发合成 */
.move { transform: translateY(50px); }

sass和less?

sass和less都是css的预处理器,他们都支持变量、嵌套、混合(mixins)等功能

  • 变量
    • 使用 @primary-color、@font-size-base 等,类似 SCSS。
    • 示例:@chart-width: 8rem 定义 ECharts 容器尺寸。
  • 嵌套
    • 语法与 SCSS 相同,.container 嵌套 .title、.input-box。
    • 示例:.input-box .input 定义输入框样式。
  • 混合(Mixins)
    • .flex-center():无参数混合,定义居中样式。
    • .border-radius(@radius):带参数混合,灵活设置圆角。
  • 全局文件
    • @import '@/styles/variables.less' 引入全局变量

css阻塞有什么优化的方法吗?

<link rel="stylesheet" href="comments.css" media="print" onload="this.media='all'">
  • media="print"

    • 浏览器一开始看到 media="print",会认为这份样式表是“只在打印模式下才生效”。

    • 所以不会阻塞页面的渲染(因为首屏渲染只关心 media="all"media="screen" 的 CSS)。

    • 但浏览器仍然会去下载这个 CSS 文件(只是不会应用)。

  • onload="this.media='all'"

    • 当 CSS 文件下载完成后,触发 onload 事件。

    • 这时把 media 改成 all,让样式真正生效。

Vue 

什么是MVVM(Model-View-ViewModel)?

 Vue通过数据劫持(Object.defineProperty / Proxy)实现model和view的同步

Vue生命周期钩子函数及作用 ?

vue3的生命周期图如下:

常用的生命钩子函数:

 

Vue的响应式原理的实现(双向绑定)?

vue3基于Proxy代理对象 

 Proxy是一个ES6的构造函数,可以创建一个代理对象,对原始对象的访问进行控制,也就是所谓的劫持对象,基本语法为:

 Vue3实现响应式数据原理主要分为一下三个步骤 

  1. 使用proxy包装原始对象,在get和set方法中进行track收集依赖和trigger触发更新
    1. const handler = {
        get(target, key, receiver) {
          // 做依赖收集
          track(target, key);
          return Reflect.get(target, key, receiver);
        },
        set(target, key, value, receiver) {
          const result = Reflect.set(target, key, value, receiver);
          // 派发更新
          trigger(target, key);
          return result;
        }
      };
      
      const proxy = new Proxy(originalObj, handler);
      
  2. Vue3维护了一个全局的Map
  Vue2通过Object.defineProperty:

Vue2通过Object.defineProperty来定义或修改对象属性

    并在存储描述符getter和setter中拦截属性的读写,实现依赖收集和触发更新:

    function defineReactive(obj, key, val) {
      Object.defineProperty(obj, key, {
        get() {
          console.log(`收集依赖: ${key}`);
          return val;
        },
        set(newVal) {
          if (newVal !== val) {
            console.log(`触发更新: ${key}`);
            val = newVal;
          }
        }
      });
    }
    
    const data = { foo: 1 };
    defineReactive(data, 'foo', data.foo);
    
    data.foo; // 输出 "收集依赖: foo"
    data.foo = 2; // 输出 "触发更新: foo"
    vue3和vue2响应式数据的区别:
    1. 性能上vue2会遍历整个对象,并设置getter和setter, 因此开销会比较大,vue3使用惰性代理,只在访问时代理,性能更优,且内存占用更小
    2. vue2的依赖收集是数组,proxy通过weak Map效率更高
    3. vue2只支持代理数组和对象,vue3支持数组/对象/Map/Set
    4. vue2在新增/删除响应式属性时需要调用vue.set, vue3直接支持;Object.defineProperty不支持索引,所以vue2中的数组方法都需要重写,vue3的proxy可以直接支持

     虚拟Dom和Diff算法?

     虚拟Dom:

    Virtual DOM 是一种轻量级的 JavaScript 对象,模拟了真实 DOM 的结构。

    它的主要作用是:

    在内存中构建一个 DOM 树的副本,然后在更新时,通过比对前后的虚拟 DOM,找到变化的地方,再有选择地操作真实 DOM。

    真实DOM操作非常昂贵(慢) ,频繁修改会导致重排(reflow)和重绘(repait),这时使用虚拟Dom可以现在内存中比对,再只更新需要的部分,以此提高性能;

    Diff算法:

    Diff 算法用于比较前后两个虚拟 DOM 树的差异,找出最小的修改代价。

    常见的 diff 操作包括:

    • 节点类型不同 → 替换整个节点;

    • 节点属性不同 → 修改属性;

    • 子节点列表变化 → 插入/删除/移动节点;

    • 文本内容不同 → 替换文本。

    ✅ Diff 的优化策略(React/Vue 都类似):

    1. 同层比较:只会比较同一层的节点,不会跨层比较;

    2. Key 的使用:列表渲染时提供 key 帮助更快识别节点是否相同;

    3. 最长递增子序列(Vue3 的优化):用于尽可能复用旧节点,减少 DOM 移动。

    Vue2的data为什么是函数?

     vue2中的组件是靠构造函数实现的,如果是对象所有实例都会共享这份数据,导致不同组件的数据污染,所以data必须是一个函数,每次调用都返回一个新的对象,确保组件间的数据隔离;

     Vue中computed和watch的区别?

     1、computed()

    computed()接受一个getter函数,返回一个计算属性ref,计算属性的ref会在模板中自动解包,因此在模板表达式中不需要用.value;计算属性值基于其响应式依赖被缓存,仅会在响应式依赖更新时重新计算。计算属性应尽量避免直接修改,且不应该有其他副作用, 不要改变其他状态、在 getter 中做异步请求或者更改 DOM!特殊场景需要修改计算属性,需要同时提供setter和getter

    <script setup>
    import { ref, computed } from 'vue'
    
    const firstName = ref('John')
    const lastName = ref('Doe')
    
    const fullName = computed({
      // getter
      get() {
        return firstName.value + ' ' + lastName.value
      },
      // setter
      set(newValue) {
        // 注意:我们这里使用的是解构赋值语法
        [firstName.value, lastName.value] = newValue.split(' ')
      }
    })
    </script>

    2、watch

    在我们计算衍生值的时候,可能会执行一些dom修改,异步请求等副作用的时候,我们可以使用watch函数在每次响应式状态变化时触发回调函数,基本语法如下:

    watch(
      source,
      (newValue, oldValue) => {
      },
      { 
        immediate: true, //立即执行,请求初始数据
        deep: true, // 深层侦听器,如果source是一个响应式对象,所有嵌套的变更时都会触发回调函数 
        once: true, // source变化时仅执行一次
      }
    )

     watch的第一个参数source可以是不同形式的数据源,它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组,但是不能直接侦听响应式对象的属性,需要提供返回该属性的getter函数:

    const obj = reactive({ count: 0 })
    
    // 错误,因为 watch() 得到的参数是一个 number
    watch(obj.count, (count) => {
      console.log(`Count is: ${count}`)
    })
    // 提供一个 getter 函数
    watch(
      () => obj.count,
      (count) => {
        console.log(`Count is: ${count}`)
      }
    )

    监听的数据不同:computed 监听的是数据的计算结果,通常是多个数据的综合运算结果;而 watch 监听的是具体的数据,可以在数据变化时进行特定的操作。

    执行时间不同: computed 支持缓存,当依赖数据发生改变,会重新进行计算。而 watch 不支持缓存,数据变化时会直接触发相应的操作。

    是否支持异步: computed 不支持异步,当其内部有异步操作时失效,无法监听数据的变化。watch 支持异步操作。

    是否需要return: computed 中的函数必须要用 return 返回,watch 中的函数不是必须要用 return。

    是否第一次加载就做监听: computed 默认第一次加载时就监听,watch 不是,除非把 watch 的属性 immediate 设置为 true。

    适用场景不同:如果需要对某些数据进行复杂的计算,并将计算结果用于模板渲染,则应该使用 computed;如果需要在数据发生变化时执行特定的操作,例如发起异步请求或执行动画效果等,则应该使用 watch

     Vue组件中的通讯方式?

    1、父子组件通过props和emit来进行传递 

    2、跨级组件可以通过provide和inject来实现(依赖注入)

    provide()第一个参数为为注入名,可以是字符串或者一个symbol,第二个参数是提供的值,可以是任意类型,包括响应式的状态;尽可能将对任何响应式状态的变更都保持在供给方组件中;

    <!-- 在供给方组件内 -->
    <script setup>
    import { provide, ref } from 'vue'
    
    const location = ref('North Pole')
    
    function updateLocation() {
      location.value = 'South Pole'
    }
    
    provide('location', {
      location,
      updateLocation
    })
    </script>
    <!-- 在注入方组件 -->
    <script setup>
    import { inject } from 'vue'
    
    const { location, updateLocation } = inject('location')
    </script>
    
    <template>
      <button @click="updateLocation">{{ location }}</button>
    </template>

    3、vuex

      - State:存储全局状态。  - Getters:计算派生状态。
      - Mutations:同步修改状态(通过 commit 触发)。
      - Actions:异步操作后提交 Mutations(通过 dispatch 触发)。

    this.$set()和this.nextTick()的作用分别是什么?

    this.$set()解决了vue2中响应式数据更新检测的限制

    • 给对象新增一个属性:

    • 直接修改数组的某个索引:

    ​
    this.obj.newKey = 'value' // ❌ Vue 不会触发视图更新
    
    this.arr[1] = 'new value' // ❌ Vue 不会触发视图更新

    原因是 Vue2 的响应式实现是通过 Object.defineProperty 劫持属性,在初始化时才会“收集依赖”。
    新加的属性和数组索引没被劫持,自然无法触发更新。

    ✅ 解决方法:用 this.$set() 强制把属性设置为响应式。

    this.$set(this.obj, 'newKey', 'value') // ✅ 响应式生效
    this.$set(this.arr, 1, 'new value')    // ✅ 响应式生效
    

    Vue 的更新是异步的,当你修改数据时,Vue 并不会立刻更新 DOM,而是把更新推到“下一个事件循环”。
    如果你想在数据修改后,立刻拿到更新后的 DOM,就要用 $nextTick()

    封装组件的时候参数props应该怎么设计?

    VUE插槽的使用?

    VUE有哪些优化的方法?

    讲一讲Vue Router?

    路由跳转方式:

    1、声明式导航

    router-link 是一个内置组件,用于创建导航链接:

    • to 属性可以是字符串路径(如 "/about")或对象(如 { name: 'About' })。
    • 使用命名路由(name)跳转时,确保 routes 配置中定义了对应的 name。
    <template>
      <div>
        <router-link to="/">跳转到首页</router-link>
        <router-link to="/about">跳转到关于页面</router-link>
      </div>
    </template>

    2、编程式导航

    在组件的 script 中,可以通过 useRouter 获取路由实例并进行跳转:

    • router.push():导航到指定路径,添加到历史记录。
    • router.replace():类似 push,但不会添加新的历史记录,而是替换当前记录。
    • router.go(n):在历史记录中前进(正数)或后退(负数)n 步,例如 router.go(-1) 相当于后退一步。

    vue-router的历史模式:

    1、HTML5 历史模式 (createWebHistory)

    • 干净的 URL:没有 # 符号,URL 更美观且符合常规网站路径结构。
    • 依赖服务器配置:需要后端服务器支持,将所有路由请求重定向到应用的入口文件(通常是 index.html),否则直接访问子路径(如 /about)可能返回 404 错误。
    • SEO 友好:更适合需要搜索引擎优化的应用。

    2、Hash 模式 (createWebHashHistory)

    • 无需服务器配置:路由信息存储在 URL 的 # 之后,浏览器不会向服务器发送这些部分,因此不需要后端支持。
    • 兼容性强:适用于静态文件服务器或不需要复杂服务器配置的环境。
    • URL 不够美观:URL 中包含 #,看起来不像标准的网站路径。
    • SEO 不友好:搜索引擎通常不会解析 # 后的内容。

    匹配404页面

    const routes = [
      { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
    ];

    /:pathMatch(.*)* 表示“匹配任意路径(包括不存在的路径)”,并将匹配到的路径内容存储在 pathMatch 参数中。

    React

    Node.js相关 

    commonJS和ESM的区别是什么?

     1、语法区别

    2、加载机制

    Cesium相关  

     cesium的坐标系有哪些?

     cesium有哪些性能优化的方法?

     cesium加载哪些影像图层?

    动态地图服务和瓦片地图服务的区别? 

    三维空间有哪些变换矩阵? 

    影像数据,矢量数据,高程数据在Cesium的处理?

    Cesium的数据缓存有哪些类型?

    • ImageryLayer
      • _imageryCache(内部对象)存储瓦片图像(如 Image 对象),位于 JavaScript 堆,属于 Memory Cache。
      • 示例:加载 https://example.com/tiles/0/0/0.png,浏览器先从 HTTP 缓存(Memory Cache 或 Disk Cache)返回 PNG,解析为 Image 后存入 _imageryCache。
    • CesiumTerrainProvider
      • tileCacheSize 控制地形瓦片缓存(网格数据),存储在 JavaScript 堆,属于 Memory Cache。
    • Cesium3DTileset
      • 几何体、纹理存储在 GPU 内存(cacheBytes 控制,默认 512MB),
      • tileset.json 或 b3dm 文件的原始数据可能存储在 HTTP 缓存(Memory Cache 或 Disk Cache)。
      • 缓存相关属性
      • cacheBytes(Number,默认 536870912,即 512 MB):控制 GPU 内存缓存大小,估算加载瓦片的几何、纹理和批处理表内存使用。如果减少该值,会在下一帧卸载瓦片。
      • maximumCacheOverflowBytes(Number,默认 536870912,即 512 MB):允许额外 GPU 内存作为缓存“溢出”空间。如果所需瓦片超过 cacheBytes + maximumCacheOverflowBytes,会动态调整 memoryAdjustedScreenSpaceError 以适应。
      • totalMemoryUsageInBytes(Readonly Number):当前 GPU 内存使用量,包括几何、纹理、批处理表和二进制元数据(点云包括每个点的元数据)。

    前两者属于缓存在浏览器内存的memory cache(浏览器缓存分为memory cache和disk cache),最后的属于GPU内存

    Webpack/Vite 

    webpack 是一个模块打包工具,负责把 Vue 项目里的 .vue.js.css、图片等资源,打包成浏览器能运行的文件。vue.config.js就是封装后的提供修改webpack配置的文件

    webpack的配置有哪些?

    • entry: 入口文件地址,可以单个/多个
    • output:输出的内容,path, filename, publicPath
    • module: loaders
    • plugins: 对打包流程进行干预
    • resolve: 别名,扩展名
    • externals:外部拓展
    • devServer: port,开发环境的配置
    • optimization: 拆chunk,cachegroup

    webpack有哪些常见的loader?

    1、js相关loader:

    • babel-loader:将 ES6+ 代码转译为向后兼容的 JavaScript(ES5),支持 React JSX

    • ts-loader / awesome-typescript-loader:处理 TypeScript 文件,编译 .ts 或 .tsx 为 JavaScript

    2、CSS相关loader

    • css-loader: 解析 CSS 文件,处理 @import 和 url(),支持 CSS 模块化
    • style-loader: 将 CSS 注入到 DOM 中(通过<style>标签)通常和css-loader搭配使用
    • sass-loader/less-loader: 将 SCSS/SASS/Less 文件编译为 CSS

    *loader执行的顺序是从右到左(或数组从后向前)执行,例如 ['style-loader', 'css-loader', 'sass-loader'] 先编译 SCSS 为 CSS,再解析 CSS,最后注入 DOM。

    3、静态资源相关的loader

    • file-loader: 处理图片字体等静态资源,输出到指定目录并返回文件路径
    • url-loader: 类似 file-loader,但可将小文件转为 Data URL(base64),减少请求

    4、框架相关loader

    • vue-loader: 处理 Vue 单文件组件(.vue),解析 template, script,style等
    • eslint-loader/tslint-loader: 打包时运行代码检查,确保代码质量

    有哪些常见的plugin?

    1、文件生成与管理

    html-webpack-plugin: 自动生成 HTML 文件,并注入打包后的 JS/CSS 资源

    CleanWebpackPlugin:打包前清理输出目录(如 dist),避免旧文件残留

    2、优化与压缩相关

    TerserWebpackPlugin:压缩 JavaScript 代码,移除未使用代码(Tree Shaking)。在 Webpack 5 中是默认压缩插件。

    MiniCssExtractPlugin:将 CSS 从 JS 中提取为单独文件,避免内联

    CssMinimizerWebpackPlugin:压缩 CSS 文件,移除无用样式

    3、开发体验相关:

    DefinePlugin:

    定义全局常量(如 process.env.NODE_ENV),供代码中使用

    const webpack = require('webpack');
    module.exports = {
      plugins: [
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': JSON.stringify('production')
        })
      ]
    };

    口喷一个配置?

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    module.exports = {
      mode: 'development',
      entry: './src/index.js',
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
      },
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: 'babel-loader'
          },
          {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
          },
          {
            test: /\.(png|jpg|gif)$/,
            use: 'file-loader'
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html'//HtmlWebpackPlugin:自动生成 index.html 并注入打包后的资源
        }),
        new CleanWebpackPlugin()//CleanWebpackPlugin:清理输出目录
      ],
      devServer: {
        contentBase: './dist',
        hot: true,
        port: 8080
      }
    };

    loader和plugin的区别是什么?

    Loader专注于文件级转换,确保不同资源能被 Webpack 作为模块处理。

    Plugin扩展 Webpack 的整个构建过程,系统级拓展功能,处理编译生命周期中的各种事件(如优化、生成文件)。

    bable-loader有哪些主要配置项?

    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          cacheDirectory: true,         // 开启缓存
          cacheCompression: false,      // 缓存不压缩,提速
          presets: [
            ['@babel/preset-env', {
              targets: { browsers: ['> 1%', 'last 2 versions', 'not ie <= 8'] },
              useBuiltIns: 'usage', // 按需引入polyfill
              corejs: 3 // 指定polyfill版本
            }],
            @babel/preset-react,//支持jsx
            @babel/preset-typescript // 支持ts
          ],
          plugins: [
            '@babel/plugin-transform-runtime', //复用 helper,减少打包体积
            '@babel/plugin-proposal-class-properties'//支持类属性。
          ]
        }
      }
    }
    

    场景面试题

    1、手写防抖和节流

    节流

    节流是指在一定时间间隔内,无论触发事件多少次,只执行一次函数。适合高频事件(实时采集音量),确保函数以固定频率运行。

    function throttle(fn, wait) {
      let lastTime = 0; // 上次执行时间
      return function (...args) {
        const now = Date.now();
        if (now - lastTime >= wait) {
          lastTime = now;
          fn.apply(this, args); // 执行函数,保持上下文和参数
        }
      };
    }
    
    // 模拟高频事件
    const log = throttle(() => console.log('Throttled!'), 1000);
    window.addEventListener('resize', log); // 每 1 秒最多执行一次

    防抖

    防抖是指在事件触发后,等待一定时间(延迟)才执行函数,如果在这段时间内再次触发,则重新计时。适合需要“冷却”的事件(如输入框搜索,多次重复点击按钮)。

    function debounce(fn, wait) {
      let timeout = null; // 定时器
      return function (...args) {
        clearTimeout(timeout); // 清除旧定时器
        timeout = setTimeout(() => {
          fn.apply(this, args); // 延迟执行,保持上下文和参数
        }, wait);
      };
    }
    
    // 模拟搜索输入
    const search = debounce((value) => console.log('Search:', value), 1000);
    input.addEventListener('input', (e) => search(e.target.value)); // 输入停止 1 秒后执行

    2、Vue怎么写倒计时组件?

    <template>
      <div class="countdown">
        {{ minutes }}:{{ seconds }}
      </div>
    </template>
    
    <script setup>
    import { ref, onMounted, onUnmounted } from "vue"
    
    // 倒计时时长(秒)
    const total = ref(600) // 10分钟 = 600秒
    const timeLeft = ref(total.value)
    let timer = null
    
    // 格式化为 mm:ss
    const minutes = computed(() =>
      String(Math.floor(timeLeft.value / 60)).padStart(2, "0")
    )
    const seconds = computed(() =>
      String(timeLeft.value % 60).padStart(2, "0")
    )
    
    onMounted(() => {
      timer = setInterval(() => {
        if (timeLeft.value > 0) {
          timeLeft.value--
        } else {
          clearInterval(timer)
        }
      }, 1000)
    })
    
    onUnmounted(() => {
      if (timer) clearInterval(timer)
    })
    </script>
    
    <style scoped>
    .countdown {
      font-size: 2rem;
      font-weight: bold;
      color: #ff4d4f;
      font-family: monospace;
    }
    </style>
    

    3、怎么手写promiseAll?

    function myPromiseAll(promises) {
      return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
          return reject(new TypeError("Argument must be an array"))
        }
    
        const results = []
        let completedCount = 0
        const total = promises.length
    
        if (total === 0) {
          return resolve([])
        }
    
        promises.forEach((p, index) => {
          // 用 Promise.resolve 包装,确保普通值也能处理
          Promise.resolve(p)
            .then(value => {
              results[index] = value  // 保证顺序
              completedCount++
              if (completedCount === total) {
                resolve(results) // 全部完成
              }
            })
            .catch(err => {
              reject(err) // 任何一个失败,直接 reject
            })
        })
      })
    }
    

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值