面试分享:二本靠7轮面试成功拿下大厂P6

前言

时隔两年再次更新了,本文主要给大家带来一些我面试的经历和经验,希望对正在求职大厂前端岗位的同学有所帮助。我先大致说下面试之前的个人情况:2021年毕业去了一家500人左右的小公司,react和vue项目经验都有,两年半后拿礼包去了一家中厂,后来在这家公司待了一年左右最近面试京东拿到了P6的offer,差不多四年的工作经验终于拿到了大厂的入场券。

我写的篇幅可能比较长,这里讲一下为什么会有七轮面试,刚开始社区找人内推的北京团队,后来三面老板面因为工作地点的原因挂掉了(本人毕业后一直在上海工作),由于前面技术轮评价比较好,又找了上海的团队内推重新面了四轮才成功拿下。

好了,接下来就大概讲讲我的多轮京东面试

北京1轮

React 代码实现:在页面上实时显示窗口宽度的变化

import { useState, useEffect, useCallback } from 'react';

// 防抖函数
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// 自定义 Hook
function useWindowWidth(delay = 300) {
  const [width, setWidth] = useState(window.innerWidth);

  const handleResize = useCallback(() => {
    setWidth(window.innerWidth);
  }, []);

  useEffect(() => {
    // 创建防抖版本的 resize 处理函数
    const debouncedHandleResize = debounce(handleResize, delay);
    
    // 添加事件监听
    window.addEventListener('resize', debouncedHandleResize);
    
    // 立即执行一次以获取初始宽度
    handleResize();
    
    // 清理函数
    return () => {
      window.removeEventListener('resize', debouncedHandleResize);
    };
  }, [handleResize, delay]);

  return width;
}

// 使用示例
function WindowWidthDisplay() {
  const windowWidth = useWindowWidth();

  return (
    <div>
      <h2>窗口宽度实时显示</h2>
      <p>当前窗口宽度: {windowWidth}px</p>
    </div>
  );
}

export default WindowWidthDisplay;

react为什么不能在if里写useEffect

React hooks的调用顺序必须稳定,React 在内部通过调用顺序来区分和管理不同的 Hooks。例如:

function Component() {
  const [count, setCount] = useState(0); // Hook 1
  useEffect(() => {});                   // Hook 2
  const [name, setName] = useState(""); // Hook 3
}

React 会按顺序将这些 Hook 存储在一个链表中(类似 [Hook1, Hook2, Hook3] )。如果组件的多次渲染中 Hook 的调用顺序不一致(比如因为条件语句跳过某个 Hook),React 就无法正确匹配状态,导致 bug

顺便吆喝一句,技术大厂,前后端or测试,多地摇人机会,待遇还可以;感兴趣的可以试一试。

useEffect的各种机制

useEffect 的工作流程依赖于 React 的渲染周期:

  1. 依赖项比对:React 会对比当前渲染和上一次渲染的依赖项数组(deps),如果不同,会重新执行 useEffect 的回调。
  2. 清理与执行:组件挂载时执行 useEffect。依赖项变化时,先执行上一次的清理函数(如果存在),再执行新的 useEffect。组件卸载时执行清理函数。

如果 Hook 的调用顺序不稳定,React 无法正确追踪哪些 useEffect 需要清理或更新

useRef和useState底层区别

useRef 和 useState 都是 React Hooks,但它们的用途和底层实现有显著不同:

存储的值

存储状态,触发重新渲染

存储可变值,不触发重新渲染

返回值

[state, setState]

(状态 + 更新函数)

{current: value

}(可变 ref 对象)

是否触发渲染

✅ 调用 

setState

 会触发重新渲染

❌ 修改 

ref.current

 不会触发渲染

底层存储位置

在 Fiber 节点的

memoizedState

链表里

在 Fiber 节点的

ref

属性上

试用场景

需要 UI 响应的数据(如表单、计数)

存储 DOM 引用、缓存变量、避免重复计算

底层实现对比

  • useState:React 在组件 Fiber 节点上维护一个 Hooks 链表,每个 useState 对应链表中的一个节点。调用 setState 会标记组件需要更新,触发 re-render。状态变化后,React 会重新执行组件函数,并返回最新的 state。
  • useRef:useRef 返回一个普通 JavaScript 对象 { current: initialValue } 。这个对象在组件的整个生命周期中保持不变(即使组件重新渲染)。修改 ref.current 不会触发 React 的更新机制,因为 React 不追踪 ref 的变化。

useEffect能监听useRef的值发生改变吗?

默认情况下,useEffect 无法监听 useRef 的变化

原因分析

  1. useRef 的修改不会触发重新渲染useEffect 的依赖项机制依赖于 组件渲染,而 ref.current 的变化不会导致重新渲染,因此 useEffect 不会自动检测到变化。
  2. useRef 返回的对象始终是同一个引用由于 ref 对象在组件的整个生命周期中引用不变,useEffect 的依赖项比对(Object.is)会认为它没有变化。

如何让 useEffect 监听 useRef 的变化?

虽然 useRef 本身不会触发 useEffect,但可以通过 额外状态 或 自定义 Hook 间接监听

function useWatchableRef(initialValue) {
  const ref = useRef(initialValue);
  const [version, setVersion] = useState(0);

  const setRef = (value) => {
    ref.current = value;
    setVersion(v => v + 1); // 更新版本号触发 useEffect
  };

  return [ref, setRef, version];
}

function Component() {
  const [ref, setRef, refVersion] = useWatchableRef(0);

  useEffect(() => {
    console.log("ref changed:", ref.current);
  }, [refVersion]); // 监听 version 变化
}

为什么 React 不直接支持监听 useRef

  1. 设计初衷不同useRef 主要用于 存储可变值而不触发渲染(如 DOM 引用、定时器 ID)。useState 才是用于 管理状态并触发 UI 更新 的。
  2. 性能优化如果 useRef 的每次修改都触发 useEffect,可能会导致不必要的副作用执行(比如频繁的 DOM 操作)。
  3. 避免滥用如果业务逻辑需要响应式更新,应该优先使用 useState 或 useReducer,而不是强行监听 useRef。

手写算法 标签树的深度

const htmlStr = `
    <div>
        <div>
            <span>123</span>
            <a>222</a>
            <div>
                <button>333</button>
                <br/>
            </div>
        </div>
    </div>
`;

答案

function getHtmlTreeDepth(htmlStr) {
    let depth = 0;
    let maxDepth = 0;
    const stack = [];
    const tagRegex = /<\/?([a-z][a-z0-9]*)[^>]*>/gi;
    
    htmlStr.replace(tagRegex, (tag) => {
        if (tag.startsWith('</')) {
            // 闭合标签
            depth--;
            stack.pop();
        } else if (!tag.endsWith('/>')) {
            // 非自闭合的开放标签
            depth++;
            maxDepth = Math.max(maxDepth, depth);
            stack.push(tag.match(/<([a-z]+)/i)[1]);
        }
        // 自闭合标签(如 <br/>)不改变深度
        return tag;
    });

    return maxDepth;
}

const depth = getHtmlTreeDepth(htmlStr);
console.log(depth); // 输出: 4

北京2轮

ssr、ssg、rsc原理及优缺点

1. SSR(Server-Side Rendering,服务端渲染)

原理

  • 用户请求页面时,服务器实时执行 JavaScript,生成完整 HTML 并返回给浏览器。
  • 浏览器接收到 HTML 后直接显示,然后再进行 hydration(注水) 使其可交互。

优点

✅ 首屏加载快:用户直接看到渲染好的内容,无需等待 JS 执行。

✅ SEO 友好:搜索引擎爬虫能直接获取完整 HTML。

✅ 低端设备兼容:不依赖客户端 JS 能力,适合性能较差的设备。

缺点

❌ 服务器压力大:每次请求都需要实时渲染,高并发时可能拖慢服务器。

❌ TTFB(首字节时间)延迟:用户需等待服务器生成 HTML。

❌ 交互延迟:必须等 hydration 完成后才能交互。

适用场景

  • 需要 SEO 的内容型网站(如新闻、博客)。
  • 对首屏速度要求高的应用。

2. SSG(Static Site Generation,静态站点生成)

原理

  • 在 构建阶段 提前生成所有页面的静态 HTML,直接托管到 CDN。
  • 用户访问时,CDN 直接返回预渲染的 HTML,无需服务器计算。

优点

✅ 极致性能:CDN 直接返回 HTML,速度极快。

✅ 零服务器压力:无需运行时渲染,节省计算资源。

✅ 高安全性:没有动态服务器逻辑,减少攻击面。

缺点

❌ 不适合动态内容:数据更新必须重新构建。

❌ 规模化成本高:页面数量多时,构建时间会很长。

❌ 无法个性化:所有用户看到相同的内容(除非结合客户端 JS)。

适用场景

  • 内容不经常变的网站(如文档、营销页)。
  • 需要极致性能的场景。

3. RSC(React Server Components,React 服务端组件)

原理

  • 按组件粒度 区分哪些在服务端渲染,哪些在客户端渲染。
  • 服务端组件直接访问数据库/API,返回 JSON 数据,客户端组合渲染。

优点

✅ 减少客户端 JS 体积:部分逻辑留在服务端,前端代码更小。

✅ 自动代码拆分:按需加载组件,优化性能。

✅ 直接访问后端:服务端组件可直接调用 DB/API,无需额外接口。

缺点

❌ 复杂度高:需要处理服务端/客户端组件的通信问题。

❌ 兼容性要求:必须使用 React 18+ 和特定框架(如 Next.js)。

❌ 调试困难:跨环境调试较麻烦。

适用场景

  • 复杂应用,需要优化包大小和加载速度。
  • 需要服务端直接获取数据的场景(如仪表盘、管理后台)。

node ssr什么原因引起的堵塞,怎么解决

  1. CPU 密集型任务阻塞事件循环
  • 复杂组件渲染(如大型列表、深度递归组件)
  • 繁重的模板计算(如Pug/EJS模板处理大数据)
  • 加密/解密操作(如JWT验证)
  1. 同步I/O操作阻塞
  • 同步文件读写(fs.readFileSync
  • 同步数据库查询(某些ORM的同步方法)
  • 阻塞式网络请求(未正确使用Promise)
  1. 内存泄漏
  • 全局变量累积(如缓存未清理)
  • 闭包引用未释放
  • 事件监听器未移除
  1. 资源竞争
  • 数据库连接池耗尽
  • 文件描述符耗尽
  • 子进程管理不当

解决方案

1.架构层优化

// 使用React的renderToNodeStream
import { renderToNodeStream } from 'react-dom/server';

app.get('/', (req, res) => {
  res.write('<!DOCTYPE html>');
  const stream = renderToNodeStream(<App />);
  stream.pipe(res, { end: false });
  stream.on('end', () => res.end());
});

2.渲染性能优化

// 使用React的renderToNodeStream
import { renderToNodeStream } from 'react-dom/server';

app.get('/', (req, res) => {
  res.write('<!DOCTYPE html>');
  const stream = renderToNodeStream(<App />);
  stream.pipe(res, { end: false });
  stream.on('end', () => res.end());
});

3.异步I/O和非阻塞操作

// 使用Promise.all优化并行请求
async function fetchData() {
  const [user, posts] = await Promise.all([
    UserModel.findById(userId),
    PostModel.findByUser(userId)
  ]);
  return { user, posts };
}

数据库/API 调用优化:

  • 使用 Data Cache(如 Redis)减少重复查询。
  • 对慢接口设置 超时 或 降级处理

站点seo怎么做的

1.语义化 HTML

  • 使用 <h1> ~ <h6> 合理嵌套标题
  • 使用 <section><article><nav> 等语义化标签
  • 图片必须加 alt 属性

2.Meta 标签优化

<head>
  <title>网站标题 - 重要关键词</title>
  <meta name="description" content="网站描述,包含核心关键词" />
  <meta name="keywords" content="关键词1, 关键词2" />
  <!-- 避免被转码 -->
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <!-- 移动端适配 -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <!-- 禁止搜索引擎转码(如百度) -->
  <meta http-equiv="Cache-Control" content="no-transform" />
  <meta http-equiv="Cache-Control" content="no-siteapp" />
</head>

3.前端框架(React/Vue)SEO优化

  • Next.js(React) / Nuxt.js(Vue) 默认支持 SSR
  • 静态生成(SSG)
  • React Helmet(Next.js 内置 Head 组件)

4.Sitemap(站点地图)

作用

  • 告诉搜索引擎网站有哪些页面可被抓取
  • 标注页面的更新频率(lastmod)和优先级(priority
  • 尤其适合 大型网站 或 动态内容网站(如电商、新闻站)

手动编写 Sitemap

<!-- public/sitemap.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="<http://www.sitemaps.org/schemas/sitemap/0.9>">
  <url>
    <loc><https://example.com/></loc>
    <lastmod>2023-10-01</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
</urlset>

5.Robots.txt(爬虫协议)

作用

  • 控制搜索引擎哪些页面可以抓取
  • 禁止抓取敏感页面(如后台、临时页)

标准配置

# public/robots.txt
User-agent: *          # 对所有爬虫生效
Disallow: /admin/      # 禁止抓取后台
Disallow: /tmp/        # 禁止抓取临时文件
Allow: /public/        # 允许抓取 public 目录
Crawl-delay: 2         # 抓取延迟(秒),防止服务器压力

# 声明 Sitemap
Sitemap: <https://example.com/sitemap.xml>

北京3轮

平时怎么学习?遇到比较难的问题怎么解决?讲一下工作中遇到的问题

上海1轮

手写算法 版本号列表排序

给定一个版本号列表从小到大排序 如[1.0.1, 0.1, 1.3.26, 1.0.3.29, 2.1.3, 1.0.9.7.25]

function sortVersions(versions) {
  return versions.sort((a, b) => {
    const partsA = a.split('.').map(Number);
    const partsB = b.split('.').map(Number);
    
    const maxLength = Math.max(partsA.length, partsB.length);
    
    for (let i = 0; i < maxLength; i++) {
      const numA = partsA[i] || 0;
      const numB = partsB[i] || 0;
      
      if (numA !== numB) {
        return numA - numB;
      }
    }
    
    return 0;
  });
}

// 测试
const versions = ["1.0.1", "0.1", "1.3.26", "1.0.3.29", "2.1.3", "1.0.9.7.25"];
console.log(sortVersions(versions));
// 输出: ["0.1", "1.0.1", "1.0.3.29", "1.0.9.7.25", "1.3.26", "2.1.3"]

聊聊ssr和segment ssr架构以及rsi相关的

1.传统 SSR(服务端渲染)

运行时渲染:用户请求时,服务器动态生成完整 HTML,工作流程:

用户请求
->NodeJs服务器
->执行React/vue组件渲染
->返回完整HTML+客户端js
->浏览器Hydration交互

✅ 优点: 首屏速度快、SEO友好、低端设备兼容

❌缺点:高服务器负载、 TTFB(首字节时间)较长、 TTFB(首字节时间)较长

2.Segment SSR(分段服务端渲染)

  • 流式分块渲染:将页面拆分为多个可独立渲染的模块(Segments)
  • 渐进式传输:边渲染边传输,而非等待整个页面完成
// 1. 定义可分段组件
import { Suspense } from 'react';

function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<Spinner />}>
        <ProductList />  // 异步加载的模块
      </Suspense>
      <Footer />
    </div>
  );
}

// 2. 服务端流式响应
import { renderToPipeableStream } from 'react-dom/server';

app.get('/', (req, res) => {
  const stream = renderToPipeableStream(<Page />);
  stream.pipe(res);
});

✅ 优势

  • 关键内容优先渲染(Critical CSS/JS),非关键模块延迟加载
  • 服务器CPU时间分段使用,减少内存峰值压力

3.RSI(Remote Server Includes)

边缘包含技术:将页面拆分为多个独立服务渲染的片段,在CDN边缘组合

与传统SSR对比

- 传统SSR: [DB → 完整渲染 → HTML]
+ RSI:    [DB → 片段A渲染] 
           [API → 片段B渲染] → CDN组合

// CDN边缘逻辑示例(伪代码)
async function handleRequest(request) {
  const [header, body] = await Promise.all([
    fetch('<https://service-a/render-header>'),
    fetch('<https://service-b/render-body>')
  ]);
  
  return new Response(`
    ${await header.text()}
    ${await body.text()}
  `);
}

✅ 优势

  • 减少回源延迟,单个片段失败不影响整体
  • 静态部分预生成,动态部分按需渲染

微前端底层怎么实现的,css隔离、通信怎么做

1. 应用隔离机制

proxy沙箱:

  • 每个子应用运行在独立的proxy上下文
  • 卸载时清除所有副作用
// 基于Proxy的沙箱实现
class Sandbox {
  constructor() {
    const fakeWindow = {};
    this.proxy = new Proxy(fakeWindow, {
      get(target, key) {
        return target[key] || window[key];
      },
      set(target, key, value) {
        target[key] = value;
        return true;
      }
    });
  }
}

CSS隔离:

Shadow DOM天然隔离样式

const shadow = element.attachShadow({ mode: 'open' });
shadow.innerHTML = `<style>h1 { color: red; }</style>`;

Scoped CSS通过前缀隔离(如**qiankunscoped-css**)

通信机制:

Custom Events

// 子应用发送事件
window.dispatchEvent(new CustomEvent('micro-event', {
  detail: { type: 'login' }
}));

// 主应用监听
window.addEventListener('micro-event', handler);

聊下ai相关的llm、mcp的 对日常工作作用

这里具体自己翻文档了解使用和简单原理,具体可以看看mcp文档

上海2轮

编辑器的多人协作冲突原理

OT (Operational Transformation) 算法

将编辑操作转换为原子操作(如**insert(pos, text)delete(pos, length)**),通过转换函数解决操作冲突

// 操作转换函数示例
function transform(op1, op2) {
  // 如果op2在op1之前发生,调整op1的位置
  if (op1.type === 'insert' && op2.type === 'insert') {
    if (op1.pos <= op2.pos) {
      return { ...op1 }; // op1不需要修改
    } else {
      return { ...op1, pos: op1.pos + op2.text.length };
    }
  }
  // 其他情况处理...
}

CRDT (Conflict-Free Replicated Data Type)

将数据结构设计保证最终一致性,每个编辑操作天生可合并

// 使用唯一ID标识每个字符
const doc = {
  "id1": { char: 'H', id: 'id1', pos: 0.1 },
  "id2": { char: 'i', id: 'id2', pos: 0.2 }
};
// 插入新字符时分配介于两者之间的position
insertAfter('id1', '!') → pos = (0.1 + 0.2)/2 = 0.15

webpack的loader和plugin原理

Loader 的作用

  • 转换文件内容:将非 JS 文件(如 .css.vue)转换成 Webpack 可识别的模块。
  • 链式调用:多个 Loader 可以串联执行(如 sass-loader → css-loader → style-loader

Loader的底层实现

Loader 本质是一个函数,接收源文件内容,返回转换后的内容:

 // 一个简单的 Loader 示例
    module.exports = function(source) {
      // 1. 处理 source(如编译、转换)
      const transformed = source.replace('red', 'blue');
      
      // 2. 返回处理后的内容(可以是 JS/CSS/其他)
      return transformed;
      
      // 可选:返回多个结果(如 source + sourceMap)
      // this.callback(null, transformed, sourceMap);
    };

Plugin 的作用

  • 扩展构建流程:在 Webpack 编译的不同阶段注入自定义逻辑。
  • 访问 Webpack 内部:可以修改模块、优化 chunks、生成 assets 等。

Plugin的底层原理

Plugin 是一个,必须实现 apply 方法:

    class MyPlugin {
      apply(compiler) {
        // 1. 注册钩子
        compiler.hooks.emit.tap('MyPlugin', (compilation) => {
          // 2. 操作 compilation(如修改生成的资源)
          compilation.assets['new-file.txt'] = {
            source: () => 'Hello Webpack!',
            size: () => 13,
          };
        });
      }
    }

    module.exports = MyPlugin;

关键点

  • compiler 代表整个 Webpack 环境。
  • compilation 包含当前构建的所有信息(模块、chunks、assets)。
  • 可以通过 Tapable 的 tapAsync/tapPromise 处理异步逻辑。

webpack的底层打包原理

1.依赖图构建

从入口文件出发,递归分析所有依赖,形成完整的模块关系图

根据 webpack.config.js 中的 entry 配置(如 ./src/index.js),创建初始模块 遍历 AST 找到所有 import/require 语句,收集依赖路径

递归处理子模块,对每个依赖路径解析为绝对路径

最终生成一个 Map 结构的依赖图:

    const dependencyGraph = {
      './src/index.js': {
        id: './src/index.js',
        dependencies: ['./src/utils.js'],
        source: '...'
      },
      './src/utils.js': {
        id: './src/utils.js',
        dependencies: ['lodash'],
        source: '...'
      },
      'lodash': { ... }
    };

2. Chunk 生成

  • Entry Chunk: 每个 entry 配置生成一个初始 Chunk
  • Async Chunk: 动态导入(import() )自动生成新 Chunk
  • SplitChunks 优化: 根据 optimization.splitChunks 配置拆分公共模块,如将 lodash 等第三方库提取到 vendors Chunk

3.模块转译

  • Loader 链式处理: 按配置顺序调用 Loader 处理模块内容
  • 生成运行时兼容代码: 将 ESM 的 import 转为 Webpack 的 __webpack_require__

4.模板渲染 生成 Runtime 代码包含模块加载、缓存等逻辑

    // 运行时伪代码
    function __webpack_require__(moduleId) {
      if (cache[moduleId]) return cache[moduleId].exports;
      const module = { exports: {} };
      modules[moduleId](module, module.exports, __webpack_require__);
      return module.exports;
    }

拼接 Chunk 内容

    (function(modules) {
      // Runtime 代码...
    })({
      './src/index.js': function(module, exports, __webpack_require__) {
        // 模块代码
      },
      './src/utils.js': function(...) { ... }
    });

生成 Asset 文件

根据 output.filename 规则命名文件(如 [name].[contenthash].js),如果配置了 devtool 还会生成SourceMap文件

5.优化阶段

Tree Shaking 基于 ES Module 的静态分析,标记未使用的导出

    // 源码:
    export function a() {}
    export function b() {}  // 未被导入

    // 打包后:
    /* unused harmony export b */

Scope Hoisting合并模块作用域,减少闭包

    // 优化前:
    (function(module) {
      module.exports = 1;
    });

    // 优化后:
    module.exports = 1;  // 直接内联

代码压缩 使用 TerserPlugin 混淆和压缩 JS

讲下esm、cmd、umd区别

1. ESM

ES6 引入的模块化方案,现代浏览器和 Node.js 原生支持,依赖关系在编译时确定

运行环境:

  • 浏览器: <script type="module">
  • Node.js: .mjs 文件或 package.json 设置 "type": "module"

✅优点:静态分析支持 Tree Shaking、浏览器原生支持,无需打包工具(但生产环境仍需打包优化)、支持循环引用(模块只被加载一次,存在缓存中)

❌缺点:旧浏览器不支持(需通过 Webpack/Babel 转译)、Node.js 中与 CommonJS 混用需注意兼容性

2.CMD

NodeJS默认规范,主要用于服务端,依赖关系在运行时确定,模块同步加载会阻塞执行

运行环境:

  • Node.js 原生支持
  • 浏览器需通过 Webpack/Browserify 打包

✅优点:Node.js 生态默认支持、简单易用

❌缺点:同步加载不适合浏览器环境(会导致性能问题)、不支持 Tree Shaking

3.UMD

同时兼容 ESM、CMD 和浏览器全局变量,根据环境自动选择导出方式

    (function (root, factory) {
      if (typeof define === 'function' && define.amd) {
        // AMD 规范(如 RequireJS)
        define(['jquery'], factory);
      } else if (typeof exports === 'object') {
        // CommonJS 规范
        module.exports = factory(require('jquery'));
      } else {
        // 浏览器全局变量
        root.MyLib = factory(root.jQuery);
      }
    }(this, function ($) {
      // 模块逻辑
      return { name: 'UMD' };
    }));

运行环境

  • 浏览器:直接引入 <script> 或通过 AMD 加载器
  • Node.js:直接 require

✅优点:跨环境兼容(浏览器/Node.js/AMD)、适合库开发(如 jQuery、Lodash)

❌缺点:代码冗余(包含多种兼容逻辑)、无法利用 ESM 的静态优化

上海3轮

聊项目深度、聊解决问题思路

上海4轮

这一轮面试官是Taro团队的,也是技术深度和面试难度最大的一轮

react18以前批处理是啥样的? react18+的批处理怎么做的,从底层讲讲

1.React 18 之前的批处理

仅合并合成事件:在 React 事件处理函数(如 onClick)中的多个 setState 会被批量处理,React 使用事务机制延迟更新(异步批处理)到事件结束

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  const handleClick = () => {
    setCount(c => c + 1);  // 不会立即触发渲染
    setFlag(f => !f);      // 不会立即触发渲染
    // React 会将这两个更新合并为一次渲染
  };

  return <button onClick={handleClick}>Click</button>;
}

局限性: setTimeout/Promise/原生事件 中的 setState 会立即触发更新

setTimeout(() => {
  setCount(c => c + 1); // 立即渲染
  setFlag(f => !f);     // 再次渲染
}, 0);

2.React 18+ 的自动批处理

全场景批处理:合并所有上下文中的更新(包括事件、定时器、Promise、原生事件等),使用新的并发调度器(Scheduler)管理更新优先级

// 以下所有场景都会批量处理!
function App() {
  const handleClick = () => {
    setCount(c => c + 1);
    setFlag(f => !f); 
    // 合并为一次渲染(即使放在 setTimeout 中)
  };

  const fetchData = async () => {
    const res = await fetch('/api');
    setData(res);
    setLoading(false); // 异步操作后仍会批量处理
  };

  return <button onClick={handleClick}>Click</button>;
}

底层实现:

1.更新队列:所有更新被放入共享队列,通过 lane(车道)模型标记优先级

function dispatchSetState(fiber, queue, action) {
  const update = {
    lane: requestUpdateLane(fiber), // 分配优先级
    action,
    next: null
  };
  enqueueUpdate(fiber, queue, update); // 入队
  scheduleUpdateOnFiber(fiber); // 调度更新
}

2.调度合并

function scheduleUpdateOnFiber(root, fiber, lane) {
  markRootUpdated(root, lane); // 标记待处理更新
  ensureRootIsScheduled(root); // 统一调度
}

  1. 车道模型(Lane)
export const SyncLane = 0b0001; // 同步优先级
export const InputContinuousLane = 0b0010; // 连续输入(如滚动)
export const DefaultLane = 0b0100; // 默认优先级

useLayoutEffect和useEffect啥区别,从底层讲讲

1.核心区别总结

执行时机

DOM 更新后、浏览器绘制前(同步)

浏览器绘制后(异步)

堵塞渲染

✅ 会阻塞浏览器绘制

❌ 不阻塞

适用场景

需要同步计算 DOM 布局的场景

数据获取、事件订阅等副作用

源码阶段

commitLayoutEffects

 阶段执行

commitBeforeMutationEffects

 阶段调度

2.底层执行流程

React 的渲染分为 Render 阶段 和 Commit 阶段,两者在 Commit 阶段的不同时机执行 Commit阶段

  • BeforeMutation:调度 useEffect 的销毁函数(如果依赖项变化)
  • Mutation:实际更新 DOM(React 17+ 使用 Fiber 直接操作)。
  • Layout:执行 useLayoutEffect 的回调、更新 **ref、**执行 componentDidMount/Update(类组件)

useLayoutEffect 执行细节

同步执行 在 commitLayoutEffects 函数中直接调用

// ReactFiberCommitWork.js
function commitLayoutEffects(root) {
  while (nextEffect !== null) {
    if (effectTag & Layout) {
      const effect = nextEffect.updateQueue.lastEffect;
      if (effect !== null) {
        do {
          if (effect.tag === Layout) {
            effect.destroy = effect.create(); // 同步执行回调
          }
          effect = effect.next;
        } while (effect !== firstEffect);
      }
    }
    nextEffect = nextEffect.nextEffect;
  }
}

阻塞流程 由于在浏览器绘制前执行,长时间运行会导致页面卡顿

useEffect 执行细节

异步调度 在 commitBeforeMutationEffects 阶段标记,由 Scheduler 异步执行

// ReactFiberWorkLoop.js
function scheduleCallback(priorityLevel, callback) {
  // 将 effect 放入调度队列
  Scheduler_scheduleCallback(priorityLevel, callback);
}

实际执行时机 React 17 使用 requestIdleCallback 的 polyfill,React 18 使用 Scheduler 包的优先级调度

fiber是什么

Fiber 是 React 16+ 的核心调度单元,解决了传统 Stack Reconciler(React 15 及之前)的不可中断问题

React 将组件树转换为 Fiber 链表(深度优先遍历),每个 Fiber 节点对应一个 React 元素(组件/DOM 节点),包含以下关键属性:

//fiber数据结构
interface Fiber {
  // 标识信息
  tag: WorkTag;        // 组件类型(函数组件/类组件/DOM节点等)
  key: string | null;  // 同级节点唯一标识
  type: any;           // 组件构造函数或DOM标签名(如 'div')

  // 树结构关系
  return: Fiber | null; // 父节点
  child: Fiber | null;  // 第一个子节点
  sibling: Fiber | null;// 下一个兄弟节点
  alternate: Fiber | null; // 当前节点对应的旧Fiber(用于Diff)

  // 状态与副作用
  memoizedState: any;   // Hook链表(函数组件状态)
  stateNode: any;       // 实例(DOM节点/类组件实例)
  flags: Flags;         // 标记需要进行的操作(如Placement/Update)
  lanes: Lanes;         // 优先级(车道模型)

  // 工作进度
  pendingProps: any;    // 待处理的props
  memoizedProps: any;   // 上次渲染的props
}

React调度怎么做的

React 18 引入 车道模型(Lane) 管理任务优先级 1.优先级划分

export const SyncLane = 0b0001;       // 同步任务(如点击事件)
export const InputContinuousLane = 0b0010; // 连续输入(如滚动)
export const DefaultLane = 0b0100;     // 普通更新

2.优先级抢占

高优先级任务可中断低优先级任务:

function ensureRootIsScheduled(root) {
  // 获取最高优先级任务
  const nextLanes = getNextLanes(root);
  if (nextLanes === NoLanes) return;

  // 如果存在更高优先级任务,取消当前任务
  if (existingCallbackPriority !== newCallbackPriority) {
    cancelCallback(existingCallbackNode);
  }
}

react可执行中断渲染从底层怎么做到的

1.关键机制:时间切片

React 将渲染任务拆分为多个 Fiber 工作单元,通过浏览器 API(如 requestIdleCallback 或 MessageChannel)在空闲时段执行

2.双缓存技术

React 维护两棵 Fiber 树:

  • Current Tree:当前渲染的树(对应屏幕上显示的内容)。
  • WorkInProgress Tree:正在构建的新树(可中断修改)。

中断时:保存当前处理的 Fiber 节点指针(workInProgress恢复时:从上次中断的 Fiber 节点继续处理

function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
  // 如果被中断,workInProgress会保留当前节点
}

微信小程序架构具体怎么做的

微信小程序的架构设计采用了独特的双线程模型,将**视图层(View Layer)逻辑层(App Service Layer)**分离,并通过微信客户端(Native)进行通信,以实现高性能、安全性和跨平台一致性

双线程模型

  • 视图层(View Layer) :运行在单独的 WebView 线程,负责 UI 渲染。
  • 逻辑层(App Service Layer) :运行在 JavaScriptCore(iOS)/V8(Android) 线程,处理业务逻辑和数据管理。
  • Native 层:微信客户端作为中间层,负责通信、权限管理和原生能力调用,wx API底层依赖于Bridge机制

wasm是什么

WebAssembly(WASM)是一种二进制指令格式,设计用于在浏览器中高效执行接近原生速度的代码,WASM 的代码通常由 C/C++/Rust 等语言编译而来,可与 JavaScript 无缝交互使用 以 Rust + WASM 为例

rust代码用wasm-pack编译成wasm

// src/lib.rs
#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

js调用wasm

import init, { add } from './pkg/your_module.js';
await init();
console.log(add(2, 3)); // 输出 5

Rust所有权

Rust的所有权用于管理内存和资源的生命周期,确保内存安全而无需垃圾回收。简单来说,所有权规则主要包括三条:

1.每个值有且只有一个所有者(Owner)

String 类型的数据存储在堆上,赋值时发生 所有权转移(Move),s1 不再有效

let s1 = String::from("hello"); // s1 是所有者
let s2 = s1;                    // 所有权从 s1 转移到 s2
println!("{}", s1);             // 编译错误!s1 已失效

2.所有权可以转移(Move),但不能共享(除非借用)

同一作用域内可存在多个不可变引用,不允许同时存在可变引用(防止数据竞争)

let s = String::from("hello");
let len = calculate_length(&s); // 传递不可变引用

fn calculate_length(s: &String) -> usize {
    s.len()                     // 可读但不可修改
}

当所有者变量离开作用域时,该值会被自动清理释放内存(调用 drop)

fn main() {
    {
        // s 进入作用域
        let s = String::from("hello"); // 在堆上分配内存
        println!("{}", s); // 输出 "hello"
    } // s 离开作用域,自动调用 drop 释放内存
    // 此处无法再访问 s
}

用React做跨端小程序框架实现

1.技术选型

  • React:作为核心渲染库,利用其虚拟 DOM 能力。
  • 自定义渲染器(如 React Reconciler) :实现跨端渲染调度能力。
  • 平台适配层:将虚拟 DOM 映射到不同平台(如微信小程序、支付宝小程序、Web等)的原生组件。
  • JSX 语法:统一开发体验。
  • 打包工具:如 webpack、Vite,结合 Babel 进行代码转换。

2.平台适配

  • Web:直接用 ReactDOM 渲染。
  • 小程序:将虚拟 DOM 转换为小程序的 JSON 结构和 WXML。
  • 原生 App:可用 React Native 或自定义桥接。

3.运行时 提供一套跨端组件(如 View、Text、Image 等),在不同平台下有不同实现,统一事件、生命周期、样式等 API

怎么做node cli的插件化,扩展给外部开发者定制化

1. 插件化的基本思路

  1. 插件发现:CLI 能自动发现和加载外部插件。
  2. 插件注册:插件能注册命令、参数、钩子等。
  3. 插件通信:插件和主程序、插件之间可以通信。
  4. 插件隔离:插件运行环境与主程序隔离,避免冲突。

2.常见实现方式

插件 API 设计: 主程序暴露 API 给插件,插件通过 API 注册命令、钩子等

主程序


class CLI {
  constructor() {
    this.commands = {};
  }
  registerCommand(name, fn) {
    this.commands[name] = fn;
  }
  run(argv) {
    const cmd = argv[2];
    if (this.commands[cmd]) {
      this.commands[cmd]();
    }
  }
}

const cli = new CLI();
module.exports = cli;

插件

module.exports = function(api) {
  api.registerCommand('hello', () => {
    console.log('Hello from plugin!');
  });
};

最后如有错误,请告知提醒

——转载自:山黎

#牛客创作赏金赛#
全部评论
太强了
点赞 回复 分享
发布于 07-16 17:23 北京
mark学习了
点赞 回复 分享
发布于 07-13 14:49 浙江
好难
点赞 回复 分享
发布于 07-03 15:36 湖北

相关推荐

11-15 14:10
门头沟学院 Java
1.怎么看待大模型场景的&nbsp;Agent,比如说我们在大模型之前写&nbsp;workflow,现在换成了&nbsp;Agent,或者之前是小模型换成了大模型,仅此而已。你认为真的&nbsp;Agent&nbsp;相比过去解决了更多的问题吗?为什么?2.你认为之前的传统行业写&nbsp;workflow&nbsp;和现在的行业&nbsp;agent,有本质的区别吗?3.你去构建一个&nbsp;Agent&nbsp;架构或者系统的时候和传统的后端架构业务架构来说,有一些什么不一样的地方吗,有哪些是要额外关注的地方吗?4.你提到性能问题,你之前做&nbsp;Agent&nbsp;的时候有提到大模型推理加速相关问题吗?5.你做&nbsp;Agent&nbsp;系统的时候,你有没有遇到一些比较深刻的问题,或者设计&nbsp;Agent&nbsp;架构的时候怎么设计的?比如比较复杂的问题,很难解决的问题。6.你刚才提到了&nbsp;Multi-Agent,你认为不能用传统的业务逻辑来解答?为什么一定西用&nbsp;Agent&nbsp;来解答。7.传统的业务后端框架,我们通常是将业务问题抽象成一个标准化的流程,设计成比如算子化等标准范式,这是传统解决大模型的思路,在大模型场景里,在大型场景化中,可能很多你需要在原来的工作环境中去抽象或者标准化的东西它自己经搞定,他不在需要你去设计什么,那这个时候在&nbsp;Agent&nbsp;中,就变成了下限很低,上限很高的东西,有些人可能就根据可视化的界面拖拽就创建了一个&nbsp;AgentRAG,那这个时候你觉得真正需要工程在里面参与的部分还存在吗?或者说你觉得在这里面到底要解决一个什么问题?还是说从业务的场景下就退下来了,真正去做像&nbsp;&amp;&amp;z&amp;,&nbsp;dify&nbsp;这种平台性的架构就可以了。8.你最近有没有看过一些和&nbsp;Agent&nbsp;相关的前沿的技术报告,论文,或者先进的技术9.算法题寻找众数,并且要求不存在众数返回-1
查看9道真题和解析
点赞 评论 收藏
分享
字节客户端,流转番茄、剪映、抖音、飞书等多个部门,历经十三次面试,终于拿下。前六面面经见上篇,记录下后续面经:七面(1h45min)1.&nbsp;自我介绍2.&nbsp;实习项目3.&nbsp;介绍下实习的项目架构4.&nbsp;讲讲对Compose声明式的理解5.&nbsp;java强引用和弱引用的区别6.&nbsp;还有其他引用类型吗7.&nbsp;ThreadLocal8.&nbsp;你平常有什么遇到的ThreadLocal的应用场景9.&nbsp;java集合类型(介绍各种集合,吟唱List、Map、Set)10.&nbsp;集合多线程访问数据竞争问题怎么解决11.&nbsp;synchronized的用法12.&nbsp;LeakCanary检测内存泄漏的机制13.&nbsp;MVVM、MVP、MVC三种架构的区别14.&nbsp;算法:搜索旋转排序数组总结:知识点知识面掌握挺不错的,基础知识的细节也了解得比较清楚,实习经历对于一个还没毕业的本科生来说还是不错的。过八面(1h)1.&nbsp;项目拷打2.&nbsp;不使用ksp开发的监测组件有什么办法定位Compose组件执行耗时3.&nbsp;LeakCanary原理机制4.&nbsp;OKHttp和Retrofit设计模式(答了七八个主要的)5.&nbsp;责任链好处是什么6.&nbsp;包大小优化措施,提升了哪些性能7.&nbsp;线程和协程的区别8.&nbsp;鸿蒙调用cpp是怎么做的9.&nbsp;了解什么跨端框架10.&nbsp;flutter为什么比原生慢11.&nbsp;flutter和react&nbsp;native在渲染机制上的区别是什么(自绘引擎独立绘制和原生组件映射)12.&nbsp;kmp是怎么实现跨端的(逻辑层统一编写,UI等原生层各自实现)13.&nbsp;view事件分发cancel事件什么情况会触发14.&nbsp;view事件分发流程15.&nbsp;Binder机制16.&nbsp;lc.215&nbsp;数组中第k个最大元素(要求先讲几种不同方法,再实现代码最复杂那个)(1)&nbsp;直接排序&nbsp;O(nlogn)(2)&nbsp;优先队列(最小堆)O(logn)(3)&nbsp;快速选择&nbsp;O(n)&nbsp;写这个的代码总结:没什么问题,秒过,进入hr面(最后倒在审批,原因是手头的offer不够多很难争取)九面(hr面45min)压力很大,像是在面技术面1.&nbsp;自我介绍2.&nbsp;为什么选择就业而不是考研3.&nbsp;工作室有什么项目需要做4.&nbsp;项目为什么做了一年(业余时间开发)5.&nbsp;项目的动机、背景6.&nbsp;还在实习吗(转正离职了)7.&nbsp;挑一个实习工作讲一下背景和做了哪些事情8.&nbsp;技术优化是谁提出来的,leader提出的还是自己找到的9.&nbsp;做完需求的成长和进步体现在哪里10.&nbsp;独立开发sdk提效是谁给你的建议吗11.&nbsp;除了技术问题还有遇到其他困难吗(联调沟通)12.&nbsp;一开始不使用联调的原因是什么,打乱工作节奏吗13.&nbsp;做好一个程序员,除了技术上扎实,还需要基本什么样的素质和能力14.&nbsp;沟通上有什么心得,可以提高工作效率15.&nbsp;有遇到矛盾和分歧吗,怎么解决的16.&nbsp;在团队中工作影响你产出的阻碍和困扰有什么17.&nbsp;新的UI框架你是花多久学清楚的18.&nbsp;mentor对你的评价是什么19.&nbsp;组内几个实习生,都转正通过了吗20.&nbsp;和其他实习生相比你的优势在哪里(来得早,知识面多)21.&nbsp;你有想过未来规划怎么样长期保持自己的优势吗(自驱力,持续学习)22.&nbsp;跨端方面你觉得哪家公司做得比较好(腾讯视频)23.&nbsp;你们组的跨端方案跟腾讯视频比有什么差距吗24.&nbsp;对字节校招面试的感受是怎么样的25.&nbsp;&nbsp;你是从其他部门流转过来抖音搜索的,对该部门面试的感受是什么26.&nbsp;有反思该部门二面没通过的原因吗27.&nbsp;会有失落和挫折的感受吗28.&nbsp;怎么排解失落的心情29.&nbsp;现在手头有哪些offer30.&nbsp;看你也投了其他非互联网公司,对发展的不同方向有倾向性吗审批一周还是倒下了,流转其他部门,后续免hr面后面的面试不怎么问八股了,更多是项目、场景、智力题十面(30min)1.&nbsp;自我介绍2.&nbsp;有了解鸿蒙如何与Kotlin通信吗3.&nbsp;Compose对比常规view的优势是什么4.&nbsp;Compose局部重组的原理,标脏机制5.&nbsp;对最近比较火的ai编程工具有了解吗,像claude这些6.&nbsp;了解ai编程的原理吗7.&nbsp;现在还在实习吗8.&nbsp;什么时候接触安卓的?写过ios,前端吗9.&nbsp;你会怎么快速上手ios10.&nbsp;编码算法就不考察了,之前面试都写出来了11.&nbsp;为啥有微信offer还面其他的总结:从二面开始,通过了就是三面,大概率也不考算法。过十一面(1h)1.&nbsp;自我介绍2.&nbsp;实习介绍3.&nbsp;Compose局部重组是怎么实现的4.&nbsp;ai工具你是怎么用的5.&nbsp;有没有用过claude&nbsp;code、cursor(需要付费,没用过)6.&nbsp;agent是怎么实现的,工作原理是什么7.&nbsp;你觉得自己比同阶段的同学优势在哪里8.&nbsp;最近在学什么9.&nbsp;最近比较感兴趣并且学得深一点的是什么10.&nbsp;Compose什么时候会跳过重组11.&nbsp;自己后面有什么规划吗12.&nbsp;Compose组件封装得更通用有什么思路吗13.&nbsp;组件点击事件的响应是怎么扩展的14.&nbsp;9枚硬币有一枚是假的,假硬币会轻一点,有一个天平,最少称几次可以找到假硬币15.&nbsp;100层楼,有两个瓶子,怎么扔,最坏情况下扔几次可以用最少次数找到瓶子会不会碎的临界楼层(想了几种方案最终只优化到18次的最坏情况)16.&nbsp;为什么apk体积比较小了还要优化17.&nbsp;apk优化方案总结:客户端经验很丰富,ai了解不够多。一周后挂十二面(1h)1.&nbsp;自我介绍2.&nbsp;实习亮点介绍3.&nbsp;跨端方案的背景4.&nbsp;跨端方案很多,为什么使用c++?写起来方便吗?5.&nbsp;java和c++是怎么通信的6.&nbsp;还了解哪些其他跨端技术7.&nbsp;怎么判断哪些代码可以下沉到逻辑复用层8.&nbsp;在跨端过程中你做了哪些9.&nbsp;举个例子说明哪些代码放在原生层,哪些代码放在复用层10.&nbsp;底层数据发生变化是怎么通知上层的?数据驱动是怎么做的11.&nbsp;原生层的viewmodel和底层的viewmodel的区别是什么12.&nbsp;使用什么手段可以主动发现线上的性能问题(流水线)13.&nbsp;MVVM的设计思路是什么,和传统MVC、MVP的区别14.&nbsp;LiveData相对于传统观察者模式有什么优势(生命周期集成)15.&nbsp;性能优化的整体思路是什么16.&nbsp;优化的收益怎么衡量17.&nbsp;怎么分组快速发现新改动的性能问题(AB实验)18.&nbsp;安卓内存泄漏的场景19.&nbsp;kotlin协程的理解20.&nbsp;多线程读取数据可能会有哪些问题,可以通过什么方式避免21.&nbsp;100瓶水,有1瓶是毒药,喝了1天后会毒发,最少多少只小白鼠和最短几天可以测出来(二进制编码)22.&nbsp;之前考了很多笔试了,就不考了,开始反问依然是从二面开始。过十三面(1h10min)1.&nbsp;自我介绍2.&nbsp;实习介绍3.&nbsp;介绍几个技术上挑战比较大的,提升比较多的4.&nbsp;介绍下java和c++通信的细节5.&nbsp;数据转换有性能上的问题吗6.&nbsp;C++和Kotlin/Java的差异有哪些7.&nbsp;SQLite使用有遇到存储和删减导致数据库文件庞大的情况吗8.&nbsp;数据库索引是怎么提高查询速度的,原理是什么9.&nbsp;除了xml解析还有没有看过其他三方库的源码10.&nbsp;okhttp的架构是怎么设计的,有哪些印象深刻的设计,哪些觉得设计得不够好的11.&nbsp;场景:微信朋友圈滑动卡顿,会怎么分析12.&nbsp;卡顿是由于什么引起的13.&nbsp;图片加载过多为什么会引起卡顿(频繁gc)14.&nbsp;怎么解决频繁gc的卡顿问题15.&nbsp;设计一个图片缓存库,会怎么设计(三级缓存)16.&nbsp;不同尺寸、分辨率的图片怎么标识(key怎么设计)17.&nbsp;不同尺寸的同一张图片怎么关联起来18.&nbsp;内存缓存图片的淘汰思路19.&nbsp;图片在磁盘的存储思路(包括图片命名,文件夹命名,图片查找策略等等)20.&nbsp;图片在磁盘的淘汰策略(包括使用频率记录方法、关联其他图片、删除方法、直接删一半空间的策略等等)最后的场景被拷打麻了。。。已经不知道自己是谁了最终等了一周还是通过了。谨以此贴记录字节面试经历,也希望能带给后来人一些参考
字节求职进展汇总
点赞 评论 收藏
分享
评论
9
19
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务