前端真题 130 题 · HTML & 浏览器基础
行内/块级元素、跨标签页通信、history/hash 路由、DOM 树、BFC、position、图片加载优化、flex、CSS 优先级与层叠上下文等 1-19 题。
来源:飞书多维表格《前端面试真题,会80%直接进大厂》
说明:以下按原表顺序整理 130 个面试知识点,每题给出详细完整答案,含关键原理、要点、示例代码与手写实现。非技术类题目给出回答思路。适用于前端中高级岗位面试准备。
一、HTML & 浏览器基础
1. 行内元素、块级元素
行内元素(inline):如 span、a、img、input、label、em、strong、br、code。
- 与其他行内元素同一行排列;
- 宽度高度由内容决定,设置 width/height 无效(
img、input这类替换元素除外); - 垂直方向的 margin/padding 不影响布局,水平方向生效;
- 可以嵌套在块级元素内。
块级元素(block):如 div、p、h1-h6、ul、li、table、section、header。
- 独占一行,默认宽度撑满父容器;
- 可设置 width/height、margin/padding(四个方向都生效);
- 可容纳行内元素与其他块级元素。
行内块级元素(inline-block):兼具两者特性——同行排列、可设宽高、垂直 margin 生效。
面试要点:
display可在这几种类型间转换;inline元素在排版时会产生"换行间隙"问题(因源码换行符)。- 现代布局中
flex/grid会改变子元素的 display 上下文(子项变为 blockified),行内元素的很多特性在 flex 容器内不再适用。
2. 跨标签页通信
标签页间通信的常用方案:
- localStorage 事件(同源):在 A 页写入
localStorage,B 页监听storage事件。storage事件只在其他标签页修改时触发,当前页不触发。
// A 页
localStorage.setItem('msg', JSON.stringify({t: Date.now(), data: 'hi'}));
// B 页
window.addEventListener('storage', e => {
if (e.key === 'msg') console.log('收到:', JSON.parse(e.newValue));
});
- BroadcastChannel(推荐,同源):原生 API,双向实时通信。
const bc = new BroadcastChannel('channel');
bc.onmessage = e => console.log(e.data);
bc.postMessage('hello');
- postMessage:适用于跨域页面、iframe、
window.open打开的窗口之间。
// A 发
targetWindow.postMessage({type:'greet', data:1}, 'https://target.com');
// B 收
window.addEventListener('message', e => {
if (e.origin !== 'https://source.com') return; // 务必校验 origin
console.log(e.data);
});
- SharedWorker:多个标签页共享同一个 Worker 线程,通过它中转消息(同源)。
- WebSocket / SSE:通过服务器中转,适用于跨标签页但可跨域、跨设备。
要点:同源优先 localStorage 事件或 BroadcastChannel;跨域用 postMessage 且必须校验 e.origin;复杂场景(多页状态同步)可上 SharedWorker。
3. history 和 hash 两种路由
单页应用路由的两种实现,核心是不刷新页面改变 URL 并触发视图更新。
Hash 路由
- URL 形如
https://a.com/#/user?id=1,hash改变不会触发浏览器请求,天然支持刷新。 - 监听
hashchange事件切换视图。 - 缺点:URL 中有
#不够美观;SEO 不友好。
window.addEventListener('hashchange', () => {
const path = location.hash.slice(1);
render(path);
});
History 路由
- 基于 History API:
pushState/replaceState修改 URL(不刷新),配合popstate事件(仅前进后退触发)。 - URL 干净美观,形如
https://a.com/user/1。 - 缺点:刷新时服务器需把所有路径都重定向回 index.html(否则 404);需要后端配置支持(如 nginx
try_files)。
history.pushState({}, '', '/user/1'); // 切页面
window.addEventListener('popstate', () => render(location.pathname));
对比要点:
| 维度 | hash | history |
|---|---|---|
| URL 美观 | 有 #,一般 | 干净 |
| 刷新 | 无感 | 需后端配合 |
| SEO | 差 | 相对好(仍需 SSR/预渲染) |
| 兼容性 | 更好 | 依赖 History API |
前端框架(Vue Router / React Router)都有两种 mode,正是对应这两套机制。
4. DOM 树
DOM(Document Object Model,文档对象模型) 是浏览器把 HTML 文档解析成的树形结构,每个 HTML 标签对应一个节点对象,节点之间按嵌套关系形成父子、兄弟的层次结构。
- 节点类型:元素节点、文本节点、属性节点、注释节点等;
- 文档解析过程:HTML → 字节流 → Tokenizer(分词)→ 构建 DOM 树;同时 CSS 解析生成 CSSOM 树,两者合成 Render Tree(渲染树);
- 根节点是
document,根元素是documentElement(即<html>)。
为什么要理解 DOM 树:
- DOM 操作是"操作一棵树",增删改查都要符合树结构——
appendChild、insertBefore、removeChild、querySelector等; - 每次 DOM 修改都可能引起重排/重绘,所以要批量修改、用文档碎片
DocumentFragment、虚拟 DOM 等手段减少操作次数; - DOM 越深越复杂,选择器查找越慢,布局计算越贵——这决定了"减少 DOM 层级/节点数"是前端性能优化的基础手段之一。
面试引申:渲染流程(DOM+CSSOM→RenderTree→Layout→Paint)、defer/async 对 DOM 解析的阻塞、为什么虚拟 DOM 快(批处理 + 最小化真实 DOM 操作)等。
5. bind 的实现
bind 与 call/apply 的区别:bind 不立即执行,返回一个新函数,并可将 this 永久绑定 + 支持预传参(偏函数)。手写实现要点:
- 返回一个新函数;
- 新函数调用时,
this指向绑定的对象(若绑定值为 null/undefined,非严格模式回退到全局); - 支持参数合并:bind 时传的参数 + 调用时传的参数;
- 支持 new:如果返回的函数被
new调用,this应指向新实例,忽略绑定的 this,并继承原函数的 prototype。
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== 'function') throw new TypeError('not a function');
const fn = this;
const bound = function (...rest) {
// 1) 若被 new 调用,this 是新对象,用新 this 并保留原型链
// 2) 否则用 context
// 注:`?? globalThis` 是简化写法。原生 bind 会区分严格模式——
// 非严格模式下 null/undefined 回退到全局对象;严格模式下则保持 null/undefined 不变。
return fn.apply(
this instanceof bound ? this : (context ?? globalThis),
[...args, ...rest]
);
};
// 让 bound 的原型指向原函数原型,使 instanceof 生效
bound.prototype = Object.create(fn.prototype);
return bound;
};
// 验证
function Point(x, y) { this.x = x; this.y = y; }
const BoundPoint = Point.bind(null, 1);
const p = new BoundPoint(2); // new 时忽略绑定的 this
p.x; // 1
考察点:this instanceof bound 判断是否被 new 调用;参数拼接顺序;原型链处理;边界(bind 结果再 bind、丢失绑定 context 为 null)。
6. 实现一个 trim 方法
String.prototype.trim 去除字符串首尾空白(含空格、\t、\n、\r 等)。注意不要用"两侧去空格"的错误正则。
// 方式一:正则(推荐,写法最贴近原生)
String.prototype.myTrim = function () {
return this.replace(/^\s+|\s+$/g, '');
};
// 方式二:显式字符表去空白(也可扩展自定义字符)
String.prototype.myTrim2 = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
// 方式三:指针扫描(体现对 Unicode 空白理解)
String.prototype.myTrim3 = function () {
const ws = /\s/;
let s = 0, e = this.length - 1;
while (s <= e && ws.test(this[s])) s++;
while (e >= s && ws.test(this[e])) e--;
return this.slice(s, e + 1);
};
' hello world \n\t '.myTrim(); // 'hello world'
考察点:\s 匹配哪些空白(空格、制表、换行、回车、\uFEFF BOM 等);两个边界是否都想清楚;是否会用 ^...|...$ 组合。
7. BFC(块级格式化上下文)
BFC(Block Formatting Context):页面上一块独立渲染区域,内部元素的布局不会影响外部,外部也不会影响内部。它是 CSS 视觉格式化模型中的一个概念。
触发 BFC 的条件(常见):
overflow非 visible(hidden/auto/scroll);float非 none;display: inline-block / table-cell / flex / grid / flow-root;position: absolute / fixed;- 根元素
<html>本身。
BFC 的特性(作用):
- 清除浮动:BFC 会包含内部浮动元素,父元素加
overflow:hidden可避免高度塌陷; - 阻止外边距合并(margin collapsing):两个相邻元素的外边距会取较大值合并,若把它们放入不同 BFC,则不会合并;
- 阻止元素被浮动元素覆盖:BFC 区域不会与浮动元素重叠(可用于实现自适应两栏布局)。
/* 经典用法:父容器触发 BFC 包裹浮动子元素,避免高度塌陷 */
.parent { overflow: hidden; }
.child { float: left; }
面试引申:为什么 overflow:hidden 能清除浮动?因为触发 BFC 后,父元素必须计算包含浮动子元素的整体高度;display:flow-root 是专为无副作用创建 BFC 而生的属性值。
8. position
CSS position 决定元素定位方式,取值与特性如下:
| 取值 | 脱离文档流 | 参考系 | 特点 |
|---|---|---|---|
static | 否 | — | 默认值,top/left/right/bottom/z-index 无效 |
relative | 否 | 自身原位置 | 相对自己原先位置偏移,原占位保留 |
absolute | 是 | 最近的非 static 定位祖先(否则为包含块/视口) | 相对定位祖先定位,原占位消失 |
fixed | 是 | 视口(viewport,除非祖先有 transform 等创建包含块) | 相对视口固定,滚动不随页面移动 |
sticky | 部分 | 最近滚动容器 | 相对定位与 fixed 结合:未到阈值时如 relative,到达后如 fixed 吸附 |
重点易错:
absolute的包含块是最近的非 static 定位祖先;若都没有,则以初始包含块(视口)为参考,并受祖先transform/perspective/filter影响(这些属性会创建新的包含块,导致 fixed 失效/相对其定位);relative偏移后原空间保留,absolute偏移后原空间不保留;- 定位元素(position 非 static)会成为后代
absolute的定位参考,也是z-index生效的前提(static 元素 z-index 无效)。
面试扩展:定位 + z-index 与层叠上下文的关系;absolute 居中技巧(left:0;right:0;margin:auto 或 transform 偏移)。
9. 大量图片加载优化
面试常问"页面有大量图片,如何优化加载与性能",从加载、解码、渲染三层回答:
- 懒加载(Lazy Load):首屏只加载可视区图片,滚动进入视口再加载。实现:
loading="lazy"(原生)或 IntersectionObserver;占位用低清模糊图或骨架屏。 - 图片格式与压缩:优先 WebP/AVIF(体积远小于 JPEG/PNG);开启服务端压缩;使用响应式图片
srcset+sizes按屏幕分辨率给不同尺寸;图标用 SVG/雪碧图。 - CDN 分发:静态图片走 CDN,多地就近加速。
- HTTP 层面:开启 HTTP/2 多路复用、
Cache-Control强缓存 + 版本化文件名(hash 命名)减少重复下载。 - 预加载关键图:首屏最重要图片用
<link rel="preload" as="image">,防止被 CSS/JS 阻塞发现。 - 减少解码开销:避免超大图(按实际展示尺寸下发);
content-visibility: auto跳过屏外渲染;图片设宽高避免布局抖动(CLS)。 - 占位与体验:blur-up(先模糊小图再切高清)、渐进式 JPEG。
衡量:用 LCP(最大内容绘制)观察首屏图加载;Lighthouse 的 image 审计项。
10. 实现一个 deepClone 方法
深拷贝要点:处理循环引用、各种数据类型(数组、对象、Date、RegExp、Map、Set、Symbol 键、函数、原型、不可枚举属性)。
function deepClone(target, map = new WeakMap()) {
if (target === null || typeof target !== 'object') return target; // 原始值/函数
if (map.has(target)) return map.get(target); // 循环引用
const ctor = target.constructor;
// 特殊类型:时间/正则/Map/Set
if (target instanceof Date) return new Date(target.getTime());
if (target instanceof RegExp) {
const r = new RegExp(target.source, target.flags);
r.lastIndex = target.lastIndex;
return r;
}
if (target instanceof Map) {
const m = new Map();
map.set(target, m);
target.forEach((v, k) => m.set(deepClone(k, map), deepClone(v, map)));
return m;
}
if (target instanceof Set) {
const s = new Set();
map.set(target, s);
target.forEach(v => s.add(deepClone(v, map)));
return s;
}
// 数组与普通对象
const clone = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target));
map.set(target, clone);
// 自有可枚举 Symbol 键 + 字符串键(含不可枚举用 Reflect.ownKeys 可全拷)
for (const key of Reflect.ownKeys(target)) {
const desc = Object.getOwnPropertyDescriptor(target, key);
if (desc && 'value' in desc) {
desc.value = deepClone(target[key], map);
Object.defineProperty(clone, key, desc);
} else if (desc) {
Object.defineProperty(clone, key, desc); // getter/setter 拷贝
}
}
return clone;
}
考察点:
- 循环引用必须处理(否则爆栈),用 WeakMap 记录已克隆对象;
- 区分"浅拷贝"(
Object.assign/展开符只拷一层); - 实际项目中可提醒:
structuredClone是浏览器原生深拷贝(但丢失函数、不支持 Symbol key、原型变普通对象);业务常用JSON.parse(JSON.stringify())的局限(丢 undefined/函数/Symbol、Date 变字符串、循环引用报错)也要能说清。
11. 斐波那契数列
斐波那契定义:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)。
递归(朴素,指数级,会爆栈/超时):
const fib = n => n <= 1 ? n : fib(n-1) + fib(n-2);
优化方案:
- 记忆化(memo)——把重复子问题缓存:
function fib(n, memo = {}) {
if (n <= 1) return n;
if (memo[n] != null) return memo[n];
return (memo[n] = fib(n-1, memo) + fib(n-2, memo));
}
- 动态规划 / 滚动迭代(O(n) 时间、O(1) 空间,最优):
function fib(n) {
if (n <= 1) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) [a, b] = [b, a + b];
return b;
}
- 矩阵快速幂:O(log n),适合超大 n(体现进阶能力,可简单提及)。
考察点:能指出朴素递归的重复计算问题,并给出迭代解法;能回答"时间复杂度/空间复杂度"。
12. 最近看的书
非技术问题,考学习态度与沉淀。回答套路:
- 说 1-2 本真实在读且与岗位相关的书,比泛泛而谈更好;
- 技术书 + 非技术书各一本,展示广度;
- 讲"读完最大的收获/改变了我什么",不要只报书名。
示例(前端方向):
《JavaScript 高级程序设计(第4版)》正在二刷,重点补 ES6+ 与浏览器原理部分;同时在看《代码整洁之道》,最大的改变是写代码时会更在意函数职责单一和命名,重构了一些历史模块。平时也刷《你不知道的 JavaScript》里的 this/闭包章节。
注意:别编造没读过的书,面试官常追问细节(某章讲了什么、你的实践)。
13. flex 布局
Flex(弹性盒)是一维布局模型,核心概念:容器(flex container)+ 项目(flex item),主轴(main axis)与交叉轴(cross axis)。
容器属性:
display: flex | inline-flexflex-direction: row | row-reverse | column | column-reverse(主轴方向)flex-wrap: nowrap | wrap | wrap-reverse(是否换行)flex-flow(direction + wrap 简写)justify-content(主轴对齐):flex-start/end、center、space-between、space-around、space-evenlyalign-items(交叉轴单行对齐):stretch、flex-start/end、center、baselinealign-content(交叉轴多行对齐,需换行才生效)
项目属性:
flex-grow:放大比例(默认 0);flex-shrink:缩小比例(默认 1);flex-basis:初始主轴尺寸flex简写:flex: 1=1 1 0%;flex: none=0 0 auto;flex: auto=1 1 autoorder:排序;align-self:覆盖容器的 align-items
经典场景:
- 水平垂直居中:容器
display:flex; justify-content:center; align-items:center - 等分:子项
flex:1 - 圣杯/左右固定中间自适应:中间
flex:1
易错点:flex-basis:0 与 auto 的区别;默认 flex-shrink:1 导致内容挤压;flex 子项 min-width:auto 导致溢出(需 min-width:0);flex 容器内子项 margin:auto 可做复杂居中。
14. CSS 优先级
CSS 层叠(cascade)判断优先级规则,从高到低:
- !important(比普通声明高,同 !important 再比权重);
- 内联样式 style(权重 1000);
- 选择器权重:
- ID 选择器 = (1,0,0)
- 类/属性/伪类(.cls、attr、:hover)= (0,1,0)
- 元素/伪元素(div、::before)= (0,0,1)
- 通配符 * 与组合器不占权重
- 后写的同权重声明覆盖先写的;
:is()/:where()特例::where权重恒为 0,:not()/:is()取参数中最高权重。
/* 权重计算示例 */
#nav .item:hover /* (1,2,0) */
div .item /* (0,1,1) */
#nav div.item /* (1,1,1) */
* /* (0,0,0) */
判断技巧:四元组 (inline, id, class, element) 从左往右比较,先出现更大值的胜出。同级比"出现顺序"(后声明的赢)。
要点:权重相同时源码顺序决定;!important 尽量少用(破坏可维护性);用 BEM/类选择器把权重控制在可预期范围。
15. 双飞翼/圣杯布局
目的:左右两栏固定宽度,中间栏自适应且优先渲染(对 SEO/首屏有利)。核心:中间栏放最前 + 三栏浮动/弹性。
圣杯布局(float + 负 margin + padding):
<div class="container">
<div class="middle">中间</div>
<div class="left">左</div>
<div class="right">右</div>
</div>
.container { padding: 0 200px; } /* 给左右留位 */
.middle { float: left; width: 100%; } /* 中间占满 */
.left { float: left; width: 200px; margin-left: -100%; position: relative; left: -200px; }
.right { float: left; width: 200px; margin-left: -200px; position: relative; right: -200px; }
缺点:依赖 position 偏移、对宽度敏感。
双飞翼布局(float + 负 margin + 内层 margin):
<div class="middle"><div class="middle-inner">中间</div></div>
<div class="left">左</div>
<div class="right">右</div>
.middle { float: left; width: 100%; }
.middle-inner { margin: 0 200px; } /* 内容腾出两侧空间,无需外层 padding */
.left { float: left; width: 200px; margin-left: -100%; }
.right { float: left; width: 200px; margin-left: -200px; }
现代方案(推荐回答):
- Flex:中间
flex:1; - Grid:
grid-template-columns: 200px 1fr 200px。
考察点:说出负 margin 让固定栏"爬上"100% 宽度行的原理;中间栏 HTML 在最前(content first);能给出 flex/grid 更简单的解法。
16. CSS3 新特性
从选择器、样式、布局、动画、特效等维度作答:
- 新增选择器:属性选择器
[attr^=] [attr$=] [attr*=]、伪类:nth-child(n) :not() :empty :root :target :enabled/:disabled :checked、:first-of-type等; - 边框与背景:
border-radius、box-shadow、text-shadow、background-size/clip/origin、多重背景、渐变linear-gradient/radial-gradient; - 文字:
@font-face自定义字体、text-overflow、word-break、white-space; - 颜色与单位:
rgba/hsla、rem/vh/vw/vmin/vmax/calc(); - 布局:
flex(一维弹性)、grid(二维网格)、多列columns、box-sizing: border-box; - 动画与过渡:
transition、@keyframes + animation、transform(translate/rotate/scale/skew,含 3D:perspective、transform-style); - 特效:
filter(模糊/灰度)、mix-blend-mode、clip-path; - 响应式:媒体查询
@media、@supports; - 新特性语义化/其他:
calc、自定义属性--var、will-change(动画优化)、content-visibility。
作答技巧:挑 5-6 类展开并举例说明"在项目中怎么用",比背目录好。
17. CSS 样式隔离
解决多团队/微前端/组件库下样式互相污染的问题,方案从简单到完整:
- BEM 命名规范:
.block__element--modifier,靠命名约定隔离(约定层,无运行时成本); - CSS Modules:构建时给类名加 hash(
.title_2x3ab_1),局部作用域,引用用import styles from './x.module.css'; - CSS-in-JS(styled-components / emotion):样式绑定组件,自动生成唯一类名,支持动态主题;
- scoped(Vue):
<style scoped>编译时给选择器加[data-v-xxx]属性,实现"伪局部"(注意子组件根节点会受影响、动态内容需); - Shadow DOM(Web Components):浏览器原生隔离——样式与 DOM 都被封在 shadow root 内,真正物理隔离;
- 原子化 CSS(Tailwind/Unocss):类名本身就是工具,配合预检(preflight)减少冲突面。
微前端场景加分:子应用样式污染父应用 → 子应用用 CSS Modules + 容器前缀 + 动态样式挂载/卸载;沙箱隔离样式(shadow DOM 或 iframe)。
面试要点:能对比各方案隔离粒度(约定/编译/运行时/浏览器级);说出 scoped 的原理(属性选择器);CSS Modules 与 scoped 本质都靠"改名+改名后选择器加约束"。
18. CSS 性能优化
分加载、渲染、书写习惯三层面回答:
- 减少体积与请求:压缩(cssnano)、移除未使用样式(PurgeCSS/Tailwind)、合并请求、HTTP/2 下多小文件反而不必合并;
- 关键 CSS 内联:首屏关键样式
<style>内联,避免阻塞渲染;非关键 CSS 异步加载(media="print"或rel="preload" as="style"加载后切换 rel); - 减少重排重绘:
- 避免频繁读写布局属性(读一帧攒批、
requestAnimationFrame里写); - 用
transform/opacity做动画(走合成层,不触发 layout/paint); - 减少 DOM 层级,降低样式匹配成本(后代选择器越深越贵,
.a .b .c {}尽量收敛为类); - 批量改 DOM(DocumentFragment);
will-change提前告诉浏览器要动画(谨慎用,会占内存);
- 避免频繁读写布局属性(读一帧攒批、
- 选择器优化:避免通配/超长后代链;类选择器优先;
- 其他:开启 gzip/br、图片等走 CDN、
content-visibility:auto跳过屏外渲染。
回答框架:可以按 Lighthouse 的 Performance 面板维度(FCP/LCP/CLS)组织:样式影响首屏 → 关键 CSS;动画卡顿 → 合成层优化;页面跳动 → 图片字体预留空间。
19. 层叠上下文
层叠上下文(Stacking Context):元素在 Z 轴上的"分组",一组元素按层叠顺序(stacking order)排列,组内比较、组间比较按组的整体。
触发条件(常见):
- 根元素
<html>; position+z-index(非 auto);opacity < 1、transform、filter、backdrop-filter、perspective、clip-path、mask、mix-blend-mode、contain、will-change指定上述属性;flex/grid子项且z-index非 auto;isolation: isolate。
层叠顺序(从底到顶):
- 背景与边框
- 负 z-index 子元素
- 块级元素(非定位)
- 浮动元素
- 行内元素
- z-index:auto / 0 的定位元素
- 正 z-index 子元素
常见坑:
- 父元素
opacity < 1会创建层叠上下文,导致子元素 z-index 无法"越级"(z-index 只在同一上下文内比较); z-index只对定位元素(或 flex/grid 子项)生效;- 判断两个元素谁上谁下:找到共同层叠上下文祖先,在其中比较各自的层叠顺序。
面试加分:层叠上下文与 z-index 的"父子限制"关系;transform 也创建上下文导致 fixed 定位异常的关联。