2026 年 6 月深圳前端工作行情

给正在找工作的朋友一些参考。

🔧技术栈

  • React 机会 >> Vue 机会,学习 Vue 的必要性不是很大
  • 不少前端岗位也要求 React Native 或 Flutter,有时间可以学习一下
  • 全栈/后端纯 Node.js 的机会不多,学习 Go/Java/Python 其中之一是巨大的补足
  • 英语口语,外企面试必备,重要程度不亚于编程技术
  • AI 相关:
    • Vibe Coding 工具:更偏好使用 Claude Code,Codex 这种先进工具的使用者,会问任务拆解、SKILLS等最佳实践
    • LLM 开发框架:LangChain,Dify,n8n,OpenClaw(稍微了解即可)
    • SSE 流式输出原理和实现,NDJSON 等流式传输数据格式

📋笔试要点

算法题类

传统企业考算法题的比较多(前端的算法这块儿本就是劣势,随缘就好,死抠算法的公司多半也不怎么样)

  • 递归、排序、查找、结构翻转、手动实现 Promise 之类的算法,实现难度并不大
  • 有一部分是机考,可以直接测试是否代码通过,但是即便代码跑用例通过了,面试官也可能人工检查代码
  • 也有一部分是现场编程(用电脑或者纸笔),面试官现场问问题
  • 时间复杂度 O(N) 是最常问的,也是最容易翻车的,除非你连刷一个月算法题,否则很难每次都找到最优算法。注意前端经常用的 Array.indexOf() 和 String.includes() 等遍历查找方法,性能极低,算法题的隐藏测试用例超长数据可能会超时,尽量不要用,尤其不要在循环、递归里面用。

最小不重复子串(Minimum Unique Substring)

  • 二分查找长度:子串长度越长,唯一的概率越大(具有单调性)。我们可以二分枚举子串的长度 L(范围为 1 ~ N)。
  • 滑动窗口计数:对于固定的长度 L,利用固定大小为 L 的滑动窗口遍历字符串,用哈希表(Map)统计每个长度为 L 的子串出现的次数。
  • 判定:只要发现某个长度为 L 的子串出现频次恰好为 1,说明存在长度为 L 的不重复子串,尝试寻找更短的长度(缩小右边界);否则增大长度(增大左边界)。
/**
 * 查找字符串中的最小不重复子串
 * @param {string} str
 * @returns {string} 长度最短且唯一的子串(若有多个同长度的,返回最先出现的)
 */
function findMinUniqueSubstring(str) {
    const n = str.length;
    if (n === 0) return '';

    let left = 1, right = n;
    let result = '';

    // 辅助函数:校验是否存在长度为 len 的唯一子串
    function check(len) {
        const counts = new Map();
        
        // 1. 滑动窗口统计所有长度为 len 的子串频次
        for (let i = 0; i <= n - len; i++) {
            const sub = str.substring(i, i + len);
            counts.set(sub, (counts.get(sub) || 0) + 1);
        }

        // 2. 查找是否有频次恰好为 1 的子串
        for (let [sub, count] of counts.entries()) {
            if (count === 1) {
                return sub; // 返回满足条件的最先出现的子串
            }
        }
        return null;
    }

    // 二分查找最短长度
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        const uniqueSub = check(mid);

        if (uniqueSub !== null) {
            result = uniqueSub; // 记录答案
            right = mid - 1;    // 尝试寻找更短的
        } else {
            left = mid + 1;     // 找不到,说明需要更长的子串
        }
    }

    return result;
}

// --- 测试用例 ---
console.log(findMinUniqueSubstring("aaaaa"));    // 输出: "aaaaa" (所有更短的 "a", "aa" 等都重复出现了)
console.log(findMinUniqueSubstring("banana"));   // 输出: "b" ("b" 只出现过 1 次,长度为 1 最短)
console.log(findMinUniqueSubstring("abcabc"));   // 输出: "abca" (1-3 长度的子串如 "a","ab","abc" 都重复了)

任务队列(Task Queue)

class TaskQueue {
    /**
     * @param {number} concurrency 最大并发数
     */
    constructor(concurrency = 2) {
        this.concurrency = concurrency; // 最大并发限制
        this.running = 0;             // 当前正在执行的任务数
        this.queue = [];               // 任务等待队列
    }

    /**
     * 添加异步任务
     * @param {Function} taskFn 返回 Promise 的任务函数
     * @returns {Promise}
     */
    add(taskFn) {
        return new Promise((resolve, reject) => {
            // 将任务与 Promise 的控制权一同推入队列
            this.queue.push({
                taskFn,
                resolve,
                reject
            });
            // 尝试触发下一个任务
            this.runNext();
        });
    }

    runNext() {
        // 如果正在运行的任务达到并发上限,或者队列为空,则停止启动新任务
        if (this.running >= this.concurrency || this.queue.length === 0) {
            return;
        }

        // 按 FIFO(先进先出)出队一个任务
        const { taskFn, resolve, reject } = this.queue.shift();
        this.running++;

        // 执行任务
        Promise.resolve()
            .then(() => taskFn())
            .then(resolve)
            .catch(reject)
            .finally(() => {
                this.running--;
                // 任务完成后,递归尝试启动后续任务
                this.runNext();
            });
    }
}

// --- 测试用例 ---

const queue = new TaskQueue(2); // 限制同时最多跑 2 个任务

// 模拟异步请求
const createTask = (id, time) => () => {
    return new Promise((resolve) => {
        console.log(`[开始] 任务 ${id}`);
        setTimeout(() => {
            console.log(`[完成] 任务 ${id}`);
            resolve(`任务 ${id} 的结果`);
        }, time);
    });
};

// 连续添加 4 个任务
queue.add(createTask(1, 1000)).then(res => console.log('输出:', res));
queue.add(createTask(2, 500)).then(res => console.log('输出:', res));
queue.add(createTask(3, 300)).then(res => console.log('输出:', res));
queue.add(createTask(4, 400)).then(res => console.log('输出:', res));

/* 
执行输出流程:
1. [开始] 任务 1 (预计 1000ms)
2. [开始] 任务 2 (预计 500ms)
   (等待 500ms...)
3. [完成] 任务 2 -> 输出: 任务 2 的结果
4. [开始] 任务 3 (任务 2 释放空位,任务 3 启动)
   (等待 300ms...)
5. [完成] 任务 3 -> 输出: 任务 3 的结果
6. [开始] 任务 4 (任务 3 释放空位,任务 4 启动)
   (再等待 200ms...)
7. [完成] 任务 1 -> 输出: 任务 1 的结果
8. [完成] 任务 4 -> 输出: 任务 4 的结果
*/

手写 Promise


class MyPromise {
    // 定义三种状态常量
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';

    constructor(executor) {
        this.status = MyPromise.PENDING; // 初始状态为 pending
        this.value = undefined;          // 成功后的值
        this.reason = undefined;         // 失败的原因

        this.onResolvedCallbacks = [];   // 存放 then 中成功的回调
        this.onRejectedCallbacks = [];   // 存放 then 中失败的回调

        const resolve = (value) => {
            // 如果 value 本身是个 Promise,递归解析
            if (value instanceof MyPromise) {
                return value.then(resolve, reject);
            }

            // 状态只能从 pending 变更一次
            if (this.status === MyPromise.PENDING) {
                this.status = MyPromise.FULFILLED;
                this.value = value;
                // 依次触发订阅的成功回调
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        };

        const reject = (reason) => {
            if (this.status === MyPromise.PENDING) {
                this.status = MyPromise.REJECTED;
                this.reason = reason;
                // 依次触发订阅的失败回调
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        };

        // 执行器报错时直接 reject
        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        // 参数校验与穿透处理(如果传入的不是函数,给默认函数)
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

        // 每次调用 .then 都返回一个新的 Promise 以支持链式调用
        const promise2 = new MyPromise((resolve, reject) => {
            
            // 封装微任务执行逻辑
            const handleFulfilled = () => {
                queueMicrotask(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });
            };

            const handleRejected = () => {
                queueMicrotask(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });
            };

            // 根据当前状态做出响应
            if (this.status === MyPromise.FULFILLED) {
                handleFulfilled();
            } else if (this.status === MyPromise.REJECTED) {
                handleRejected();
            } else if (this.status === MyPromise.PENDING) {
                // 异步任务:暂存回调
                this.onResolvedCallbacks.push(handleFulfilled);
                this.onRejectedCallbacks.push(handleRejected);
            }
        });

        return promise2;
    }
}

/**
 * 规范的核心:解析 Promise 逻辑
 */
function resolvePromise(promise2, x, resolve, reject) {
    // 1. 循环引用判断(不能自己等待自己)
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }

    // 2. 状态锁,防止反复调用 resolve / reject
    let called = false;

    // 3. 判断 x 是不是对象或函数
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            const then = x.then;

            if (typeof then === 'function') {
                // 如果 then 是函数,说明 x 是一个 thenable(比如 Promise 实例)
                then.call(
                    x,
                    (y) => {
                        if (called) return;
                        called = true;
                        // 递归解析 resolve 的值(因为 y 可能又是一个 Promise)
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    (r) => {
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                );
            } else {
                // 普通对象/函数,直接 resolve
                resolve(x);
            }
        } catch (error) {
            if (called) return;
            called = true;
            reject(error);
        }
    } else {
        // 普通原始值(number, string 等),直接 resolve
        resolve(x);
    }
}

手写 EventEmitter

type Listener = (...args: any[]) => void;

class EventEmitter {
  // 维护事件名到监听函数数组的映射
  private events: Map<string, Listener[]> = new Map();

  /**
   * 订阅事件
   */
  on(event: string, listener: Listener): this {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event)!.push(listener);
    return this; // 支持链式调用
  }

  /**
   * 触发事件
   */
  emit(event: string, ...args: any[]): boolean {
    const listeners = this.events.get(event);
    if (!listeners || listeners.length === 0) {
      return false;
    }

    // 防御性浅拷贝:防止在执行 listener 期间有人调用 off 导致数组索引错乱
    const callbacks = [...listeners];
    callbacks.forEach((fn) => fn(...args));
    return true;
  }

  /**
   * 取消订阅
   */
  off(event: string, listener: Listener): this {
    const listeners = this.events.get(event);
    if (!listeners) return this;

    // 过滤掉匹配的监听器(同时需兼容 once 包装后的原始函数引用)
    const index = listeners.findIndex(
      (fn) => fn === listener || (fn as any)._origin === listener
    );

    if (index !== -1) {
      listeners.splice(index, 1);
    }

    // 若该事件下已无监听器,清理 key 避免内存泄漏
    if (listeners.length === 0) {
      this.events.delete(event);
    }

    return this;
  }

  /**
   * 单次订阅:触发一次后自动销毁
   */
  once(event: string, listener: Listener): this {
    // 包装原始函数
    const wrapper: Listener = (...args: any[]) => {
      this.off(event, wrapper); // 执行前先解绑
      listener(...args);
    };

    // 挂载原始引用,确保可以通过 off(event, originalListener) 正确卸载
    (wrapper as any)._origin = listener;

    this.on(event, wrapper);
    return this;
  }

  /**
   * 清除指定事件的所有监听,或清除所有事件
   */
  removeAllListeners(event?: string): this {
    if (event) {
      this.events.delete(event);
    } else {
      this.events.clear();
    }
    return this;
  }
}

项目实现类

外企考项目实现的比较多

  • 你会接到一个具体的项目要求,做一个小型的全栈网站
  • 有一周左右的时间完成代码编写,并部署到 AWS 之类的云上
  • 要特别注意代码结构清晰,功能上打磨丝滑、交互考虑全面
  • 避免套模板产生过多多余代码(尤其是 AI 生成的代码)
  • 面试官会根据你提交的成果提问,包括系统设计,技术选型,实现细节,CI/CD

🤵‍♂️面试要点

  • 自我介绍时,着重介绍和岗位匹配的部分,比如对方要全栈,就多讲一些 Node.js 数据库之类的
  • 准备几个自己做的最有深度的技术项目案例,设计思路,技术难点,原创亮点

🔍招聘筛选咨询要点

  • 岗位预算,预算不足的直接拒掉
  • 工作时间和加班情况,尽量避免常态固定加班的,如果能接受也要提高期望薪资才不亏
  • 办公地点,通勤是个大问题,可能还要搬家
  • 技术团队规模,少于 10 人的公司慎选,公司研发能力和资本厚度都要打个问号,尽量选 50 人以上的公司

⚠️避坑的行业和公司

Web3 区块链

  • 合规风险极高,尤其是用 USDT 发工资的,容易被抓被没收,最后一场空
  • 加密货币低迷,比特币等价格大幅跳水,市场前景堪忧
  • 蹭世界杯热度,世界杯结束后可能会裁员

AI 初创:具身智能 & 自动驾驶 & 大模型应用

  • 加班强度极大,995 或 10105 是常态,甚至有些还大小周,这是身体上的累
  • 内部管理混乱,很多都无法产品落地,经常调整方向,个人还要背锅,这是心理上的累

其他避坑关键点

  • 有年龄限制的,比如某社交媒体公司限制 28 岁以下。如果一个公司/团队除了领导全是年轻人,说明人员流动性极大,工作强度极高,末位淘汰严重,老人活不下来
  • 军事化或准军事化管理,推崇狼性文化,搞战役、军团、服从性测试
  • 用期权给薪资注水的(现金只给一半),老板疯狂画饼的(让你当“储备”CTO)

互联网大厂

字节

我的面试经历

互联网大厂

  • 腾讯:
    • CodeBuddy/WorkBuddy AI 全栈:
      • 一面(技术面):大致聊了了一下经历,没有考代码,面完直接告诉你寄了
  • 字节
    • 国际化广告前端:
      • 简历寄了
    • 国际电商:
      • 内推无反应
    • 飞书 AI Agent 前端(被调剂了)
      • 一面(技术交叉面):面试官比较随和,项目经历聊的比较广,算法题也是比较简单的(数组操作、时间复杂度),可能是前端的缘故,后续还没消息
  • 京东
    • 国际电商
      • 非常混乱,投的前端,被调剂到了Java后端开发,直接放弃
  • 虾皮
    • FOOD外卖
      • 一面(技术面):面试官非常压抑,感觉就不是很想和你聊,算法题比较简单(最小不重复序列,数组扁平化之类的),没后续(传闻开启了大裁员)

硬件公司

  • 大疆
    • 加班强度太高,直接放弃
  • 安克
    • 一面(技术面):感觉面试官问不到点子上,你讲的项目他也不感兴趣,没考算法题,面完直接寄了
    • 有几个大厂前端朋友去面也是一面寄了,前端岗位招聘的真实性存疑
  • 汝原
    • 大疆系的创业团队,公司规模几百人,研发几十人,有成熟产品线,需要加班
    • 一面(技术面):大致了解一下项目经历
    • 二面(现场技术面):技术和算法题,以及对职业规划的期望
    • 谈薪资预期:应该是超了对方预算,寄了
  • 快造
    • 3D打印小公司,公司规模几百人,研发几十人,实力肯定是比不了大疆系的拓竹,但是压力会小很多
    • 线下一口气走完面试全流程,非常挑战体力,聊的内容比较轻松愉快
    • 一面(技术面):了解一下项目经历,技术细节,开放话题,没考算法
    • 二面(技术面):,即兴英语口语对话(应该不是必需,非留学生不会考)

金融证券

  • 汇丰(新开深圳,也有广州、西安可选)
    • 全栈开发(偏后端):
      • 投完简历,等了一周通知岗位停招,目前传闻汇丰全球裁员一万人
  • 中信证券
    • 全栈开发(偏后端):
      • 笔试 2 小时,手搓代码(没有运行测试环境),直接放弃
      • 月薪比较低(低于 4 万),全靠奖金
      • 上班时间比较早,办公地点在福田
  • 平安保险
    • 前端开发:
      • 内推无反应

区块链 Web3

  • 领创集团
    • 公司情况:新加坡金融外企,规模还算比较大,原本在东南亚做类似蚂蚁小额贷芝麻信用类似的东西,新开了 Web3 期货交易业务(面向中东客户)
    • 岗位情况:移动端开发(React Native + H5),不强制移动端开发经验
    • 一面(技术面):聊的比较轻松,没有算法题,主要是项目经历,技术细节,体验设计思维
    • 没有后续
  • 某 AI + Web3 公司
    • 公司情况:香港公司,深圳有不到十个开发,彻底本地化,996,避坑快跑

自动驾驶

聊了几个小公司都是要 995 或者 10105 的,薪资预算也偏低,只能给到 50万以内的年包。大厂出来的同学,建议直接 pass 这个行业的初创和小公司。

大型外企

  • 沃尔玛
    • 刚投递还没消息
  • 西门子
    • 投递简历没反应,近期传出全球裁员数万人

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理