前端面试题详解整理100|堆和栈,zustand和redux状态管理库,重排重绘,flexbox,

358 阅读13分钟

字节前端实习一面

58min

1.说一下学前端的历程和路线,都是通过什么方式学的

2.介绍一下项目的难点

3.说一下技术选型的原因

4.zustand和redux状态管理库的区别

Zustand 和 Redux 都是流行的状态管理库,用于管理 React 应用程序中的状态。它们之间的主要区别包括以下几点:

  1. API 和用法:

    • Redux: Redux 是一个基于 Flux 架构的状态管理库,它使用统一的 Store 来存储整个应用程序的状态。Redux 使用纯函数来处理状态的变化,通过 reducer 函数来处理 action,并通过中间件来扩展 Redux 的功能。Redux 还提供了一系列辅助函数和工具来简化状态管理的流程。
    • Zustand: Zustand 是一个基于 Hooks 的状态管理库,它与 React 更紧密地集成在一起。Zustand 允许您在组件中使用 useState 风格的 API 来创建和管理状态。与 Redux 不同,Zustand 不需要单独的 Store,每个状态都是一个独立的 Hook,因此可以更灵活地管理状态。
  2. 复杂性:

    • Redux: Redux 通常被认为是一个相对复杂的库,特别是对于初学者来说,需要理解一些概念,比如 actions、reducers、middleware、selectors 等。虽然 Redux 提供了强大的工具和生态系统,但有时可能需要花费一些时间来学习和实现。
    • Zustand: 相比之下,Zustand 更加简单直观。它不需要编写独立的 reducers 和 actions,而是将状态逻辑直接放在组件中。这样可以减少一些繁琐的代码,并使状态管理更加直观。
  3. 性能:

    • Redux: Redux 在性能方面表现良好,因为它使用了严格的不可变数据模式和单一的状态树,这使得状态的变化可以被有效地跟踪和管理。
    • Zustand: Zustand 也被设计成具有良好的性能。由于每个状态都是一个独立的 Hook,因此状态更新可以更加精确地触发,并且不会影响到其他状态。此外,Zustand 还提供了许多性能优化选项,例如订阅的选择和批量更新。

总的来说,Redux 适用于大型、复杂的应用程序,需要严格的状态管理和数据流程控制;而 Zustand 更适用于中小型的应用程序,可以更快地上手和使用,同时也提供了足够的灵活性和性能。选择哪个库取决于您的项目需求、团队的经验水平以及个人偏好。

5.react和vue学习上有哪些区别有什么感受

学习 React 和 Vue 有一些区别,主要涉及到它们的生态系统、概念和 API 设计。以下是一些关于学习 React 和 Vue 的区别和感受:

  1. 生态系统

    • React:React 生态系统更加广泛和成熟,有大量的第三方库和工具可供选择,例如 React Router 用于路由管理、Redux 用于状态管理、Material-UI 和 Ant Design 用于 UI 组件等。React 还有强大的社区支持,文档和教程非常丰富。
    • Vue:Vue 生态系统也很健全,虽然相对于 React 来说规模稍小,但也有许多优秀的库和工具。Vue 的设计理念更加一致,很多官方推荐的库都可以很好地集成到 Vue 项目中。Vue 社区也在不断壮大,提供了很多高质量的资源和解决方案。
  2. 概念

    • React:React 的核心概念包括组件、虚拟 DOM、状态和 props 等。React 的设计理念是将 UI 拆分成可复用的组件,并通过状态管理来管理组件的数据和交互。React 也支持函数式编程的思想,使得组件之间的数据流动更加清晰。
    • Vue:Vue 的核心概念包括组件、响应式数据、指令和计算属性等。Vue 的设计理念是通过响应式数据来驱动视图的变化,并且提供了一套灵活的指令和计算属性来处理 DOM 操作和数据逻辑。
  3. API 设计

    • React:React 的 API 设计更加灵活和函数式,通过函数式组件和 Hooks 来实现组件的逻辑复用和状态管理。React 还提供了一些高阶组件和 render props 等模式来实现组件的复用和组合。
    • Vue:Vue 的 API 设计更加直观和一致,通过选项式 API 和组件选项来实现组件的定义和逻辑编写。Vue 也提供了一些内置的指令和生命周期钩子等来处理组件的渲染和交互。

总的来说,学习 React 和 Vue 都需要掌握一定的前端基础知识,例如 JavaScript、HTML 和 CSS。React 更注重于函数式编程和组件化思想,适合喜欢函数式编程和灵活性的开发者;Vue 则更注重于一致性和直观性,适合喜欢模板语法和选项式 API 的开发者。选择学习哪个框架取决于个人的偏好、项目需求和团队的技术栈。

l6.css优先级

在 CSS 中,样式的优先级决定了在多个规则应用到同一个元素时哪个规则会被应用。CSS 的优先级是根据选择器的特定性(specificity)来计算的,特定性越高的规则拥有更高的优先级。如果两个规则具有相同的特定性,则后声明的规则将覆盖先声明的规则。

特定性可以根据以下规则计算:

  1. 对于每个 ID 选择器,特定性增加 100。
  2. 对于每个类选择器、属性选择器或者伪类选择器,特定性增加 10。
  3. 对于每个元素选择器或者伪元素选择器,特定性增加 1。
  4. 通用选择器(*)、子选择器(>)、相邻兄弟选择器(+)等不增加特定性。
  5. 内联样式的特定性始终最高,为 1000。

以下是一个特定性的示例:

  • p:特定性为 1。
  • .example:特定性为 10。
  • #header:特定性为 100。
  • p.example:特定性为 11(1 + 10)。
  • #header .example:特定性为 110(100 + 10)。

如果两个规则具有相同的特定性,则后声明的规则将覆盖先声明的规则。例如:

.example {
  color: red;
}

p.example {
  color: blue;
}

在这个例子中,p.example 的特定性为 11,而 .example 的特定性为 10,因此 p.example 的颜色属性将会被应用,文字颜色为蓝色。

如果特定性相同,那么后声明的样式表规则会覆盖先声明的规则。这意味着,当样式表中有多个相同选择器的规则时,位于后面的规则将会覆盖前面的规则。

例如:

.example {
  color: red;
}

.example {
  color: blue;
}

在这个例子中,.example 的文字颜色最终会是蓝色,因为后面的规则覆盖了前面的规则。

7.flex布局的常用属性

Flexbox(弹性盒子布局)是一种用于在容器中对子元素进行布局的 CSS 技术。以下是 Flexbox 布局中常用的属性:

  1. 容器属性

    • display: 设置容器为 flex 或 inline-flex,决定了该容器使用 Flexbox 布局。
    • flex-direction: 定义主轴的方向,可以是 row(默认值,水平方向)、row-reversecolumn(垂直方向)、column-reverse
    • flex-wrap: 定义子元素在容器内的换行方式,可以是 nowrap(默认值,不换行)、wrapwrap-reverse
    • justify-content: 定义了子元素在主轴上的对齐方式,可以是 flex-startflex-endcenterspace-betweenspace-around
    • align-items: 定义了子元素在交叉轴上的对齐方式,可以是 flex-startflex-endcenterbaselinestretch
    • align-content: 仅在多行情况下生效,定义了多行子元素在交叉轴上的对齐方式,可以是 flex-startflex-endcenterspace-betweenspace-aroundstretch
  2. 子元素属性

    • order: 设置子元素的排列顺序,默认为 0。可以为负值,负值越小,排列越靠前。
    • flex-grow: 定义了子元素的放大比例,默认为 0,即不放大。如果所有子元素的 flex-grow 都为 1,则它们会等分剩余空间。
    • flex-shrink: 定义了子元素的缩小比例,默认为 1。如果父容器空间不足,可以设置该属性来决定是否缩小。
    • flex-basis: 定义了子元素在没有放大或缩小之前的初始尺寸。它可以是一个固定的长度,也可以是 auto(默认值)。
    • flex: 是 flex-growflex-shrinkflex-basis 的缩写形式,可以一次性设置这三个属性的值。

这些是 Flexbox 布局中最常用的属性,通过这些属性可以灵活地控制容器和子元素的布局方式。Flexbox 布局非常适合用于构建响应式和灵活的布局结构。

8.重排和重绘,如何减少重排

重排(Reflow)和重绘(Repaint)是浏览器渲染页面时的两个重要概念。

  1. 重排(Reflow):当 DOM 的尺寸、结构或某些属性发生改变时,浏览器需要重新计算元素的位置和大小,以及重新布局整个页面的结构,这个过程就称为重排。重排会触发重新布局,消耗较大的计算资源,因此性能开销较高。

  2. 重绘(Repaint):当元素的样式发生改变,但不影响其布局时,浏览器只需要重新绘制元素的样式,而不需要重新布局页面,这个过程就称为重绘。重绘的性能开销比重排小很多。

减少重排的方法包括:

  1. 批量修改样式:避免在多个操作之间频繁地修改 DOM 样式,而是尽可能将样式修改操作合并到一起,这样可以减少重排次数。例如,使用 CSS 类一次性修改多个元素的样式。

  2. 离线操作:在对 DOM 进行修改之前,先将其从文档中移除(display: none),进行完修改后再重新插入文档中。这样可以避免多次重排。

  3. 使用文档片段:当需要创建多个 DOM 节点时,可以先创建一个文档片段(DocumentFragment),在其中进行节点的创建和修改操作,然后一次性将文档片段添加到文档中。这样可以减少多次重排。

  4. 缓存布局信息:如果需要多次读取元素的布局信息(比如宽度、高度等),可以将这些信息缓存起来,避免多次重排。比如使用 getBoundingClientRect() 方法获取元素的位置和大小,然后缓存起来。

  5. 使用 CSS3 动画:使用 CSS3 的动画效果(如 transform、opacity)可以利用 GPU 加速,减少重排和重绘的开销。

  6. 避免强制同步布局:某些属性(如 offsetWidth、offsetHeight、scrollTop 等)会强制浏览器同步布局,因此在读取这些属性时会触发重排。尽量避免在频繁读取这些属性的情况下触发重排。

通过以上方法,可以有效地减少页面的重排次数,提高页面渲染性能。

9.commonjs和es6的模块化两种导入规范的区别

CommonJS 和 ES6 是两种不同的模块化规范,在 JavaScript 中被广泛使用。它们之间的主要区别包括以下几点:

  1. 语法

    • CommonJS:CommonJS 使用 require() 来导入模块,使用 module.exportsexports 来导出模块。例如:
      // 导入模块
      const moduleA = require('./moduleA');
      
      // 导出模块
      module.exports = { foo: 'bar' };
      // 或者
      exports.foo = 'bar';
      
    • ES6:ES6 使用 import 来导入模块,使用 export 来导出模块。例如:
      // 导入模块
      import moduleA from './moduleA';
      
      // 导出模块
      export const foo = 'bar';
      // 或者
      export default { foo: 'bar' };
      
  2. 静态导入

    • CommonJS:CommonJS 是动态导入模块的,意味着模块的依赖关系在运行时确定。这意味着 require() 可以放在任何地方,甚至在条件语句中,从而导致一些运行时的不确定性。
    • ES6:ES6 是静态导入模块的,模块的依赖关系在编译时就已经确定。这意味着 import 必须在模块的顶层使用,不能放在条件语句或循环中。
  3. 导入导出形式

    • CommonJS:CommonJS 导入的是整个模块对象或其成员,例如 const moduleA = require('./moduleA') 导入的是整个 moduleA 对象。
    • ES6:ES6 支持默认导出和命名导出。默认导出可以是任何值(对象、函数、类等),而命名导出则导出指定的变量、函数、类等。默认导出使用 export default,命名导出使用 export
  4. 循环依赖

    • CommonJS:CommonJS 允许循环依赖,但可能会导致一些问题,比如模块导入的时候可能得到不完整的对象。
    • ES6:ES6 不允许循环依赖,会在导入循环依赖时抛出错误,这有助于避免一些潜在的问题。

总的来说,CommonJS 和 ES6 是两种不同的模块化规范,各有其特点和适用场景。CommonJS 主要用于服务器端(Node.js)的模块化开发,而 ES6 则是浏览器端和现代前端开发中的主流模块化规范。

10.js的基础类型,堆内存和栈内存 JavaScript 的基础类型包括原始类型和引用类型。而关于堆内存和栈内存,则是与内存管理相关的概念。

JavaScript 的基础类型:

  1. 原始类型(Primitive Types)

    • number: 数值类型,包括整数和浮点数。
    • string: 字符串类型。
    • boolean: 布尔类型,只有 truefalse
    • undefined: 表示未定义或未初始化的值。
    • null: 表示空值或不存在的对象。
    • symbol: 符号类型,表示唯一的标识符。
    • bigint: 大整数类型,用于表示大于 Number.MAX_SAFE_INTEGER 的整数。
  2. 引用类型(Reference Types)

    • object: 对象类型,包括普通对象、数组、函数等。
    • function: 函数类型。
    • 其他引用类型:例如 Date、RegExp 等。

堆内存和栈内存:

  1. 堆内存(Heap Memory)

    • 堆内存用于存储引用类型的值,如对象、数组等。它是一块动态分配的内存区域,用于存储动态创建的对象和数据。
    • 在堆内存中,存储的是引用类型的值,而变量保存的是对这些值的引用(指针)。
    • 堆内存的内存空间由垃圾回收机制来管理,当不再有引用指向某个对象时,该对象占用的内存空间将被自动回收。
  2. 栈内存(Stack Memory)

    • 栈内存用于存储基本数据类型的值以及函数调用时的局部变量和执行上下文。
    • 在栈内存中,每个函数调用都会创建一个执行上下文(Execution Context),包括函数的参数、局部变量、返回地址等。
    • 当函数执行完成后,其对应的执行上下文会被销毁,栈内存中的空间会被释放。

在 JavaScript 中,基本类型的值通常存储在栈内存中,而引用类型的值则存储在堆内存中。变量在栈内存中存储的是对应值在堆内存中的引用。这种内存管理方式使得 JavaScript 具有一定的灵活性和便利性,但也需要注意内存泄漏等问题。