跳到主要内容MetaAPP 前端一面面经深度解析 | 极客日志JavaScript大前端
MetaAPP 前端一面面经深度解析
综述由AI生成MetaAPP 前端一面涉及项目封装、CSS 响应式、动画实现、JS 语言特性(原型链)、Promise 及 Vue 原理(响应式、nextTick)。文章通过源码级解析和代码示例,详细阐述了组件 API 兼容性设计、rem 适配原理、抛物线动画方案、包装对象属性访问机制以及 Vue3 依赖收集过程。重点强调了内存泄漏处理、低版本浏览器兼容策略及异步编程最佳实践,适合准备中大厂前端面试的开发者参考。
极客工坊29 浏览 MetaAPP 前端一面面经深度解析
面试概况
| 维度 | 特征 |
|---|
| 公司定位 | MetaAPP - 游戏业务(H5/官网) |
| 面试风格 | 项目深潜型 + 原理追问型 + 边界拓展型 |
| 难度评级 | ⭐⭐⭐(三星,从项目到原理到边界问题) |
| 考察重心 | 组件封装能力、响应式布局、动画实现、JS 语言特性、Vue 源码原理 |
| 特殊之处 | 问题很有启发性,尤其是 number 强转对象的题目考察了对 JS 语言本质的理解 |
面试问题回顾
- 项目相关
- 自我介绍
- 实习拷打 - 看你组件封装那么多,讲一个最牛逼的吧
- 怎么确保你封装组件之后的 api 兼容性?
- CSS/动画
4. css 如何做响应式设计移动端 PC 段 iPad 端?
5. rem 的原理是什么
6. 用 css 实现一个抛物线
7. 复杂运动怎么办?
- JS 进阶
8. let a=5 经过一系列操作之后,if(a.b=100 && a=5) 是 true,这一系列操作是什么?
9. promise.all 跟 allseted 区别
- Vue 原理
10. vue 组件都是一个实例吗?
11. a 组件定时器跳转到 b,a 的会销毁吗?
12. vue3 响应式
13. 副作用函数知道吗?数据改变原理是什么?在源码的数据格式是什么?
14. 为什么使用 weakmap
15. nexttick 是微任务吗?
16. 低版本浏览器还能用吗?
逐题深度解析
组件封装与 API 兼容性
发散性问题,需要平时多做积累,这里我们只复盘一下 API 兼容性设计的内容
<Modal v-model="visible"
title="提示"
width="500"
:before-close="handleClose"
@on-ok="handleOk"
@on-cancel="handleCancel">
<div>内容</div>
</Modal>
<Button = = @= =>
按钮
: {
: {
: ,
: ,
: [,,].(value)
},
: {
: ,
:
}
}
: {
: {},
: {}
},
: [, ]
type
"primary"
:loading
"loading"
click
"handleClick"
custom-prop
"value"
</Button>
<button v-bind="$attrs" class="buttonClass">
<slot />
</button>
props
size
type
String
default
'medium'
validator
(value) =>
'small'
'medium'
'large'
includes
type
type
String
default
'default'
props
modelValue
value
emits
'update:modelValue'
'input'
响应式设计
@media(max-width: 767px){
.container{width: 100%;padding: 10px;}
}
@media(min-width: 768px)and(max-width: 1024px){
.container{width: 750px;margin: 0 auto;}
}
@media(min-width: 1025px){
.container{width: 1200px;margin: 0 auto;}
}
.container{display: flex;flex-wrap: wrap;}
.item{flex: 1 1 300px;}
.grid{display: grid;grid-template-columns:repeat(12, 1fr);gap: 20px;}
.col-4{grid-column: span 4;}
.hero{height: 100vh;width: 100vw;font-size:clamp(16px, 4vw, 24px);}
img{max-width: 100%;height: auto;}
picture{display: block;}
source{media:"(max-width: 767px)"}
@container(max-width: 500px){
.card{flex-direction: column;}
}
rem 原理
html { font-size: 16px; }
.box {width: 10rem;height: 5rem;}
(function flexible(){
function setRem(){
const width = document.documentElement.clientWidth;
const rem = width / 7.5;
document.documentElement.style.fontSize = rem + 'px';
}
setRem();
window.addEventListener('resize', setRem);
})();
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 75,
propList: ['*']
}
}
};
CSS 实现抛物线
.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);}
}
@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;}
.ball-combo{animation: horizontal 2s linear forwards, vertical 2s ease-in forwards;}
@keyframes horizontal{to{transform:translateX(300px);}}
@keyframes vertical{to{transform:translateY(200px);}}
复杂运动处理
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('动画完成'); };
gsap.to('.ball',{x:300,y:200,duration:2,ease:'power2.out',onUpdate:()=>{},onComplete:()=>{ console.log('完成'); }});
class Ball{
constructor(x, y){this.x = x; this.y = y; this.vx = 5; this.vy = -10; this.gravity = 0.5;}
update(){
this.x += this.vx; this.y += this.vy; this.vy += this.gravity;
if(this.y > canvas.height - 50){this.y = canvas.height - 50; this.vy *= -0.7;}
}
draw(ctx){ ctx.beginPath(); ctx.arc(this.x,this.y,25,0, Math.PI*2); ctx.fillStyle ='red'; ctx.fill(); }
}
JS 语言特性(包装对象)
let a = 5;
console.log(a.toString());
Object.defineProperty(Number.prototype,'b',{
set(value){ console.log('设置 b 为', value); },
get(){ return 100; }
});
let a = 5;
if(a.b = 100 && a === 5){ console.log('true'); }
Promise.all vs allSettled
const promises = [fetch('/api/user'), fetch('/api/posts'), fetch('/api/comments')];
try{
const [user, posts, comments] = await Promise.all(promises);
}catch(error){ }
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); }
});
Vue 组件实例
export default {
mounted(){
console.log(this._uid);
console.log(this===this.$root);
}
}
组件销毁与定时器
export default {
data(){ return{timer:null}},
mounted(){
this.timer = setInterval(()=>{ console.log('A 组件定时器执行'); },1000);
},
beforeDestroy(){
if(this.timer){ clearInterval(this.timer); console.log('定时器已清理'); }
},
destroyed(){ console.log('A 组件已销毁'); }
}
export default {
mounted(){ this.timer = setInterval(this.tick,1000); },
beforeDestroy(){ clearInterval(this.timer); },
deactivated(){ clearInterval(this.timer); },
activated(){ this.timer = setInterval(this.tick,1000); }
}
Vue3 响应式原理
let activeEffect;
function effect(fn){
activeEffect = fn;
fn();
activeEffect = null;
}
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());
}
function reactive(target){
return new Proxy(target,{
get(target, key, receiver){
const value = Reflect.get(target, key, receiver);
track(target, key);
return value;
},
set(target, key, value, receiver){
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if(oldValue !== value){ trigger(target, key); }
return result;
}
});
}
nextTick 机制
let callbacks = [];
let pending = false;
function flushCallbacks(){
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
copies.forEach(cb=>cb());
}
let timerFunc;
if(typeof Promise !=='undefined'){
timerFunc=()=>{ Promise.resolve().then(flushCallbacks); }
}else if(typeof MutationObserver !=='undefined'){
timerFunc=()=>{ }
}else if(typeof setImmediate !=='undefined'){
timerFunc=()=>{ setImmediate(flushCallbacks); }
}else{
timerFunc=()=>{ setTimeout(flushCallbacks,0); }
}
export function nextTick(cb, ctx){
callbacks.push(()=>{ if(cb){ cb.call(ctx); }});
if(!pending){ pending = true; timerFunc(); }
}
低版本浏览器兼容
module.exports = {
presets: [['@babel/preset-env',{targets:{ie:'11'},useBuiltIns:'usage',corejs:3}]]
};
if(isIEBrowser()){
import('./vue2-app.js');
}else{
import('./vue3-app.js');
}
面试复盘
| 类型 | 问题 | 考察点 |
|---|
| 项目深挖 | 组件封装、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、动态降级 |
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online