菜鸟科技网

前端校招题都考什么?

HTML & CSS 基础

这部分主要考察你对页面结构和样式的理解,是前端工程师的基石。 1:HTML 语义化

问题: 什么是HTML语义化?它有什么好处?

考察点: 对HTML核心概念的理解。

参考答案: HTML语义化就是使用恰当的HTML标签来标记内容,例如使用 <header><nav><main><article><section><footer> 等标签来构建页面结构。

好处:

  1. SEO优化: 搜索爬虫能更好地理解页面结构和内容,从而提高网站在搜索引擎中的排名。
  2. 可读性高: 代码结构清晰,便于开发者阅读和维护。
  3. 可访问性: 屏幕阅读器等辅助技术可以根据语义标签更好地为残障人士朗读页面内容。
  4. 利于SEO和设备兼容: 便于搜索引擎抓取和索引,也有利于在不同设备(如手机、屏幕阅读器)上正确解析。

2:盒模型

问题: 请解释一下标准的盒模型(W3C盒模型)和IE盒模型(怪异盒模型)的区别,CSS中如何设置使用哪种盒模型?

考察点: CSS核心布局概念。

参考答案:

  1. 区别:

    • 标准盒模型 (W3C): 元素的宽度 = widthheight同理。paddingborder 都在 width 之外
    • IE盒模型 (怪异盒模型): 元素的宽度 = width + padding + borderheight同理。width 已经包含了 paddingborder
  2. 设置方式: 通过 box-sizing CSS属性来控制。

    • box-sizing: content-box; (默认值) -> 使用标准盒模型。
    • box-sizing: border-box; -> 使用IE盒模型(也称为“怪异盒模型”)。

最佳实践: 在现代前端开发中,我们通常会在全局CSS中重置 box-sizingborder-box,这样可以避免在计算元素大小时反复加减 paddingborder,使布局更直观。

*, *::before, *::after {
  box-sizing: border-box;
}

3:Flexbox 布局

问题: 如何使用Flexbox实现一个三列等宽布局,并且它们之间有10px的间距?当容器宽度小于某个值时,三列变成一列?

考察点: 现代CSS布局能力。

参考答案: 这是一个非常经典的Flexbox应用场景。

<style>
  .container {
    display: flex; /* 启用Flexbox布局 */
    flex-wrap: wrap; /* 允许项目换行,是实现响应式的关键 */
    gap: 10px; /* 直接设置项目之间的间距,非常方便 */
  }
  .item {
    flex: 1; /* 每个项目占据可用空间的三分之一(因为有三项) */
    min-width: 200px; /* 设置一个最小宽度,当容器宽度太小时,项目会换行 */
    background-color: lightblue;
    text-align: center;
    padding: 20px;
  }
</style>
<div class="container">
  <div class="item">列 1</div>
  <div class="item">列 2</div>
  <div class="item">列 3</div>
</div>

解析:

  • display: flex; 将容器变为Flex容器。
  • flex-wrap: wrap; 允许子项在空间不足时换行。
  • gap: 10px; 是设置Flex项目之间间距的现代标准方法,比 margin 更简单。
  • flex: 1;flex-grow: 1; flex-shrink: 1; flex-basis: 0%; 的简写,它让所有子项平分容器的宽度。
  • min-width: 200px; 是实现响应式的关键,当容器宽度小于 3 * 200px + 2 * 10px = 620px 时,单个项目的宽度会达到其最小值,导致无法在一行放下,从而触发 flex-wrap: wrap;,项目换行显示,形成单列布局。

JavaScript 基础

这是面试的重中之重,会深入考察JavaScript的底层原理。 1:作用域与闭包

问题: 什么是闭包?请写一个闭包的例子,并说明它的应用场景。

考察点: JavaScript核心概念,面试高频考点。

参考答案: 闭包是指一个函数能够访问并记住其词法作用域,即使该函数在其词法作用域之外执行,就是内部函数可以访问外部函数的变量。

例子:

function outer(x) {
  // 外部函数的变量
  const y = 10;
  // 内部函数
  function inner(z) {
    // inner函数可以访问outer函数的变量x和自己的变量z
    console.log(x + y + z);
  }
  // 返回内部函数
  return inner;
}
// 创建闭包
const myClosure = outer(5); // 此时x=5被记住
// 调用闭包
myClosure(3); // 输出 18 (5 + 10 + 3)

应用场景:

  1. 数据私有化: 创建私有变量,避免全局污染。
    function createCounter() {
      let count = 0; // count是私有变量,外部无法直接访问
      return {
        increment: function() {
          count++;
          console.log(count);
        },
        decrement: function() {
          count--;
          console.log(count);
        }
      };
    }
    const counter = createCounter();
    counter.increment(); // 1
    counter.increment(); // 2
    // count 无法从外部访问和修改
  2. 函数柯里化: 创建一个已经部分参数化的函数。
  3. 防抖和节流: 在事件处理中,使用闭包来保存定时器ID,实现防抖和节流逻辑。

2:异步编程

问题: 分别用 setTimeoutPromiseasync/await 实现一个“等待1秒后打印 'Hello World'”的功能,并解释 Promise 的三种状态。

考察点: JavaScript异步演进和核心API。

参考答案:

  1. setTimeout

    console.log('开始');
    setTimeout(() => {
      console.log('Hello World');
    }, 1000);
    console.log('结束');
  2. Promise

    console.log('开始');
    const promise = new Promise((resolve) => {
      setTimeout(() => {
        resolve('Hello World');
      }, 1000);
    });
    promise.then((message) => {
      console.log(message);
    });
    console.log('结束');
  3. async/await

    console.log('开始');
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    async function printMessage() {
      await delay(1000); // 暂停1秒
      console.log('Hello World');
    }
    printMessage();
    console.log('结束');

Promise 的三种状态:

  1. Pending (进行中): 初始状态,既没有被成功,也没有被失败。
  2. Fulfilled (已成功): 意味着操作成功完成。
  3. Rejected (已失败): 意味着操作失败。

Promise 对象从 Pending 状态变为 FulfilledRejected 状态后,状态就固定了,不会再改变。


3:this 指向

问题: 请说出以下代码中 console.log(this.a) 的输出结果,并解释原因。

function foo() {
  console.log(this.a);
}
const obj = {
  a: 2,
  foo: foo
};
const bar = obj.foo;
obj.foo(); // 输出?
bar();     // 输出?

考察点: this 的绑定规则。

参考答案:

  • obj.foo(); 输出:2

    • 原因: 这里应用的是隐式绑定,当函数被一个对象“拥有”或“包含”时,通过对象属性引用来调用函数,this 指向该对象。obj.foo() 的调用方式,this 指向 objthis.a obj.a,值为2。
  • bar(); 输出:undefined

    • 原因: 这里应用的是默认绑定const bar = obj.foo; 这行代码只是将 foo 函数的引用赋值给了 barbar 本身只是一个指向函数的变量,它不包含任何上下文信息。bar() 的调用等同于 foo() 的直接调用,在非严格模式下,当函数独立调用时,this 指向全局对象(在浏览器中是 window),而 window 对象上并没有 a 属性,this.aundefined

框架与工程化

这部分考察你是否具备现代前端开发的能力。 1:虚拟 DOM (Virtual DOM)

问题: 什么是虚拟DOM?它的工作原理是什么?使用它有什么优缺点?

考察点: 对 React/Vue 等现代框架核心思想的理解。

参考答案: 虚拟DOM是一个轻量级的JavaScript对象,它是真实DOM的抽象表示,它包含了真实DOM的结构和属性信息,但并不直接操作浏览器DOM。

工作原理:

  1. 创建: 当状态或数据发生变化时,框架会创建一个新的虚拟DOM树(新的JS对象)。
  2. 对比: 新的虚拟DOM树与旧的虚拟DOM树进行对比,这个过程称为 Reconciliation(协调)Diffing(差异比对),通过高效的算法(如深度优先遍历),找出两个树之间最小的差异。
  3. 更新: 将计算出的最小差异,以批处理的方式,一次性地更新到真实的浏览器DOM上。

优点:

  1. 性能提升: 通过批处理和最小化DOM操作,减少了昂贵的真实DOM操作次数,提高了页面渲染性能。
  2. 跨平台: 虚拟DOM是纯JavaScript对象,可以渲染到不同的平台(如Web、iOS、Android),是框架实现跨平台(如React Native)的基础。
  3. 编程模型简单: 开发者只需关心状态变化,而不需要手动操作DOM,降低了心智负担。

缺点:

  1. 内存消耗: 需要在内存中维护两棵虚拟DOM树,会占用额外的内存。
  2. 首次渲染可能较慢: 需要将虚拟DOM转换为真实DOM,这个过程比直接操作真实DOM要慢。

2:React Hooks

问题: useStateuseEffect 是做什么的?请描述一下 useEffect 的依赖数组 [], [dep], 不填 分别有什么作用?

考察点: React Hooks 的实际应用和理解。

参考答案:

  • useState: 用于在函数组件中添加状态,它返回一个状态值和一个更新该状态的函数。

    const [count, setCount] = useState(0);
  • useEffect: 用于在函数组件中执行副作用操作(如数据获取、订阅、手动修改DOM等)。

useEffect 依赖数组的作用: useEffect 的第二个参数是一个依赖数组,它决定了副作用函数的执行时机。

  1. [] (空数组):

    • 作用: 副作用函数只会在组件首次渲染后执行一次,类似于 componentDidMount
    • 应用场景: 适合用于初始化操作,如添加全局事件监听器、发起初始数据请求等。
  2. [dep] (有依赖项):

    • 作用: 副作用函数会在组件首次渲染后执行,并且在依赖数组中的任何一个依赖项发生变化时再次执行
    • 应用场景: 最常见的用法,当副作用依赖于某个状态或props时,使用它来确保在依赖变化时更新逻辑,根据用户ID获取用户详情。
  3. 不填写依赖数组:

    • 作用: 副作用函数会在每次组件渲染后都执行
    • 应用场景: 极少使用,这可能会导致性能问题,因为它在每次渲染时都会运行,包括由父组件引起的无关渲染,通常用于调试或一些必须与最新渲染同步的特殊场景。

3:前端路由

问题: 什么是前端路由?它与后端路由有什么区别?常见的实现方式有哪些?

考察点: SPA(单页应用)的核心概念。

参考答案: 前端路由是一种在不重新加载整个页面的情况下,更新浏览器URL,并渲染不同视图内容的技术,它是构建单页应用的基础。

与后端路由的区别: | 特性 | 前端路由 | 后端路由 | | :--- | :--- | :--- | | 请求方式 | 浏览器URL变化,不发送HTTP请求到服务器 | 每次URL变化,都会向服务器发送新的HTTP请求 | | 页面跳转 | 只更新页面部分内容 | 整个页面重新加载 | | 用户体验 | 流畅,快速 | 有白屏/加载闪烁感 | | SEO | 相对困难(需服务端渲染SSR支持) | 相对容易 | | 实现 | 通过 History APIhash 变更监听 | 服务器根据URL路径返回不同的HTML |

常见的实现方式:

  1. Hash 模式:

    • 原理:监听URL中 后面的内容变化。
    • 优点:兼容性极好,兼容所有浏览器,因为 后面的内容不会被发送给服务器。
    • 缺点:URL中带有 ,不美观。
    • 实现:window.onhashchange 事件。
  2. History 模式 (HTML5 History API):

    • 原理:使用 history.pushState()history.replaceState() 方法来修改浏览器历史记录栈,同时监听 popstate 事件。
    • 优点:URL干净美观,和传统网站一样。
    • 缺点:需要服务器配置支持,因为用户直接访问如 https://example.com/user 这个URL时,服务器如果没有对应的路由,会返回404,需要服务器配置将所有请求都指向 index.html
    • 实现:history.pushState(), window.onpopstate

综合与软技能

1:性能优化

问题: 你认为可以从哪些方面对一个Web页面进行性能优化?(至少说出5点)

考察点: 知识广度和实践经验。

参考答案: 这是一个开放性问题,可以从不同层面回答:

  1. 网络传输优化:

    • 资源压缩: 对HTML, CSS, JavaScript文件进行Gzip/Brotli压缩。
    • 图片优化: 使用WebP、AVIF等现代图片格式,对图片进行压缩,使用 srcsetsizes 属性实现响应式图片。
    • CDN: 将静态资源部署到CDN,利用边缘节点加速访问。
    • 减少HTTP请求: 合并CSS/JS文件,使用CSS Sprites(雪碧图)。
    • 使用HTTP/2或HTTP/3: 多路复用,头部压缩,进一步提升传输效率。
  2. 渲染性能优化:

    • 代码分割: 使用Webpack等工具,将代码按需加载,减小初始包体积。
    • 懒加载: 对图片、路由、组件等进行懒加载,只在需要时才加载。
    • 减少DOM操作: 使用文档片段、虚拟DOM等技术,减少重排和重绘。
    • 使用 requestAnimationFrame 优化动画性能。
    • 避免长任务: 将耗时任务拆分成多个小任务,避免阻塞主线程。
  3. 缓存策略:

    • 浏览器缓存: 合理设置 Cache-Control, Expires, ETag 等HTTP头,利用浏览器缓存。
    • Service Worker: 实现离线缓存和高级缓存控制。
  4. 构建优化:

    • Tree Shaking: 移除未使用的代码。
    • Scope Hoisting: 作用域提升,减少代码体积。
    • 使用生产环境构建: 压缩代码、混淆代码。
  5. 服务端优化:

    • 服务端渲染: 对首屏渲染要求高或SEO要求高的页面,使用SSR或SSG(静态站点生成)。
    • 数据库优化: 提升API接口响应速度。

2:项目与协作

问题: 在你的项目经历中,遇到的最大挑战是什么?你是如何解决的?

考察点: 解决问题的能力、沟通能力和抗压能力。

参考答案: 这是一个行为面试题,使用 STAR法则 来回答会非常清晰有力。

  • S (Situation - 情景): 简要描述项目背景和你的角色。
    • “在我之前的一个电商项目中,我负责商品详情页的开发,该页面包含大量图片、规格选择、库存展示和推荐模块,在低端手机上首次加载时间超过5秒,用户体验很差。”
  • T (Task - 任务): 明确你的任务和目标。
    • “我的任务是优化这个页面的加载性能,将首屏加载时间控制在2秒以内。”
  • A (Action - 行动): 详细说明你采取了哪些具体行动,这是回答的核心。
    • *“我使用Chrome DevTools的Performance和Network工具进行了性能分析,发现主要瓶颈在于:1. 首屏图片过大;2. 商品详情数据API较慢;3. 第三方推荐库加载阻塞了页面渲染。 针对这些问题,我采取了以下措施:
      1. 图片优化: 实现了图片懒加载,并对首图使用WebP格式,同时使用srcset为不同分辨率的设备提供合适的图片尺寸。
      2. 数据请求优化: 与后端同事沟通,将商品基本信息(如名称、价格)和库存信息合并到一个API中,并开启了HTTP缓存。
      3. 资源加载优化: 将第三方推荐库的加载方式从同步改为异步,并使用async/await确保它在首屏渲染完成后再加载。
      4. 代码层面: 使用React.memo对规格选择等组件进行记忆化,避免不必要的重渲染。”*
  • R (Result - 结果): 说明你的行动带来了什么结果。
    • “经过优化后,我们使用低端手机测试,页面首屏加载时间从5秒降低到了1.8秒,转化率提升了约5%,这次经历也让我对前端性能优化有了更系统和深入的理解。”

总结与建议

  1. 基础为王: HTML/CSS/JavaScript是根本,务必学扎实,面试官可以从这些基础中问出非常深的问题。
  2. 框架要懂原理: 不要只会 useStatemap,要理解虚拟DOM、Diff算法、Hooks原理等,这是区分你和“API调用工程师”的关键。
  3. 项目要深入: 准备1-2个你最熟悉的项目,能清晰地讲出它的技术栈、你负责的模块、遇到的挑战以及你的解决方案。
  4. 刷题与思考: LeetCode(简单和中等题)和牛客网上的前端题目是很好的练习材料,但更重要的是,做完题后要思考“为什么这么做?”“还有没有更好的方法?”。
  5. 保持热情与好奇心: 前端技术发展很快,展现出你对新技术的关注和学习热情,会给面试官留下很好的印象。

祝你面试顺利,拿到心仪的Offer!

分享:
扫描分享到社交APP
上一篇
下一篇