小题库总结

10 阅读15分钟

JS

事件循环

浏览器主线程调度任务的一种方式,会先执行宏任务,然后执行微任务,触发浏览器渲染(如果需要的话)

Promise 链式调用

// Promise 有三种状态,默认的 Pending,以及变更后的 Fulfilled & Rejected
// new Promise 中的回调会同步被执行
// then 方法会同步执行,then 方法中成功和失败的回调会异步执行;可以链式调用,会返回新的 promise
class Promise{
    constructor(executor) {
    this.state = 'pending'
    this.value = null
    this.reason = null
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []
    function resolve(value) {
        if(this.state === 'pending') {
            this.state = 'fullfilled'
            this.value = value
            this.onFulfilledCallbacks.forEach(fn => fn())
        }
    }

    function reject(reason) {
        if(this.state === 'pending') {
            this.state = 'rejected'
            this.reason = reason
            this.onRejectedCallbacks.forEach(fn => fn())
        }
    }

    try {
        excutor(resolve, reject)
    } catch (err) {
        reject(err)
    }
   }

    then(onFullfilled, onRejected) {
        const onFullfilledFn = typeof onFullfilled === 'function' ? onFullfilled : (value) => value
        const onRejectedFn = typeof onRejected === 'function' ? onRejected : () => { throw reason }
        
        const promise = new Promise((resolve, reject) => { 
            const handleFullfilled = () => {                    setTimeout(() => {
                        // 此处可以使用 try catch
                        const value = onFullfilled(this.value)
                        resolvePromise(promise, value, resolve, reject)
                    })
               }              const handleRejected = () => {                    
                setTimeout(() => {                        // 此处可以使用 try catch
                        const value = onRejected(this.reason)
                        resolvePromise(promise, value, resolve, reject)
                    })
               }
         })

        return promise
    }


    function resolvePromise(promise, result, resolve, reject) {
        if(promise === result) return reject(new TypeError('Chaining cycle detected'))
        if(result instanceof Promise) {
            result.then(resolve, reject)
        }else {
            resolve(result)
        }
    }
}

React 

源码

Hooks 

useState: 状态管理,可能批量合并

useEffect:替代类组件的 mount 与 update 钩子,处理副作用,比如数据获取、事件监听、DOM 操作等,不会阻塞渲染

useLayoutEffect:读取 DOM 同步更新的场景,可能阻塞渲染,用于滚动位置恢复(返回之前的页面滚动到具体位置)、动画等

useReducer:复杂状态管理,reducer 方法一般接受两个参数,一个是维护的旧状态,一个是新状态相关的行为和值,行为维护在 type 中,可以集中管理状态逻辑,比如维护表单的状态,一个表单通常要做的处理就是用户输入、校验报错、提交、提交失败、提交成功

// 表单状态管理示例
const initialState = {
  username: '',
  password: '',
  errors: { username: '', password: '' },
  loading: false
};

function formReducer(state, action) {
  switch (action.type) {
    case 'FIELD_CHANGE':
      return {
        ...state,
        [action.field]: action.value,
        errors: { ...state.errors, [action.field]: '' } // 清除当前字段错误
      };
    case 'VALIDATION_ERROR':
      return {
        ...state,
        errors: { ...state.errors, [action.field]: action.message }
      };
    case 'SUBMIT_START':
      return { ...state, loading: true };
    case 'SUBMIT_SUCCESS':
      return initialState; // 重置表单
    case 'SUBMIT_FAILURE':
      return { ...state, loading: false, errors: action.errors };
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(formReducer, initialState);
dispatch({ type: 'FIELD_CHANGE', field: name, value });

useSyncExternalStore:安全订阅外部数据源(如浏览器 API、第三方库等)

useTransition:标记低优先级更新,比如搜索结果

useDeferredValue:延迟计算某个值的更新,旧值会保持显示,知道新值计算完成

useRef:需要在多次渲染过程中共享值且不需要触发重新渲染的场景,比如获取 DOM 元素、缓存第三方库实例(如地图初始化)、解决闭包陷阱,实现中断请求时定义 AbortController 实例等(曾经遇到过的 bug)

 const [count, setCount] = useState(0);
  const countRef = useRef(count);
  
  // 保持 ref 与最新状态同步
  useEffect(() => {
    countRef.current = count;
  }, [count]);

  const handleClick = () => {
    // 异步操作中使用 ref 获取最新值
    setTimeout(() => {
      console.log('Current count:', countRef.current);
    }, 3000);
  };

useImperativeHandle:自定义 ref 暴露,配合 forwordRef, 通过这个属性可以给父组件暴露一些方法,比如表单的 onSubmit(受控与非受控)

useContext:层级数据传递,createContext + useContext, 会有跨组件传递的问题

useMemo:缓存复杂运算,参数传递给子组件,子组件使用了 memo 进行优化;react19 进行了优化,大的计算自动缓存

useCallback:缓存函数引用,函数传递给子组件,子组件使用了 memo 进行优化;不过 react19 已经进行了优化,如何依赖稳定的 props 或者 state 会自动保持引用稳定,避免子组件重新渲染

React.memo:子组件属性是静态的,父组件存在频繁的更新操作;比如父组件有 input 组件,子组件是 Card

闭包陷阱

react 的闭包陷阱是指可能在副作用函数中使用过期的值,这是 react 组件的渲染机制与闭包的特性共同作用的结果,react 每次渲染都只能拿到当前状态的值,导致旧的渲染的闭包里只能拿到旧的值,构成闭包的条件就是词法作用域的保留,代码存在副作用就会存在词法作用域的保留,副作用是指与当前函数主功能无关的功能

useEffect 依赖数组

虚拟 DOM 

用 JavaScript 对象表示真实 DOM,包括 type、props 等属性

它只是对真实 DOM 的表示,为 React 的跨端提供了可能

Diff 逻辑

是否复用旧节点的一种比较方式,旧节点复用后可以减少 DOM 操作

逐层比较 + 子节点比较(用于列表)

若子列表配置了 key,开启双端比较算法

双端比较算法:通过两端分别比较,简化了算法复杂度,解决了高频列表变化的场景,比如顺序改变,在头部插入、尾部插入都能实现高效比对

React 的 Diff 采用了 Fiber 架构,核心逻辑如下:

如果同一位置新旧节点的类型相同,会直接复用旧节点 的 DOM,并把更新标记为 'UPDATE'

如果同一位置新旧节点的类型不同,会创建新节点,并把更新标记为 'REPLACEMENT';删除老节点,并把更新标记标记为 'DELETION'

Fiber 架构核心思想

传统的 diff 算法采用递归遍历,只能同步渲染,如果组件树比较大就会造成页面卡顿,因为组件树大,处理他们比较耗时嘛

Fiber 架构采用增量渲染、可中断/恢复执行、优先级调度的机制实现了异步渲染,解决了这个问题

增量渲染:采用任务切片 + 时间切片的方式,每次 Fiber 节点的处理就是一个任务,使用 requestIdleCallback API 实现,这个 API 的作用是在浏览器主线程每一帧结尾的空闲时执行

可中断恢复执行:Fiber 树采用链表结构,每个 Fiber 节点上都保存了父节点、兄弟节点、以及子节点的引用,可以很方便拿到下一次的任务节点

优先级调度:将任务分为紧急与非紧急,比如用户输入、动画比渲染的优先级更高,用户输入的任务可以抢占渲染的任务,这就避免了用户在渲染过程中用户操作的卡顿

实现简易 Hooks

解决请求竞技问题的钩子、

QA

useReducer + context 与 redux 如何选型?

useReducer + context 适合维护单组件或者轻量的跨组件的状态,redux 有更丰富的生态,也提供了相关的调试工具,适合维护跨大量组件的全局状态

react 为什么设计成 immutable 的,与 vue 的区别?

diff 高效对比的基础,仅仅比较引用,带来的问题:深层数据更新,性能问题,改变数组的方法无法触发重新渲染;vue 是可变的

useEffect 与 useState 的实现,setState 是如何做到批处理与批量更新的

Vue 

响应式原理

性能优化

性能优化好处:提升用户体验:FCP 从 12.9 -> 2.2,LCP 从 13.4 -> 2.5、驱动业务增加、降低运营成本

字体与资源加载优化:

字体预加载与子集切割:

使用 unplugin-fonts 预加载字体,减少阻塞时间

通过 vite-plugin-font 按页面切割字体,首屏字体体积从 2.1MB 降至 50KB

图片格式转换与懒加载:

转换为 WebP 格式,体积减少 40%

自定义指令实现图片懒加载,减少首屏请求数

代码构建与分包优化

**路由按需加载:**配置 vite-plugin-pages 异步加载非首屏路由

Tree Shaking: 移除副作用模块和循环依赖,主包体积减少 70KB

**手动分包策略:将 Vue VueRouter 直接包含在主包,**将 lodash-es、axios 等拆分为独立 chunk,提升首屏 FCP 与 LCP

CSS 优化: Critial CSS 与 Tailwind CSS purge

OA

Q: 如何选择优化优先级

A: 阻塞资源 > 代码体积 > 运行时性能

通过 Lighthouse 、Network 面板定位阻塞点

通过 Bundle 分析工具优化代码体积以及依赖

Q: 遇到的最大的挑战是什么

A: 在 CSR 项目中落地 Critical CSS,解决 CSS 报错的问题;从 Critical 不生效 -> 预渲染的概念 -> vite-ssg 引入报错 -> 搜索报错,没有找到方案 -> 新项目中引入 -> github 寻找成功项目 -> 屏蔽自动引入 + 使用手动引入 -> 自动安装 CSS + 手写 resolver 导入自动安装的 CSS -> 配置项解读 -> 自动安装 CSS + vite resolve 钩子 -> 自动安装 CSS + vite resolve 配置 -> 服务端构建不引入 CSS

通过对 CriticalCSS 的基本理解,找到了预渲染 + Critical CSS 的解决方案

通过对服务端渲染、模块化、VantResolver 源码、Vite 打包流程、Vite 插件机制、Vite-ssg 插件构建流程的学习,同步异步打包的深度理解,新建项目、查找成功案例的实践方式等方式,解决了引入 vite-ssg 后的报错问题:包括无法解析 CSS 文件,找不到 window 下面的一些变量

Q:如何验证优化效果

A:自动化监控:配置 GitHub Actions 每日运行 Lighthouse,生成趋势报告;线上埋点:监控用户行为数据(如页面停留时长、按钮点击率);A/B 测试:对比优化前后同一页面的跳出率差异

低代码

动态表单组件

表单的通用特性有哪些?

固定的几种类型、字段名称、校验、动态联动

某些字段特有的配置,在封装通用的功能后最好使用一个通用性可以灵活的配置

曾经遇到的场景?封装基础表单后又需要支持选项联动功能,选项动态从接口获取

表单联动功能抽象

Map<string/*被控制的字段*/, Record<string, action>[]> // 数据显示隐藏联动,数据验证联动,字段值自动填充,是否禁用联动

// 通过计算属性
// 常见的逻辑:等于,不等于,包含,不包含,字符串支持正则匹配
type ConditionMode = 'AND' | 'OR';
type CompareCondition = '==' | '!=' | '>' | '<' | '>=' | '<=' | 'pattern' | 'in' | 'notIn';

interface ConditionUnit {
  field: string; // 影响当前字段的字段
  condition: CompareCondition; // 影响当前字段的条件,等于,大于,等于,包含等
  value: any; // 值,比如是等于多少,大于多少
}

interface Condition {
  mode: ConditionMode  group: (ConditionUnit | Condition)[]}


type ComputedRule = Condition & {
  linkage?: string; // 值来源
  invert?: boolean; // 是否对条件取反,比如满足某一条件是启用还是禁用;是显示还是隐藏
}

type ComputedConfigKey = 'hidden' | 'required' | 'value' | 'disabled'

interface ComputedConfig {
  [key: ComputedConfigKey]: ComputedRule;}

// 下拉联动
提供 fetchHandler

校验逻辑抽象

包含一些常规的验证,包括最小值、最大值、长度、正则表达式、自定义验证等

自定义验证可以把当前字段的规则以及值给到用户,如果要支持异步验证,可以提供 callback

type Validate = {
  transform: (val: any) => any; // 给用户前先进行值得转换
  mode: 'validator' | 'len' | 'pattern' | 'min' | 'max';
  trigger: 'blur' | 'change' | string;
  validator?: (rule: any, value: any, callback: (error?: Error) => void) => void; // callback 的设计可以支持异步校验
  len?: number;
  pattern?: string | RegExp;
  min?: number
  max?: number
};

邻接矩阵与邻接表的优劣势

数据量大比较卡顿怎么处理

Field 组件

高阶组件,每个字段都使用通用样式与逻辑包裹

使用了策略模式,使用策略模式可以让增加新的类型更好扩展,不必太改动旧代码;总之坚持对修改封闭、对扩展开放、单一职责等原则

低代码设计思路

定义配置字段,用数据驱动,代码只负责解析配置

流程通用库

背景:低代码平台提供原子性的接口,没有清晰的文档,在对接接口前需要去理解流程模板、流程实例、流程节点,任务派发等多个概念

两大难点:

灵活性:跨框架,跨客户端

内部不引用任何框架,使用纯 JS 库;跨客户端:客户端由使用者提供

对流程业务以及低代码平台对流程能力抽象的理解

对于业务而言,常用的就是一些通过、回退、发起、重新发起等功能,需要把原子接口封装成支持特定功能的具体接口

低代码平台提供原子性的接口,没有清晰的文档,在对接接口前需要去理解流程模板、流程实例、流程节点,任务派发等多个概念

就拿通过来说,都需要调用两次 API,获取当前用户的任务的 API ,以及修改任务状态的 API

OA

如何避免随着业务逻辑的增加然后需要添加功能

采用合适的设计模式,比如策略模式,遵循单一职责的原则

Field 组件:每个 Field 组件都接收相同的参数 field 与 fieldValue, 然后根据不同的 field 类型渲染不同的 field 组件

**表单校验逻辑:**将不同的类型的校验定义成单独的逻辑,然后根据不同的校验类型执行具体的校验逻辑

function lenValidator(rule, value, callback) {}
function patternValidator(rule, value, callback) {}
function userValidator(rule, value, callback) {}
...
function validator(rule, value, callback) {
  switch(rule.mode)
    case: 'len':
        lenValidator(rule, value, callback)
  ...
}

实现表单的数据联动:比如将不同的验证逻辑单独封装

// value 是当前表单的值,condition 是抽象的条件策略、target 是比较对象
function compare(value: any, condition: CompareCondition, target: any){}

// 看某个字段是否满足某个条件
function checkResult(condition: Condition, formData){
    // 条件可能是多层次的逻辑嵌套,这里需要递归
    // 递归其实和写非递归函数没有区别,只是在特定条件下多调用下函数,函数返回什么值最终就返回什么值
    const { mode, group } = condition;  const results = group.map((unit) => {
    if (typeof unit === 'object' && 'field' in unit) {
      const { field, condition: cond, value } = unit;
      const formValue = formData[field];
      return compare(formValue, cond, value);
    } else {
      return checkCondition(unit as Condition, formData);
    }
  });

  if (mode === 'AND') {
    return results.every((result) => result);
  }
  return results.some((result) => result);}// 根据配置计算当前字段受影响的状态
function computeConfig(config: ComputeConfig, formData): {  hidden: boolean;
  required: boolean;
  disabled: boolean;
  value: any;
} {}

动态表单的联动逻辑如何实现的呢

某个字段是是什么值的时候当前字段才显示,这就是一个计算,当前字段是否显示是一个计算结果;某个字段是什么值可以可以拆分成某个字段、是、什么值;fieldkey, condition, value

动态表单联动的核心,是基于其他字段的值动态计算当前字段的状态(如是否显示、是否必填、是否禁用等)。实现这一功能的关键在于:

  1. 抽象配置结构:用标准化的配置描述计算逻辑,包括计算目标(如控制字段的显示状态)、依赖条件(哪些字段影响当前字段)、条件策略(比较方式,如大于、等于、包含等)和字段间关系(ANDOR 等逻辑关系)。

  2. 核心计算函数:通过 compare 函数判断依赖条件是否成立,再用 computed 函数基于判断结果计算出目标字段的最终状态。

例如,在人员表单中,“驾驶证” 字段的显示状态依赖于 “年龄” 字段的值:只有当 “年龄” 字段的值 大于等于 18 时,“驾驶证” 字段才会显示。这一逻辑可以通过配置中的条件策略(>=18)和逻辑关系(单个条件无需 AND/OR)来实现,每次 “年龄” 字段的值变化时,触发 comparecomputed 函数重新计算 “驾驶证” 字段的显示状态。

考虑到组件联动逻辑比较多的时候可能造成页面卡顿,让组件重新渲染的时机应该是什么呢

在处理联动逻辑时有没有遇到数据更新不及时,或者循环联动导致程序异常的问题,又是如何解决的呢

用户频繁操作值导致状态更新混乱的问题如何解决

表单校验?

如何保证验证逻辑清晰高效地执行?

使用某种模式来优化验证逻辑地组织和扩展?

策略模式

如何用小程序封装一个 client?

小程序针对 request API 提供特定的 get、post 方法

这段时间看到了适配器的概念,axios 也提供了自定义适配器的能力,不过开放适配器还是不行,毕竟每个应用可能都有自己的 get 方法,可能要进行不同的拦截操作,直接让客户端提供 API 会更灵活

以通过举例

Monorepo 架构

共享构建工具,但带来了权限控制的问题

非常通用的前端能力

预设计通用类型,流程能力封装,通用组件封装

组件

高阶组件的不可替代性:需要统一包裹渲染结构,比如 field 组件,可以把校验器的逻辑封装到 高阶 field 组件上面

浏览器

浏览器的渲染流程?

构建 DOM -> 构建 CSSOM -> 生成 RenderTree -> Layout -> Layers -> Paint -> 合成

重绘与重排:广义上是指浏览器的重新渲染,比如让元素大小、位置变化会触发重,颜色等样式变化会触发重绘;要避免重排,应该避免使用一些布局敏感属性,比如动画应使用 css3 新特性来避免触发重排重绘

Layout: 确定每个元素在页面中的位置和大小

layers: 现代浏览器为了性能,对图像进行了分层,layer 是独立的绘制单元,在布局属性变化后,只需要重新绘制单独的图层,css3 的动画元素 transform 会把元素提升到单独的层级,利用 GPU 加速,后续更新不需要再生成绘制指令,仅仅触发合成线程

Paint: 生成绘制指令

**合成:**将绘制指令转换为位图,并存储在 GPU 中;合成 Layers, 生成最终的屏幕元素

从输入 URL 到浏览器拿到响应的过程?

DNS 解析 ——> 建立 TCP 链接 ->发送请求数据 -> 服务器接收请求 -> 服务器处理请求 -> 服务器发送响应数据 -> 浏览器接受响应 -> 主线程解析完成渲染

说完流程后,详细说一下浏览器渲染的流程

浏览器每一帧都会做的事情,对浏览器帧的理解,对浏览器主线程每一帧会做的事情?

浏览器帧:浏览器为了确保能够实时渲染页面,会保持 60HZ 的刷新频率,也就是一帧会花费 16.6ms,浏览器在每一帧都会处理事件、执行JavaScript、渲染、图形绘制

主线程每一帧:执行 JavaScript,渲染操作

事件循环、浏览器一次渲染的区别?

事件循环:浏览器主线程调度任务的一种方式,先执行宏任务、然后执行然后执行所有微任务,然后执行 requestAnimationFrame 的回调,如果需要渲染的话执行渲染

浏览器一次渲染:更改元素的几何属性会触发浏览器的重绘和重排,重新渲染

权限系统

有两种实现方案:以角色角度出发(RBAC),以功能角度出发(ABAC)

之前的系统:只是路由页面的管理,不同的角色有不同页面的全新啊,采用角色有哪些功能的方式管理;现在的系统:不仅仅是单一的页面权限,存在按钮级别的权限,并且有角色组合的情况,采用以功能角度出发的方式

什么是单点登录?

开发性问题

如何估点?

拆分子任务,需求评分阶段不确定的点预留 buffer,就比如曾经做过一个打标平台,就是对资源的清洗,因为涉及到对百万数据标签的清洗,人工成本非常高,如果在需求评审过程中还没有提到怎么去清洗旧标签,比如满足特定规则的模板使用机器清洗;这个需求其实跟前端也有关系,因为整个应用加了机器清洗的功能,前端也可能会加对应的筛选功能

画流程图的好处

需求评审:梳理功能完整链路,找到需求不清晰的点,比如未通过的模板要怎么办,又分为两种情况,确实质量不行或者点错了,涉及到模板修改以及回退操作

模板的状态:待审核、已通过、未通过、已上线、已下线

学习:找到关键卡点

InputTag 组件的难点如何实现 input 标签的自动换行呢?如何实现 input 的宽度自动变化呢?

换行直接采用 flex 布局的方式,放不下自动换行,Input 宽度自动变化需要采用提取 Input 中文字的方式设置到 span 标签上,然后改变父元素的宽度

跨部门沟通的案例?

addin 内容配置

首页内容配置、搜索结果配置、内容组配置,不仅仅是要关注配置,还需要关注配置后的业务形态,配置完是干嘛的,能够保证开发功能的正确性

项目中的难点亮点?

**流程通用库:**库的设计以及对流程业务以及低代码平台对于流程能力抽象的数据结构的理解

动态表单组件:如何实现表单的动态联动

权限模块:如何设计能够保证后续代码的可扩展