前端经典面试题
2026/8/27 12:02:06 网站建设 项目流程

一、JS

var let const 区别

for(vari=0;i<3;i++){setTimeout(()=>{console.log(i);},1000);}输出333

for循环是同步的,它会一口气把3个setTimeout全注册完,此时i已经变成3了。等100ms后主线程空闲了,回调才开始执行,这时去读i,读到的自然是3。
原因:即使延迟是0,setTimeout 的回调依然会被放到任务队列,必须等主线程的 for 循环全部执行完毕(同步代码)才会被执行。所以读到的 i 依然是3,var 没有块级作用域,整个循环共用同一个 i

js数据类型 typeof

基本类型 string number boolean null undefined symbol bigint
引用类型 Object 、Array 、Function 、Data Map / Set RegExp

经典案例:leta=100;letb=a;a=200;console.log(a);//200console.log(b);//100letobj1={name:"st"};letobj2=obj1;obj1.name="pp";console.log(obj1.name);//ppconsole.log(obj2.name);//ppconsole.log({}==={})//false 两个独立的引用地址

– typeof陷阱
typeof null //object null指针是(0*00) 对象的类型标签恰好是0 改的话涉及的比较多解决 ===
typeof [] typeof{} //object 无法分别具体的对象类型 [] {}都是object解决 Array.isArray()

– 终极解决方案:
Object.prototype.toString.call()最精确,判断所有类型
instanceofconsole.log({} instanceof Object); // true 判断对象是否为某构造函数的实例 对基本类型无效

堆栈/内存

堆(Heap)和栈(Stack)是内存中的两种存储区域
**栈内存(Stack):**存储基本数据类型(Number、String、Boolean等)和引用数据类型的内存地址(指针)。它的特点是“先进后出、大小固定、自动释放”。
堆内存(Heap):存储引用数据类型(Object、Array、Function等)的实际数据。它的特点是“无序存储、大小动态、需要垃圾回收(GC)”。先进先出
基本类型赋值是“复制值”,互不影响;引用类型赋值是“复制地址”,会相互影响;
递归太多会栈溢出,闭包/定时器没清理会堆内存泄漏。

this call/apply/bind

“this 指向函数调用时的上下文,不是定义时。谁调用我,我指向谁。箭头函数例外,它指向定义时的外层作用域。Vue2 的 methods 中 this 指向组件实例,不能用箭头函数。”
追问1:call、apply、bind 的区别?
“三者都是手动改变 this 指向。call 和 apply 立即执行,区别是传参方式不同:call 逐个传,apply 数组传。bind 返回新函数,不立即执行,固定this指向,适合需要绑定的回调函数(如定时器)。”
追问2:箭头函数和普通函数 this 的区别?
“箭头函数 this 是定义时继承外层作用域,不能通过 call/apply/bind 改变;普通函数 this 是调用时动态绑定,可以通过 call/apply/bind 改变。箭头函数没有 arguments,不能 new。”
追问3:setTimeout 里 this 为什么指向 window?
setTimeout 的回调是 setTimeout 内部通过普通函数调用的方式执行的,而普通函数调用时 this 默认指向全局对象 window
追问4:Vue3 组合式 API 中 this 怎么用?
“Vue3 的 setup 中没有 this,响应式数据用 ref/reactive 声明,直接使用变量名,不用 this。生命周期钩子也一样。”

闭包

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

闭包有三个特性:
1.函数嵌套函数
2.函数内部可以引用外部的参数和变量
3.参数和变量不会被垃圾回收机制回收

**优点:**可以创建私有变量、数据封装、实现柯里化等高级函数式编程模式。
**潜在问题:**主要是内存泄漏(因为外部变量无法被回收)和循环中的变量引用问题。
**避免内存泄漏的方法:**在不需要时手动解除引用(=null),以及及时移除事件监听器。

柯里化/高阶函数

柯里化: 将多参数函数转化为一系列只接收单一参数的函数
高阶函数:参数或返回值为函数的函数 ① 参数是函数(如 map、filter、setTimeout) ② 返回值是函数(如 bind、debounce)
柯里化应用场景: 延迟执行 复用性 组合性

递归

“递归是函数自己调用自己的一种编程技巧,适合处理重复性、层级未知的问题,比如树形结构遍历、深拷贝、菜单渲染等。”

// 阶乘functionfactorial(n){if(n<=1)return1// ✅ 终止条件:n 减小到 1 时停止returnn*factorial(n-1)// 🔄 递归体:自己调自己,n 逐渐减小}factorial(5)// 120

拷贝

“浅拷贝拷贝的是引用地址,深拷贝拷贝的是所有数据。浅拷贝新旧对象共用同一块内存,深拷贝新旧对象完全独立。”
1、浅拷贝
浅拷贝只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存
浅拷贝只复制一层属性,会污染二层的数据

constobj={name:'张三',info:{age:18}}// 1. 扩展运算符constcopy1={...obj}// 2. Object.assign() 合并对象constcopy2=Object.assign({},obj)// 3. 数组的 slice() / concat()constarr=[1,2,[3,4]]constcopy3=arr.slice()// 4. 手写循环constcopy4={}for(letkeyinobj){copy4[key]=obj[key]}

2、深拷贝
深拷贝会创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。

constobj={name:'张三',info:{age:18,hobbies:['游泳','跑步']}}// 方式1:JSON 序列化(最常用,但有局限)constcopy1=JSON.parse(JSON.stringify(obj))// ✅ 缺点:无法拷贝函数、undefined、Symbol、循环引用// 方式2 首选constcopy1=structuredClone(obj)// ✅ 缺点:无法拷贝函数、Error对象 DOM节点// 方式3lodash.cloneDeep()// 方式4:手写递归(面试常考)functiondeepClone(obj){// 非对象直接返回if(obj===null||typeofobj!=='object')returnobjconstresult=Array.isArray(obj)?[]:{}for(letkeyinobj){// 只拷贝自身属性if(obj.hasOwnProperty(key)){result[key]=deepClone(obj[key])// 递归拷贝}}returnresult}constcopy2=deepClone(obj)
[]==![]//true[]==false// 先算 ![] → false[]==0// false 转数字 → 0""==0// [] 转原始值 → "" 走toString()0==0// "" 转数字 → 0true

localStorange sessionStorange cookie

for循环 数组方法 字符串方法 对象方法

foreach 没有返回值 不能链式调用
map 返回一个新数组 可以链式调用(因为返回的新数组)
for(var item of arr){ 不能遍历对象 遍历出来就是每一项
for(var item in arr){ 可以遍历对象 遍历出来是对象的键名

数组:map foreach filter find findIndex some every includes reduce(累加器,初始值) push pop shift unshift splice slice join reverse tostring concat
返回新数组:map filter concat slice
字符串:split trim slice indexof substring (match search匹配正则) replace toUpperCase toLowerCase startsWith(是否以某字符开头) endsWith
对象:Object.keys(obj) Object.values(obj) Object.entries(obj)对象转为数组格式

回流和重绘

回流:当元素的几何属性(宽高、位置、边距)或布局结构(显示隐藏、DOM 增删、文字变化)发生改变时,浏览器需要重新计算元素的位置和大小,重新构建布局树,这个过程叫回流。
**重绘:**当元素的外观风格(颜色、背景、边框、阴影)发生改变,但几何尺寸和布局不变时,浏览器只需要重新绘制像素,不需要重新计算布局,这个过程叫重绘。

“回流和重绘是浏览器渲染页面的两个关键步骤。回流是重新计算布局,重绘是重新绘制外观。回流一定触发重绘,重绘不一定触发回流。”

跨域

“跨域是浏览器的同源策略导致的,解决跨域的核心思路就是让浏览器认为请求是合法的,或者绕过浏览器的限制。主要分开发环境和生产环境两个场景。”
浏览器同源策略:协议 + 域名 + 端口 三者必须完全一致,任何一个不同就是跨域。
代理跨域:

“开发环境用 Vite/Webpack proxy 代理,配置 changeOrigin: true 转发到后端。生产环境主流方案是 CORS,后端配 Access-Control-Allow-Origin 响应头;或者 Nginx 反向代理,让前端和接口同源。JSONP 只支持 GET,已经淘汰了。非简单请求会先发 OPTIONS 预检,询问后端是否支持。”

解绑事件

btn1.onclick = null
btn2.removeEventListener(‘click’, showMsg, false);
vue2 $off
Vue3 移除了on/on/on/off,推荐用 mitt

函数防抖(debounce)

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
场景:直播点赞、输入框搜索建议 resize

函数节流(throttle**)

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
场景:窗口resize scroll事件

宏微任务

先微后宏
1.宏任务:
分类:setTimeout setInterval setImmediate
2.微任务:
分类:new promise().then(回调)
process.nextTick(独立的队列 优先级高于微任务)
先同步,再微任务,后宏任务。
每一轮:执行一个宏任务 → 执行完立即处理当前所有微任务 → 再取下一个宏任务,如此循环。

event loop循环机制

“Event Loop 是 JS 的异步执行机制。JS 是单线程,遇到异步任务会放到任务队列。执行顺序:先执行所有同步代码,然后清空所有微任务,再取一个宏任务执行,执行完再清微任务,如此循环。微任务包括 Promise.then、process.nextTick;宏任务包括 setTimeout、setInterval、事件回调。微任务优先级高于宏任务。”

diff

"Diff 是虚拟 DOM 的核心算法,用来对比新旧两棵虚拟 DOM 树的差异
“Diff 算法对比新旧虚拟 DOM 树,找出差异,只更新变化的部分,减少 DOM 操作。Vue3 比 Vue2 多了静态标记和块树优化,编译时就标记动态节点,Diff 时跳过静态内容,性能提升 2 倍。key 用唯一 id 而不是 index,帮助 Diff 正确复用节点。”

虚拟DOM

“虚拟 DOM 是用 JS 对象模拟真实 DOM 的树形结构。数据变化时生成新虚拟 DOM,通过 Diff 算法对比新旧树找出差异,再批量更新到真实 DOM,减少直接操作 DOM 的昂贵开销。虚拟 DOM 的优点是减少 DOM 操作、跨平台、数据驱动视图;缺点是首次渲染比直接操作 DOM 慢,需要额外的 Diff 开销。Vue3 通过静态标记和块树优化了虚拟 DOM,只比对动态节点,比 Vue2 快约 2 倍。”

route router的区别

“router 是VueRouter的一个对象,route 是当前路由状态信息。 简单说:router 负责跳转,route 负责读取。”
$router.push({path:‘home’})
$router.replace({path:‘home’});
route为当前router跳转对象里面可以获取name、path、query、params等
router为VueRouter实例,想要导航到不同URL,则使用router.push方法

get post的区别

二、vue

ref和reactive的区别

二者都能创建响应式数据
ref() 用于基本类型,访问.value
reactive() 用于对象/数组类型,直接访问,会深层响应式

Vue2 vs Vue3 核心区别(面试速记版)

双向绑定的原理

Vue 通过Object.defineProperty()来劫持各个属性的 setter / getter,每一个被劫持的属性都会生成 get 和 set 拦截器。它采用数据劫持结合观察者模式的方式,在数据变动时通过 setter 发布消息给订阅者,触发相应的监听回调,从而驱动视图更新。
“Vue3 这块把defineProperty 换成了 Proxy,不需要再遍历每个属性了,直接代理整个对象。”

watch监听

deep: true—— 深度监听,可以监听到对象或数组内部值的变化
默认情况下(不开启 deep),watch 只能监听简单类型可以直接监听到值的变化,但对于对象/数组,修改内部属性不会触发监听
immediate: true—— 立即执行一次监听回调(默认第一次不会执行)

watch和computed的区别

使用场景:computed----当一个属性受多个属性影响的时候,使用computed-----购物车商品结算。
watch–当一条数据影响多条数据的时候,使用watch-----搜索框.

MVC和MVVM的区别

“MVC 是数据变了要手动操作 DOM 去更新视图,开发者既要管数据又要管页面。MVVM 通过 ViewModel 实现了双向绑定,数据变视图自动变,视图操作数据也自动变,开发者只关心数据,DOM 操作全部交给框架。Vue 就是典型的 MVVM 思想。”

webpack和vite的区别

项目构建打包工具


精简版
Webpack 是"全部打包好再给你",Vite 是"你要什么我现给你编译"。 新项目追求开发体验选 Vite,老项目/复杂工程化需求选 Webpack。

vuex和pinia的区别

“Pinia 是 Vuex 的升级版,Vuex 4 是 Vue3 的适配版,但 Pinia 是专为 Vue3 设计的状态管理库,官方现在也推荐用 Pinia。”

“Vuex 有 state/getters/mutations/actions 四个核心,修改数据要走 commit mutations 或 dispatch actions,TS 支持一般。Pinia 去掉了 mutations,只有 state/getters/actions,数据直接改就行,TS 原生支持,代码量少 30%。跨模块调用直接 import 其他 Store,还有类型提示。逻辑复用可以封装组合式函数,比 Vuex 的 mixins 好用。新项目官方推荐 Pinia。”

使用场景:用户登录信息(token、用户资料、权限标识)
动态路由权限控制(根据角色生成菜单/路由表)
全局UI主题配置(暗黑模式、多语言、侧边栏折叠状态)
多步骤表单数据暂存(分步提交场景下的跨组件数据共享)
购物车数据(商品数量、选中状态,跨页面共享)
全局消息/通知数量(未读红点,WebSocket推送更新)

和全局变量的区别
vuex的状态存储是响应式的,当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会得到高效更新。
全局变量可能操作命名污染,但是vuex不会,每个组件可以根据自己vuex的变量名引用不受影响

监听vuex中数据变化

mixins和hooks的区别

“mixin 和 Hook 都是逻辑复用的方式,mixin 是 Vue2 的方案,Hook(组合式函数)是 Vue3 的方案。Hook 是 mixin 的升级替代,解决了 mixin 的所有痛点。”

精简版
“mixin 是 Vue2 的逻辑复用方式,但问题很多:数据来源不明、命名冲突、隐式依赖、TS 支持差。Hook 是 Vue3 的组合式函数,通过显式 import 和解构,数据来源清晰、重名可控、TS 原生支持。Vue3 官方推荐用 Hook 替代 mixin,新项目不要再写 mixin 了。”

如果组件里有重名

精简版
“Hook 如果和本组件方法同名,解构时重命名就行,const { add: addCount } = useCounter(),互不影响。如果重名不处理,Vue 会直接报错,逼你解决。而 Mixin 同名是静默覆盖,不报错,你根本不知道出了问题,调试非常痛苦。这也是 Hook 比 Mixin 更好的原因之一。”

ES6

Array.from 将对象或者字符串转为数组
includes()表示某个数组是否包含给定的值
“async/await 是 Promise 的语法糖,让异步代码像同步一样写,错误处理用 try/catch。底层还是 Promise。”

精简版:
“ES6 新增了 let/const、箭头函数、解构、模板字符串、扩展运算符、Class、模块化 import/export、Promise、async/await、Map/Set 等。Vue3 源码和日常开发都在用,比如 setup 里解构 props、import 组件、async/await 发请求。最关键的是模块化和箭头函数,基本天天用。”

Promise

“Promise 是 ES6 新增的异步编程解决方案,用来解决传统的回调地狱问题。它相当于一个’承诺’,表示一个异步操作将来会返回成功或失败的结果。”

“Promise 是 ES6 的异步解决方案,有三个状态:进行中(Pending)、已成功(Fulfilled)和已失败(Rejected),状态一旦改变不可逆。常用 then 处理成功、catch 捕获失败、finally 收尾。Promise.all 并行请求所有接口都成功才返回,Promise.race 谁先完成返回谁。解决了回调地狱问题,async/await 是它的语法糖,让异步代码像同步写。”

父子组件执行顺序

父组件beforeCreated ->父组件created ->父组件beforeMounted ->子组件beforeCreated ->子组件created ->子组件beforeMounted ->子组件mounted -> 父组件mounted

keep-alive

可以在组件切换时 缓存的作用
include - 字符串或正则表达,只有匹配的组件会被缓存
exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存

生命周期

beforeCreate() 创建前 还未进行数据代理
created() 已完成初始化
beforeMount 初始化dom 还不能用
mounted dom初始化完成
beforeUpdate 数据页面更新前
updated 此时数据和页面都以更新
beforeDestroy 实例销毁之前调用
destroyed 实例销毁之后

ref

获取dom元素
获取子组件中的data 调用子组件的方法

<HelloWorld ref="hello"/> this.$refs.hello

“Vue2 的 ref 是 DOM 引用,用 this.$refs 获取元素或组件实例。Vue3 的 ref 是响应式 API,声明响应式数据用 ref(0),访问/修改要 .value,模板中自动解包。ref 也保留了获取 DOM 和组件实例的能力。和 reactive 比,ref 支持任意类型、可以整体重新赋值、解构不丢响应式,所以我习惯优先用 ref。”

事件修饰符

.stop:阻止冒泡
.prevent:阻止默认事件的发生
.capture:捕获冒泡
.self:将事件绑定到自身
.once:设置事件只能触发一次
.sync就是一个更新数据 @update的缩写

三、AI相关

SOP(标准作业程序)

使用场景:
基于AI前后端开发流程

MCP (模型上下文协议)

一个开放协议,定义了AI应用与外部数据源、工具之间标准化的"连接方式"。
使用场景:
• 连接数据库执行sql
• 操作Git:自动生成commit信息、创建分支、提交PR。
• 连接设计工具:从Figma API直接拉取设计稿数据生成代码骨架。

Skills技能包

封装好的"技能包",把一组指令、约束、工作流程打包成一个可重复使用的标准化模块。
使用场景:
• 组件库开发规范:新建组件时的目录结构、命名、测试用例编写流程。
• 项目发布流程:从打包、测试到部署到不同环境的操作步骤。
• “创建新页面”:自动按照项目规范生成页面,并在路由配置里注册。
• “代码审查”:按照团队Code Review Checklist(命名规范、边界处理、性能隐患)逐项检查你的PR代码。
• “生成API接口调用”:根据后端apifox文档自动生成TS类型定义和请求函数。

RAG (检索增强生成):AI的"本地知识库"

让AI在回答前,先从指定的知识库中检索相关资料,再结合这些资料生成答案。
使用场景:
• 查阅内部组件库文档:让AI准确使用公司自研的Button/Table组件API。
• 检索历史故障案例:遇到报错时,先查团队Wiki里的踩坑记录。
• 读取项目架构决策记录(ADR) :让AI理解为什么项目选择了某种技术方案。

四、综合问题

封装的方法

商品 订单 封装请求拦截、金额 / 日期等通用工具、支付与埋点工具,二次封装业务通用组件 tab 轮播图 购物车 倒计时 弹框
包括二次封装uView里面的组件(弹框 轮播图 预览图片 分页 搜索)

性能优化

图片放在oss + 懒加载
减少http请求
路由懒加载、分包,UI 按需引入,大列表用虚拟列表,图片懒加载压缩
代码方面:封装组件 封装方法 减少对DOM的操作 按需加载

并发

减少请求数量
利用缓存减少一些不必要的请求
节流、防抖 + 提交锁

项目中遇到难解决的问题

vod相关

兼容问题

五、其它

浏览器的执行过程

“从输入URL到页面展示,核心是 DNS解析 → TCP连接 → 请求响应 → 解析渲染 四个阶段。”

“浏览器执行过程分四步:① DNS解析,把域名转成IP;② 建立TCP连接(三次握手);③ 发送HTTP请求,服务端返回资源(HTML、CSS、JS等);④ 浏览器解析渲染:解析HTML构建DOM树,解析CSS构建CSSOM树,两者合并成渲染树,然后布局(回流)、绘制(重绘)、合成显示。遇到JS会阻塞解析,defer/async可以控制加载时机。”

浏览器的缓存机制

“浏览器缓存分为强缓存和协商缓存,核心是减少重复请求,提升加载速度。”

“浏览器缓存分两种:强缓存和协商缓存。强缓存用 Cache-Control(优先级最高)和 Expires 控制,命中后直接读缓存,不走服务器。协商缓存用 Last-Modified / If-Modified-Since 和 ETag / If-None-Match 控制,会向服务器发请求验证,资源没变就返回304,变就返回200和新资源。ETag 比 Last-Modified 更精准(基于文件内容变化)。”

浏览器的安全机制

“浏览器安全机制核心是同源策略,以及在此基础上扩展的 CORS、CSP、沙箱隔离 等。”

“浏览器的安全基石是同源策略:协议、域名、端口必须一致,否则跨域请求被拦截。同源策略限制了 Cookie/LocalStorage 的跨域读取、DOM 访问和 AJAX 请求。安全的扩展机制包括:CORS(服务端配置允许跨域)、CSP(内容安全策略)(限制资源加载来源,防XSS)、沙箱隔离(iframe的sandbox属性限制JS执行)。HTTPS也是浏览器安全的重要一环,防中间人攻击。”

浏览器防止xss攻击

“XSS(跨站脚本攻击)的核心是注入恶意脚本,防御核心是 ‘不信任用户输入’,对输入输出做过滤和转义。”

“XSS分三种:存储型(恶意脚本存服务器)、反射型(恶意脚本在URL参数中)、DOM型(通过DOM操作注入)。**防御措施:**① 输出转义,HTML实体编码 <> 等特殊字符;② CSP(内容安全策略),限制只能加载白名单内的脚本,禁止内联 onclick 和 eval();③ Cookie 设置 HttpOnly,禁止JS读取Cookie,防止窃取Session;④ 用户输入过滤,对输入内容做白名单校验,移除

http和https的区别

实现1rem=100px

假设设计稿是 750px 宽,clientWidth / 7.5 就得到 100px,这样 1rem 就等于 100px。再通过监听 resize 和 pageshow 事件,在窗口变化或页面从缓存加载时重新计算,保证适配实时更新。"

git基本操作

git branch 新分支名称 新建分支
git add . 把所有更改的文件添加到仓库
git commit -m"xxx" 提交文件
git push

flex-direction / flex-wrap / flex-flow / justify-content / align-items / align-content

openid是表示用户在你的当前应用中的唯一标识,多个应用,同一个用户的openId可能并不相同。
unionid同一用户,对同一个微信开放平台下的不同应用,是相同的

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询