🗣️面试官: 那些常见的前端面试场景问题

392 阅读12分钟

1. 页面白屏如何排查?

第一步:快速分类(30秒) "页面白屏主要有五种原因:JavaScript执行错误、资源加载失败、CSS样式问题、接口异常和浏览器兼容性。其中JavaScript错误最常见,特别是SPA应用中的未捕获异常。"

第二步:排查方法(1分钟) "我的排查步骤是:首先查看Console面板的错误信息,这能快速定位JS异常;然后检查Network面板确认资源加载状态;接着用Elements面板验证DOM和样式;移动端问题会用vConsole或真机调试。生产环境结合Sentry等监控系统分析。"

第三步:预防措施(30秒) "预防方面建立错误边界、资源容错机制、统一接口异常处理、兼容性检测,同时搭建监控告警体系。"

一、基础检测流程

1. 控制台检查(Console)

// 主动捕获全局错误(放在入口文件最前面)
window.addEventListener('error', function(event) {
  console.error('全局捕获:', event.error);
  // 可上报到监控系统
});

// 检查console是否有以下类型错误:
// - SyntaxError (语法错误)
// - TypeError (类型错误)
// - ReferenceError (引用错误)
// - 404资源加载失败

2. 网络请求检查(Network)

  • 关键指标

    • HTML文档状态码(200/304/404/500)
    • JS/CSS资源加载状态
    • 接口请求是否阻塞渲染
  • 检测示例

// 检查关键资源是否加载完成
const resourceCheck = () => {
  const entries = performance.getEntriesByType('resource');
  const criticalResources = entries.filter(entry => 
    entry.initiatorType === 'script' || 
    entry.initiatorType === 'css'
  );
  
  criticalResources.forEach(res => {
    if(res.responseStatus >= 400) {
      console.error(`资源加载失败: ${res.name}`, res);
    }
  });
};
window.addEventListener('load', resourceCheck);

二、深度检测方法

1. DOM渲染检测

// 检测DOM树是否正常构建
function checkDOMReady() {
  return new Promise((resolve) => {
    const check = () => {
      if(document.body && document.body.children.length > 0) {
        resolve(true);
      } else {
        setTimeout(check, 50);
      }
    };
    check();
  });
}

// 使用示例
checkDOMReady().then((isReady) => {
  if(!isReady) {
    console.error('DOM渲染超时');
    // 上报白屏信息
  }
});

2. 框架特定检测

Vue应用检测:
// 在main.js中添加
new Vue({
  render: h => h(App),
  errorCaptured(err, vm, info) {
    console.error('Vue组件错误:', err, info);
    // 可上报错误
    return false; // 阻止错误继续向上传播
  }
}).$mount('#app');

// 检查根组件挂载
if(!document.querySelector('#app').__vue__) {
  console.error('Vue根实例挂载失败');
}
React应用检测:
// Error Boundary组件
class ErrorBoundary extends React.Component {
  componentDidCatch(error, info) {
    console.error('React组件错误:', error, info);
    // 上报错误
  }
  
  render() {
    return this.props.children; 
  }
}

// 检查React根组件
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);

三、性能相关检测

1. 长任务检测

// 检测阻塞渲染的长时间任务
const observer = new PerformanceObserver((list) => {
  for(const entry of list.getEntries()) {
    if(entry.duration > 50) { // 超过50ms的任务
      console.warn('长任务影响渲染:', entry);
    }
  }
});
observer.observe({entryTypes: ["longtask"]});

2. 关键渲染路径监控

// 使用Performance API监控关键时间点
const perfData = window.performance.timing;
const metrics = {
  domReady: perfData.domComplete - perfData.domLoading,
  loadTime: perfData.loadEventEnd - perfData.navigationStart
};

if(metrics.domReady > 3000) {
  console.error('DOM解析时间过长:', metrics);
}

四、自动化检测方案

1. Puppeteer检测脚本

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // 监听控制台错误
  page.on('console', msg => {
    if(msg.type() === 'error') {
      console.log('页面错误:', msg.text());
    }
  });
  
  // 设置超时检测
  await Promise.race([
    page.goto('https://your-site.com'),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('页面加载超时')), 5000)
    )
  ]);
  
  // 检查可见内容
  const content = await page.evaluate(() => {
    return {
      bodyText: document.body.innerText,
      childCount: document.body.children.length
    };
  });
  
  if(content.childCount === 0 || content.bodyText.length < 10) {
    console.error('检测到白屏现象');
  }
  
  await browser.close();
})();

2. 真实用户监控(RUM)

// 使用浏览器的MutationObserver监控DOM变化
const observer = new MutationObserver((mutations) => {
  if(!document.querySelector('#app')?.innerHTML) {
    // 上报白屏事件
    beacon.send('white-screen', {
      url: location.href,
      ua: navigator.userAgent
    });
  }
});

observer.observe(document.body, {
  childList: true,
  subtree: true
});

五、常见白屏场景示例

  1. 资源加载失败

    <!-- 错误的资源路径 -->
    <script src="/wrong-path/app.js"></script>
    
  2. 语法错误

    // 缺少括号导致整个脚本不执行
    function test() {
      console.log('hello'
    }
    
  3. 框架初始化失败

    // Vue示例 - 挂载元素不存在
    new Vue({el: '#not-exist'});
    
  4. CSS阻塞

    <!-- 不正确的CSS引入阻塞渲染 -->
    <link rel="stylesheet" href="nonexist.css">
    
  5. 第三方库冲突

    // 两个库都修改了Array原型
    libraryA.modifyPrototype();
    libraryB.modifyPrototype(); // 冲突导致错误 ```
    

2.前端埋点

一、页面生命周期埋点

1. 页面加载阶段

// 记录页面开始加载时间
const pageStartTime = Date.now();

// 监听页面加载完成
window.addEventListener('load', () => {
  const loadTime = Date.now() - pageStartTime;
  track('page_load', {
    load_time: loadTime,
    referrer: document.referrer,
    resource_status: checkResources()
  });
});

// 检查关键资源加载状态
function checkResources() {
  return performance.getEntriesByType('resource').map(res => ({
    name: res.name,
    type: res.initiatorType,
    duration: res.duration.toFixed(2)
  }));
}

2. 用户交互阶段

// 点击事件埋点(支持事件委托)
document.body.addEventListener('click', (e) => {
  const target = e.target.closest('[data-track]');
  if(target) {
    track('element_click', {
      element_id: target.id,
      track_type: target.dataset.track,
      position: `${e.clientX},${e.clientY}`
    });
  }
});

// 滚动深度记录
let maxScroll = 0;
window.addEventListener('scroll', _.throttle(() => {
  const currentScroll = window.scrollY / document.body.scrollHeight;
  if(currentScroll > maxScroll) {
    maxScroll = currentScroll;
    track('scroll_depth', { depth: Math.round(maxScroll * 100) });
  }
}, 1000));

3. 页面停留时长计算

let activeStart = Date.now();
let inactiveTime = 0;

// 用户活跃状态检测
document.addEventListener('mousemove', resetActiveTimer);
document.addEventListener('keydown', resetActiveTimer);

function resetActiveTimer() {
  if(inactiveTime > 0) {
    track('user_inactive', { duration: inactiveTime });
    inactiveTime = 0;
  }
  activeStart = Date.now();
}

// 每10秒检测一次活跃状态
setInterval(() => {
  if(Date.now() - activeStart > 15000) { // 15秒无操作视为不活跃
    inactiveTime += 10000;
  } else {
    track('user_active', { duration: 10000 });
  }
}, 10000);

二、特殊场景处理

1. 页面隐藏/显示

// 页面可见性变化监听
document.addEventListener('visibilitychange', () => {
  if(document.hidden) {
    track('page_hide', { 
      stay_time: Date.now() - pageStartTime,
      scroll_depth: maxScroll
    });
  } else {
    track('page_show');
  }
});

2. 页面关闭前上报

// 确保页面关闭前数据上报
window.addEventListener('beforeunload', () => {
  const totalStay = Date.now() - pageStartTime;
  navigator.sendBeacon('/api/track', JSON.stringify({
    event: 'page_close',
    active_time: totalStay - inactiveTime,
    scroll_depth: maxScroll
  }));
});

三、数据上报优化方案

1. 批量上报机制

let eventQueue = [];
const MAX_QUEUE = 5;
const FLUSH_INTERVAL = 3000;

function addToQueue(event) {
  eventQueue.push(event);
  if(eventQueue.length >= MAX_QUEUE) {
    flushQueue();
  }
}

function flushQueue() {
  if(eventQueue.length === 0) return;
  
  const batchData = { batch: eventQueue };
  navigator.sendBeacon('/api/batch', JSON.stringify(batchData));
  eventQueue = [];
}

// 定时刷新队列
setInterval(flushQueue, FLUSH_INTERVAL);

2. 关键指标计算

// 计算FMP(首次有效绘制)
new PerformanceObserver((entryList) => {
  const [entry] = entryList.getEntriesByName('first-contentful-paint');
  track('fmp', { value: entry.startTime.toFixed(2) });
}).observe({type: 'paint', buffered: true});

// 计算LCP(最大内容绘制)
new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  const lastEntry = entries[entries.length - 1];
  track('lcp', { value: lastEntry.startTime.toFixed(2) });
}).observe({type: 'largest-contentful-paint', buffered: true});

四、面试回答精简版

"我们实现全链路埋点主要分三个阶段:

  1. 加载阶段

    • performance API采集DNS/TTFB等指标
    • 监听load事件记录完整加载时间
    • 检查关键资源状态(如图片/脚本)
  2. 交互阶段

    • 事件委托监听全局点击(带data-track属性)
    • 节流处理滚动事件计算最大深度
    • 通过mousemove/keydown检测活跃状态
  3. 离开阶段

    • visibilitychange处理页面切换
    • beforeunload+sendBeacon确保关闭前上报
    • 计算总停留时长和有效活跃时间

3.那为什么大家都使用请求 GIF 图片的方式上报埋点数据呢?

  • 防止跨域问题:前端监控的请求常常会遇到跨域问题,这可能会影响监控的准确性和可用性。然而,图片的src属性并不会跨域,因此使用GIF图片作为埋点可以正常发起请求,从而有效避免跨域问题。

  • 防止阻塞页面加载:在创建资源节点后,通常只有当对象注入到浏览器的DOM树后,浏览器才会实际发送资源请求。但反复操作DOM会引发性能问题,且载入js/css资源会阻塞页面渲染,从而影响用户体验。与此不同,构造图片打点不需要插入DOM,只要在js中new出Image对象就能发起请求,这样就不会有阻塞问题。即使在没有js的浏览器环境中,也能通过img标签正常打点,这是其他类型的资源请求所做不到的。

  • 体积小,节约流量:相比其他图片格式(如BMP和PNG),GIF图片具有更小的体积。例如,最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF只需要43个字节。因此,使用GIF作为埋点可以显著节约流量,提高数据传输效率。

  • 浏览器支持性好:所有浏览器都支持Image对象,即使不支持XMLHttpRequest对象也一样。这意味着使用GIF进行埋点上报可以在各种浏览器环境中稳定运行。

  • 记录错误的过程很少出错:某些情况下,如Ajax通信过程中页面跳转,请求可能会被取消。但使用图片进行埋点上报则不会遇到这个问题,特别是在记录离开页面打点行为的时候会很有用。

4. 前端SEO怎样做

前端 SEO(Search Engine Optimization) 是指通过优化网站的前端代码、结构和内容,使搜索引擎能够更好地理解和收录网站,从而提升在搜索结果中的自然排名。以下是前端ESO优化手段

  • 设置TDK:TDK 是 Title(标题)、Description(描述)和 Keywords(关键词)的缩写,TDK 是一个网站 SEO 的核心。 Keywords:逗号分隔的关键词列表, description:网站描述,搜索引擎会把这个描述显示在搜索结果中, format-detection:格式检测,比如禁止识别电话,邮箱等, Robots:用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引, theme-color:网站主题色,

<title>2025 最新 Python 全栈开发教程 - 从入门到精通(含实战项目)</title>
<meta name="description" content="分享 Python 全栈开发完整学习路线,涵盖 Python 基础、Web 框架(Django/Flask)、数据分析、爬虫实战,附 10+ 企业级项目源码,帮助零基础小白 6 个月入行高薪岗位。">
<meta name="keywords" content="Python 教程,Python 全栈开发,Python 入门,Web 开发,Django,Flask,数据分析,Python 爬虫">

  • HTML语义化:h1、h2、h3、h4、h5 和 h6;强调标签:strong、em;图片标签: 使用时加上 alt 属性对图片进行描述,可以帮助蜘蛛快速理解图片的具体内容;段落标签: 页面中段落文字可使用 p 标签替代 列表标签:ul、ol、li,搜索引擎能够通过这些标签更好地理解信息的层次结构和关联性,从而更准确地评估网页的内容和价值;布局标签:header、nav、article、section、aside、footer,根据页面的区域模块划分选择对应布局标签

  • HTML lang:设置 HTML 标签的 lang 属性主要用于指定网页内容的语言。这个属性对于搜索引擎优化(SEO)是有益的,因为它帮助搜索引擎了解页面内容的语言,从而能够更准确地将页面呈现给搜索特定语言内容的用户。

  • 服务端渲染:由于爬虫只能抓取到网页的静态源代码,而无法执行其中的 js 脚本,当网站采用 Vue 构建的单页面应用时,实际上是采用客户端渲染的方式,页面上的大部分 DOM 元素是在客户端通过JavaScript 动态生成的。客户端渲染的过程是需要时间的,爬虫不会等你渲染好,因此爬虫能够直接抓取和分析的内容会大幅减少。

  • Sitemap(站点地图) 是 SEO 的“基础设施”,它不直接提升排名,而是通过确保搜索引擎能够发现和高效抓取你的所有重要页面,为后续所有SEO工作奠定基础。是一种 xml 文件

  • robots文件 蜘蛛在访问一个网站时,会首先会检查该网站的根域下是否有一个叫做 robots.txt 的纯文本文件,这个文件用于指定 spider 在您网站上的抓取范围,掘金的robots文juejin.cn/robots.txt

  • 内链和外链 内链决定搜索引擎如何“看待”你的网站内部,外链决定搜索引擎如何“看待”你的网站在整个互联网中的位置,a标签属性 rel="nofollow" ,nofollow:会告诉搜索引擎忽略这个链接,阻止搜索引擎对该页面进行追踪,从而避免权重分散,external:会告诉搜索引擎这是一个外部链接,非本站的链接

  • 结构化数据 结构化数据是 HTML 中标记数据的方式,有助于搜索引擎理解网站内容并引导更高质量的搜索结果,搜索引擎可以知道您的页面包含哪些信息以及如何将其呈现给用户

  • 使用HTTPS 使用HTTPS 谷歌曾发公告表示,使用 HTTPS 是搜索引擎排名的一项参考因素,HTTPS 站点相比于 HTTP 站点,能获得更好的排名。

  • 网站重定向 301/302重定向是 SEO优化中一种重要的自动转向技术。301重定向是当搜索引擎向网站服务器发出访问请求时,服务返回的HTTP数据流中头信息(header)部分状态码的一种,表示本网址永久性转移到另一个地址。302重定向则表示暂时转移。301重定向与上一点所说的网址规范化有着类似的作用,与此同时,它还具有以下作用:集中域名权重,301 网址跳转其实是对域名权重进行转移,比如 www.juejin.cn 重定向到 juejin.cn,其实是把 www.juejin.cn 的权重转移到了 juejin.cn,从而增加 juejin.cn 域名的权重;避免重复收录;网页PR(PageRank-网页级别)是用来评估一个网站页面相对于网站其他页面重要性的一个算法,301定向跳转有利于网站PR的传递;优化用户体验,网址规范化可以让用户更好地记住我们的网站,可以将域名统一重定向到某一个域名,增加网站的记忆度,获取更好的用户体验。

  • 各搜索引擎提交站点收录 除了 robots.txt + sitemap.xml 方式增加网址被收录的可能性外,还可以在各搜索引擎站长平台手动提交网址,以缩短爬虫发现网站链接时间,加快爬虫抓取速度

  • 预渲染prerender-spa-plugin 如果你只想改善部分页面的SEO,可以不采用 SSR 的解决方案,毕竟无论是next.js,还是nuxt.js,都是有一定学习成本的。那么你可以使用 prerender-spa-plugin 等插件来实现预渲染页面,在构建时就针对特定的路有生成静态的 html 文件。

  • Open Graph 协议标签 Open Graph 协议标签通过 OG Tags (OG 标签)实现的,它属于 Meta 标签的一种,可以用来标识网页类型和元素,让分享到社交网络的内容可以被有效的抓取,还可以控制分享的网站卡片呈现我们想要显示的内容。

只要看到以 og: 为前缀的 Meta 标签就可以判断该网页支持 OG 标签了,如下:

<meta property="og:title" content="设置Open Graph 标签!">
<meta property="og:site_name" content="前端">
<meta property="og:type" content="article">
<meta property="og:description" content="Open Graph Protocol(开放图谱协议),简称 OG 协议或 OGP。">

设置og协议前后的网站分享卡片对比:

生成 Open Graph 对比图(1).png 生成 Open Graph 对比图.png