前端面试常见问题及解析

前端面试主要围绕 HTML/CSS 基础、JavaScript 核心、DOM/BOM、框架原理(Vue/React)、性能优化、浏览器机制、网络知识 等核心领域展开。以下是分类整理的高频面试题及解析,帮助你系统备考。

一、HTML 基础


1. 什么是 HTML 语义化?有什么作用?

定义:语义化是指使用恰当的 HTML 标签(如 <header>、<nav>、<article>)描述内容的含义,而非仅通过 <div> + 类名。

作用

提升可读性和可维护性(开发者更易理解结构)。

帮助搜索引擎优化(SEO,爬虫更易解析内容)。

增强 accessibility(无障碍,屏幕阅读器可正确识别内容)。

2. HTML5 新增了哪些特性?

语义化标签:<header>、<footer>、<section>、<article>、<nav> 等。

媒体标签:<video>、<audio> 原生支持音视频播放。

表单增强:新输入类型(type="email"、number、date)、表单验证属性(required、pattern)。

API 扩展

本地存储(localStorage、sessionStorage)。

地理定位(Geolocation API)。

画布(<canvas> 用于绘图)。

拖放(Drag & Drop API)。

其他:DOCTYPE 简化(<!DOCTYPE html>)、废除过时标签(如 <font>、<center>)。

3. DOCTYPE 的作用是什么?

DOCTYPE(文档类型声明)位于 HTML 文档最顶部,用于 告诉浏览器当前文档使用的 HTML 版本规范,确保浏览器以正确的模式解析页面。

若缺失 DOCTYPE,浏览器会进入 “怪异模式(Quirks Mode)”,可能导致样式渲染不一致(如盒模型计算差异)。

4. meta 标签有哪些常用属性及作用?

charset="UTF-8":指定文档字符编码,避免中文乱码。

name="viewport":适配移动端,常用配置:

html

预览

<metaname="viewport"content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

作用:设置页面宽度为设备宽度,初始缩放比例 1:1,禁止用户缩放(优化移动端体验)。

name="description"/name="keywords":提供页面描述和关键词,辅助 SEO。

二、CSS 基础与进阶

1. CSS 选择器的优先级如何计算?

优先级从高到低为:

内联样式(style 属性,权重 1000)。

ID 选择器(#id,权重 100)。

类选择器(.class)、属性选择器([type="text"])、伪类(:hover)(权重 10)。

元素选择器(div)、伪元素(::before)(权重 1)。

规则:优先级高的样式覆盖低的;优先级相同则后定义的生效;!important 可强制提升优先级(不推荐滥用)。

2. 什么是 CSS 盒模型?标准盒模型与 IE 盒模型的区别?

定义:盒模型是 CSS 布局的基础,每个元素都被视为一个 “盒子”,包含 内容(content)、内边距(padding)、边框(border)、外边距(margin)

区别

标准盒模型(默认,box-sizing: content-box):

总宽度 = content-width + padding + border。

IE 盒模型(box-sizing: border-box):

总宽度 = width(包含 content + padding + border),更直观易控(常用于响应式布局)。

3. 如何实现元素的垂直居中?

根据场景不同,常见方案:

单行文本 / 内联元素:line-height: 父元素高度。

块级元素(已知高度)

绝对定位:position: absolute; top: 50%; margin-top: -高度/2;

块级元素(未知高度)

绝对定位 + transform:position: absolute; top: 50%; transform: translateY(-50%);

Flexbox:父元素 display: flex; align-items: center;(推荐,简单灵活)。

Grid:父元素 display: grid; place-items: center;(现代布局方案)。

4. Flexbox 布局的核心概念及常用属性?

核心概念:通过给父容器设置 display: flex,使其成为 “弹性容器”,子元素为 “弹性项”,通过主轴(默认水平)和交叉轴(默认垂直)控制布局。

容器常用属性

flex-direction:主轴方向(row/column)。

justify-content:主轴对齐方式(flex-start/center/space-between)。

align-items:交叉轴对齐方式(flex-start/center/stretch)。

flex-wrap:弹性项是否换行(nowrap/wrap)。

项目常用属性

flex: 1:简写(flex-grow 扩张比例 + flex-shrink 收缩比例 + flex-basis 基准尺寸),实现自适应布局。

align-self:单独设置项目的交叉轴对齐方式。

5. 什么是 BFC?如何触发 BFC?作用是什么?

定义:BFC(Block Formatting Context,块级格式化上下文)是一个独立的渲染区域,内部元素的布局不受外部影响,反之亦然。

触发条件(满足其一即可):

根元素(<html>)。

浮动元素(float: left/right)。

绝对 / 固定定位元素(position: absolute/fixed)。

overflow: hidden/auto/scroll(非 visible)。

弹性容器(display: flex/grid)。

作用

解决浮动元素导致的父元素高度塌陷。

阻止元素被浮动元素覆盖。

隔离不同 BFC 区域,避免 margin 重叠。

6. CSS 中 position 的取值及区别?

取值含义应用场景

static默认值,元素遵循正常文档流,top/left 等属性无效。普通布局

relative相对自身原位置偏移,不脱离文档流,保留原占位。微调元素位置、作为绝对定位的父容器

absolute绝对定位,相对于最近的非 static 定位祖先元素偏移,脱离文档流。弹窗、导航菜单

fixed固定定位,相对于浏览器视口偏移,脱离文档流,滚动时位置不变。顶部导航栏、回到顶部按钮

sticky粘性定位,滚动到阈值前为 relative,之后为 fixed(如吸顶效果)。列表标题吸顶

三、JavaScript 核心

1. 基本数据类型与引用数据类型的区别?

基本数据类型:String、Number、Boolean、Null、Undefined、Symbol(ES6)、BigInt(ES11)。

特点:存储在栈内存,值不可变,赋值时直接拷贝值。

引用数据类型:Object(包括 Array、Function、Date 等)。

特点:存储在堆内存,栈内存仅存地址(引用),赋值时拷贝引用(指向同一对象),修改会影响原对象。

检测方式

typeof:可检测基本类型(除 null 会返回 object),引用类型除 function 均返回 object。

instanceof:检测引用类型的具体类型(基于原型链)。

Object.prototype.toString.call():最准确,返回 [object 类型](如 [object Array])。

2. 什么是原型和原型链?

原型(prototype):每个函数(类)都有 prototype 属性,指向一个对象(原型对象),用于存储实例共享的方法和属性。

隐式原型(proto):每个对象都有 __proto__ 属性,指向其构造函数的 prototype(如 arr.__proto__ === Array.prototype)。

原型链:当访问对象的属性 / 方法时,若自身不存在,会通过 __proto__ 向上查找原型对象,直至 Object.prototype(顶端为 null),这种链式查找机制即原型链。

作用:实现继承和属性共享,减少内存消耗。

3. 什么是闭包?闭包的作用及可能引发的问题?

定义:函数嵌套时,内部函数引用外部函数的变量 / 参数,且内部函数被外部访问,导致外部函数的变量不会被垃圾回收,这种组合称为闭包。

javascript

运行

functionouter(){letcount=0;returnfunctioninner(){// 闭包  count++;returncount;};}constfn=outer();fn();// 1 

作用

封装私有变量(模块化)。

延长变量生命周期(如防抖节流中的定时器)。

问题:变量长期驻留内存,可能导致内存泄漏(需及时释放引用,如 fn = null)。

4. this 指向的规则?

this 指向在函数调用时确定,而非定义时:

全局环境:this 指向全局对象(浏览器为 window,Node 为 global)。

函数调用:fn() → this 指向全局对象(严格模式下为 undefined)。

对象方法调用:obj.fn() → this 指向调用者 obj。

构造函数调用:new Fn() → this 指向新创建的实例对象。

apply/call/bind:手动指定 this 指向(如 fn.call(obj) → this 指向 obj)。

箭头函数:无自己的 this,继承外层作用域的 this(固定不变,无法通过 call 等修改)。

5. 异步编程方案及演进?

回调函数:最早方案,易产生 “回调地狱”(嵌套过深)。

Promise:ES6 引入,通过 then() 链式调用解决回调地狱,三种状态:pending(进行中)、fulfilled(成功)、rejected(失败),状态一旦改变不可逆转。

javascript

运行

newPromise((resolve,reject)=>{setTimeout(()=>resolve("成功"),1000);}).then(res=>console.log(res)).catch(err=>console.log(err));

async/await:ES7 语法糖,基于 Promise,用同步代码风格写异步,更简洁易读。

javascript

运行

asyncfunctionfn(){try{constres=awaitnewPromise(resolve=>resolve("成功"));console.log(res);}catch(err){console.log(err);}}

6. 事件循环(Event Loop)的原理?

JavaScript 是单线程,通过事件循环实现非阻塞异步:

执行栈:同步任务按顺序执行,执行完后处理异步任务。

任务队列:异步任务(如 setTimeout、Promise.then、DOM 事件)完成后,回调函数放入队列。

微任务(Microtasks) vs 宏任务(Macrotasks)

微任务优先级更高,包括:Promise.then/catch/finally、process.nextTick(Node)、queueMicrotask。

宏任务包括:setTimeout、setInterval、DOM 事件、fetch、script 整体代码。

流程

执行同步代码(执行栈清空)。

执行所有微任务(清空微任务队列)。

执行一个宏任务,然后重复步骤 2(循环)。

7. ES6+ 有哪些常用新特性?

变量声明:let(块级作用域,不可重复声明)、const(声明常量,值不可改)。

箭头函数:(a, b) => a + b,简化语法,无自己的 this。

解构赋值:const { name } = obj; const [a, b] = arr,快速提取对象 / 数组属性。

模板字符串:`Hello ${name}`,支持换行和变量嵌入。

类与继承:class 语法糖,extends 实现继承(基于原型)。

模块化:import/export 替代 CommonJS,支持静态导入。

其他:Set/Map 数据结构、Promise、async/await、扩展运算符(...)、默认参数(function fn(a=1) {})。

四、DOM 与 BOM

1. 如何操作 DOM?常用 API 有哪些?

创建节点:document.createElement(tagName)、document.createTextNode(text)。

添加节点:parent.appendChild(child)、parent.insertBefore(newNode, referenceNode)。

删除节点:parent.removeChild(child)。

修改节点:element.textContent = "内容"、element.innerHTML = "<div>HTML</div>"。

查询节点

document.getElementById(id)、document.getElementsByClassName(class)。

document.querySelector(selector)(返回第一个匹配元素)、document.querySelectorAll(selector)(返回 NodeList)。

2. 事件冒泡与事件捕获的区别?如何阻止?

事件捕获:事件从最外层祖先元素向目标元素传播(“从上到下”)。

事件冒泡:事件从目标元素向最外层祖先元素传播(“从下到上”,默认触发)。

阻止方式

阻止冒泡:event.stopPropagation()。

阻止默认行为(如表单提交、链接跳转):event.preventDefault()。

3. 什么是事件委托?有什么优势?

定义:将子元素的事件监听委托给父元素,利用事件冒泡触发父元素的事件处理函数,再通过 event.target 判断具体触发的子元素。

javascript

运行

// 为 ul 委托监听 li 的点击事件  document.querySelector('ul').addEventListener('click',(e)=>{if(e.target.tagName==='LI'){console.log('点击了 li');}});

优势

减少事件监听数量,优化性能(尤其子元素动态生成时)。

动态新增的子元素自动继承事件监听,无需重新绑定。

4. 本地存储方案及区别(Cookie、localStorage、sessionStorage)?

特性CookielocalStoragesessionStorage

存储大小约 4KB约 5MB约 5MB

有效期可设置过期时间(expires)永久有效(除非手动删除)会话级(页面关闭后清除)

通信每次请求自动携带(HTTP 头部)不参与网络传输不参与网络传输

作用域同源且符合路径规则同源页面共享仅当前标签页 / 窗口

API 复杂度需手动封装(document.cookie)简洁(setItem/getItem)同 localStorage

五、前端框架(以 Vue 和 React 为例)

Vue 核心问题

1. Vue 的响应式原理是什么?

Vue 2 基于 Object.defineProperty 实现:

初始化时,对 data 中的属性递归遍历,通过 Object.defineProperty 为属性添加 getter 和 setter。

getter 触发时收集依赖(Watcher),setter 触发时通知依赖更新(重新渲染)。

缺陷:无法监听对象新增属性、数组索引 / 长度变化(需用 Vue.set 或 this.$set 手动触发更新)。

Vue 3 改用 Proxy 实现:

直接代理整个对象(而非属性),支持监听对象新增属性、数组变化,无需手动处理,性能更优。

2. Vue 组件的生命周期钩子有哪些?常用场景?

创建阶段

beforeCreate:实例初始化后,数据和方法未挂载,无法访问 data/methods。

created:数据和方法已挂载,可访问数据,但 DOM 未生成(常用于初始化数据、发请求)。

挂载阶段

beforeMount:模板编译完成,未挂载到 DOM。

mounted:DOM 已挂载,可操作 DOM(常用于初始化插件、图表)。

更新阶段

beforeUpdate:数据更新后,DOM 未更新前触发。

updated:DOM 已更新,可获取最新 DOM 状态。

销毁阶段

beforeDestroy:实例销毁前,可清理定时器、解绑事件。

destroyed:实例已销毁,所有监听和子组件被移除。

3. Vue 组件通信方式有哪些?

父子组件

父传子:props 传递数据,子组件通过 props 接收。

子传父:子组件 $emit('事件名', 数据),父组件 @事件名 监听。

跨级 / 兄弟组件

Vuex/Pinia:全局状态管理(推荐)。

provide/inject:祖先组件 provide 提供数据,后代组件 inject 注入(非响应式,适合深层传递)。

事件总线(EventBus):通过 new Vue() 创建总线,$on 监听、$emit 触发(小型项目)。

React 核心问题

1. React 的虚拟 DOM(Virtual DOM)是什么?作用?

定义:虚拟 DOM 是内存中的 JavaScript 对象,映射真实 DOM 的结构(如 { tag: 'div', props: {}, children: [] })。

作用

减少真实 DOM 操作:React 先对比新旧虚拟 DOM 的差异(Diff 算法),仅更新变化的部分到真实 DOM,降低性能损耗。

跨平台:虚拟 DOM 可渲染到不同平台(如 React Native 渲染到原生组件)。

2. React Hooks 是什么?常用 Hooks 及作用?

Hooks 是 React 16.8 新增特性,允许函数组件使用状态和生命周期等特性,无需编写类组件。

useState:为函数组件添加状态。

javascript

运行

const[count,setCount]=useState(0);// 初始值 0 

useEffect:处理副作用(如请求、定时器、事件监听),替代类组件的生命周期。

javascript

运行

// 相当于 componentDidMount + componentDidUpdate  useEffect(()=>{consttimer=setInterval(()=>setCount(c=>c+1),1000);// 清理函数(相当于 componentWillUnmount)  return()=>clearInterval(timer);},[]);// 空依赖数组:仅执行一次 

其他常用 Hooks:useContext(共享状态)、useReducer(复杂状态管理)、useRef(获取 DOM 或保存变量)。

六、性能优化

1. 页面加载性能优化有哪些方法?

资源优化

压缩资源(JS/CSS 压缩,图片压缩 / 格式优化如 WebP)。

代码分割(按需加载,如路由懒加载 React.lazy/Vue 路由懒加载)。

利用 CDN 分发静态资源(减少网络延迟)。

加载策略

懒加载(图片、组件:loading="lazy" 或 IntersectionObserver)。

预加载(link rel="preload" 提前加载关键资源)。

减少 HTTP 请求(合并 JS/CSS,使用雪碧图)。

缓存优化

强缓存(Cache-Control: max-age=31536000)和协商缓存(ETag/Last-Modified)。

2. 渲染性能优化有哪些方法?

减少重排(Reflow)和重绘(Repaint)

重排:DOM 布局变化(如尺寸、位置改变),开销大;重绘:样式变化(如颜色),开销较小。

优化:批量修改 DOM(离线操作 documentFragment)、避免频繁读取布局属性(如 offsetHeight)、使用 transform/opacity(触发合成层,无重排)。

JavaScript 优化

防抖(debounce):避免高频事件(如 resize)多次触发(如搜索输入联想)。

节流(throttle):限制事件触发频率(如滚动加载)。

避免长任务阻塞主线程(用 Web Worker 处理复杂计算)。

七、浏览器与网络

1. 浏览器的渲染流程是什么?

解析 HTML:生成 DOM 树(Document Object Model)。

解析 CSS:生成 CSSOM 树(CSS Object Model)。

构建渲染树(Render Tree):结合 DOM 树和 CSSOM 树,过滤不可见元素(如 display: none)。

布局(Layout):计算渲染树中元素的位置和尺寸(重排发生在此阶段)。

绘制(Paint):将元素绘制到屏幕(重绘发生在此阶段)。

合成(Composite):将绘制的图层合并,显示到屏幕(如 z-index 层级处理)。

2. 跨域问题的原因及解决方案?

原因:浏览器的同源策略限制(协议、域名、端口三者必须相同),禁止跨域请求资源。

解决方案

CORS(跨域资源共享):服务器端设置响应头 Access-Control-Allow-Origin: *(或指定域名),支持所有 HTTP 方法。

JSONP:利用 <script> 标签不受同源限制,动态创建 script 发送请求,服务器返回回调函数包裹数据(仅支持 GET)。

代理服务器:开发环境用 Webpack Dev Server 代理,生产环境用 Nginx 反向代理,将跨域请求转为同域请求。

八、手写题(高频)

1. 手写防抖(Debounce)

javascript

运行

// 触发后延迟 wait 毫秒执行,若期间再次触发则重置计时  functiondebounce(fn,wait){lettimer=null;returnfunction(...args){clearTimeout(timer);timer=setTimeout(()=>{fn.apply(this,args);},wait);};}

2. 手写节流(Throttle)

javascript

运行

// 每隔 wait 毫秒最多执行一次  functionthrottle(fn,wait){letlastTime=0;returnfunction(...args){constnow=Date.now();if(now-lastTime>=wait){fn.apply(this,args);lastTime=now;}};}

3. 手写深拷贝(考虑对象、数组、函数)

javascript

运行

functiondeepClone(obj,hash=newWeakMap()){if(obj===null||typeofobj!=='object')returnobj;// 非对象直接返回  if(hash.has(obj))returnhash.get(obj);// 处理循环引用  letcloneObj;// 区分数组和对象  if(objinstanceofArray){cloneObj=[];}elseif(objinstanceofObject){cloneObj={};}hash.set(obj,cloneObj);// 缓存已拷贝对象  // 递归拷贝属性  Reflect.ownKeys(obj).forEach(key=>{cloneObj[key]=deepClone(obj[key],hash);});returncloneObj;}

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容