药帮忙前端一面面经:CSS 布局、this 指向与项目鉴权
- 轮次
- 一面
- base
- 武汉
- 结果
- 面试官告知准备二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
CSS 与布局
- 讲讲 flex 布局,以及它一般用在什么场景?
- 说一下 grid 布局?
- 场景题:外部盒子内部有若干盒子,一排四个,怎么让内部盒子的间距自适应?
JavaScript 基础
- 讲一下 var、let、const 的区别?
- 为什么会有变量提升?var 为什么没有暂时性死区?
- 深拷贝和浅拷贝有什么区别?
- 匿名函数和普通函数的 this 有什么区别?
- 函数的 this 是在执行前确定还是执行后确定?
执行顺序与框架
- 一段同步代码后面跟着两个任务,它们的执行顺序是怎样的?
- TypeScript 中三个概念的权重分别是多少?
- Vue 实现组件通信的方式有哪些?
项目拷打
- 你这个商城项目是后台管理端吗?
- 项目里的鉴权是怎么实现的?
- 场景题:怎么实现一个页面内某个按钮的权限控制?
反问
- 公司主要是做什么业务的?
- 目前这个岗位进去是负责哪一部分?
- 后期的职业发展规划是怎样的?
《参考解析》
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,和菜单一起由后端权限表驱动,避免前端多处维护同一份清单。要主动点明的是:隐藏按钮挡不住直接调接口,所以后端仍要逐个接口鉴权,前端这层只是减少误操作。作者说自己的思路和面试官不太一样但基本答对,面试官大概率就是在等「权限点来自后端、前端只做展示层控制」这句。
这场面试整体是基础题加项目细节,覆盖面广但深度有限,面试官对实习生也没有硬压深题。三类反问(业务方向、进去负责什么、职业发展)是这时候最稳的一组,能问出岗位的真实职责和团队阶段;这场得到的信号是实习到毕业基本可以转正、团队更想留稳定的人,还被提前问了期望薪资——薪资话题提前出现通常意味着流程在往后走,可以顺手准备一个区间。