浏览器相关
-
url输入会发生什么?
- 浏览器解析URL,查找DNS解析IP
- 建立TCP(http)/TLS(https)连接
- 发起http请求,服务器返回http响应(HTML,状态码)
- 浏览器解析HTML构建DOM,下载CSS和JS
- 构建Render Tree, 布局和绘制
- 加载外部资源,运行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是不包含重复值的集合,主要用于存储一组唯一值,常用方法有:

- Map是一种键值对集合,保持插入顺序,key可以是任意一种类型,常用的方法有:
- 对象和数组的解构赋值
- 扩展运算符spread和剩余参数Rest
- Spread把数组/对象展开为独立的元素/属性对

- Rest把元素/属性对打包成数组/对象

- Spread把数组/对象展开为独立的元素/属性对
- 模块化import/export
Promise相关问题?
- 基本语法
Promise的构造器语法如下:
let promise = new Promise(function(resolve, reject) {
// executor(生产者代码,“歌手”)
});
其中传入的excutor生产者代码有以下特点:
-
executor 被自动且立即调用(通过
new Promise)。 -
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 有两个作用:
- 让这个函数总是返回一个 promise。
- 允许在该函数内使用
await。
Promise 前的关键字 await 使 JavaScript 引擎等待该 promise settle,然后:
- 如果有 error,就会抛出异常 —— 就像那里调用了
throw error一样。 - 否则,就返回结果。
- 现代浏览器在 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)。
- 特点:微任务优先级高于宏任务,当前宏任务结束后会清空微任务队列。
事件循环执行顺序
- 执行当前宏任务中的同步代码。
- 清空当前宏任务的微任务队列(执行所有微任务)。
- 进行 DOM 渲染(如果需要)。
- 从任务队列中取出下一个宏任务,重复上述步骤。

如何理解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]] 的隐藏属性,该属性保存了对创建该函数的词法环境的引用;
闭包的两个关键点
- 函数嵌套:内部函数引用了外部函数的变量。
- 外部作用域保留:即使外部函数执行完毕,其作用域中的变量依然被内部函数保留(未被垃圾回收)。
闭包的核心特点
- 词法作用域: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页面中一个独立的渲染区域,内部元素的布局不会影响外部元素。 核心特征如下:
-
隔离性:BFC内部的元素与外部完全隔离,互不影响
-
自包含性:
-
自动包含内部所有浮动元素
-
计算高度时会包含浮动元素的高度
-
-
边距控制:阻止相邻元素的边距折叠
-
浮动隔离: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';(从显示到隐藏可能触发重排)。
- 添加或删除 DOM 节点。
- 几何属性变化:
- 修改元素的 width、height、margin、padding、border、position 等。
- 示例:element.style.width = '100px';
- 修改 top、left、right、bottom(对于定位元素)。
- 示例:element.style.top = '50px';
- 修改元素的 width、height、margin、padding、border、position 等。
优化重排:
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实现响应式数据原理主要分为一下三个步骤
- 使用proxy包装原始对象,在get和set方法中进行track收集依赖和trigger触发更新
-
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);
-
- 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响应式数据的区别:
- 性能上vue2会遍历整个对象,并设置getter和setter, 因此开销会比较大,vue3使用惰性代理,只在访问时代理,性能更优,且内存占用更小
- vue2的依赖收集是数组,proxy通过weak Map效率更高
- vue2只支持代理数组和对象,vue3支持数组/对象/Map/Set
- 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 都类似):
-
同层比较:只会比较同一层的节点,不会跨层比较;
-
Key 的使用:列表渲染时提供
key帮助更快识别节点是否相同; -
最长递增子序列(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
})
})
})
}





1624

被折叠的 条评论
为什么被折叠?



