面灵AI→

药帮忙前端一面面经:CSS 布局、this 指向与项目鉴权

轮次
一面
base
武汉
结果
面试官告知准备二面
时间
2026-10
来源
牛客网

《面试题目》

CSS 与布局

  1. 讲讲 flex 布局,以及它一般用在什么场景?
  2. 说一下 grid 布局?
  3. 场景题:外部盒子内部有若干盒子,一排四个,怎么让内部盒子的间距自适应?

JavaScript 基础

  1. 讲一下 var、let、const 的区别?
  2. 为什么会有变量提升?var 为什么没有暂时性死区?
  3. 深拷贝和浅拷贝有什么区别?
  4. 匿名函数和普通函数的 this 有什么区别?
  5. 函数的 this 是在执行前确定还是执行后确定?

执行顺序与框架

  1. 一段同步代码后面跟着两个任务,它们的执行顺序是怎样的?
  2. TypeScript 中三个概念的权重分别是多少?
  3. Vue 实现组件通信的方式有哪些?

项目拷打

  1. 你这个商城项目是后台管理端吗?
  2. 项目里的鉴权是怎么实现的?
  3. 场景题:怎么实现一个页面内某个按钮的权限控制?

反问

  1. 公司主要是做什么业务的?
  2. 目前这个岗位进去是负责哪一部分?
  3. 后期的职业发展规划是怎样的?

《参考解析》

CSS 布局:flex 与 grid 各管什么

flex 是一维布局,管的是同一行(或同一列)里子项怎么分配空间:主轴用 justify-content、交叉轴用 align-items,子项用 flex-grow/flex-shrink/flex-basis 决定伸缩,适合导航条、工具栏、居中和简单样式的调整。grid 是二维布局,行列同时定义:grid-template-columns/rows、gap、fr 单位、grid-area 定位,适合先搭复杂页面的整体骨架,再在格子内部用 flex 处理细节——「外层 grid 搭框架、内层 flex 排内容」是常见的组合。两者的取舍不在新旧,而在维度:只需要一个方向排队就用 flex,需要行列对齐、跨行跨列就用 grid。

「一排四个盒子间距自适应」要先区分问的是哪种自适应。要子项随容器宽度伸缩且间距固定,用 display: flex; gap: 20px 加子项 flex: 1;要让子项等分并自动撑开间距,用 justify-content: space-between(首尾贴边)或 space-around;用 grid 则是 grid-template-columns: repeat(4, 1fr); gap: 20px。优先用 gap 而不是给子项加 margin,因为 margin 会让首尾多出半个间距、还得额外给 :last-child 打补丁。用 flex 分配宽度时记得给子项 min-width: 0,否则内部长内容会把等分挤破。

JS 基础:变量提升、深浅拷贝与 this 指向

var、let、const 的差别集中在作用域、提升行为和可重复声明:var 是函数作用域、可以重复声明,let/const 是块级作用域、同一作用域不能重复声明,const 只约束「绑定不可重新赋值」,指向的对象内部照样能改。提升的本质是声明在进入作用域时就被处理:var 被创建并初始化为 undefined,函数声明整体提升;let/const 同样会提升,但会停在暂时性死区里,声明语句执行前访问直接抛 ReferenceError。var 没有 TDZ 并不是设计得多好,而是 ES5 之前只有函数作用域模型,创建即给 undefined 属于历史包袱;let/const 引入 TDZ,正是为了把「先访问后声明」从静默的 undefined 变成显式报错。这类题答不出深层原因时,把「现象 + 规范行为」说清也很正常,这场面试官就没有深追。

深浅拷贝的区别只有一条:嵌套的引用类型是复制一份还是共用同一个。浅拷贝只处理第一层,Object.assign、展开运算符、数组的 slice/concat 都是这一类,改嵌套对象会同时影响两边。深拷贝要递归复制,可靠又省事的是 structuredClone(),它支持循环引用,但遇到函数、DOM 节点会抛错;JSON.parse(JSON.stringify()) 最省事,代价是丢掉 undefined、函数和 Symbol,Date 变字符串,循环引用直接报错。实际项目里的取舍是:数据只有一层或本来就按不可变方式更新,浅拷贝足够;跨层传递且要独立修改,才上结构化深拷贝。

this 的规则可以按调用方式记:普通函数的 this 由调用点决定——直接调用在非严格模式指向全局、严格模式是 undefined,作为对象方法调用指向该对象,call/apply/bind 显式指定,new 调用指向新建的实例。箭头函数没有自己的 this,它取定义时外层作用域的 this,且 call/apply 也改不动。所以严格说「匿名函数」和「普通函数」的 this 规则是一样的(function () {} 表达式并不特殊),真正不同的是箭头函数——如果面试官问的是这组对比,先确认他指的是不是箭头函数。至于绑定时机:this 在函数被调用、进入执行上下文时确定,不在定义时确定;new 调用是先创建实例并把 this 指向它,再执行函数体,所以「先指向、后执行」的顺序是对的。

执行顺序、TypeScript 与 Vue 组件通信

事件循环的顺序可以背成一个循环:同步代码在调用栈上排干 → 清空整队微任务(Promise.then、queueMicrotask、MutationObserver)→ 取一个宏任务执行(setTimeout、I/O 等)→ 再清空微任务,如此往复。所以「同步代码后面跟两个任务」要分类型回答:两个都是 Promise.then,就按注册顺序先后执行;一个 setTimeout 一个 Promise.then,无论谁先注册,微任务都排在微任务队列里先执行。如果题目里的「任务」指的是渲染或 I/O,还要补一句每个宏任务结束后、下一个宏任务开始前才有机会触发渲染。

TypeScript 那道题原帖作者没听清,只捕捉到一个词,这里不替面试官补题面。TS 面试的高频考点集中在类型系统本身:type 与 interface 的取舍(interface 可声明合并、适合对象契约,type 能表达联合与条件类型)、enum 与 const enum 的区别、泛型约束与类型收窄(typeof、in、instanceof、自定义类型守卫)、any/unknown/never 的边界,以及 Partial/Pick/Omit/Record 这类工具类型。准备时把「什么时候真的该用 TS」讲清楚,比背语法更能加分。

Vue 的组件通信按数据流向选:父子间用 props 下行、emit 上行,表单双向绑定用 v-model;跨两三层又不想逐层透传,用 provide/inject;父组件要拿子组件的方法,用模板 ref 加 expose;$attrs 适合对中间层做属性透传;兄弟或全应用共享的状态交给 Pinia 这类状态管理。选择判据就一句话——就近用 props/emit、跨层用 provide/inject、全局用 Pinia,别一上来就把所有状态塞进全局 store。

项目与鉴权:从登录态到按钮级权限

鉴权链路面试官想听完整闭环:登录后拿到凭证(JWT 或后端会话 Cookie),前端存的位置有取舍——httpOnly Cookie 能挡 XSS 窃取但需要后端配合 CSRF 防护,localStorage 用起来简单却怕 XSS;之后每个请求带上它(axios 拦截器加 Authorization 头,或者 Cookie 自动携带),路由守卫在跳转前判断登录态与角色、未登录重定向到登录页,接口返回 401/403 时统一处理(刷新 token 或踢回登录页)。最关键的一句是:前端鉴权只负责体验,真正的权限必须由后端在每个接口上再校验一遍。

按钮级权限的通行做法是「权限点 + 指令/组件」。后端在用户信息接口里返回权限码数组(例如 order:delete),前端全局存一份,再封一个 v-permission 指令或 <Auth code="..."> 组件,渲染时比对无权限就移除元素;页面级权限则写进路由 meta,和菜单一起由后端权限表驱动,避免前端多处维护同一份清单。要主动点明的是:隐藏按钮挡不住直接调接口,所以后端仍要逐个接口鉴权,前端这层只是减少误操作。作者说自己的思路和面试官不太一样但基本答对,面试官大概率就是在等「权限点来自后端、前端只做展示层控制」这句。

这场面试整体是基础题加项目细节,覆盖面广但深度有限,面试官对实习生也没有硬压深题。三类反问(业务方向、进去负责什么、职业发展)是这时候最稳的一组,能问出岗位的真实职责和团队阶段;这场得到的信号是实习到毕业基本可以转正、团队更想留稳定的人,还被提前问了期望薪资——薪资话题提前出现通常意味着流程在往后走,可以顺手准备一个区间。