前端面试含鸿蒙面试题

112 阅读19分钟

前端工程师面试题纲(含答案)

React部分

基础概念

  1. 什么是React?它有哪些特点和优势?

    • React是用于构建用户界面的JavaScript库。它采用组件化开发方式,代码可维护和可复用性高;使用虚拟DOM提高性能,支持服务器端渲染和移动端开发。其特点包括声明范式、可与已知库或框架配合等。优势有提高应用性能、方便在客户端和服务器端使用、代码可读性好、易与其他框架集成、便于编写UI测试用例等。
  2. 解释JSX的概念,为什么浏览器无法直接读取JSX,如何处理?

    • JSX是React中特有的语法扩展,允许在JavaScript代码中编写类似HTML的标记,实际会被转换为JavaScript函数调用,最终创建React元素,能方便地将界面和逻辑紧密结合。浏览器只能处理JavaScript对象,不能读取常规JavaScript对象中的JSX,为使浏览器能读取JSX,需用像Babel这样的JSX转换器将JSX文件转换为JavaScript对象,再传给浏览器。
  3. 详细阐述虚拟DOM的工作原理和优势,以及它与真实DOM的区别。

    • 工作原理:虚拟DOM是一个轻量级的JavaScript对象,是真实DOM的内存表示。当底层数据发生改变时,整个UI会在虚拟DOM描述中重新渲染,然后计算之前DOM表示与新表示的差异,最后只用实际更改的内容更新真实DOM。优势包括提高性能(通过diff算法找出最小差异进行更新,减少对真实DOM的操作)、跨平台(方便在不同环境中使用)等。与真实DOM的区别:真实DOM更新缓慢、可直接更新HTML、元素更新时创建新DOM、DOM操作代价高、消耗内存多;虚拟DOM更新更快、无法直接更新HTML、元素更新时更新JSX、DOM操作简单、内存消耗少。
  4. React的ES6语法与ES5相比有哪些不同?

    • 语法区别如下:

      • requireimport:ES5用var React = require('react'),ES6用import React from 'react'
      • exportexports:ES5用module.exports = Component,ES6用export default Component
      • componentfunction:ES5用var MyComponent = React.createClass({ render: function() { ... }}),ES6用class MyComponent extends React.Component { render() { ... }}
      • props:ES5用var App = React.createClass({ propTypes: { name: React.PropTypes.string }, render: function() { ... }}),ES6用class App extends React.Component { render() { ... }}

组件开发

  1. 类组件和函数组件的区别是什么,各有什么应用场景?

    • 语法上,类组件需要继承React.Component并定义render方法,函数组件是纯函数接收props并返回React元素。状态管理方面,类组件可用setState管理内部状态,函数组件在React 16.8之前无状态,之后可用useState等Hooks管理状态。生命周期方面,类组件有完整的生命周期方法,函数组件通过useEffect模拟生命周期。类组件适用于有复杂状态管理和生命周期方法的场景,函数组件适用于简单展示、无状态或状态管理简单的场景。
  2. 如何理解React中的Props和State,它们之间有什么区别,如何更新组件的状态?

    • Props是传递给组件的属性,是不可变的,用于组件间数据传递;State是组件内部的状态,可变,用于存储组件自身的数据。区别在于Props由父组件传递,State由组件自身管理。更新组件状态在类组件中用this.setState方法,在函数组件中用useState返回的更新函数。若新状态依赖旧状态,建议使用函数式更新。
  3. 简述React组件的生命周期,在函数组件中如何模拟生命周期方法?

    • React类组件的生命周期分为挂载、更新、卸载三个阶段。挂载阶段:constructor -> static getDerivedStateFromProps -> render -> componentDidMount;更新阶段:static getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate;卸载阶段:componentWillUnmount。在函数组件中,用useEffect模拟生命周期,不传依赖数组时,每次渲染后执行,类似componentDidMountcomponentDidUpdate;传递空数组时,仅在挂载和卸载时执行,清理函数模拟componentWillUnmount
  4. 如何实现组件之间的通信,有哪些常见的方式?

    • 常见方式有:

      • 父子组件:通过Props传递数据,父组件向子组件传递数据;子组件通过回调函数向父组件传递数据。
      • 兄弟组件:可通过状态提升将共享状态提升到共同的父组件,或使用Context上下文。
      • 复杂场景:使用Redux、Zustand等状态管理库结合新Hook useSyncExternalStore

Hooks相关

  1. 介绍React Hooks的优势和限制,使用时需要注意什么?

    • 优势:可以在不编写class的情况下使用state和其他React特性,复用状态逻辑,让代码更简洁。限制:不能在条件语句、循环或嵌套函数中调用Hooks,必须在React函数的顶层调用。使用时需遵循Hooks规则,确保每次渲染时Hooks的调用顺序一致。
  2. 实现一个自定义Hook,并说明其用途。

    • 示例:
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
     .then((response) => response.json())
     .then((data) => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
}
- 用途:复用数据获取逻辑,在不同组件中使用相同的逻辑获取数据。
  1. useMemo和useCallback的区别是什么,如何使用它们进行性能优化? - useMemo用于缓存变量,返回一个计算结果;useCallback用于缓存函数。使用useMemo缓存计算开销大且依赖特定propsstate的计算结果,避免重复计算;使用useCallback避免函数重建,当函数作为props传递时,防止因函数引用变化导致子组件不必要的重新渲染。

性能优化

  1. React性能优化的常用方法有哪些,举例说明。

    • 常用方法包括:

      • 使用React.memo:用于避免组件不必要的重新渲染,如const MemoizedComponent = React.memo(MyComponent)
      • 使用useMemouseCallbackuseMemo缓存计算结果,useCallback缓存函数。
      • 代码分割:使用React.lazySuspense动态加载组件,如const LazyComponent = React.lazy(() => import('./Component')); <Suspense fallback={<div>Loading...</div>}><LazyComponent /></Suspense>
      • 避免父组件频繁更新:减少父组件的状态更新。
      • 列表渲染优化:为列表中的每一项设置稳定的key
      • 使用生产环境构建:移除开发模式的一些额外检查。
  2. 如何避免组件的不必要重新渲染,有哪些工具和技巧?

    • 可使用React.memo包裹函数组件,进行浅比较,仅在props变化时重新渲染;在类组件中使用shouldComponentUpdate生命周期方法,根据propsstate的变化决定是否更新组件;使用useMemouseCallback缓存值和函数,避免因引用变化导致的重新渲染。
  3. 解释React 18的新特性,如并发模式、Suspense、自动批处理等。

    • 并发模式:允许React在渲染时根据任务优先级中断和恢复工作,优先处理高优先级任务(如用户输入),提升用户体验,通过时间切片和优先级调度优化渲染性能。
    • Suspense:增强了对服务器端渲染和数据获取的支持,可用于延迟加载组件、数据获取和服务端渲染,配合React.lazy使用。
    • 自动批处理:默认对所有状态更新进行批处理(包括异步操作),减少不必要的重新渲染,将多个状态更新合并为一次重新渲染。

原理深挖

  1. React的Diff算法是如何工作的,有哪些优化策略?

    • 工作原理:采用同层比较,假设同一层级的节点可以高效比较,使用唯一key提高比较性能,相同类型的组件仅更新属性,不重新创建。优化策略包括使用唯一key、避免跨层级移动节点、相同类型组件尽量只更新属性等。
  2. React Fiber架构的核心思想是什么,解决了什么问题?

    • 核心思想是将任务拆分为多个小任务,支持可中断的渲染,采用链表结构和双缓冲树实现可中断更新,使用requestIdleCallback调度任务优先级。解决了旧架构递归不可中断导致的卡顿问题,优先处理高优先级任务,提升用户体验。
  3. JSX编译后的代码结构是怎样的,Babel插件对JSX做了哪些转换?

    • JSX编译后会转换为React.createElement调用,例如<Button type="primary">Click</Button>编译后为React.createElement(Button, {type: "primary"}, "Click")。Babel插件将JSX语法转换为JavaScript代码,使其能在浏览器中运行。

JavaScript部分

基础语法

  1. 列举JavaScript的基本数据类型和引用数据类型,如何判断数据类型?

    • 基本数据类型包括numberstringbooleannullundefinedsymbolbigint;引用数据类型包括object(如普通对象、数组、函数等)。判断数据类型的方法有:typeof运算符可判断基本数据类型,但对null会返回object,对引用数据类型只能判断出objectfunctioninstanceof运算符用于判断一个对象是否是某个构造函数的实例;Object.prototype.toString.call()方法可以准确判断各种数据类型。
  2. 解释变量提升和作用域链的概念,结合代码说明。

    • 变量提升是指在JavaScript代码执行之前,JavaScript引擎会将所有的变量和函数声明“提升”到其所在作用域的顶部,但只有声明会被提升,赋值操作不会。例如:
console.log(a); // 输出undefined
var a = 10;
- 作用域链是从内到外的查找顺序,函数内先找自己的作用域,再找外层,最后是全局。例如:
var a = 10;
function fn() {
  var b = 20;
  console.log(b); // 20
  console.log(a); // 10
}
fn();
  1. var、let和const的区别是什么,使用场景有哪些?

    • var声明的变量具有函数作用域,存在变量提升,可重复声明和赋值;letconst具有块级作用域,不存在变量提升,let可重新赋值,const声明时必须赋值且不能重新赋值。var适用于旧代码或需要函数作用域的场景;letconst适用于现代JavaScript开发,优先使用const,当值需要改变时用let
  2. 什么是闭包,在实际开发中有哪些应用场景?

    • 闭包是函数和声明该函数的词法环境的组合。在JavaScript中,闭包常用于封装私有变量或状态,或实现回调函数和高阶函数。应用场景包括实现私有变量、函数防抖/节流、模块化开发中封装内部逻辑等。例如:
function outer() {
  let count = 0;
  return function inner() {
    count++;
    return count;
  };
}
const add = outer();
console.log(add()); // 1
console.log(add()); // 2

异步编程

  1. 解释JavaScript中的异步编程,列举几种实现异步编程的方法。

    • 异步编程允许JavaScript在等待某个操作(如网络请求或定时器)完成时,继续执行其他代码,避免阻塞UI或浪费CPU资源。实现异步编程的方法有回调函数、Promises、async/await等。
  2. 如何使用Promise处理异步操作,链式调用和错误捕获的机制是怎样的?

    • 创建Promise实例时,传入一个执行器函数,该函数接收resolvereject两个参数,分别用于标记操作成功和失败。例如:
const fetchData = new Promise((resolve, reject) => {
  setTimeout(() => {
    const data = '成功获取数据';
    resolve(data);
  }, 1000);
});
- 链式调用通过`then`方法实现,可连续处理多个异步步骤,如`fetchData.then(result => { return result.split(' '); }).then(arr => { console.log(arr); });`。错误捕获使用`catch`方法,当`Promise``reject`时会执行`catch`中的回调函数。
  1. 简述async/await的实现原理,与Promise相比有什么优势? - async/await是基于Promise实现的语法糖,async函数返回一个Promise对象,await只能在async函数中使用,用于等待一个Promise的解决,并返回其结果。优势在于代码更简洁、更像同步代码,避免了回调地狱,提高了代码的可读性和可维护性。

事件机制

  1. 解释JavaScript中的事件委托,它的原理和优势是什么?

    • 事件委托是指将事件监听器添加到父元素上,利用事件冒泡的原理,当子元素上的事件触发时,事件会冒泡到父元素上,由父元素的事件监听器处理。原理基于事件冒泡,即事件从触发的元素开始,依次向上传播到父元素。优势包括减少事件监听器的数量,提高性能;动态添加子元素时无需为新元素单独添加事件监听器。
  2. 如何处理跨域问题,有哪些常见的解决方案?

    • 常见解决方案有:

      • CORS(跨域资源共享):服务器端设置响应头,允许特定域名的请求访问资源。
      • JSONP(JSON with Padding):利用<script>标签的src属性不受同源策略限制的特点,通过动态创建<script>标签实现跨域数据请求。
      • PostMessage:用于在不同窗口或iframe之间传递消息。
  3. 简述Cookie、LocalStorage和SessionStorage的区别和使用场景。

    • 区别:

      • Cookie:数据会随HTTP请求发送到服务器,有大小限制(一般不超过4KB),可设置过期时间,存在安全风险(如CSRF攻击)。
      • LocalStorage:数据会永久存储在浏览器中,除非手动删除,大小限制一般为5MB,不随HTTP请求发送到服务器。
      • SessionStorage:数据在会话期间有效,关闭窗口或标签页后数据会被清除,大小限制一般为5MB,不随HTTP请求发送到服务器。
    • 使用场景:Cookie用于在客户端和服务器之间传递数据,如用户登录状态;LocalStorage用于长期保存数据,如用户的偏好设置;SessionStorage用于临时保存会话期间的数据,如购物车信息。

性能优化

  1. 如何优化JavaScript代码的性能,有哪些常见的技巧?

    • 常见技巧包括:减少全局变量的使用,避免变量污染和查找时间过长;使用事件委托减少事件监听器的数量;避免使用with语句和eval函数;合理使用缓存,避免重复计算;使用requestAnimationFrame代替setTimeoutsetInterval进行动画处理;压缩和合并代码,减少HTTP请求。
  2. 解释事件循环机制,宏任务和微任务的区别是什么?

    • 事件循环是JavaScript处理异步任务的机制,JavaScript是单线程的,同一时间只能做一件事。事件循环先执行同步任务(主线程排队),再处理异步任务。异步任务分为宏任务和微任务,微任务优先级更高,会在宏任务之前执行。宏任务如setTimeoutsetInterval等,微任务如Promise.thenMutationObserver等。
  3. 如何避免内存泄漏,有哪些常见的原因和解决方法?

    • 常见原因包括:意外的全局变量、未清除的定时器或回调函数、闭包、DOM引用未释放等。解决方法有:避免意外的全局变量,使用严格模式;在组件销毁时清除定时器和回调函数;合理使用闭包,避免不必要的闭包;及时释放不再使用的DOM引用。

工程化与工具

  1. 介绍常见的前端构建工具,如Webpack、Vite等,它们的原理和区别是什么?

    • Webpack是一个模块打包工具,通过loader处理不同类型的模块,用plugin扩展功能。原理是从入口文件开始,递归分析所有依赖的模块,将这些模块打包成一个或多个文件。Vite是基于ES模块的开发服务器和构建工具,利用浏览器原生ES模块的支持,实现快速冷启动。区别在于Webpack功能强大,适用于大型复杂项目,配置复杂;Vite冷启动快,开发体验好,适用于小型项目和快速迭代开发。
  2. 如何进行代码规范和质量检查,有哪些工具可以使用?

    • 可使用ESLint进行代码规范检查,通过配置规则检查代码是否符合规范;使用Prettier进行代码格式化,使代码风格统一。还可使用Jest、Mocha等工具进行单元测试,确保代码质量。
  3. 简述前端自动化测试的重要性和常见的测试框架。

    • 重要性:提高代码质量、减少人为错误、便于代码重构、增强团队协作。常见测试框架有Jest(功能强大、易于配置和使用)、Mocha(灵活、可搭配其他断言库)、Jasmine(自带断言和模拟功能)等。

鸿蒙ArkTS部分

基础概念

  1. 什么是ArkTS,它与TypeScript有什么关系和区别?

    • ArkTS是HarmonyOS优选的主力应用开发语言,保持了TypeScript的基本风格,同时通过规范定义强化开发期静态检查和分析,提升程序执行稳定性和性能。它基于TypeScript扩展,强化静态类型检查,新增鸿蒙专属语法,如装饰器、状态管理等。与TypeScript的区别在于扩展了UI(定义声明式UI描述、自定义组件等),强化了检查(不支持部分TypeScript特性)。
  2. 简述ArkTS的核心优势和特点,如声明式UI、状态管理等。

    • 核心优势和特点包括:静态类型检查,在编译时进行类型检查,有助于在代码运行前发现和修复错误;声明式UI,允许开发者以更简洁、更自然的方式开发跨端应用;状态管理,提供多维度的状态管理机制,支持单向和双向数据流;渲染控制,支持条件渲染、循环渲染和数据懒加载;兼容性,兼容TS/JavaScript生态,可使用TS/JS进行开发或复用已有代码;并发机制,支持轻量化的并发机制,提高应用的性能和响应速度。

语法特性

  1. 介绍ArkTS中的装饰器,如@Entry、@Component、@State等,它们的作用是什么?

    • @Entry:标记应用入口组件。
    • @Component:定义可复用的UI组件。
    • @State:管理组件内部状态,状态变化会触发UI更新。
    • @Prop:父组件向子组件传递单向数据。
    • @Link:实现父子组件双向数据绑定。
    • @Provide@Consume:用于与后代组件的双向数据同步,适用于状态数据在多个层级之间传递的场景。
  2. 解释ArkTS中的状态管理机制,如何实现组件之间的状态同步?

    • ArkTS通过装饰器实现状态管理,如@State管理组件内部状态,@Prop实现单向数据传递,@Link实现双向数据绑定,@Provide@Consume用于跨层级数据同步。实现组件之间的状态同步可通过以下方式:父子组件使用@Prop@Link;跨组件使用@Provide@Consume;对于多层嵌套的情况,使用@Observed@ObjectLink

开发实践

  1. 如何进行ArkTS应用的开发,包括环境搭建、项目结构等。

    • 环境搭建:需安装DevEco Studio,获取OpenHarmony SDK,搭建烧录环境(如使用RK3568开发板),完成开发环境配置,使用工程向导创建工程(模板选择“Empty Ability”),选择真机进行调测。项目结构一般包括代码区(如entry/src/main/ets)和资源文件区(如entry/src/main/resources)。
  2. 简述ArkTS应用的生命周期管理,以及如何处理组件的创建和销毁。

    • 页面生命周期(被@Entry装饰的组件):onPageShow(页面每次显示时触发一次)、onPageHide(页面每次隐藏时触发一次)、onBackPress(用户点击返回按钮时触发)。组件生命周期(一般用@Component装饰的自定义组件):aboutToAppear(组件即将出现时回调)、onDidBuild(组件build()函数执行完成之后回调)、aboutToDisappear(自定义组件析构销毁之前执行)。在aboutToDisappear中可进行资源释放等操作,但不允许改变状态变量。

框架与组件

  1. 介绍ArkUI框架的主要组件和布局容器,如何实现自定义组件?

    • 主要组件有Text(显示文本)、Button(按钮)、Image(图片)等;布局容器有Column(纵向排列)、Row(横向排列)、Stack(层叠)等。实现自定义组件步骤:用@Component定义结构体,build()返回UI描述。例如:
@Component
struct CustomButton {
  build() {
    Button('Custom Button').onClick(() => {
      console.log('Button clicked');
    })
  }
}
  1. 如何提高ArkTS应用的性能,有哪些优化策略?

    • 优化策略包括:避免频繁更新@State,使用@ObjectLink处理复杂对象;列表渲染时通过ForEachkeyGenerator提升复用效率;及时释放资源,在aboutToDisappear中取消定时器、关闭文件句柄;减少全局变量,优先使用@State替代AppStorage

典型面试题示例

  1. 如何实现父子组件间的双向绑定?

    • 子组件使用@Link修饰变量,父组件通过$符号传递(如$variable)。例如:
// 父组件
@Component
struct Parent {
  @State count: number = 0
  build() {
    Child({ count: $count })
  }
}
// 子组件
@Component
struct Child {
  @Link count: number
  build() {
    Button('Increment').onClick(() => {
      this.count++
    })
  }
}
  1. 指出以下代码的问题并给出解决方案:
@Component
struct Demo {
  @State count: number = 0
  build() {
    Button('Click').onClick(() => {
      this.count += 1
    })
  }
}
- 问题:缺少`@Entry`装饰器,无法作为入口组件;`Button`未添加到布局容器(如`Column`)中。解决方案:添加`@Entry`装饰器,将`Button`添加到布局容器中。
@Entry
@Component
struct Demo {
  @State count: number = 0
  build() {
    Column() {
      Button('Click').onClick(() => {
        this.count += 1
      })
    }
  }
}
  1. 如何实现一个跨设备同步的待办清单应用?

    • 使用DistributedDataManager同步数据,通过@Observed@ObjectLink监听列表变化。例如,定义待办事项数据模型,使用DistributedDataManager进行数据存储和同步,在组件中使用@Observed@ObjectLink监听数据变化并更新UI。

面试题

  1. 你在前端工作中有什么是你引以为傲的,有什么优势

    • 性能优化
    • 效率提升