2026前端面试汇总

1,135 阅读43分钟

前言

随着技术的不断迭代和市场对人才需求的变化,前端工程师面临的就业环境也变得越来越具有挑战性。一方面,互联网行业的整体增速放缓,导致岗位竞争加剧;另一方面,企业对于前端开发者的要求也在不断提高。

在这样的背景下,本文根据笔者的面试经历,总结出了一些常见且高频的面试题。希望能够帮助每一位前端工程师,在激烈的竞争中脱颖而出,最终找到既符合个人职业规划又能实现自我价值的理想工作。无论你是初出茅庐的新手,还是有着丰富经验的老兵,希望你能从本文中获得启发,勇敢迎接未来的每一个挑战,实现职业生涯的持续成长和发展。

如果有不对或者希望补充的问题,也请在留言区留言~ 如果本文帮助到了你,希望可以获得一个点赞。❥(^_-)

本文会持续更新~

编程基础篇

面向对象的三大特征是什么?

  • 封装。封装是面向对象的核心思想,它涉及将对象的属性和行为封装起来,仅对外公开需要公开的属性和行为,同时对这些属性和行为的访问进行限制,防止外部程序随意访问和修改。
  • 继承。继承描述了类与类之间的关系,通过继承,子类可以继承父类的属性和行为,无需重新编写原有类,从而对原有类的功能进行扩展。
  • 多态。多态性是指在程序中允许同名操作作用于不同的对象,从而产生不同的执行结果,这使得同一个属性和方法在不同的类中具有不同的语义,多态性包括参数化多态性和包含多态性。

为何要将数据分为可变的变量和不可变的常量?

将数据分为可变的变量和不可变的常量有助于提高程序的可靠性和可维护性。

  • 防止意外修改:将数据声明为常量可以防止在程序的其他部分意外修改数据,从而避免出现意料之外的行为。
  • 线程安全性:在多线程环境中,使用不可变的常量可以避免数据竞争和并发问题,提高程序的线程安全性。
  • 代码可读性:将常量用于不会被修改的数据可以使代码更易于理解和维护,因为开发人员可以清楚地知道哪些数据是固定的。
  • 优化编译器:编译器可以根据数据的可变性进行优化,例如在编译时进行常量折叠等操作,提高程序的性能。

什么是函数式编程?简单谈一下你的理解。

函数式编程就是一种抽象程度很高的编程范式,它将计算视为数学函数的求值,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的语言包括Haskell、Lisp、Scheme、Erlang等,但许多非纯函数式编程语言也逐渐引入了对FP的支持,比如Python、JavaScript和C#等。采用FP风格可以使代码更清晰、更可靠,同时也可能提升性能,尤其是在多核处理器环境中。

设计模式

发布-订阅模式

JavaScript设计模式:发布-订阅模式

单例模式

JavaScript设计模式:单例模式

CSS篇

什么是CSS盒模型?

CSS盒模型是Web开发中的一个核心概念,它描述了HTML元素如何在页面上呈现,并且如何与其他元素相互作用。盒模型定义了元素的宽度、高度、边距、边框和内边距(填充)等属性如何共同作用来确定元素在页面上的布局和大小。

盒模型主要分为两种:标准盒模型(Standard Box Model)与怪异盒模型(Quirks Box Model)。

标准盒模型:

元素的 widthheight 只包括内容区域的尺寸。内边距和边框会增加元素的总宽度和高度。

怪异盒模型:

元素的 widthheight 包括内容区域、内边距和边框的尺寸。这种模型使得元素的总宽度和高度更容易预测。

为了兼容不同的盒模型,CSS引入了一个 box-sizing 属性,它允许你指定元素应该遵循哪种盒模型。

  • box-sizing: content-box;
    • 这是默认值,表示使用标准盒模型。
  • box-sizing: border-box;
    • 表示使用怪异盒模型。

单行文本与多行文本溢出显示省略号如何实现?

对于单行文本溢出的情况,可以使用 text-overflow: ellipsis;white-space: nowrap; 结合 overflow: hidden; 来实现。

.single-line {
    width: 200px; /* 设置容器宽度 */
    overflow: hidden; /* 隐藏超出的内容 */ 
    white-space: nowrap; /* 不换行 */ 
    text-overflow: ellipsis; /* 溢出部分显示为省略号 */ 
}

对于多行文本溢出的情况,可以使用 -webkit-line-clamp 属性(这是一个 WebKit 引擎特有的属性)结合 display: -webkit-box;-webkit-box-orient: vertical; 来实现。

.multi-line { 
    display: -webkit-box; /* 使用弹性盒子模型 */ 
    -webkit-box-orient: vertical; /* 盒子的方向是垂直的 */ 
    overflow: hidden; /* 隐藏超出的内容 */ 
    text-overflow: ellipsis; /* 溢出部分显示为省略号 */ 
    -webkit-line-clamp: 3; /* 显示的行数 */ 
}

尽可能多的说出CSS将元素水平垂直居中的方法

  1. FlexBox
.container {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center; /* 垂直居中 */
}
  1. Grid
.container {
  display: grid;
  place-items: center; /* 水平垂直居中 */
}
  1. Position
.container {
  position: relative;
  width: 100%; /* 或者你需要的宽度 */
  height: 100%; /* 或者你需要的高度 */
}

.item {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 补偿元素自身的宽度和高度 */
}
  1. Table Cell
.container {
  display: table-cell;
  vertical-align: middle;
  text-align: center; /* 水平居中 */
  height: 100%; /* 或者你需要的高度 */
}

什么是块级格式化上下文?如何触发?

块级格式化上下文(Block Formatting Context,简称 BFC)是 CSS 中的一个概念,它定义了元素如何在其容器内进行布局。BFC 主要决定了元素的浮动、清除、定位以及大小等方面的行为。在 BFC 中,元素的布局规则与其他上下文中的规则有所不同。

BFC 的特点

  1. 浮动元素不会影响 BFC 内的元素:
    • 浮动元素(使用 float 属性的元素)不会影响 BFC 内的其他元素的布局。换句话说,BFC 内的元素会忽略外部的浮动元素。
  2. 清除浮动:
    • BFC 的边界框会包含内部的浮动元素。
    • BFC 的边界框也会包含内部的清除浮动元素(使用 clear 属性的元素)。
  3. 垂直边距折叠:
    • BFC 内部的垂直边距会发生折叠,即相邻的垂直边距会被合并成一个更大的边距。
  4. 包含性:
    • BFC 内部的元素不会影响到外部的元素,反之亦然。

触发 BFC 的方式

BFC 可以通过以下几种方式触发:

  1. 使用 overflow 属性:
    • 设置 overflowautoscrollhidden 会触发 BFC。
  2. 使用 display 属性:
    • 设置 displayflexgrid 会触发 BFC。
    • 设置 displaytable-celltable-caption 也会触发 BFC。
  3. 使用 position 属性:
    • 设置 positionabsolutefixed 并且具有一个相对定位的父元素时,会触发 BFC。
  4. 使用 float 属性:
    • 设置 floatleftright 会触发一个 BFC。
  5. 使用 contain 属性:
    • 设置 containlayoutpaintcontent 会触发 BFC。

如何解决margin重叠问题?

触发新的块级格式化上下文或者设置边框。

块级元素与行内元素的区别?

块级元素(block-level elements)和行内元素(inline elements)是 HTML 中两种基本的元素类型,它们在布局和样式方面有着显著的区别:

  • 块级元素独占一行,即每个块级元素都会从一个新行开始。块级元素可以设置宽高。块级元素可以包含其他的块级元素或者行内元素。
  • 行内元素默认在同一行显示,不会独占一行。行内元素的宽度和高度不能直接设置,宽高由内容决定。设置垂直外边距不会生效。行内元素通常包含文本或其他行内元素,但不能包含块级元素,如果非要包含块级元素,那么浏览器会把外层的行内元素变为块级元素。

伪类和伪元素有什么区别?

CSS 伪类和伪元素是 CSS 中两种重要的特性,它们都可以用来添加样式,但它们的作用和用途有所不同。

  • 伪类并不是真正的 HTML 元素或类,而是用来表示元素的一种状态或条件。用于改变元素在特定状态下的样式,例如鼠标悬停、激活、访问过的链接等。
    • :hover: 当用户将鼠标指针悬停在元素上时应用样式。
    • :active: 当元素被激活时(例如鼠标按下时)应用样式。
    • :visited: 当链接被访问过后应用样式。
    • :focus: 当元素获得焦点时应用样式。
  • 伪元素也不是真正的 HTML 元素,而是用来表示文档树中不存在的节点。用于向元素添加额外的内容或装饰,如插入文本或图标。
    • ::before: 在元素内容之前插入内容。
    • ::after: 在元素内容之后插入内容。

在项目中有哪些样式隔离的方案?

  • CSS Modules
    • CSS Modules 是一种将CSS类名局部化的方法,可以为每个组件生成唯一的类名。
// styles.module.css
.button {
  background-color: blue;
}

// Component.js
import React from 'react';
import styles from './styles.module.css';

function Button() {
  return <button className={styles.button}>Click me</button>;
}

export default Button;
  • Shadow DOM
    • Shadow DOM 是一种Web组件技术,它允许在一个元素内部创建一个独立的DOM树,并且可以将样式限制在这个内部DOM树中。
class CustomButton extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });

    const style = document.createElement('style');
    style.textContent = `
      :host {
        display: inline-block;
      }
      
      button {
        background-color: blue;
      }
    `;
    
    const button = document.createElement('button');
    button.textContent = 'Click me';

    shadow.appendChild(style);
    shadow.appendChild(button);
  }
}

customElements.define('custom-button', CustomButton);
  • BEM Methodology
    • BEM(Block Element Modifier)是一种命名约定,它鼓励使用具有明确含义的类名来减少样式冲突。
// App.js
import React from 'react';

function App() {
  return (
    <div className="app__container">
      <button className="app__button app__button--primary">Click me</button>
    </div>
  );
}

export default App;
  • CSS-in-JS Libraries
    • CSS-in-JS 是一种将CSS样式嵌入到JavaScript中的方法,通常使用如styled-components、emotion等库来编写CSS样式。
import React from 'react';
import styled from 'styled-components';

const Button = styled.button`
  background-color: blue;
`;

function App() {
  return <Button>Click me</Button>;
}

export default App;
  • Tailwindcss等原子化的库
    • 提供一些原子化的类名,通过类名的组合来完成样式的编写。
<div class="gap-8 columns-3 ...">
  <img class="w-full aspect-video ..." src="..." />
  <img class="w-full aspect-square ..." src="..." />
  <!-- ... -->
</div>

flex布局

flex属性是flex-grow ,flex-shrinkflex-basis 的简写,默认值是0 1 auto

  • flex-grow是如果有剩余空间,是否扩大,0为不扩大
  • flex-shrink是如果剩余空间不够,是否缩小,1为缩小
  • flex-basis为项目本身的大小,默认值是auto

flex: 1分别为1 1 0%,相当于可扩大,可缩小。

flex: auto分别为1 1 auto,相当于可扩大,可缩小。

两者区别在于flex: 1是平均分配项目空间,flex: auto是根据项目内容的大小分配。

flex: 0分别为0 1 0%,相当于不可扩大,可缩小。

flex: none分别为0 0 auto,相当于不可扩大,不可缩小。

JavaScript篇

clientHeight、offsetHeight、scrollHeight的区别

1. clientHeight

clientHeight 属性返回的是元素的内容高度(不包括边框、内边距和外边距),如果元素有滚动条,滚动条也不包含在这个值内。

  • 返回值: 元素内容区的高度(单位是像素)。
  • 包含: 内容区的高度。
  • 不包含: 边框、内边距、外边距、滚动条。

2. offsetHeight

offsetHeight 属性返回的是元素的总高度,包括内容、内边距、边框和滚动条(如果有的话),但不包括外边距。

  • 返回值: 元素的高度(单位是像素),包括边框、内边距和滚动条。
  • 包含: 内容区的高度、内边距、边框、滚动条(如果有的话)。
  • 不包含: 外边距。

3. scrollHeight

scrollHeight 属性返回的是元素的总高度,包括可见部分和不可见(溢出)部分,即使这些不可见的部分可以通过滚动条访问,它也包括在里面。

  • 返回值: 元素的总高度(单位是像素),包括可见和不可见的内容。
  • 包含: 内容区的高度,包括溢出的内容。
  • 不包含: 边框、内边距、外边距、滚动条。

请说一下JavaScript中的原型以及原型链

每个对象都有一个属性叫做 __proto__,它引用了一个对象,我们通常称这个对象为该对象的“原型”。原型本身也是一个普通的 JavaScript 对象,因此也有自己的原型。当我们尝试访问一个对象的属性时,如果该对象上不存在这个属性,JavaScript 引擎会在原型对象上依次查找这个属性,这个隐式的链条就称作为“原型链”。

JavaScript中的Event Loop是什么?

Event Loop 的主要任务是协调执行栈与回调队列之间的操作。调度流程是这样的:

  1. JavaScript引擎启动时,创建一个全局执行上下文,并将其压入执行栈中。然后开始执行同步代码,遇到异步代码时会注册回调函数,并将控制权交还给Event Loop。
  2. Event Loop 持续检查执行栈是否为空,同时监听异步操作的完成。
  3. 当执行栈为空且回调队列中有待处理的任务时,Event Loop 会从回调队列中取出第一个宏任务,并创建一个新的函数执行上下文,然后将其压入执行栈。
  4. 当当前宏任务执行完毕后,Event Loop 会检查是否有微任务需要执行。如果有,它会执行所有的微任务,直到微任务队列为空。
  5. 此过程会不断重复,直到回调队列为空。

什么是闭包,解决了什么问题,有哪些使用场景?

闭包是指一个函数可以访问并操作其外部函数作用域中的变量的能力,即使外部函数已经执行完毕,闭包仍然可以访问和修改外部函数的变量。闭包常见的使用场景包括:

  • 保护变量:通过闭包可以创建私有变量,避免全局变量污染。
  • 延长变量的生命周期:外部函数执行完毕后,内部函数仍然可以访问外部函数的变量,延长了变量的生命周期。
  • 模块化开发:通过闭包可以实现模块化开发,将相关功能封装在闭包中,避免全局命名冲突。

var、let、const有什么区别?

它们都是用于声明变量的关键字。var声明的变量会存在变量的声明提升,意味着我们可以在声明之前使用它;还可以在同一作用域中多次声明同一个变量,只会覆盖原有的值。let在声明之前是不能被使用的,会出现ReferenceError报错;也不能在同一作用域中多次声明;let声明的变量具有块级作用域。const与let类似,但const声明的变量不能被重新赋值。

常用的数组方法有哪些,哪些会对自身进行改变?

常用的方法有push,pop,shift,unshift,concat,slice,splice,forEach,map,filter,find,indexOf,includes,sort,reverse等。其中,会对自身进行改变的数组方法有:push(), pop(), shift(), unshift(), splice(), sort() 和 reverse()。这些方法会直接修改原数组,而不是返回一个新数组。

script 标签上 async 和 defer 的属性是什么?

在 JavaScript 中, <script> 标签上的 asyncdefer 属性用于控制脚本的加载和执行方式。

  • async : 当浏览器遇到带有 async 属性的 <script> 标签时,会异步加载并执行脚本,执行脚本时如果页面还没渲染完毕会阻塞页面的渲染。多个带有 async 属性的脚本可能会并行加载和执行,但它们的执行顺序不一定按照它们在页面中出现的顺序。
  • defer : 当浏览器遇到带有 defer 属性的 <script> 标签时,会异步加载脚本,但会在文档解析完成后,DOMContentLoaded事件前,按照它们在页面中出现的顺序依次执行。 defer 属性保证脚本的执行顺序与它们在页面中的顺序一致,但不会阻塞页面的渲染。

如何取消 HTTP 请求

在JavaScript中,取消HTTP请求通常是通过使用 AbortController 来实现的。 AbortController 是一个用于控制取消异步操作的API,可以用来取消fetch请求或其他异步操作。

以下是一个简单的示例,演示如何使用 AbortController 取消fetch请求:

// 创建一个新的AbortController实例
const controller = new AbortController();
const signal = controller.signal;

// 发起fetch请求并传入signal
fetch('https://api.example.com/data', { signal })
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Fetch error:', error);
  });

// 取消请求
controller.abort();

在上面的示例中,我们首先创建了一个 AbortController 实例,并从中获取一个 signal 对象,然后在发起fetch请求时将该 signal 传递给请求的 options 参数中。最后,通过调用 controller.abort() 方法来取消请求。

请注意,取消请求并不是立即生效的,而是在请求被接收后才会生效。因此,需要在适当的时机调用 controller.abort() 方法来取消请求。

什么是防抖?什么是节流?请给出最简洁的实现代码示例?

防抖的主要目的是为了延迟函数的执行,直到最后一次调用后的一定时间间隔过去以后才执行一次。可以用在搜索框自动补全的场景。示例代码如下:

// 只有在最后一次调用后的 wait 时间间隔过后才会执行函数。如果在这段时间内函数再次被调用,将会重置计时器。
function debounce(fn, wait) {
    let timer;
    return function(...args) {
        const context = this;
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(context, args), wait);
    }
}

节流的主要目的是确保函数不会被过于频繁的调用,它在固定的时间间隔内只允许执行一次。可以用于优化窗口滚动事件等高频繁触发的事件。示例代码如下:

// 确保函数至少在 limit 时间间隔内只执行一次。即使在短时间内连续调用,函数也会按照固定的时间间隔执行。
function throttle(fn, limit) {
    let inThrottle;
    return function(...args) {
        const context = this;
        if (!inThrottle) {
            fn.apply(fn, args);
            inThrottle = true;
            setTimeout(() => (inThrottle = false), limit);
        }
    }
}

类数组转化为数组有哪些办法?

// 方法一
function toArray(arrayLike) {  
    return Array.prototype.slice.call(arrayLike);  
} 

// 方法二
function toArray(arrayLike) {  
    return [...arrayLike];  
} 

// 方法三
function toArray(arrayLike) {  
    return Array.from(arrayLike);  
} 

TypeScript篇

在TypeScript中interface 和 type 的区别是什么?

在 TypeScript 中, interfacetype 都用于定义自定义类型,但它们在功能和用法上有一些不同之处:

  • interface 接口:
    • 接口主要用于定义对象类型,并强制对象具有特定的结构。
    • 接口只能表示对象类型,不能用于创建原始类型、联合类型、交叉类型或元组的别名。
    • 接口支持使用 extends 关键字扩展其他接口。
    • 接口可以被类实现,允许对类实例进行类型检查。
    • 接口是开放的,意味着可以添加新属性而不会影响已有代码的兼容性。
  • type 类型别名:
    • 类型别名可以用于定义任何类型,包括对象类型、原始类型、联合类型、交叉类型和元组。
    • 类型别名可以使用联合类型、交叉类型和映射类型等高级类型操作。
    • 类型别名不能被类实现,因为它们不会创建新的类型。
    • 类型别名可以更灵活地定义类型,但不支持扩展其他类型。

总的来说,如果你需要定义对象类型并且需要支持扩展和类实现,则使用 interface ;如果你需要定义更复杂的类型别名,包括原始类型和高级类型操作,则使用 type 类型别名。

在TypeScript中什么是泛型,泛型的作用场景,你在哪里使用过?

在 TypeScript 中,泛型是一种在定义函数、类或接口时,可以使用类型参数来增加代码的灵活性和重用性的特性。

泛型的作用场景包括但不限于:

  1. 提高代码的灵活性:可以编写更通用的函数或类,适用于不同类型的数据。
  2. 增强类型安全性:在编译时就能发现类型错误,避免在运行时出现类型不匹配的问题。
  3. 减少重复代码:避免为不同类型的数据编写重复的代码,提高代码的重用性和维护性。

我在实际项目中使用泛型的场景包括但不限于:

  1. 创建通用的数据结构:如队列、栈等数据结构,可以用泛型来定义存储的数据类型。
  2. 编写通用的函数:例如数组操作函数、数据转换函数等,可以使用泛型来处理不同类型的数据。
  3. 使用第三方库:许多第三方库和框架都使用泛型来增强类型安全性和代码的灵活性。

React篇

什么是React?

React是一个网页UI框架,通过组件化的方式解决视图层开发复用的问题,本质上是一个组件化的框架。它的核心思路有三点,分别是声明式、组件化与通用性。声明式的优点在于直观与组合。组件化的优势在于视图的拆分与复用,可以更容易的做到高内聚低耦合。通用性在于一次学习,随处编写,无论是Web、Native、VR,甚至是Shell应用都可以进行开发,这也是React的优势。

但作为一个现代前端框架,React的劣势也十分明显。他并没有提供完整的一揽子解决方案,在开发大型前端应用时,需要向社区寻找和整合解决方案。虽然在一定程度上促进了社区的繁荣,但也为开发者在技术选型和学习使用上造成了一定的成本。

什么是JSX?

JSX是一种JavaScript的语法拓展,可以在JavaScript 文件中编写类似 HTML 的标记。在React中使用JSX来编写组件,极大的提升了效率与代码的可维护性,通过Babel的编译,最终会变成React.createElement的函数调用,调用React.createElement返回的就是虚拟DOM对象。相比于Angular使用模板开发组件的方式,使用JSX可以使用 JavaScript 的条件语句和循环结构,而无需像在Angular中那样使用模板语法。当与 TypeScript 结合使用时,JSX 可以提供静态类型的检查,有助于在开发过程中捕获潜在的错误。

什么是虚拟DOM?

虚拟DOM是通过JS对象来模拟真实DOM节点。Facebook在设计React时,考虑到要提高代码的抽象能力以及避免人为的操作DOM降低代码风险的因素,引入了虚拟DOM。以React为例,在render函数中写的JSX在Babel插件作用下编译为React.createElement,createElement执行后会返回一个JS对象,他会描述自己的类型、props属性以及children等信息,这些JS对象通过树形结构组成一个虚拟DOM树,当状态变更时将变更前后的虚拟DOM树进行比较,这个过程叫做diff,对比生成的结果称为patch,计算完成后会完成对patch的渲染。虚拟DOM的优点有改善大规模操作DOM的性能;规避XSS的风险;较低的成本实现跨平台开发。其缺点有内存占用较高,因为需要模拟整个页面中的真实DOM;高性能场景下存在难以进行极致优化的情况。

除了渲染页面,虚拟DOM还有哪些应用场景?

虚拟 DOM不仅用于渲染页面,还可以应用于其他场景:

  • rrweb 是一个用于录制和回放网页交互的库,使用虚拟 DOM 来记录页面的状态变化,包括元素的添加、删除、属性变化,还有记录用户的交互事件,如点击、滚动等,使用虚拟 DOM 可以更准确地捕捉到这些事件,并在回放时重现它们。在回放时,通过使用虚拟 DOM,rrweb 可以最小化实际 DOM 的更新次数,从而提高回放的速度和性能。
  • React Testing Library、Jest等用于构建测试的库中也使用了虚拟DOM来模拟组件的渲染。

类组件与函数组件有什么优缺点?

类组件是面向对象编程思想在React中的一种体现,我们编写的组件是继承于React.Component类的。React为其实现了很多强大的功能,比如生命周期函数等,它更像一艘“重装战舰”。函数组件相比于类组件要轻量很多,它也更契合React框架的设计理念,我们经常看到的一个公式是UI=f(data)UI=f(data), 可以说React组件本身就是函数,是一个输入数据,返回UI的函数。函数组件更像一艘快艇,Hooks的出现就像是工具包,函数组件结合Hooks使得我们可以定制出自己需要的!

在线案例

在类组件中,在每次渲染的时候都会获取最新的props和state对象。因此,当定时器触发时,this.props.user 将引用的是当前的 user 值。当我们将 SayHi 组件从类组件改为函数组件时,确实可以实现预期的行为,即在定时器触发时弹出的消息将是 "Hi, Dan!"。这是因为函数组件在每次渲染时都会重新创建闭包环境,而类组件中的方法则不会重新创建闭包环境。

函数组件会捕获render内部的状态,这是两类组件最大的不同。函数组件真正的将数据和渲染绑定在了一起。

Hooks的优点:

  • 能帮我们把实现业务的逻辑聚合到一起,避免复杂的组件和冗余的代码。
  • 复用状态逻辑,在Hooks出现前我们都是依靠HOC和Render Props这些设计模式来实现,它们在实现逻辑复用的同时,也破坏了组件的结构,最常见的一个问题就是“嵌套地狱”现象。现在我们可以通过自定义Hook达到既不破坏组件结构又现实逻辑的复用。

Hooks的不足:

  • 没有如 getDerivedStateFromPropsgetSnapshotBeforeUpdate 这样的静态方法,但可以通过使用 useEffectuseState 来实现类似的功能。
  • 没有错误边界,需要使用类组件来作为错误边界。

如何设计React组件?

请描述React中的事件机制?

React的事件机制是基于合成事件,它是一个跨浏览器的事件模型,抹平了各个浏览器间的差异。合成事件使用事件代理的方式,将事件监听器挂载到文档的根节点上,17版本以后将事件监听器挂载到应用的root节点上,避免了多应用共存时的问题。在浏览器的事件机制中会经历捕获、目标以及冒泡阶段,React的事件处理是在冒泡阶段处理的。

Fiber 是什么?它的出现解决了什么问题?

Fiber 是React 16版本引入的一种数据结构和架构模式,用于改进 React 的性能和可扩展性。Fiber 数据结构是一种双向链表式的结构,其中每个节点(Fiber)都包含关于虚拟 DOM 节点的信息,如类型、属性、状态等。每个 Fiber 节点都有一个 sibling 指向同级节点,以及一个 child 指向子节点。此外,每个 Fiber 节点还包含一个 alternate 指针,指向同一个 DOM 节点的前一个版本。这种结构使得 React 能够高效地进行虚拟 DOM 的比较,并且能够在需要时回滚到前一个状态。它的出现解决了这些问题:

  • 早期版本的 React 在更新用户界面时,会一次性执行所有的更新操作。这意味着如果一个更新需要很长时间来完成,它可能会阻塞浏览器的主线程,导致页面假死。Fiber 的出现为 React 带来了中断和恢复的能力,将工作分割成更小的批处理,并在浏览器的空闲时间逐步完成。这样可以确保用户界面保持响应性。

  • Fiber 支持更新的优先级,这意味着用户交互事件可以优先处理,而较低优先级的任务(如定时器触发的更新)可以在后台进行。

  • Fiber 引入了错误边界的概念,允许开发者指定某些组件作为错误边界,以隔离错误并避免整个应用程序崩溃。

React Hooks 的使用有哪些限制?

限制主要有以下两条:不能在循环、条件或嵌套函数中调用 Hooks;不能在类组件中使用 Hooks。为什么有这些限制,因为React的设计初衷是为了改进组件的开发模式,在以往我们使用类组件时会遇到组件间难以复用逻辑的问题,通常会使用高阶组件或者render props的设计方式来解决。另一方面我们的组件中业务逻辑分散到各个生命周期函数中,难以维护与理解。最后对于开发者来说还有类组件中this的指向、值捕获问题的困扰以及React团队难以对类组件做一些编译优化。这些问题在一定程度上阻碍了React的发展,为了解决这些问题Hooks基于函数组件去设计,在设计时Hooks是以双向链表的数据结构存储的,所以不能在循环、条件或嵌套函数中调用 Hooks。

追问:怎么去防范在编写代码时出现上述的情况?

可以在工程化的角度去解决,在eslint中引入eslint-plugin-react-hooks完成自动检查

useEffect与useLayoutEffect有什么区别?

在源码中:

  • useEffect先调用mountEffect再调用mountEffectImpl
  • useLayoutEffect 先调用mountLayoutEffect 再调用mountEffectImpl

useEffect与useLayoutEffect两者都是用于处理副作用,例如改变DOM、设置订阅、操作定时器等。useLayoutEffect 是在DOM更新后浏览器渲染也面前同步执行,如果在这个阶段去执行一些耗时的任务,就有可能阻塞渲染,对用户的交互体验造成影响。useEffect 在浏览器完成渲染后异步执行,不会阻塞浏览器的渲染。通常我们会把引起屏幕闪烁的样式修改放到useLayoutEffect 中执行。

setState是同步还是异步?

官方回答过“为什么setState为什么是异步的”这个问题。

  • 保持内部的一致性,如果把setState修改为同步的,但props不是。
  • 为后续的架构升级并启用并发更新。

setState不是真正的异步,只是看上去像异步,在源码中是通过isBatchingUpdates来控制setState是先存进队列中还是直接更新。如果值为true则执行异步操作,为false则直接更新。什么时候是true呢?那就是React可以控制的地方,比如在React的生命周期事件以及合成事件中都会走批量操作延迟更新的策略,在React不能控制的地方,比如原生事件addEventListenner、setTimeout、setInterval等事件中就只能同步更新。一般认为做异步更新是为了做性能优化,减少渲染次数。

在React中每次 setState,Hook 函数会重新跑一遍吗?

是的,在React的函数组件中,每次setState之后所有的Hooks都会按照它在组件中定义的顺序重新执行。有的Hook可以指定依赖项,如果某个Hook指定了依赖项,那么只有当依赖项改变时该Hook才会重新执行。

描述 setState 一次之后 React 的整个更新流程

当你在函数组件中调用 useStatesetState 方法时,实际上是在调用 useState Hook 内部定义的一个函数。这个函数会将状态更新放入更新队列中。

React 会批量处理这些更新。这意味着即使你连续调用多次 setState,React 也会等到批处理时机(例如在事件处理完成后或者微任务结束后)才开始处理更新队列。

一旦开始处理更新队列,React 会重新执行函数组件。在这个过程中,所有的 Hooks(如 useState, useEffect, useRef 等)都会按照它们在组件顶部定义的顺序重新执行。

对于使用 useState 的组件,新的状态值会根据 setState 的调用计算得出。如果 setState 接受一个函数作为参数,这个函数将会被调用以计算新的状态值。

组件重新执行时,React 会比较新的渲染输出与之前的输出,确定需要更新的 DOM 节点。这个过程被称为“reconciliation”(diff算法也发生在该阶段中)。

对于每个使用 useEffect 的 Hook,如果它的依赖项数组中的值发生了变化,那么相应的副作用将会被重新执行。如果没有变化,则不会重新执行。

一旦确定了需要更新的 DOM 节点,React 会更新实际的 DOM,将最新的状态反映到界面上。

如果 useEffect 中的副作用有返回一个清理函数,那么在重新渲染之前,React 会先调用这个清理函数。然后,如果依赖项发生变化,React 会重新执行副作用。

完成上述步骤后,React 完成了这次更新周期,组件现在处于最新的状态。

React的diff算法

diff算法讨论的就是虚拟DOM树发生变化后生成DOM树更新补丁的方式。它的更新流程是这样的:

  1. 真实的DOM首先会映射为虚拟DOM
  2. 当虚拟DOM变化后会根据差异计算出patch,patch是一个结构化的数据,包含了增加、移除以及更新
  3. 根据patch去更新真实的DOM,反馈到用户界面上。

在这个过程中会涉及到虚拟DOM树的遍历,对树的遍历我们通常有两种方式,一个是树的深度优先遍历一个是树的广度优先遍历。React采用的是深度优先遍历算法,因为广度优先遍历算法可能会导致组件的生命周期时序错乱。虽然深度遍历算法可以保证组件的生命周期时序不错乱,但传统的diff算法带了一个严重的性能瓶颈,复杂度为O(n3)O(n^3) ,其中n表示树的节点总数。所以React在比对策略上做了优化使复杂度降低到了O(n1)O(n^1),它忽略了节点跨层级操作的场景,只对比同层级的节点,提升了对比效率,在对比时如果发现节点已不存在,则该节点及其子结点会被完全删除,不会用于进一步比较;检查节点类型是否相同。如果类型不同,则需要替换整个节点;同一层级子节点,可以通过标记key的方式进行列表的对比。 在React16引入Fiber后,节点和树分别采用FiberNode和FiberTree进行重构。FiberNode使用了双链表的结构,可以方便的找到兄弟节点和子节点,使得更新过程可以中断和恢复,FiberTree则是由FiberNode构成的树,整个更新过程由current和workInProgress两棵树双缓冲完成,workInProgress更新完成后会修改current指向workInProgress。

React项目首屏优化?

React 应用的首屏优化对于提高用户体验至关重要。首屏加载速度直接影响用户的满意度和网站的转化率。以下是一些React首屏优化的方案:

  • 编码
    • 使用动态导入(import())来实现代码分割,只加载用户当前需要的部分。
    • 使用React.lazy 进行懒加载,仅当组件首次渲染时才加载它们。
    • 使用<link rel="preload"><link rel="prefetch">来提前加载关键资源。
    • 使用React.memouseMemo来避免不必要的重渲染。
    • 使用骨架屏(skeleton screens)来展示加载状态,改善用户感知的加载速度。
    • 减少第三方库的使用,特别是那些体积比较大的。可以选择一些轻量级的替代方案,或者按需引入特定功能。
  • 构建
    • 对JavaScript以及CSS进行压缩,并启用gzip压缩,以减小传输的数据量。
    • 可以对图片进行适当的压缩,减小文件大小,还可以使用webp等现代格式替换jpg或png。对于一些小的图标还可以转换为base64以减少HTTP请求数。
    • 使用externals来排除一些第三方库,用cdn的方式引入。

React项目中为什么要使用状态管理工具?可以使用localStorage 代替吗?

状态管理工具更适合于处理复杂的应用状态,而localStorage 更适合简单的持久化储存需求。并且状态管理工具一般支持中间件,时间旅行调试等,而localStorage 没有这样的功能。

状态管理工具很容易在组件间共享状态,而使用localStorage 需要手动协调状态的读写逻辑。

状态管理工具的状态更新逻辑易于测试和维护,并且只在状态发生变化时才会渲染相关组件,从而提高性能。直接使用localStorage 可能会导致不必要的重渲染。最后localStorage 存储的数据容易被用户篡改或窃取。

前端工程化篇

什么是前端工程化?你所理解的前端工程化包含哪些内容?

前端工程化是指将前端开发过程标准化、自动化和模块化的一种开发理念与实践方式。其目的是为了提高开发效率、保证代码质量以及便于团队协作。前端工程化主要涉及以下几个方面:

  1. 代码规范与统一性
    • 定义和实施统一的编码规范,例如 ESLint、Prettier 等工具可以帮助自动检查和格式化代码,确保代码风格的一致性。
  2. 自动化构建工具
    • 使用自动化工具(如 Webpack、Rollup、Gulp、Grunt 或 Vite)来简化构建流程,包括压缩、合并、编译等操作,以提高构建效率和减少人为错误。
  3. 版本控制
    • 使用 Git 或其他版本控制系统进行代码版本管理,方便多人协作开发,并记录每一次修改的历史。
    • 使用stand-vrsion来进行项目的语义化版本控制。
  4. 持续集成/持续部署 (CI/CD)
    • 自动化测试、构建和部署流程,确保每次提交代码后都能快速反馈结果,并能自动发布到生产环境。
  5. 测试与调试
    • 实施单元测试、集成测试、端到端测试等,确保应用的稳定性和可靠性。使用调试工具如 Chrome DevTools 来辅助开发过程中的调试
  6. 性能优化
    • 包括但不限于资源压缩、图片优化、懒加载、代码分割等技术手段来提升应用性能。
  7. 文档和知识管理
    • 建立文档体系,记录项目架构、技术选型、接口说明等内容,帮助新成员快速上手。
  8. 开发环境搭建
    • 通过脚本或工具如 Docker 快速搭建开发环境,确保所有开发者有一致的开发环境。
  9. 前端监控
    • 实施前端监控系统来收集用户行为数据、性能指标和错误报告,帮助快速定位问题并进行优化。

Vite为什么比Webpack快?为什么快?

Vite比Webpack快,尤其是在开发阶段,主要原因有以下几点:

  • ESM原生支持:Vite 利用了现代浏览器对 ES Modules (ESM) 的原生支持,在开发环境中可以直接运行源代码,无需像Webpack那样先进行打包。
  • 按需编译:Vite 采用按需编译的策略,即只有当浏览器请求某个模块时,Vite 才会对该模块进行编译。这与Webpack不同,后者会在构建时对整个项目进行打包,无论模块是否立即被使用。
  • 快速启动:Vite 启动时不需要预先打包或分析模块依赖,因此启动速度非常快。
  • 高效热更新:Vite 的热更新(Hot Reload)机制更为高效,当模块内容变化时,仅需重新请求该模块,而不是像Webpack那样重新编译整个依赖树。

Vite有哪些优化方案?

在前端工程化中,Vite 提供了多种优化方案来提高开发效率和生产环境的性能。以下是一些常见的优化方案:

  1. 图片转 Base64
    • 使用 Vite 插件如 vite-plugin-inline-svg 或自定义配置将小图片转换为 Base64 编码,减少 HTTP 请求次数。
  • 2. 懒加载
    • 利用动态导入 (import()) 实现代码分割,按需加载非关键路径上的模块,降低初始加载时间。
  1. 压缩和优化
    • 使用 Vite 插件进行文件压缩和优化,例如 rollup-plugin-terser 进行 JavaScript 和 CSS 文件的压缩。
  2. 分析构建输出
    • 使用 vite-plugin-analyze@rollup/plugin-bundle-analyzer 插件来分析构建后的文件大小分布,找出优化点。
  3. 自定义服务端渲染 (SSR)
    • 虽然不是所有项目都需要 SSR,但对于需要首屏快速加载的项目,可以使用 vite-plugin-ssr 进行服务端渲染优化。
  4. 配置 Rollup 插件
    • Vite 基于 Rollup 构建,可以配置各种 Rollup 插件来实现特定的优化目标。
  5. 资源指纹
    • 通过为生成的文件添加哈希指纹来缓存 busting,确保浏览器总是加载最新的文件版本。
  6. 预加载和预取
    • 使用 HTML 的 <link rel="preload"><link rel="prefetch"> 标签来提前加载关键资源。
  7. 优化网络请求
    • 使用 CDN 分发静态资源,减少延迟。
  8. 优化构建配置
    • 根据项目需求调整 Vite 的配置文件 vite.config.jsvite.config.ts 中的选项,比如开启 build.minify 选项来进一步减小文件体积。

Webpack中Loader和Plugin有什么区别?

  • Loader在文件被导入时运行,它们使得Webpack可以处理.js 文件以外的其他类型的文件。Loader能解析和转换文件内容,使其可以被当做模块来使用。Loader可以串联使用,形成“Loader链”。
  • Plugin直译的话就是插件的意思,它是一种更强大的拓展点,它可以做很多的事情,包括修改Webpack的内部状态,生成新的文件,注入代码等。通常在特定的Webpack生命周期钩子处被调用。

总的来说。Loader只在文件被导入时调用,用于转换单个文件。Plugin可以对整个编译流程进行控制,执行更多复杂的任务。

Node.js后端篇

什么是中间件?

在Node.js中,中间件(middleware)是一个函数或一组函数,用于处理HTTP请求和响应过程中的中间环节。中间件可以访问请求对象(request object - req)、响应对象(response object - res)和应用程序的下一个中间件函数(next)。中间件函数可以执行一些操作,修改请求或响应对象,或者将控制权传递给下一个中间件函数。

在Express框架中,中间件是一个关键概念。Express中的中间件函数可以用来实现各种功能,如日志记录、身份验证、请求处理等。中间件函数可以按照顺序串联在一起,形成中间件链,每个中间件函数都可以对请求和响应进行处理。

一个简单的Express中间件示例如下:

// 日志记录中间件
const loggerMiddleware = (req, res, next) => {
    console.log(`${new Date()} - ${req.method} request for ${req.url}`);
    next(); // 调用next()将控制权传递给下一个中间件
};

// 应用中间件
app.use(loggerMiddleware);

// 路由处理
app.get('/', (req, res) => {
    res.send('Hello World');
});

在上面的示例中,loggerMiddleware是一个简单的日志记录中间件函数,它打印出每次请求的方法和URL。通过调用 next() 方法,控制权会传递给下一个中间件或路由处理函数。

谈一谈对洋葱圈模型的理解

洋葱模型(Onion Model)是一种在Node.js中常用的中间件模式,用于处理HTTP请求和响应。洋葱模型的核心思想是将请求和响应的处理过程看作是一层层剥开的洋葱,每一层都可以在请求和响应的过程中执行一些操作,然后再将控制权传递给下一层,最终再将处理结果返回给客户端。

在洋葱模型中,请求会从外层中间件开始处理,然后逐层向内传递,直到达到内层中间件,再按照相反的顺序返回结果。这种模型可以让开发者更清晰地控制请求的处理过程,方便实现各种功能和需求。

洋葱模型的特点包括:

  1. 顺序执行:中间件按照顺序执行,每个中间件都可以对请求和响应进行处理。
  2. 控制权传递:通过调用 next() 方法,控制权可以传递给下一个中间件。
  3. 异步处理:中间件可以进行异步操作,如数据库查询、网络请求等。
  4. 错误处理:可以通过中间件捕获和处理错误,保证程序的稳定性。

在Node.js的Express框架中,洋葱模型被广泛应用。开发者可以通过串联多个中间件函数,形成一个中间件链,实现请求的处理和响应的生成。

移动端篇

移动端开发时遇到过哪些兼容性问题?

  • click 事件有 300ms 的延时问题,以及移动端点击穿透问题。可以引入 fastclick 库来解决。
  • 在安卓和 IOS 上都会禁止自动播放和使用 JS 的触发播放音视频,必须由用户触发。解决方法是先通过用户 touchstart 触碰触发播放并暂停(让音频开始加载),后面用 JS 再操作就没问题了
  • CSS 动画闪烁、卡顿。尽可能地使用合成属性 transform 和 opacity 来设计 CSS3 动画,transform: translate3d(0, 0, 0);开启硬件加速。
  • 安卓手机端软键盘弹出顶起页面布局。$('body').height($('body')[0].clientHeight);
  • 上下拉动滚动条时卡顿/慢怎么解决。
body {
    -webkit-overflow-scrolling: touch;
    overflow-scrolling: touch;
}

1像素问题

retina 屏下 1px 问题是个常谈的问题,相比较普通屏,retina 屏的 1px 线会显得比较粗,设计美感欠缺;在视觉设计师眼里的 1px 是指设备像素 1px,而如果我们直接写 css 的大小 1px,那在 dpr = 2 时,则等于 2px 设备像素,dpr = 3 时,等于 3px 设备像素。所以对于要求处理 1px 的场景,我们要进行特殊处理。

使用 **transform: scale(0.5) + :before / :after **方案解决问题

.calss1 {
  position: relative;
  &::after {
    content:"";
    position: absolute;
    bottom:0px;
    left:0px;
    right:0px;
    border-top:1px solid #666;
    transform: scaleY(0.5);
  }
}

综合篇

强缓存与协商缓存

强缓存(Strong Caching)和协商缓存(Negotiated Caching)是HTTP协议中两种主要的缓存机制,它们帮助浏览器决定如何处理资源,以减少网络流量并提高页面加载速度。

强缓存

强缓存是利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的 expires 和 cache-control 判断目标资源是否“命中”强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通信。

命中强缓存的情况下,返回的 HTTP 状态码为 200。

协商缓存

协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。

如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304

Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers 返回:

Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT

随后我们每次请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:

If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT

服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化。如果发生了变化,就会返回一个完整的响应内容,并在 Response Headers 中添加新的 Last-Modified 值;否则,返回如上图的 304 响应,Response Headers 不会再添加 Last-Modified 字段。

Etag 是由服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的,反之亦然。因此 Etag 能够精准地感知文件的变化。

Etag 和 Last-Modified 类似,当首次请求时,我们会在响应头里获取到一个最初的标识符字符串,那么下一次请求时,请求头里就会带上一个值相同的、名为 if-None-Match 的字符串供服务端比对了。Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。

什么情况会发送预检请求?

  • 对于GET、HEAD、POST这种简答请求是不会发送预检请求的。
  • 但是如果在请求头中包含了自定义字段,那就需要发送。
  • Content-Type不是常见的applicate/x-www-urlencodedmultipart/form-datatext/plain 也需要发送预检请求。

如果我当前在A页面编辑文字内容,不小心点击到了返回可能会退回B页面,那如何阻断这个过程?

在前端开发中会遇到哪些安全问题,你是怎么解决的?

在前端开发中,安全问题是非常重要的考虑因素。我遇到过这些问题:

  1. 跨站脚本攻击(XSS)
    1. 问题描述:XSS 攻击是指攻击者通过注入恶意脚本来窃取用户数据、执行未经授权的操作等。
    2. 问题解决
      1. 对用户提交的数据进行严格验证,在数据输出到页面之前进行编码防止恶意脚本被执行。
      2. 使用 HTTP Only 标志,阻止脚本访问 Cookie。
      3. 设置 CSP 头来限制外部资源加载,减少 XSS 的风险。
  2. 跨站请求伪造
    1. 问题描述:CSRF 攻击是指攻击者诱使已登录用户执行未授权的操作。
    2. 问题解决
      1. 在表单中加入 CSRF token,并在服务器端验证。
      2. Cookie中设置 Same-Site 属性为 StrictLax,减少 CSRF 的风险。
  3. 点击劫持(Clickjacking)
    1. 问题描述:点击劫持是指攻击者通过透明的覆盖层诱骗用户点击,从而执行未授权的操作。
    2. 问题解决
      1. 设置 HTTP 响应头 X-Frame-OptionsDENYSAMEORIGIN,防止页面被嵌入到其他站点的 iframe 中。
      2. 使用 CSP 的 frame-ancestors 指令来进一步限制页面被嵌入的位置。
  4. 第三方库和框架的安全性
    1. 问题描述:第三方库和框架可能存在已知的安全漏洞。
    2. 问题解决:定期检查使用的第三方库和框架的安全性,使用工具如 npm audit 来管理依赖关系的安全性,及时更新依赖项以修复已知的安全漏洞。

Nginx 的负载均衡策略有哪些?

  1. 轮询(Round Robin):默认的负载均衡策略,依次将请求分发给不同的后端服务器。
  2. 加权轮询(Weighted Round Robin):根据服务器的权重分配请求,可以根据服务器性能的不同设置不同的权重。
  3. IP Hash:根据客户端的IP地址进行哈希计算,将同一个客户端的请求分发到同一个后端服务器。
  4. Least Connections:将请求分发给当前连接数最少的后端服务器。
  5. Least Time:将请求分发给响应时间最短的后端服务器。
  6. Random:随机选择一个后端服务器来处理请求。
## 加权轮询
http {
    upstream backend {
        server 192.168.1.10 weight=3;
        server 192.168.1.11 weight=2;
        server 192.168.1.12 weight=1;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}
## IP Hash
http {
    upstream backend {
        ip_hash;

        server 192.168.1.10:8080;
        server 192.168.1.11:8080;
        server 192.168.1.12:8080;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

Dockerfile 中 ADD 和 COPY 有什么区别?

COPY 指令只能从执行 docker build 所在的主机上读取资源并复制到镜像中,而 ADD 指令可以通过 URL 从远程服务器读取资源并复制到镜像中。需求相同时更推荐使用 COPY,ADD 更擅长从远程拉取和解压 tar 包。

Dockerfile 常见的指令以及含义?

  • FROM:基于一个基础镜像来修改
  • WORKDIR:指定当前工作目录
  • COPY:把容器外的内容复制到容器内
  • EXPOSE:声明当前容器要访问的网络端口,比如这里起服务会用到 8080
  • RUN:在容器内执行命令
  • CMD:容器启动的时候执行的命令

MySQL 和 MongoDB 有什么区别,各适用于什么场景?

  • MongoDB 适合用于大数据量、高并发的场景,特别是在需要灵活的数据模型和快速的读写操作时非常适用。它是一个文档型数据库,支持非结构化数据的存储和查询。
  • MySQL 适合用于传统的关系型数据库场景,特别是在需要强一致性和复杂的事务处理时非常适用。它是一个关系型数据库,支持 SQL 查询语言和事务操作。不适合非结构化数据、可扩展性较弱、性能在大数据量场景下有限。

说一说 Vue 和 React 两个框架的区别?

首先从语法层面对别的话:

  • Vue 的 api 较多,React 的 api 相对较少
  • Vue template 结构表现分离,React 使用 jsx 结构表现融合,html、css 都可以写到 js 中
  • Vue 使用插槽,React 万物皆可 props
  • Vue 的 fragment、hook 到 Vue3 才有,Vue 还有丰富的指令

生态方面的话:

  • React 官方只关注底层,上层应用解决方案都交给社区,所以 React 生态体系丰富,社区强,而且每次更新改动小等。
  • Vue 是由官方主导开发和维护,生态没那么丰富,虽然上手比 React 简单一些,但每次更新堪称破土重来。比如 Vue2 有过滤器,Vue3 却没了。

从 Hook 方面来说:

  • React Hook 是以链表的方式储存的,所以有一些限制,比如不能在循环、条件判断、嵌套函数中使用,而且必须在函数的最顶层调用 hook。
  • Vue3 Hook 是基于响应式的,他声明在 setup 里,一次组件实例化调用一次 setup,而 React 每次重新渲染都要重新调用,性能上自然不言而喻,而且可以在循环、条件判断、套函数里使用,并且因为是基于响应式实现的,还自动实现了依赖收集,而 React 需要手动传入依赖等。

什么是微前端?微前端的出现解决了什么问题?

我理解的微前端是一种架构设计或者技术手段,他可以将多个独立的 web 应用聚合起来,提供统一的访问入口。一个微前端应用给用户的感观就是一个完整且独立的应用。但是在技术角度上是由一个个独立的应用组合通过某种方式组合而成的。

他主要解决的问题有:

  • 大型单体应用的可维护性和可拓展性,微前端将应用程序分解为多个独立的部分,使得每个部分都能独立扩展和维护,从而提高整个应用的维护性和灵活性。
  • 多团队开发协同问题、技术栈不一致问题。让每个团队独立地开发和维护他们各自负责的部分,即使每个团队使用的技术栈不一样。
  • 程序的可测试性和可部署性提升,使得每个部分都可以独立进行测试和部署。
  • 增量升级能力,通过微前端的架构模式,可以实现增量升级,这意味着应用程序的升级过程会更加容易和快速。
  • 重构风险降低,通过低风险的局部替换,可以逐步完成大规模的重构工作。

作为团队的负责人,在项目开发的过程中你更关心哪些点?

作为一名前端团队的负责人,在整个项目开发过程中需要关注多个方面以确保项目的顺利进行和高质量交付。

我首先关注的是,需求的理解与沟通。在与产品、设计以及后端团队的紧密合作中,要确保每位成员对需求的理解是清晰的。

然后再到开发流程以及代码质量的管控,做好任务的细分和及时同步开发进度。强调代码规范,使用代码审查工具保证代码质量。

还有风险管控与应对,多与项目组成员沟通项目是否遇到苦难与挑战。在项目遇到挑战时及时寻求解决方案或者调整策略,确保项目里程碑或者版本迭代周期。

最后一个很重要的点是团队的成长与发展,鼓励技术分享和自我提升,创造积极的工作氛围,促进团队协作以及整体能力的提升。