MetaAPP 前端一面面经深度解析
MetaAPP 前端一面面经包含项目经验、CSS 动画、JS 特性及 Vue 原理等核心考点。面试侧重组件封装 API 兼容性、响应式布局实现、CSS 抛物线动画、JS 包装对象原型链操作、Promise 区别、Vue 实例生命周期、响应式源码原理(WeakMap)、nextTick 机制及浏览器兼容性处理。内容提供详细代码示例与深度解析,适合前端求职者参考复习,掌握大厂面试考察重点与技术底层逻辑。

MetaAPP 前端一面面经包含项目经验、CSS 动画、JS 特性及 Vue 原理等核心考点。面试侧重组件封装 API 兼容性、响应式布局实现、CSS 抛物线动画、JS 包装对象原型链操作、Promise 区别、Vue 实例生命周期、响应式源码原理(WeakMap)、nextTick 机制及浏览器兼容性处理。内容提供详细代码示例与深度解析,适合前端求职者参考复习,掌握大厂面试考察重点与技术底层逻辑。

| 维度 | 特征 |
|---|---|
| 公司定位 | MetaAPP - 游戏业务(H5/官网) |
| 面试风格 | 项目深潜型 + 原理追问型 + 边界拓展型 |
| 难度评级 | ⭐⭐⭐(三星,从项目到原理到边界问题) |
| 考察重心 | 组件封装能力、响应式布局、动画实现、JS 语言特性、Vue 源码原理 |
| 特殊之处 | 问题很有启发性,尤其是 number 强转对象的题目考察了对 JS 语言本质的理解 |
发散性问题,需要平时多做积累,这里我们只复盘一下 API 兼容性设计的内容
// 1. 组件封装的核心原则
// 1.1 组件设计思路
// 以弹窗组件为例
<Modal v-model="visible"
title="提示"
width="500"
:before-close="handleClose"
@on-ok="handleOk"
@on-cancel="handleCancel">
<div>内容</div>
</Modal>
// 1.2 API 兼容性设计
// 方案 1:属性透传
// 让用户可以传递任意原生属性
<Button type="primary" :loading="loading" @click="handleClick" custom-prop="value">
按钮
</Button>
// 组件内部
<button v-bind="$attrs" class="buttonClass">
<slot />
</button>
// 方案 2:默认值 + 扩展
props: {
size: {
type: String,
default: 'medium',
validator: (value) => ['small','medium','large'].includes(value)
},
type: {
type: String,
default: 'default'
}
}
// 方案 3:版本兼容
// 同时支持新旧 API
props: {
// 新 API
modelValue: {},
// 兼容旧 API
value: {}
},
emits: ['update:modelValue', 'input']
/* 1. 媒体查询 */
/* 移动端 */
@media(max-width: 767px){
.container{
width: 100%;
padding: 10px;
}
}
/* iPad 端 */
@media(min-width: 768px) and(max-width: 1024px){
.container{
width: 750px;
margin: 0 auto;
}
}
/* PC 端 */
@media(min-width: 1025px){
.container{
width: 1200px;
margin: 0 auto;
}
}
/* 2. 弹性布局 */
.container{
display: flex;
flex-wrap: wrap;
}
.item{
flex: 1 1 300px; /* 基础 300px,可伸缩 */
}
/* 3. 栅格系统 */
.grid{
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 20px;
}
.col-4{
grid-column: span 4;
}
/* 4. 视口单位 */
.hero{
: ;
: ;
: (, , );
}
{
: ;
: auto;
}
{
: block;
}
{
media:
}
(max-width: ){
{
: column;
}
}
// 1. rem 定义
// rem = root em,相对于根元素 (html) 的字体大小
// 2. 原理
// 设置根元素字体大小
html {
font-size: 16px; // 默认
}
// 1rem = 16px
.box {
width: 10rem; // 160px
height: 5rem; // 80px
}
// 3. 动态 rem 适配
// 根据屏幕宽度动态设置根元素字体大小
(function flexible(){
function setRem(){
const width = document.documentElement.clientWidth // 设计稿宽度 750px,分成 7.5 份,1rem = 100px
const rem = width / 7.5
document.documentElement.style.fontSize = rem + 'px'
}
setRem()
window.addEventListener('resize', setRem)
})()
// 4. postcss-pxtorem 插件
// 在 webpack 中配置,自动将 px 转换为 rem
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 75, // 设计稿宽度/10
propList: ['*']
}
}
}
// 5. rem vs vw/vh
// rem:基于根元素,需要 JS 动态设置
// vw:基于视口宽度,纯 CSS,不需要 JS
/* 1. 使用 transform + 动画 */
.ball{
width: 50px;
height: 50px;
background: red;
border-radius: 50%;
animation: parabola 2s ease-out forwards;
}
@keyframes parabola{
0%{transform:translate(0, 0);}
100%{transform:translate(300px, 200px);}
}
/* 2. 更精确的抛物线(使用贝塞尔曲线) */
@keyframes parabola-bezier{
0%{transform:translate(0, 0);}
100%{transform:translate(300px, 200px);}
}
.ball-bezier{
animation: parabola-bezier 2s cubic-bezier(0.2, 0.8, 0.4, 1) forwards;
}
/* 3. 使用两个动画组合(水平匀速 + 竖直加速) */
.ball-combo{
animation: horizontal 2s linear forwards, vertical 2s ease-in forwards;
}
@keyframes horizontal{
to{transform:translateX(300px);}
}
vertical{
{:();}
}
// 1. Web Animation API
const element = document.querySelector('.ball')
const animation = element.animate([
{transform:'translate(0, 0)'},
{transform:'translate(300px, 200px)'}
],{
duration:2000,
easing:'cubic-bezier(0.2, 0.8, 0.4, 1)',
iterations:1
})
animation.onfinish = ()=>{
console.log('动画完成')
}
// 2. GSAP(GreenSock)
gsap.to('.ball',{
x:300,
y:200,
duration:2,
ease:'power2.out',
onUpdate:()=>{
// 每一帧回调
},
onComplete:()=>{
console.log('完成')
}
})
// 3. 使用 Canvas(适合复杂物理模拟)
class Ball{
constructor(x, y){
this.x = x
this.y = y
this.vx = 5
this. = -
. =
}
(){
. += .
. += .
. += .
(. > canvas. - ){
. = canvas. -
. *= -
}
}
(){
ctx.()
ctx.(., ., , , .*)
ctx. =
ctx.()
}
}
<motion.
animate={{:,:,:,:[,,]}}
transition={{:,:,:[,,],:}}
/>
// 这道题考察的是 JS 的包装对象和原型链
// 1. 背景知识
// JS 中,基本类型(number、string、boolean)有对应的包装对象
// 当访问基本类型的属性时,JS 会临时创建一个包装对象
let a = 5
console.log(a.toString()) // 临时创建 Number 对象
// 2. 解题思路
// 我们需要让 a.b = 100 这个赋值操作成功,同时不改变 a 本身的值
// 3. 解决方案:修改 Number 原型
Object.defineProperty(Number.prototype,'b',{
set(value){
console.log('设置 b 为', value)
// 这里可以什么都不做,或者返回什么
},
get(){
return 100 // 让 a.b 返回 100
}
})
let a = 5
// 现在执行 if(a.b =100 && a ===5){ console.log('true') }
// 执行过程
// 1. a.b = 100:访问 a.b,触发 getter 返回 100,赋值操作成功
// 2. 100 && a === 5:100 为真,继续判断 a === 5
// 3. a === 5 为真
// 4. 整个条件为真
// 4. 更完整的解法
Number.prototype.b = 100 // 直接设置属性
let a = 5
console.log(a.b) // 100(临时包装对象访问到原型上的 b)
// 现在条件成立
(a. = && a ===){
.()
}
// 1. Promise.all
// - 全部成功才成功
// - 一个失败就失败
const promises = [
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
]
try{
const [user, posts, comments] = await Promise.all(promises)
// 全部成功才能走到这里
}catch(error){
// 任何一个失败就会进入 catch
}
// 2. Promise.allSettled
// - 等待所有完成
// - 返回每个 promise 的状态和结果
const results = await Promise.allSettled(promises)
results.forEach((result, index)=>{
if(result.status ==='fulfilled'){
console.log(`请求${index}成功:`, result.value)
}else{
console.log(`请求${index}失败:`, result.reason)
}
})
// 3. 返回结果对比
// Promise.all: [userData, postsData, commentsData]
// Promise.allSettled: [
// {status:'fulfilled',value: userData},
// {status:'rejected',reason: error},
// {status:'fulfilled',value: commentsData}
// ]
// 4. 适用场景
// 1. 组件与实例的关系
// 每个组件在使用时都会创建一个实例
// 2. 不同情况
// 2.1 单次使用
// <MyComponent /> -> 创建一个实例
// 2.2 多次使用
// <div v-for="item in 3" :key="item">
// <MyComponent />
// </div>
// -> 创建 3 个独立实例
// 2.3 动态组件
// <component :is="currentComponent"/>
// -> 每次切换创建新实例或复用(取决于 keep-alive)
// 3. 实例标识
export default {
mounted(){
console.log(this._uid) // 每个实例有唯一 id
console.log(this===this.$root) // 判断是否是根实例
}
}
// 4. 单例组件特殊情况
// 通过 Vue.extend 创建的可复用构造函数
const ComponentClass = Vue.extend({
data(){
return{count:0}
}
})
// 每次 new 都会创建新实例
const instance1 = new ComponentClass()
const instance2 = new ComponentClass()
// 1. 路由切换时的组件生命周期
// 组件 A
export default {
data(){
return{
timer:null
}
},
mounted(){
this.timer = setInterval(()=>{
console.log('A 组件定时器执行')
},1000)
},
// 路由离开时会调用 beforeDestroy
beforeDestroy(){
// 需要手动清理定时器
if(this.timer){
clearInterval(this.timer)
console.log('定时器已清理')
}
},
destroyed(){
console.log('A 组件已销毁')
}
}
// 2. 如果不清理会发生什么?
// 组件 A 虽然销毁了,但定时器还在执行
// 造成内存泄漏,定时器回调中的 this 会报错
// 3. 最佳实践
export default {
mounted(){
// 方案 1:在 beforeDestroy 中清理
this.timer = setInterval(this.tick,1000)
},
beforeDestroy(){
clearInterval(.)
},
(){
timer = (.,)
.$once(,{
(timer)
})
},
(){
(.)
},
(){
. = (.,)
}
}
// 1. 副作用函数
// 副作用函数是指会对外部环境产生影响的函数
let activeEffect
function effect(fn){
activeEffect = fn
fn() // 执行时会触发依赖收集
activeEffect = null
}
// 2. 响应式原理
const targetMap = new WeakMap()
function track(target, key){
if(!activeEffect) return
let depsMap = targetMap.get(target)
if(!depsMap){
targetMap.set(target,(depsMap = new Map()))
}
let dep = depsMap.get(key)
if(!dep){
depsMap.set(key,(dep = new Set()))
}
dep.add(activeEffect)
}
function trigger(target, key){
const depsMap = targetMap.get(target)
if(!depsMap) return
const effects = depsMap.get(key)
effects && effects.forEach(fn=>fn())
}
// 3. 为什么使用 WeakMap?
// - WeakMap 的 key 是弱引用,不影响垃圾回收
// - 当 target 对象不再被使用时,可以被正常回收
// - 防止内存泄漏
// 4. 源码中的数据结构
// targetMap: WeakMap<target, Map<key, Set<effect>>>
(){
(target,{
(){
value = .(target, key, receiver)
(target, key)
value
},
(){
oldValue = target[key]
result = .(target, key, value, receiver)
(oldValue !== value){
(target, key)
}
result
}
})
}
// 1. nextTick 的作用
// 在下次 DOM 更新循环结束后执行回调
// 2. 实现原理
// Vue 的 nextTick 会根据环境选择不同的实现
// 优先使用微任务,降级使用宏任务
let callbacks = []
let pending = false
function flushCallbacks(){
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
copies.forEach(cb=>cb())
}
let timerFunc
// 优先级:Promise > MutationObserver > setImmediate > setTimeout
if(typeof Promise !=='undefined'){
// 微任务
timerFunc = ()=>{
Promise.resolve().then(flushCallbacks)
}
}else if(typeof MutationObserver !=='undefined'){
// 微任务
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode,{characterData:true})
timerFunc = ()=>{
counter = (counter + )%
textNode. = (counter)
}
} ( setImmediate !==){
timerFunc = {
(flushCallbacks)
}
}{
timerFunc = {
(flushCallbacks,)
}
}
(){
callbacks.({
(cb){
cb.(ctx)
}
})
(!pending){
pending =
()
}
}
. =
.$nextTick({
.()
})
// 1. Vue3 的浏览器兼容性
// Vue3 不支持 IE11
// 支持:Chrome、Firefox、Edge、Safari 等现代浏览器
// 2. 解决方案
// 2.1 使用 Vue2
// Vue2 支持 IE9+
// 2.2 引入 polyfill
// 在 index.html 中引入
// <!--[if IE]><script src="https://polyfill.io/v3/polyfill.min.js"></script><![endif]-->
// 2.3 配置 babel
module.exports = {
presets: [['@babel/preset-env',{
targets:{ie:'11'},
useBuiltIns:'usage',
corejs:3
}]]
}
// 2.4 动态降级
if(isIEBrowser()){
// 加载 Vue2 版本
import('./vue2-app.js')
}else{
// 加载 Vue3 版本
import('./vue3-app.js')
}
// 3. 需要支持的 ES6 特性
// - Promise: 需要 polyfill
// - Proxy: 无法 polyfill(Vue3 核心)
// - Map/Set: 可 polyfill
// - Symbol: 可 polyfill
| 类型 | 问题 | 考察点 |
|---|---|---|
| 项目深挖 | 组件封装、API 兼容性 | 工程实践能力 |
| CSS 基础 | 响应式、rem、抛物线 | 布局和动画能力 |
| JS 语言 | 包装对象、原型链 | 语言本质理解 |
| Promise | all/allSettled | 异步编程能力 |
| Vue 原理 | 响应式、nextTick、WeakMap | 源码理解深度 |
| 知识点 | 核心要点 |
|---|---|
| 组件封装 | 属性透传、默认值、版本兼容 |
| 响应式设计 | 媒体查询、弹性布局、栅格、视口单位 |
| rem 原理 | 根元素字体、动态适配、px 转 rem |
| 抛物线 | 组合动画、贝塞尔曲线、JS 计算 |
| 包装对象 | 基本类型属性访问、原型链修改 |
| Promise | all(全成功)、allSettled(全结果) |
| Vue 实例 | 每个使用创建一个实例、_uid 标识 |
| 组件销毁 | beforeDestroy 清理定时器、hook 事件 |
| 响应式原理 | WeakMap、Map、Set 存储依赖 |
| nextTick | 微任务优先、降级策略 |
| 兼容性 | Vue3 不支持 IE、polyfill、动态降级 |
这种面试不是为了难倒你,而是为了激发你对技术的深度思考。即使有压力,也要感谢这样的面试经历,因为它让你知道自己还能往哪个方向成长。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online