2026 前端面试全攻略:手写题、算法与计网/TS 高频考点

最近在看新的机会,把之前整理的前端面试基础知识重新梳理了一遍。内容涵盖

CSS 布局、JavaScript 核心概念、前端手写题、高频算法、框架原理、
计算机网络与浏览器、TypeScript 高频考点

这份资料既可以作为答案索引,也提供了针对"看懂但现场写不出来"的训练方法。

标注 的内容为本次整理时补充的高频考点。

2026-06-14 | 持续更新中


目录


一、CSS 布局与技巧

1.1 三栏布局

css 复制代码
/* 方法1:float */
.left { float: left; width: 200px; background: red; }
.right { float: right; width: 200px; background: blue; }
.middle { margin: 0 200px; background: green; }

/* 方法2:flex */
.container { display: flex; }
.left { flex: 0 0 200px; }
.middle { flex: 1; }
.right { flex: 0 0 200px; }

关键点:float 方案需注意 DOM 顺序(left/right 写在 middle 前面);flex 方案更直观。

1.2 两栏布局

css 复制代码
/* float */
.left { float: left; width: 200px; }
.right { margin-left: 200px; }

/* flex */
.container { display: flex; }
.left { flex: 0 0 200px; }
.right { flex: 1; }

1.3 水平居中

css 复制代码
/* 1. text-align(行内元素/文本) */
.parent { text-align: center; }

/* 2. margin:auto(定宽块级) */
.child { width: 200px; margin: 0 auto; }

/* 3. flex */
.parent { display: flex; justify-content: center; }

/* 4. absolute + transform */
.child {
    position: absolute; left: 50%;
    transform: translateX(-50%);
}

1.4 垂直居中

css 复制代码
/* 1. line-height(单行文本,height == line-height) */

/* 2. table-cell */
.parent { display: table-cell; vertical-align: middle; }

/* 3. flex */
.parent { display: flex; align-items: center; }

/* 4. absolute + transform */
.child {
    position: absolute; top: 50%;
    transform: translateY(-50%);
}

1.5 0.5px 的线

css 复制代码
.line {
    height: 1px;
    transform: scaleY(0.5);
    transform-origin: 0 0;
}

1.6 CSS 三角形

css 复制代码
.triangle {
    width: 0; height: 0;
    border: 20px solid transparent;
    border-bottom-color: red; /* 尖角朝上 */
}

原理:四个 border 交汇处各占一个三角区域,将其中三边设为透明即可。

1.7 五点骰子

html 复制代码
<!-- 使用 flexbox + justify-content + align-items 实现骰子五点布局 -->
<div class="dice">
    <div class="row"><span></span><span></span></div>
    <div class="row"><span></span></div>
    <div class="row"><span></span><span></span></div>
</div>

1.8 宽度为高度的一半

css 复制代码
/* 方法1:vw */
.box { width: 50vw; height: 25vw; }

/* 方法2:CSS变量 + calc */
.box { --w: 200px; width: var(--w); height: calc(var(--w) / 2); }

/* 方法3:padding-bottom百分比(相对于父元素宽度) */
.box { width: 100%; padding-bottom: 50%; }

二、JavaScript 核心概念

2.1 变量提升与作用域

javascript 复制代码
// 🔴 经典题1:函数内 var 提升
var a = 10;
function test() {
    a = 100;
    console.log(a);        // 100
    console.log(this.a);   // 10(window.a)
    var a;                 // 变量提升到函数顶部
    console.log(a);        // 100
}
test();

// 🔴 经典题2:var vs let 在 for 循环 + setTimeout
for (var i = 1; i <= 3; i++) {
    setTimeout(() => console.log(i), 0);
}
// 输出:4, 4, 4(var没有块级作用域)

for (let i = 1; i <= 3; i++) {
    setTimeout(() => console.log(i), 0);
}
// 输出:1, 2, 3(let有块级作用域,每次迭代新绑定)

// 🔴 经典题3:函数提升高于变量提升
var fun = 123;
function fun() { console.log('function'); }
fun(); // TypeError: fun is not a function(fun被覆盖为123)

// 🔴 经典题4:参数与变量提升
function fun(n) {
    console.log(n);    // 123(形参n已有值,var n声明被忽略)
    var n = 456;
    console.log(n);    // 456
}
var n = 123;
fun(n);

// 🔴 经典题5:没有var声明 => 全局变量
(function() {
    var a = b = 3; // 等价于 b=3; var a=b;
})();
console.log(b); // 3(b是全局变量)
console.log(a); // ReferenceError: a is not defined

// 🔴 字节真题:函数提升遮蔽全局变量
var a = 1;
function b() {
    a = 10;
    return;
    function a() {} // 函数声明提升,a变成局部函数
}
b();
console.log(a); // 1(函数作用域内的a是局部函数,不影响全局a)

2.2 闭包

javascript 复制代码
// 🔴 闭包陷阱
var data = [];
for (var i = 0; i < 3; i++) {
    data[i] = function() { console.log(i); };
}
data[0](); // 3(闭包捕获的都是同一个i)
data[1](); // 3
data[2](); // 3

// ✅ 解决方案1:let(块级作用域)
for (let i = 0; i < 3; i++) {
    data[i] = function() { console.log(i); };
}

// ✅ 解决方案2:IIFE
for (var i = 0; i < 3; i++) {
    (function(j) { data[j] = function() { console.log(j); }; })(i);
}

// 🔴 小米题:嵌套闭包
var n = 10;
function fn() {
    var n = 20;
    function f() { n++; console.log(n); }
    f();       // 21
    return f;
}
var x = fn(); // 21
x();          // 22(闭包保持对fn内n的引用)
x();          // 23
console.log(n); // 10(全局n未变)

2.3 this 指向

javascript 复制代码
// 🔴 规则速记:谁调用指向谁(箭头函数除外)
var obj = {
    a: 2,
    f: function() { console.log(this.a); },
    m: () => { console.log(this.a); } // 箭头函数this=定义时的外层this
};
var f2 = obj.f;
var a = 'hello';

f2();      // 'hello'(无调用者 => window/globalThis)
obj.f();   // 2(obj调用)
obj.m();   // 'hello'(箭头函数,this指向window)

// 🔴 setTimeout中的this
var id = 'Global';
function fun1() {
    setTimeout(function() {
        console.log(this.id); // 普通函数,this=window
    }, 1000);
}
function fun2() {
    setTimeout(() => {
        console.log(this.id); // 箭头函数,this=fun2的this
    }, 1000);
}
fun1.call({id: 'Obj'}); // 'Global'
fun2.call({id: 'Obj'}); // 'Obj'

// 🔴 arguments 中的 this
var length = 100;
function f1() { console.log(this.length); }
var obj = {
    x: 100,
    f2: function(f1) {
        f1();             // 100(window.length)
        arguments[0]();   // 2(arguments.length=2,arguments调用了f1)
    }
};
obj.f2(f1, 1);

2.4 事件循环(Event Loop)

javascript 复制代码
// 🔴 经典题:宏任务 vs 微任务
setTimeout(() => {
    console.log(1);
    new Promise(resolve => resolve()).then(() => console.log(2));
});
setTimeout(() => { console.log(3); });
new Promise(resolve => {
    console.log(4);
    resolve();
    console.log(5);
}).then(() => { console.log(6); });
console.log(7);
new Promise(resolve => resolve()).then(() => { console.log(8); });

// 输出:4, 5, 7, 6, 8, 1, 2, 3
// 解析:
// 同步:4,5,7
// 微任务队列:then6, then8 → 6,8
// 宏任务1:1, then2 → 1,2(微任务插队)
// 宏任务2:3

// 🔴 百度题
console.log(3);
Promise.resolve().then(() => {
    console.log('Promise1');
    setTimeout(() => console.log('setTimeout2'), 0);
});
setTimeout(() => {
    console.log("setTimeout1");
    Promise.resolve().then(() => console.log('Promise2'));
}, 0);
// 输出:3, Promise1, setTimeout1, Promise2, setTimeout2

核心规则

  • 同步代码 → 微任务队列(Promise.then) → 宏任务队列(setTimeout)
  • 每个宏任务执行完后,会清空微任务队列再执行下一个宏任务
  • async/awaitawait 后面的代码相当于 .then() 回调
javascript 复制代码
// 🔴 async/await 执行顺序
async function async1() {
    console.log('async1 start');  // 2
    await async2();               // 立即执行async2
    console.log('async1 end');    // 7(await 后续进入微任务队列)
}
async function async2() {
    console.log('async2');        // 3
    setTimeout(() => console.log('async2宏'), 0); // 9
    new Promise(resolve => resolve())
        .then(() => console.log('2内微'));  // 6
    console.log('2内同步2');      // 4
}
console.log('script start');      // 1
async1();
new Promise(resolve => resolve())
    .then(() => console.log('resolve')); // 8
console.log('script end');        // 5
// 输出:1, 2, 3, 4, 5, 2内微, async1 end, resolve, async2宏
// 原因:async2 内部的 then 比 await async2 的续体更早进入微任务队列

2.5 原型链与继承

五种继承方式

javascript 复制代码
// 1. 原型链继承
Child.prototype = new Parent(); // 核心
// ❌ 弊端1:所有子实例共享父类引用属性
// ❌ 弊端2:子类原型上的方法必须在替换原型之后添加

// 2. 构造函数继承
function Child() { Parent.call(this, args); } // 核心
// ✅ 解决引用属性共享问题
// ❌ 弊端:无法继承父类原型上的方法

// 3. 组合继承 = 构造 + 原型链
function Child() { Parent.call(this, args); }
Child.prototype = new Parent();
// ✅ 前两种的结合,但调用了两次父类构造函数

// 4. 寄生组合继承(最优方案)
function Child() { Parent.call(this, args); }
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

// 5. ES6 class
class Child extends Parent {
    constructor(...args) { super(...args); }
}

原型链关键结论

  • Object.create(x) --- 浅拷贝,创建新对象,原型指向x
  • delete yideng.address 删除不了原型上的属性,所以还是输出原型上的值

2.6 箭头函数

javascript 复制代码
// 箭头函数特性:
// 1. 没有自己的this,this继承自外层作用域(定义时的this,不是调用时的)
// 2. 不能用作构造函数(没有prototype)
// 3. 没有arguments对象

var x = 11;
var obj = {
    x: 22,
    say: () => { console.log(this.x); } // this = window
};
obj.say(); // 11,不是22!

// 返回匿名函数的坑
var obj = {
    x: 33,
    getAge: function() {
        return function() { console.log(this.x); }; // this = window
    }
};
obj.getAge()(); // 11(全局的x)

2.7 其他陷阱题

javascript 复制代码
// 🔴 parseInt 陷阱
[1,2,3].map(parseInt);
// 等于 [parseInt(1,0), parseInt(2,1), parseInt(3,2)]
// 结果:[1, NaN, NaN]
// parseInt第二个参数是进制(2-36),0视为10进制

// 🔴 浮点数精度
0.1 + 0.2 === 0.3 // false(二进制无限循环)
0.1 + 0.5 === 0.6 // true
// 解决:Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON

// 🔴 自执行函数变量泄露
(function() { var a = b = 3; })();
console.log(typeof a); // 'undefined'
console.log(b);        // 3(b没var,泄漏为全局)

// 🔴 NaN === NaN // false
// 🔴 Symbol('a') === Symbol('a') // false

// 🔴 null == undefined // true
// 🔴 1 == 2 == 0 // true(解析为 (1==2)==0 → false==0 → true)
// 🔴 null instanceof Object // false(null是基本类型)

// 🔴 值传递 vs 引用传递
var a = 1, b = 2;
function swap(a, b) { var tmp = a; a = b; b = tmp; }
swap(a, b);
console.log(a, b); // 1, 2(基本类型值传递,不影响外部)
// 如果是对象属性则可以通过引用修改
var obj = {a:1, b:2};
function swapO(obj, k1, k2) { var t = obj[k1]; obj[k1] = obj[k2]; obj[k2] = t; }
swapO(obj, 'a', 'b');
console.log(obj); // {a:2, b:1}(对象引用传递)

三、前端手写题全集(38项)

标注 ✨ 新增 的为本次整理时补充的高频题目。

3.1 防抖(Debounce)

javascript 复制代码
function debounce(fn, delay) {
    let timer;
    return function () {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, arguments), delay);
    };
}

// ✨ 增强版:支持立即执行 + 取消
function debounce(fn, delay, immediate = false) {
    let timer = null;
    const debounced = function (...args) {
        const callNow = immediate && !timer;
        clearTimeout(timer);
        timer = setTimeout(() => {
            timer = null;
            if (!immediate) fn.apply(this, args);
        }, delay);
        if (callNow) fn.apply(this, args);
    };
    debounced.cancel = () => { clearTimeout(timer); timer = null; };
    return debounced;
}
// 场景:搜索框输入、窗口resize

3.2 节流(Throttle)

javascript 复制代码
// 方法1:时间戳版(第一次立即触发,最后一次不触发)
function throttle(fn, delay) {
    let pre = Date.now();
    return function () {
        let now = Date.now();
        if (now - pre > delay) {
            fn.apply(this, arguments);
            pre = Date.now();
        }
    };
}

// 方法2:定时器版(第一次延迟触发,最后一次会触发)
function throttle(fn, delay) {
    let timer;
    return function () {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, arguments);
                timer = null;
            }, delay);
        }
    };
}

// ✨ 增强版:首尾都触发(时间戳+定时器结合)
function throttle(fn, delay) {
    let timer = null, lastTime = 0;
    return function (...args) {
        const now = Date.now();
        const remaining = delay - (now - lastTime);
        if (remaining <= 0) {
            if (timer) { clearTimeout(timer); timer = null; }
            lastTime = now;
            fn.apply(this, args);
        } else if (!timer) {
            timer = setTimeout(() => {
                lastTime = Date.now();
                timer = null;
                fn.apply(this, args);
            }, remaining);
        }
    };
}
// 场景:scroll事件、按钮点击

3.3 深拷贝

javascript 复制代码
// ✅ 方法1:JSON(最简单,有局限性)
JSON.parse(JSON.stringify(obj));
// ❌ 无法拷贝:函数、undefined、Symbol、循环引用、Date/RegExp

// ✅ 方法2:递归(简化版,支持数组和对象)
function deepClone(obj) {
    if (typeof obj !== 'object' || obj === null) return obj;
    let result = obj.constructor === Array ? [] : {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            result[key] = deepClone(obj[key]);
        }
    }
    return result;
}

// ✅ 方法3:面试增强版(支持循环引用、Date、RegExp、Map、Set、Symbol键)
function deepClone(obj, cache = new WeakMap()) {
    if (typeof obj !== 'object' || obj === null) return obj;
    if (cache.has(obj)) return cache.get(obj); // 循环引用
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);

    if (obj instanceof Map) {
        const result = new Map();
        cache.set(obj, result);
        obj.forEach((value, key) => {
            result.set(deepClone(key, cache), deepClone(value, cache));
        });
        return result;
    }
    if (obj instanceof Set) {
        const result = new Set();
        cache.set(obj, result);
        obj.forEach(value => result.add(deepClone(value, cache)));
        return result;
    }

    const newObj = Array.isArray(obj)
        ? []
        : Object.create(Object.getPrototypeOf(obj));
    cache.set(obj, newObj);

    Reflect.ownKeys(obj).forEach(key => {
        const descriptor = Object.getOwnPropertyDescriptor(obj, key);
        if ('value' in descriptor) {
            descriptor.value = deepClone(descriptor.value, cache);
        }
        Object.defineProperty(newObj, key, descriptor);
    });
    return newObj;
}
// 面试时先声明支持范围;函数和 WeakMap/WeakSet 通常不做深复制

3.4 浅拷贝

javascript 复制代码
function shallowClone(obj) {
    var newObj = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) newObj[key] = obj[key];
    }
    return newObj;
}
// 其他方法:Object.assign({}, obj)、{...obj}

3.5 Promise 系列

3.5.1 Promise 状态机教学版(不可链式)
javascript 复制代码
class MyPromise {
    constructor(executor) {
        this.status = 'pending';
        this.value = null;
        this.fulfilledCallbacks = [];
        this.rejectedCallbacks = [];

        const resolve = (value) => {
            if (this.status === 'pending') {
                this.status = 'resolved';
                this.value = value;
                this.fulfilledCallbacks.forEach(fn => fn(value));
            }
        };
        const reject = (value) => {
            if (this.status === 'pending') {
                this.status = 'rejected';
                this.value = value;
                this.rejectedCallbacks.forEach(fn => fn(value));
            }
        };
        try { executor(resolve, reject); }
        catch (err) { reject(err); }
    }

    then(onFulfilled, onRejected) {
        if (this.status === 'pending') {
            this.fulfilledCallbacks.push(() => onFulfilled(this.value));
            this.rejectedCallbacks.push(() => onRejected(this.value));
        }
        if (this.status === 'resolved') onFulfilled(this.value);
        if (this.status === 'rejected') onRejected(this.value);
    }
}

这段只用于理解状态、回调队列和状态不可逆,不支持链式调用、值穿透、

thenable 吸收和微任务调度,不能当作完整 Promise 实现。

3.5.2 Promise.all
javascript 复制代码
function promiseAll(promises) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            return reject(new TypeError('arguments must be an array'));
        }
        let count = 0;
        const n = promises.length;
        const results = new Array(n);
        if (n === 0) return resolve([]);

        for (let i = 0; i < n; i++) {
            Promise.resolve(promises[i]).then(
                (val) => {
                    count++;
                    results[i] = val;
                    if (count === n) resolve(results);
                },
                (err) => reject(err) // 任何一个失败就整个失败
            );
        }
    });
}
// 标准 Promise.all 接收 iterable;面试简化版若限制为数组,应主动说明
3.5.3 Promise.allSettled
javascript 复制代码
Promise.myAllSettled = function (promises) {
    return new Promise((resolve) => {
        const results = [];
        let count = 0;
        if (promises.length === 0) { resolve(results); return; }

        promises.forEach((promise, index) => {
            Promise.resolve(promise).then(
                value => {
                    results[index] = { status: 'fulfilled', value };
                    count++;
                    if (count === promises.length) resolve(results);
                },
                reason => {
                    results[index] = { status: 'rejected', reason };
                    count++;
                    if (count === promises.length) resolve(results);
                }
            );
        });
    });
};
// 关键:与all不同,allSettled不会因某个reject而中断,总是等全部完成
3.5.4 Promise.race
javascript 复制代码
Promise.myRace = function (promises) {
    return new Promise((resolve, reject) => {
        for (const promise of promises) {
            Promise.resolve(promise).then(resolve, reject);
        }
    });
};
// 关键:谁先完成(无论成功/失败)就用谁的结果

3.6 发布订阅(EventEmitter)

javascript 复制代码
var shoeObj = {};
shoeObj.list = [];
shoeObj.listen = function (key, fn) {
    if (!this.list[key]) this.list[key] = [];
    this.list[key].push(fn);
};
shoeObj.trigger = function () {
    var key = Array.prototype.shift.call(arguments);
    var fns = this.list[key];
    if (!fns || fns.length === 0) return;
    for (let i = 0, fn; fn = fns[i++];) {
        fn.apply(this, arguments);
    }
};

class Event {
    constructor() { this.eventMap = {}; }
    register(key, fn) {
        if (!this.eventMap[key]) this.eventMap[key] = [];
        this.eventMap[key].push(fn);
    }
    run(key, ...args) {
        if (this.eventMap[key]?.length) {
            this.eventMap[key].forEach(fn => fn.apply(null, args));
        }
    }
}

// ✨ 完整版:支持 once、off
class EventEmitter {
    constructor() { this.events = {}; }

    on(event, callback) {
        if (!this.events[event]) this.events[event] = [];
        this.events[event].push(callback);
    }

    once(event, callback) {
        const wrapper = (...args) => {
            callback.apply(this, args);
            this.off(event, wrapper);
        };
        wrapper.originCallback = callback;
        this.on(event, wrapper);
    }

    off(event, callback) {
        if (!this.events[event]) return;
        this.events[event] = this.events[event].filter(
            cb => cb !== callback && cb.originCallback !== callback
        );
    }

    emit(event, ...args) {
        if (!this.events[event]) return;
        this.events[event].forEach(cb => cb.apply(this, args));
    }
}

3.7 call / apply / bind

javascript 复制代码
// ✅ 手写 call
Function.prototype.myCall = function (context, ...args) {
    if (typeof this !== 'function') throw new TypeError('not a function');
    context = context == null ? globalThis : Object(context);
    const key = Symbol('fn');
    context[key] = this;
    try {
        return context[key](...args);
    } finally {
        delete context[key];
    }
};

// ✅ 手写 apply
Function.prototype.myApply = function (context, args) {
    if (typeof this !== 'function') throw new TypeError('not a function');
    context = context == null ? globalThis : Object(context);
    const key = Symbol('fn');
    context[key] = this;
    try {
        return args == null ? context[key]() : context[key](...args);
    } finally {
        delete context[key];
    }
};

// ✅ 手写 bind
Function.prototype.myBind = function (context, ...args) {
    if (typeof this !== 'function') throw new TypeError('not a function');
    const target = this;
    function bound(...rest) {
        // 使用 new 调用时忽略绑定的 context
        const thisArg = this instanceof bound ? this : context;
        return target.apply(thisArg, [...args, ...rest]);
    }
    if (target.prototype) {
        bound.prototype = Object.create(target.prototype);
        Object.defineProperty(bound.prototype, 'constructor', {
            value: bound,
            configurable: true
        });
    }
    return bound;
};

3.8 手写 new

javascript 复制代码
function _new(constructor, ...args) {
    // 1. 创建新对象,原型指向构造函数的 prototype
    let obj = Object.create(constructor.prototype);
    // 2. 执行构造函数,绑定 this
    let result = constructor.apply(obj, args);
    // 3. 如果构造函数返回了对象,则返回该对象;否则返回新对象
    return typeof result === 'object' && result !== null ? result : obj;
}

3.9 数组扁平化(Flatten)

javascript 复制代码
function flatten(arr, res = []) {
    for (const item of arr) {
        if (Array.isArray(item)) flatten(item, res);
        else res.push(item);
    }
    return res;
}
// flatten([[1,2,3],4,5,6,[[7]],[]]) → [1,2,3,4,5,6,7]

// 其他方法:
// arr.flat(Infinity)
// arr.toString().split(',').map(Number)

3.10 数组去重

javascript 复制代码
function unique(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[i] === arr[j]) {
                arr.splice(j, 1);
                j--;
            }
        }
    }
    return arr;
}

// 其他方法:
// [...new Set(arr)]
// arr.filter((item, index) => arr.indexOf(item) === index)

3.11 手写 Array.map

javascript 复制代码
Array.prototype.map2 = function (callback) {
    const result = [];
    for (let i = 0; i < this.length; i++) {
        result.push(callback(this[i], i, this));
    }
    return result;
};

3.12 sleep 函数

javascript 复制代码
// 方法1:Promise
function sleep(delay) {
    return new Promise(resolve => setTimeout(resolve, delay));
}
sleep(2000).then(() => console.log('2秒后执行'));

// 方法2:async/await
async function test() {
    console.log('hello');
    await sleep(2000);
    console.log('world');
}

3.13 once 函数

javascript 复制代码
function once(fn) {
    let flag = false, result;
    return function (...args) {
        if (flag) return result;
        result = fn(...args);
        flag = true;
        return result;
    };
}

const f = (x) => x;
const onceF = once(f);
onceF(3); // 3
onceF(4); // 3(缓存了第一次结果)

3.14 深合并(Deep Merge)

javascript 复制代码
function deepMerge(target, ...sources) {
    if (sources.length === 0) return target;
    const source = sources.shift();
    if (source == null) return deepMerge(target, ...sources);

    for (const key of Reflect.ownKeys(source)) {
        const sourceValue = source[key];
        const targetValue = target[key];
        const sourceIsObject = sourceValue !== null && typeof sourceValue === 'object';
        const targetIsObject = targetValue !== null && typeof targetValue === 'object';

        if (sourceIsObject && targetIsObject
            && !Array.isArray(sourceValue) && !Array.isArray(targetValue)) {
            target[key] = deepMerge(targetValue, sourceValue);
        } else {
            target[key] = deepClone(sourceValue);
        }
    }
    return deepMerge(target, ...sources);
}
// 此版本约定:普通对象递归合并,数组直接覆盖;实际面试先确认数组策略

3.15 并发请求控制

javascript 复制代码
const limitRequest = (urls, maxNum) => {
    return new Promise((resolve) => {
        if (urls.length === 0) { resolve([]); return; }
        if (!Number.isInteger(maxNum) || maxNum <= 0) {
            throw new RangeError('maxNum must be a positive integer');
        }
        const results = [];
        let index = 0, count = 0;

        async function request() {
            if (index === urls.length) return;
            const i = index++;
            const url = urls[i];
            try {
                results[i] = await fetch(url);
            } catch (err) {
                results[i] = err;
            } finally {
                count++;
                if (count === urls.length) resolve(results);
                request(); // 完成后启动下一个
            }
        }

        const times = Math.min(maxNum, urls.length);
        for (let i = 0; i < times; i++) request();
    });
};
// 核心:用 index 指针分配任务,每个完成后再调用 request() 启动新的

3.16 异步串行执行

javascript 复制代码
function queuePromise(promises) {
    const results = [];
    let p = Promise.resolve();
    promises.forEach(item => {
        p = p.then(() => item().then(val => results.push(val)));
    });
    return p.then(() => results);
}

// 用法:
// queuePromise([() => createPromise(1, 3000), () => createPromise(2, 1000)])
// 输出:3s后→1, 再1s后→2

3.17 数组转树 & 树转数组

javascript 复制代码
function arraytoTree(arr) {
    let root = arr[0]; arr.shift();
    return {
        id: root.id, val: root.val,
        children: arr.length > 0 ? toTree(root.id, arr) : []
    };
}
function toTree(parentId, arr) {
    let children = [];
    for (let i = 0; i < arr.length; i++) {
        if (arr[i].parentId === parentId) {
            children.push({
                id: arr[i].id, val: arr[i].val,
                children: toTree(arr[i].id, arr)
            });
        }
    }
    return children;
}

// ✨ 优化版:O(n) Map法
function arrayToTree(items) {
    const map = new Map();
    const roots = [];
    items.forEach(item => {
        map.set(item.id, { ...item, children: [] });
    });
    items.forEach(item => {
        const node = map.get(item.id);
        if (item.parentId == null || !map.has(item.parentId)) {
            roots.push(node);
        } else {
            map.get(item.parentId).children.push(node);
        }
    });
    return roots;
}

// 树转数组
function treeToArray(tree) {
    const result = [];
    const flatten = (nodes, parentId = 0) => {
        nodes.forEach(node => {
            const { children, ...rest } = node;
            result.push({ ...rest, parentId });
            if (children?.length) flatten(children, rest.id);
        });
    };
    flatten(tree);
    return result;
}

3.18 LRU 缓存

javascript 复制代码
// ES6 Map 简洁版(Map 的 key 顺序即插入顺序)
class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.cache = new Map();
    }

    get(key) {
        if (!this.cache.has(key)) return -1;
        const value = this.cache.get(key);
        this.cache.delete(key);
        this.cache.set(key, value); // 移到末尾
        return value;
    }

    put(key, value) {
        if (this.cache.has(key)) this.cache.delete(key);
        this.cache.set(key, value);
        if (this.cache.size > this.capacity) {
            // Map.keys().next() 返回最久未使用的 key
            this.cache.delete(this.cache.keys().next().value);
        }
    }
}

// 双向链表版见算法部分 LeetCode 146

3.19 JSONP

javascript 复制代码
function addScriptTag(src) {
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.src = src;
    document.body.appendChild(script);
}
// 使用:addScriptTag("http://api.example.com?callback=handleData")
// 原理:利用 script 标签不受同源策略限制

3.20 手写 AJAX

javascript 复制代码
function ajax(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(JSON.parse(xhr.responseText));
                } else {
                    reject(new Error(`${xhr.status} error`));
                }
            }
        };
        xhr.send(null);
    });
}

// XMLHttpRequest readyState 状态:
// 0 - UNSENT, 1 - OPENED, 2 - HEADERS_RECEIVED, 3 - LOADING, 4 - DONE

3.21 ES5 实现 const

javascript 复制代码
function _const(key, value) {
    window[key] = value;
    Object.defineProperty(window, key, {
        enumerable: false,
        configurable: false,
        get: () => value,
        set: (newValue) => {
            if (newValue !== value) throw new TypeError('Assignment to constant variable');
        }
    });
}
// ⚠️ 局限:挂载在 window 上,不是真正的块级作用域

3.22 驼峰 ↔ 下划线

javascript 复制代码
// 下划线转驼峰
function toHump(name) {
    return name.replace(/\_(\w)/g, (all, letter) => letter.toUpperCase());
}
toHump('hello_js_go'); // 'helloJsGo'

// 驼峰转下划线
function toLine(name) {
    return name.replace(/([A-Z])/g, '_$1').toLowerCase();
}
toLine('aBcdEfg'); // 'a_bcd_efg'

3.23 虚拟DOM Diff

javascript 复制代码
// 1. 虚拟DOM → 真实DOM
function createElement(vnode) {
    let { tag, attrs = {}, children = [] } = vnode;
    if (!tag) return null;
    let elem = document.createElement(tag);
    for (let attrName in attrs) {
        if (attrs.hasOwnProperty(attrName)) elem.setAttribute(attrName, attrs[attrName]);
    }
    children.forEach(childVnode => elem.appendChild(createElement(childVnode)));
    return elem;
}

// 2. 仅用于解释"同层比较"的极简伪代码
function updateChildren(vnode, newVnode) {
    let children = vnode.children || [];
    let newChildren = newVnode.children || [];
    children.forEach((childVnode, index) => {
        let newChildVnode = newChildren[index];
        if (childVnode.tag === newChildVnode.tag) {
            updateChildren(childVnode, newChildVnode); // 同tag继续递归
        } else {
            replaceNode(childVnode, newChildVnode); // 不同直接替换
        }
    });
}

注意:以上不是可运行的完整 Diff。真正实现还需处理文本节点、属性更新、

节点增删、key、真实 DOM 映射和组件生命周期。面试中优先讲算法目标和框架策略,

不要把这段伪代码描述为完整实现。

3.24 链式调用

javascript 复制代码
// 核心:每个方法 return this
class Person {
    setAge(data) { this.age = data; return this; }
    setSex(data) { this.sex = data; return this; }
}

const p = new Person();
p.setAge(12).setSex('male'); // 链式调用

3.25 拖拽

javascript 复制代码
// 核心:mousedown 记录偏移 → mousemove 更新位置 → mouseup 停止
box.onmousedown = function (e) {
    let x = e.clientX - box.offsetLeft;
    let y = e.clientY - box.offsetTop;
    let isDrop = true;
    document.onmousemove = function (e) {
        if (!isDrop) return;
        box.style.left = Math.min(maxX, Math.max(0, e.clientX - x)) + 'px';
        box.style.top = Math.min(maxY, Math.max(0, e.clientY - y)) + 'px';
    };
    document.onmouseup = function () { isDrop = false; };
};

3.26 Promise 调度器(Scheduler)✨ 新增

javascript 复制代码
/**
 * 实现一个带并发限制的异步调度器
 * 保证同时最多运行 max 个异步任务
 */
class Scheduler {
    constructor(max) {
        this.max = max;
        this.running = 0;
        this.queue = [];
    }

    add(task) {
        return new Promise((resolve, reject) => {
            const run = async () => {
                this.running++;
                try {
                    resolve(await task());
                } catch (error) {
                    reject(error);
                } finally {
                    this.running--;
                    this._runNext();
                }
            };

            if (this.running < this.max) run();
            else this.queue.push(run);
        });
    }

    _runNext() {
        if (this.running < this.max && this.queue.length) {
            const next = this.queue.shift();
            next();
        }
    }
}

// 测试
// const scheduler = new Scheduler(2);
// const timeout = (delay, value) =>
//     () => new Promise(resolve => setTimeout(() => resolve(value), delay));
// scheduler.add(timeout(1000, 1)).then(console.log);
// scheduler.add(timeout(500, 2)).then(console.log);
// scheduler.add(timeout(300, 3)).then(console.log);
// scheduler.add(timeout(400, 4)).then(console.log);
// 期望:最多同时2个在执行

3.27 异步串行 + 重试 ✨ 新增

javascript 复制代码
async function serialWithRetry(tasks, retries = 2) {
    const results = [];
    for (const task of tasks) {
        let lastError;
        for (let attempt = 0; attempt <= retries; attempt++) {
            try {
                results.push(await task());
                lastError = null;
                break;
            } catch (err) {
                lastError = err;
            }
        }
        if (lastError) results.push({ error: lastError });
    }
    return results;
}

// 不使用 async/await 的版本
function retryTask(task, retries) {
    return task().catch(err => {
        if (retries > 0) return retryTask(task, retries - 1);
        throw err;
    });
}

3.28 React Hook 实现思路 ✨ 新增

javascript 复制代码
// 教学级伪实现:用数组和调用顺序解释"为什么 Hook 不能写在条件分支中"
const hooks = [];
let hookIndex = 0;

function useState(initialValue) {
    const currentIndex = hookIndex++;
    if (!(currentIndex in hooks)) {
        hooks[currentIndex] =
            typeof initialValue === 'function' ? initialValue() : initialValue;
    }
    const setState = nextValue => {
        const previous = hooks[currentIndex];
        hooks[currentIndex] =
            typeof nextValue === 'function' ? nextValue(previous) : nextValue;
        render(); // 教学占位:真实 React 会调度更新
    };
    return [hooks[currentIndex], setState];
}

function render() {
    hookIndex = 0;
    // Component();
}

面试重点

  • 以上只是解释调用顺序的伪实现,不是 React 源码
  • 多个组件需要各自的 Fiber/Hook 链表,不能共享一个全局数组
  • Hook 的闭包陷阱(stale closure):定时器中的 state 不会更新
  • 为什么需要 deps 数组:决定何时重新执行
  • useMemo vs useCallback:前者缓存值,后者缓存函数

3.29 函数柯里化(Curry)✨ 新增

javascript 复制代码
// 基础版:收集够参数才执行
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        }
        return function (...nextArgs) {
            return curried.apply(this, [...args, ...nextArgs]);
        };
    };
}

// 用法示例
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6

// 面试追问:柯里化的价值?
// 1. 参数复用(生成偏函数)
// 2. 函数组合(compose/pipe 需要单参数函数)
// 3. 延迟执行

3.30 手写 instanceof ✨ 新增

javascript 复制代码
function myInstanceof(left, right) {
    // 基本类型直接返回 false
    if (typeof left !== 'object' || left === null) return false;

    let proto = Object.getPrototypeOf(left);
    const prototype = right.prototype;

    while (proto) {
        if (proto === prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
}

// 测试
myInstanceof([], Array);      // true
myInstanceof([], Object);     // true
myInstanceof(123, Number);    // false(基本类型)
myInstanceof(null, Object);   // false
// 核心:沿原型链向上查找 left.__proto__ === right.prototype

3.31 compose / pipe ✨ 新增

javascript 复制代码
// ✅ compose(从右向左执行)
const compose = (...fns) => {
    if (fns.length === 0) return (arg) => arg;
    if (fns.length === 1) return fns[0];
    return fns.reduceRight((prev, next) => (...args) => next(prev(...args)));
};

// ✅ pipe(从左向右执行,更符合阅读习惯)
const pipe = (...fns) => {
    if (fns.length === 0) return (arg) => arg;
    if (fns.length === 1) return fns[0];
    return fns.reduce((prev, next) => (...args) => next(prev(...args)));
};

// 实战用法(结合柯里化)
const filter = curry((fn, arr) => arr.filter(fn));
const map = curry((fn, arr) => arr.map(fn));
const getAdminNames = pipe(
    filter(u => u.role === 'admin'),
    map(u => u.name)
);
getAdminNames(users);
// 面试追问:Redux 中间件的 applyMiddleware 内部就用了 compose

3.32 LazyMan(链式调用 + 任务队列)✨ 新增

javascript 复制代码
class LazyMan {
    constructor(name) {
        this.tasks = [];
        this.tasks.push(() => {
            console.log(`Hi! This is ${name}`);
            this.next();
        });
        // 异步启动任务队列
        setTimeout(() => this.next(), 0);
    }

    next() {
        const task = this.tasks.shift();
        task && task();
    }

    eat(food) {
        this.tasks.push(() => {
            console.log(`Eat ${food}`);
            this.next();
        });
        return this; // 链式调用
    }

    sleep(seconds) {
        this.tasks.push(() => {
            setTimeout(() => {
                console.log(`Wake up after ${seconds} seconds`);
                this.next();
            }, seconds * 1000);
        });
        return this;
    }

    sleepFirst(seconds) {
        this.tasks.unshift(() => { // unshift 到队头!
            setTimeout(() => {
                console.log(`Wake up after ${seconds} seconds`);
                this.next();
            }, seconds * 1000);
        });
        return this;
    }
}

function createLazyMan(name) { return new LazyMan(name); }

// LazyMan('Tony').eat('lunch').sleep(2).eat('dinner').sleepFirst(1);
// 输出:Wake up after 1s → Hi! Tony → Eat lunch → Wake up after 2s → Eat dinner
// 核心考点:任务队列 + 链式调用 + 异步控制 + sleepFirst前置插入

3.33 Array.prototype.reduce ✨ 新增

javascript 复制代码
Array.prototype.myReduce = function (callback, initialValue) {
    if (typeof callback !== 'function') throw new TypeError('callback must be a function');
    const hasInitialValue = arguments.length > 1;
    let index = 0;
    let accumulator;

    if (hasInitialValue) {
        accumulator = initialValue;
    } else {
        while (index < this.length && !(index in this)) index++;
        if (index >= this.length) {
            throw new TypeError('Reduce of empty array with no initial value');
        }
        accumulator = this[index++];
    }

    for (; index < this.length; index++) {
        if (!(index in this)) continue;
        accumulator = callback(accumulator, this[index], index, this);
    }
    return accumulator;
};

// 测试
[1, 2, 3, 4].myReduce((acc, cur) => acc + cur, 0); // 10
// 面试追问:没有 initialValue 时,用数组第一个元素作为初始值

3.34 版本号比较 ✨ 新增

javascript 复制代码
function compareVersion(v1, v2) {
    const arr1 = v1.split('.');
    const arr2 = v2.split('.');
    const len = Math.max(arr1.length, arr2.length);

    for (let i = 0; i < len; i++) {
        const n1 = parseInt(arr1[i] || 0);
        const n2 = parseInt(arr2[i] || 0);
        if (n1 > n2) return 1;
        if (n1 < n2) return -1;
    }
    return 0;
}

compareVersion('1.2.3', '1.2.4'); // -1
compareVersion('2.0', '1.9.9');   // 1
// 常见扩展:比较"1.2.0-alpha.1"这种带预发布标签的版本号

3.35 Promise.prototype.finally + 静态方法 ✨ 新增

javascript 复制代码
// ✅ Promise.prototype.finally
Promise.prototype.myFinally = function (callback) {
    return this.then(
        value => Promise.resolve(callback()).then(() => value),
        reason => Promise.resolve(callback()).then(() => { throw reason; })
    );
};

// ✅ Promise.resolve
Promise.myResolve = function (value) {
    if (value instanceof Promise) return value;
    return new Promise((resolve) => resolve(value));
};

// ✅ Promise.reject
Promise.myReject = function (reason) {
    return new Promise((_, reject) => reject(reason));
};

// ✅ Promise.any(ES2021)
Promise.myAny = function (promises) {
    return new Promise((resolve, reject) => {
        const errors = [];
        let count = 0;
        if (promises.length === 0) {
            return reject(new AggregateError([], 'All promises were rejected'));
        }
        promises.forEach((p, i) => {
            Promise.resolve(p).then(resolve, (err) => {
                errors[i] = err;
                count++;
                if (count === promises.length) {
                    reject(new AggregateError(errors, 'All promises were rejected'));
                }
            });
        });
    });
};
// Promise.any 与 Promise.race 的区别:any 等第一个成功的,全失败才 reject

3.36 Promise/A+ 的 then 解析核心 ✨ 升级

javascript 复制代码
// 重点展示 then 链式调用与 resolvePromise 解析过程。
// 这是核心片段,需与统一使用 resolved/rejected、value 字段的构造器配套。

class MyPromise {
    // ... 构造函数同上

    then(onFulfilled, onRejected) {
        // 值穿透:非函数参数自动转为函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
        onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };

        const promise2 = new MyPromise((resolve, reject) => {
            const fulfilledMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                });
            };

            const rejectedMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        const x = onRejected(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                });
            };

            if (this.status === 'resolved') fulfilledMicrotask();
            else if (this.status === 'rejected') rejectedMicrotask();
            else if (this.status === 'pending') {
                this.fulfilledCallbacks.push(fulfilledMicrotask);
                this.rejectedCallbacks.push(rejectedMicrotask);
            }
        });
        return promise2;
    }

    catch(onRejected) {
        return this.then(null, onRejected);
    }
}

// resolvePromise --- Promise/A+ 核心
function resolvePromise(promise2, x, resolve, reject) {
    // 1. 不可自己等待自己
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected'));
    }
    // 2. 如果 x 是 Promise 实例,采用它的状态
    if (x instanceof MyPromise) {
        x.then(
            value => resolvePromise(promise2, value, resolve, reject),
            reject
        );
    } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 3. 如果 x 是 thenable(对象/函数且有 then 方法)
        let called = false;
        try {
            const then = x.then;
            if (typeof then === 'function') {
                then.call(x,
                    value => {
                        if (called) return;
                        called = true;
                        resolvePromise(promise2, value, resolve, reject);
                    },
                    reason => {
                        if (called) return;
                        called = true;
                        reject(reason);
                    }
                );
            } else {
                resolve(x);
            }
        } catch (err) {
            if (called) return;
            called = true;
            reject(err);
        }
    } else {
        // 4. 普通值直接 resolve
        resolve(x);
    }
}
// 面试官评分点:
// ✅ 状态不可逆  ✅ 值穿透  ✅ 异步执行(queueMicrotask)
// ✅ then 返回新 Promise  ✅ resolvePromise 处理 thenable  ✅ 循环引用检测

面试通常优先考 Promise.all、并发控制和事件循环。除非岗位明确偏底层,

不建议把完整 Promise/A+ 实现作为最高优先级背诵内容。

3.37 setTimeout 模拟 setInterval

javascript 复制代码
function mySetInterval(delay) {
    let timer = setTimeout(() => {
        console.log(1);
        clearTimeout(timer);
        mySetInterval(delay); // 递归调用
    }, delay);
}
// 为什么不用setInterval?setInterval不关心回调执行时间,可能堆积

3.38 对象判空

javascript 复制代码
// 方法1:JSON.stringify(obj) === '{}'
// 方法2:for (let key in obj) return false; return true;
// 方法3:Object.getOwnPropertyNames(obj).length === 0
// 方法4:Object.keys(obj).length === 0

四、算法高频题

标注 ✨ 新增 的为本次整理时补充的 LeetCode 高频题。

📌 双指针 & 滑动窗口

4.1 两数之和(LC 1)⭐
javascript 复制代码
function twoSum(nums, target) {
    const map = new Map();
    for (let i = 0; i < nums.length; i++) {
        const complement = target - nums[i];
        if (map.has(complement)) return [map.get(complement), i];
        map.set(nums[i], i);
    }
    return [];
}
// 时间:O(n)  空间:O(n)
4.2 三数之和(LC 15)⭐⭐
javascript 复制代码
function threeSum(nums) {
    nums.sort((a, b) => a - b);
    const res = [];
    for (let i = 0; i < nums.length - 2; i++) {
        if (nums[i] > 0) break; // 剪枝
        if (i > 0 && nums[i] === nums[i - 1]) continue; // 去重
        let l = i + 1, r = nums.length - 1;
        while (l < r) {
            const sum = nums[i] + nums[l] + nums[r];
            if (sum === 0) {
                res.push([nums[i], nums[l], nums[r]]);
                while (l < r && nums[l] === nums[l + 1]) l++;
                while (l < r && nums[r] === nums[r - 1]) r--;
                l++; r--;
            } else if (sum < 0) l++;
            else r--;
        }
    }
    return res;
}
// 时间:O(n²)  空间:O(1)
4.3 最长无重复子串(LC 3)⭐⭐
javascript 复制代码
function lengthOfLongestSubstring(s) {
    const map = new Map();
    let left = 0, maxLen = 0;
    for (let right = 0; right < s.length; right++) {
        const char = s[right];
        if (map.has(char) && map.get(char) >= left) {
            left = map.get(char) + 1;
        }
        map.set(char, right);
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}
// 时间:O(n)  空间:O(min(n, 128))
4.4 接雨水(LC 42)⭐⭐⭐
javascript 复制代码
function trap(height) {
    if (height.length < 3) return 0;
    let left = 0, right = height.length - 1;
    let leftMax = 0, rightMax = 0, water = 0;
    while (left < right) {
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);
        if (leftMax < rightMax) {
            water += leftMax - height[left];
            left++;
        } else {
            water += rightMax - height[right];
            right--;
        }
    }
    return water;
}
// 时间:O(n)  空间:O(1)
// 延伸:单调栈解法(面试常要求讲思路)
4.5 最长回文子串(LC 5)⭐⭐
javascript 复制代码
function longestPalindrome(s) {
    if (s.length < 2) return s;
    let start = 0, maxLen = 1;

    function expand(l, r) {
        while (l >= 0 && r < s.length && s[l] === s[r]) { l--; r++; }
        const len = r - l - 1;
        if (len > maxLen) { maxLen = len; start = l + 1; }
    }

    for (let i = 0; i < s.length; i++) {
        expand(i, i);     // 奇数长度
        expand(i, i + 1); // 偶数长度
    }
    return s.substring(start, start + maxLen);
}
// 时间:O(n²)  空间:O(1)
4.6 合并区间(LC 56)⭐⭐
javascript 复制代码
function merge(intervals) {
    if (intervals.length <= 1) return intervals;
    intervals.sort((a, b) => a[0] - b[0]);
    const res = [intervals[0]];
    for (let i = 1; i < intervals.length; i++) {
        const last = res[res.length - 1];
        if (intervals[i][0] <= last[1]) {
            last[1] = Math.max(last[1], intervals[i][1]);
        } else {
            res.push(intervals[i]);
        }
    }
    return res;
}
// 时间:O(n log n)  空间:O(1)

📌 链表

4.7 反转链表(LC 206)⭐⭐
javascript 复制代码
// 迭代法(推荐)
function reverseList(head) {
    let prev = null, curr = head;
    while (curr) {
        const next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

// 递归法
function reverseList(head) {
    if (!head || !head.next) return head;
    const newHead = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return newHead;
}
// 时间:O(n)  空间:迭代O(1) / 递归O(n)
4.8 环形链表(LC 141)⭐
javascript 复制代码
function hasCycle(head) {
    if (!head || !head.next) return false;
    let slow = head, fast = head;
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow === fast) return true;
    }
    return false;
}

// 找环的入口(LC 142)
function detectCycle(head) {
    if (!head || !head.next) return null;
    let slow = head, fast = head;
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow === fast) {
            slow = head;
            while (slow !== fast) { slow = slow.next; fast = fast.next; }
            return slow;
        }
    }
    return null;
}
4.9 相交链表(LC 160)⭐
javascript 复制代码
function getIntersectionNode(headA, headB) {
    let pA = headA, pB = headB;
    while (pA !== pB) {
        pA = pA ? pA.next : headB;
        pB = pB ? pB.next : headA;
    }
    return pA;
}
// 双指针走完自己走对方,最终会在交点相遇或同时为null

📌 二叉树

4.10 二叉树层序遍历(LC 102)⭐⭐
javascript 复制代码
function levelOrder(root) {
    if (!root) return [];
    const res = [], queue = [root];
    while (queue.length) {
        const levelSize = queue.length, level = [];
        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift();
            level.push(node.val);
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
        res.push(level);
    }
    return res;
}
4.11 二叉树的最近公共祖先(LC 236)⭐⭐
javascript 复制代码
function lowestCommonAncestor(root, p, q) {
    if (!root || root === p || root === q) return root;
    const left = lowestCommonAncestor(root.left, p, q);
    const right = lowestCommonAncestor(root.right, p, q);
    if (left && right) return root; // p,q在两侧
    return left || right;            // 在同一侧
}
// 时间:O(n)  空间:O(h)
4.12 二叉树的右视图(LC 199)⭐⭐
javascript 复制代码
function rightSideView(root) {
    if (!root) return [];
    const res = [], queue = [root];
    while (queue.length) {
        const levelSize = queue.length;
        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift();
            if (i === levelSize - 1) res.push(node.val);
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
    }
    return res;
}

📌 回溯

4.13 全排列(LC 46)⭐⭐
javascript 复制代码
function permute(nums) {
    const res = [];
    const used = new Array(nums.length).fill(false);

    function backtrack(path) {
        if (path.length === nums.length) { res.push([...path]); return; }
        for (let i = 0; i < nums.length; i++) {
            if (used[i]) continue;
            used[i] = true;
            path.push(nums[i]);
            backtrack(path);
            path.pop();
            used[i] = false;
        }
    }
    backtrack([]);
    return res;
}
4.14 子集(LC 78)⭐⭐
javascript 复制代码
function subsets(nums) {
    const res = [];
    function backtrack(start, path) {
        res.push([...path]);
        for (let i = start; i < nums.length; i++) {
            path.push(nums[i]);
            backtrack(i + 1, path);
            path.pop();
        }
    }
    backtrack(0, []);
    return res;
}
4.15 组合总和(LC 39)⭐⭐
javascript 复制代码
function combinationSum(candidates, target) {
    const res = [];
    function backtrack(start, path, remain) {
        if (remain < 0) return;
        if (remain === 0) { res.push([...path]); return; }
        for (let i = start; i < candidates.length; i++) {
            path.push(candidates[i]);
            backtrack(i, path, remain - candidates[i]); // 可重复,用i
            path.pop();
        }
    }
    backtrack(0, [], target);
    return res;
}
4.16 括号生成(LC 22)⭐⭐
javascript 复制代码
function generateParenthesis(n) {
    const res = [];
    function backtrack(left, right, str) {
        if (str.length === n * 2) { res.push(str); return; }
        if (left < n) backtrack(left + 1, right, str + '(');
        if (right < left) backtrack(left, right + 1, str + ')');
    }
    backtrack(0, 0, '');
    return res;
}

📌 动态规划

4.17 最长递增子序列(LC 300)⭐⭐
javascript 复制代码
function lengthOfLIS(nums) {
    const tails = []; // tails[i] = 长度为i+1的LIS的最小末尾值
    for (const num of nums) {
        let l = 0, r = tails.length;
        while (l < r) {
            const mid = (l + r) >> 1;
            if (tails[mid] < num) l = mid + 1;
            else r = mid;
        }
        if (l === tails.length) tails.push(num);
        else tails[l] = num;
    }
    return tails.length;
}
// 时间:O(n log n)  空间:O(n)
// 延伸:输出具体序列需要 O(n²) DP
4.18 爬楼梯(LC 70)⭐
javascript 复制代码
function climbStairs(n) {
    if (n <= 2) return n;
    let a = 1, b = 2;
    for (let i = 3; i <= n; i++) { [a, b] = [b, a + b]; }
    return b;
}
// 本质:斐波那契,dp[i] = dp[i-1] + dp[i-2]
4.19 打家劫舍(LC 198)⭐⭐
javascript 复制代码
function rob(nums) {
    if (nums.length === 0) return 0;
    if (nums.length === 1) return nums[0];
    let prev2 = nums[0], prev1 = Math.max(nums[0], nums[1]);
    for (let i = 2; i < nums.length; i++) {
        const cur = Math.max(prev1, prev2 + nums[i]);
        prev2 = prev1; prev1 = cur;
    }
    return prev1;
}
// dp[i] = max(dp[i-1], dp[i-2] + nums[i])
4.20 最大子数组和(LC 53)⭐
javascript 复制代码
function maxSubArray(nums) {
    let dp = nums[0], max = nums[0];
    for (let i = 1; i < nums.length; i++) {
        dp = Math.max(nums[i], dp + nums[i]);
        max = Math.max(max, dp);
    }
    return max;
}
4.21 买卖股票的最佳时机(LC 121)⭐
javascript 复制代码
function maxProfit(prices) {
    let minPrice = Infinity, maxProfit = 0;
    for (const price of prices) {
        minPrice = Math.min(minPrice, price);
        maxProfit = Math.max(maxProfit, price - minPrice);
    }
    return maxProfit;
}

📌 二分 & 排序

4.22 排序数组中查找第一个和最后一个位置(LC 34)⭐⭐
javascript 复制代码
function searchRange(nums, target) {
    function findBound(isLeft) {
        let l = 0, r = nums.length - 1, res = -1;
        while (l <= r) {
            const mid = (l + r) >> 1;
            if (nums[mid] === target) {
                res = mid;
                if (isLeft) r = mid - 1; // 继续向左
                else l = mid + 1;          // 继续向右
            } else if (nums[mid] < target) l = mid + 1;
            else r = mid - 1;
        }
        return res;
    }
    return [findBound(true), findBound(false)];
}
// 💡 二分模板:while(l <= r),找到后不立即返回,继续缩范围
4.23 数组中的第K个最大元素(LC 215)⭐⭐
javascript 复制代码
function findKthLargest(nums, k) {
    const target = nums.length - k; // 第k大 = 第(n-k)小
    let left = 0, right = nums.length - 1;

    while (left <= right) {
        const pivotIndex = partition(nums, left, right);
        if (pivotIndex === target) return nums[pivotIndex];
        else if (pivotIndex < target) left = pivotIndex + 1;
        else right = pivotIndex - 1;
    }
    return -1;
}

function partition(nums, left, right) {
    const pivot = nums[right];
    let i = left;
    for (let j = left; j < right; j++) {
        if (nums[j] < pivot) { [nums[i], nums[j]] = [nums[j], nums[i]]; i++; }
    }
    [nums[i], nums[right]] = [nums[right], nums[i]];
    return i;
}
// 时间:O(n)期望  空间:O(1)
4.24 快速排序
javascript 复制代码
function quickSort(nums, left, right) {
    if (left >= right) return;
    let i = left, j = right;
    while (i < j) {
        while (i < j && nums[left] <= nums[j]) j--;
        while (i < j && nums[i] <= nums[left]) i++;
        [nums[i], nums[j]] = [nums[j], nums[i]];
    }
    [nums[i], nums[left]] = [nums[left], nums[i]];
    quickSort(nums, left, i - 1);
    quickSort(nums, i + 1, right);
}

📌 图 & 搜索

4.25 岛屿数量(LC 200)⭐⭐
javascript 复制代码
function numIslands(grid) {
    if (!grid.length) return 0;
    const m = grid.length, n = grid[0].length;
    let count = 0;

    function dfs(i, j) {
        if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] === '0') return;
        grid[i][j] = '0'; // 沉岛
        dfs(i + 1, j); dfs(i - 1, j); dfs(i, j + 1); dfs(i, j - 1);
    }

    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (grid[i][j] === '1') { count++; dfs(i, j); }
        }
    }
    return count;
}
// 时间:O(m×n)  空间:O(m×n)
4.26 二进制矩阵最短路径(BFS)
javascript 复制代码
// 类似 LC 1091,八方向可改为四方向
function minRoad(nums, n) {
    const dir = [[1, 0], [-1, 0], [0, 1], [0, -1]];
    if (nums[0][0] === 0 || nums[n - 1][n - 1] === 0) return -1;
    if (n === 1) return 0;

    let q = [[0, 0]];
    nums[0][0] = 0; // 标记已访问
    let step = 0;

    while (q.length) {
        let l = q.length;
        while (l--) {
            let [x, y] = q.shift();
            if (x === n - 1 && y === n - 1) return step;
            for (let i = 0; i < dir.length; i++) {
                let xi = x + dir[i][0], yi = y + dir[i][1];
                if (xi < 0 || xi >= n || yi < 0 || yi >= n || nums[xi][yi] === 0) continue;
                q.push([xi, yi]);
                nums[xi][yi] = 0;
            }
        }
        step++;
    }
    return -1;
}

📌 杂项

4.27 LRU 缓存(LC 146)⭐⭐
javascript 复制代码
class ListNode {
    constructor(key, val) {
        this.key = key; this.val = val;
        this.prev = null; this.next = null;
    }
}

class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.map = new Map();
        this.dummyHead = new ListNode(-1, -1);
        this.dummyTail = new ListNode(-1, -1);
        this.dummyHead.next = this.dummyTail;
        this.dummyTail.prev = this.dummyHead;
    }

    _removeNode(node) { node.prev.next = node.next; node.next.prev = node.prev; }
    _addToHead(node) {
        node.next = this.dummyHead.next; node.prev = this.dummyHead;
        this.dummyHead.next.prev = node; this.dummyHead.next = node;
    }
    _moveToHead(node) { this._removeNode(node); this._addToHead(node); }
    _removeTail() { const tail = this.dummyTail.prev; this._removeNode(tail); return tail.key; }

    get(key) {
        if (!this.map.has(key)) return -1;
        const node = this.map.get(key);
        this._moveToHead(node);
        return node.val;
    }

    put(key, value) {
        if (this.map.has(key)) {
            const node = this.map.get(key);
            node.val = value;
            this._moveToHead(node);
        } else {
            const node = new ListNode(key, value);
            this.map.set(key, node);
            this._addToHead(node);
            if (this.map.size > this.capacity) {
                this.map.delete(this._removeTail());
            }
        }
    }
}
// 时间:O(1) get/put
4.28 有效的括号(LC 20)⭐
javascript 复制代码
function isValid(s) {
    const stack = [];
    const map = { ')': '(', ']': '[', '}': '{' };
    for (const c of s) {
        if (!map[c]) stack.push(c);
        else if (stack.pop() !== map[c]) return false;
    }
    return stack.length === 0;
}
4.29 螺旋矩阵(LC 54)⭐⭐
javascript 复制代码
function spiralOrder(matrix) {
    if (!matrix.length || !matrix[0].length) return [];
    const res = [];
    let top = 0, bottom = matrix.length - 1;
    let left = 0, right = matrix[0].length - 1;
    while (top <= bottom && left <= right) {
        for (let i = left; i <= right; i++) res.push(matrix[top][i]);
        top++;
        for (let i = top; i <= bottom; i++) res.push(matrix[i][right]);
        right--;
        if (top <= bottom) {
            for (let i = right; i >= left; i--) res.push(matrix[bottom][i]); bottom--;
        }
        if (left <= right) {
            for (let i = bottom; i >= top; i--) res.push(matrix[i][left]); left++;
        }
    }
    return res;
}
4.30 汇总区间
javascript 复制代码
// 输入:[0,1,2,4,5,7]  输出:["0->2","4->5","7"]
function solve(arr) {
    const result = [];
    const n = arr.length;
    let i = 0;
    while (i < n) {
        let p = i; i++;
        while (arr[i] === arr[i - 1] + 1) i++;
        let q = i - 1;
        result.push(p < q ? `${arr[p]}->${arr[q]}` : `${arr[p]}`);
    }
    return result;
}
补充:字符串相加(LC 415)⭐
javascript 复制代码
function addStrings(num1, num2) {
    let i = num1.length - 1, j = num2.length - 1, carry = 0, res = '';
    while (i >= 0 || j >= 0 || carry) {
        const a = i >= 0 ? +num1[i--] : 0;
        const b = j >= 0 ? +num2[j--] : 0;
        const sum = a + b + carry;
        res = (sum % 10) + res;
        carry = Math.floor(sum / 10);
    }
    return res;
}
补充:复原 IP 地址(LC 93)
javascript 复制代码
function restoreIpAddresses(s) {
    if (s.length < 4 || s.length > 12) return [];
    const res = [];

    function isValid(str) {
        if (str.length > 1 && str.startsWith('0')) return false;
        const num = parseInt(str);
        return num >= 0 && num <= 255;
    }

    function backtrack(level, max, path, s) {
        if (level === max) {
            if (isValid(s)) res.push([...path, s].join('.'));
            return;
        }
        for (let i = 1; i < 4; i++) {
            const tmp = s.slice(0, i);
            const restStr = s.slice(i);
            if (isValid(tmp) && restStr.length / (max - level) >= 1) {
                path.push(tmp);
                backtrack(level + 1, max, path, s.slice(i));
                path.pop();
            }
        }
    }
    backtrack(0, 3, [], s);
    return res;
}
补充:盛最多水的容器 ✨ 新增(LC 11)⭐⭐
javascript 复制代码
function maxArea(height) {
    let left = 0, right = height.length - 1;
    let maxWater = 0;
    while (left < right) {
        const h = Math.min(height[left], height[right]);
        maxWater = Math.max(maxWater, h * (right - left));
        // 移动较矮的一边(移动高的那一边只会让面积变小)
        if (height[left] < height[right]) left++;
        else right--;
    }
    return maxWater;
}
// 时间:O(n)  空间:O(1)
补充:合并两个有序链表 ✨ 新增(LC 21)⭐
javascript 复制代码
function mergeTwoLists(l1, l2) {
    const dummy = new ListNode(-1);
    let cur = dummy;
    while (l1 && l2) {
        if (l1.val < l2.val) { cur.next = l1; l1 = l1.next; }
        else { cur.next = l2; l2 = l2.next; }
        cur = cur.next;
    }
    cur.next = l1 || l2;
    return dummy.next;
}
补充:零钱兑换 ✨ 新增(LC 322)⭐⭐
javascript 复制代码
function coinChange(coins, amount) {
    // dp[i] = 凑出金额 i 的最少硬币数
    const dp = new Array(amount + 1).fill(Infinity);
    dp[0] = 0;
    for (let i = 1; i <= amount; i++) {
        for (const coin of coins) {
            if (i >= coin) {
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
    }
    return dp[amount] === Infinity ? -1 : dp[amount];
}
// 时间:O(amount × n)  空间:O(amount)
补充:验证二叉搜索树 ✨ 新增(LC 98)⭐⭐
javascript 复制代码
function isValidBST(root, min = -Infinity, max = Infinity) {
    if (!root) return true;
    if (root.val <= min || root.val >= max) return false;
    return isValidBST(root.left, min, root.val)
        && isValidBST(root.right, root.val, max);
}
// 核心:每个节点有一个合法的值范围 (min, max),递归传递约束
补充:二叉树中序遍历 ✨ 新增(LC 94)--- 迭代法 ⭐⭐
javascript 复制代码
// 迭代法(不用递归),必掌握
function inorderTraversal(root) {
    const res = [], stack = [];
    let cur = root;
    while (cur || stack.length) {
        while (cur) {
            stack.push(cur);
            cur = cur.left; // 一路向左
        }
        cur = stack.pop();
        res.push(cur.val);  // 访问节点
        cur = cur.right;    // 转向右子树
    }
    return res;
}
补充:最长公共前缀 ✨ 新增(LC 14)⭐
javascript 复制代码
function longestCommonPrefix(strs) {
    if (!strs.length) return '';
    let prefix = strs[0];
    for (let i = 1; i < strs.length; i++) {
        while (strs[i].indexOf(prefix) !== 0) {
            prefix = prefix.slice(0, -1); // 不断缩短前缀
            if (!prefix) return '';
        }
    }
    return prefix;
}
补充:每日温度 ✨ 新增(LC 739)--- 单调栈 ⭐⭐
javascript 复制代码
function dailyTemperatures(temperatures) {
    const n = temperatures.length;
    const answer = new Array(n).fill(0);
    const stack = []; // 单调递减栈,存索引

    for (let i = 0; i < n; i++) {
        // 当前温度 > 栈顶温度 → 找到栈顶索引的答案
        while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) {
            const prevIndex = stack.pop();
            answer[prevIndex] = i - prevIndex;
        }
        stack.push(i);
    }
    return answer;
}
// 输入:[73,74,75,71,69,72,76,73]  输出:[1,1,4,2,1,1,0,0]
// 核心:单调栈用于"找下一个更大的元素"

📌 面试高优先补充题

这一组用于补齐基础模板。优先做到能闭卷写出,不要求继续扩充相似题。

合并两个有序数组(LC 88)⭐
javascript 复制代码
function mergeSortedArray(nums1, m, nums2, n) {
    let i = m - 1, j = n - 1, k = m + n - 1;
    while (j >= 0) {
        if (i >= 0 && nums1[i] > nums2[j]) nums1[k--] = nums1[i--];
        else nums1[k--] = nums2[j--];
    }
}
// 从后向前写,避免覆盖 nums1 中尚未处理的元素
反转链表 II(LC 92)⭐⭐
javascript 复制代码
function reverseBetween(head, left, right) {
    const dummy = { next: head };
    let prev = dummy;
    for (let i = 1; i < left; i++) prev = prev.next;

    const current = prev.next;
    for (let i = 0; i < right - left; i++) {
        const moved = current.next;
        current.next = moved.next;
        moved.next = prev.next;
        prev.next = moved;
    }
    return dummy.next;
}
// 头插法:每次把区间内下一个节点插到区间头部
重排链表(LC 143)⭐⭐
javascript 复制代码
function reorderList(head) {
    if (!head?.next) return;
    let slow = head, fast = head;
    while (fast.next && fast.next.next) {
        slow = slow.next;
        fast = fast.next.next;
    }

    let second = reverseList(slow.next);
    slow.next = null;
    let first = head;
    while (second) {
        const next1 = first.next, next2 = second.next;
        first.next = second;
        second.next = next1;
        first = next1;
        second = next2;
    }
}
// 三步:找中点、反转后半段、交替合并
二叉树最大深度(LC 104)⭐
javascript 复制代码
function maxDepth(root) {
    if (!root) return 0;
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
对称二叉树(LC 101)⭐
javascript 复制代码
function isSymmetric(root) {
    function mirror(left, right) {
        if (!left || !right) return left === right;
        return left.val === right.val
            && mirror(left.left, right.right)
            && mirror(left.right, right.left);
    }
    return mirror(root?.left, root?.right);
}
从前序与中序构造二叉树(LC 105)⭐⭐
javascript 复制代码
function buildTree(preorder, inorder) {
    const indexMap = new Map(inorder.map((value, index) => [value, index]));
    let preorderIndex = 0;

    function build(left, right) {
        if (left > right) return null;
        const value = preorder[preorderIndex++];
        const root = new TreeNode(value);
        const middle = indexMap.get(value);
        root.left = build(left, middle - 1);
        root.right = build(middle + 1, right);
        return root;
    }
    return build(0, inorder.length - 1);
}
用栈实现队列(LC 232)⭐
javascript 复制代码
class MyQueue {
    constructor() {
        this.input = [];
        this.output = [];
    }
    push(value) {
        this.input.push(value);
    }
    _move() {
        if (!this.output.length) {
            while (this.input.length) this.output.push(this.input.pop());
        }
    }
    pop() {
        this._move();
        return this.output.pop();
    }
    peek() {
        this._move();
        return this.output[this.output.length - 1];
    }
    empty() {
        return !this.input.length && !this.output.length;
    }
}
// 每个元素最多进出两个栈各一次,均摊 O(1)
标准二分查找(LC 704)⭐
javascript 复制代码
function search(nums, target) {
    let left = 0, right = nums.length - 1;
    while (left <= right) {
        const middle = left + ((right - left) >> 1);
        if (nums[middle] === target) return middle;
        if (nums[middle] < target) left = middle + 1;
        else right = middle - 1;
    }
    return -1;
}
搜索旋转排序数组(LC 33)⭐⭐
javascript 复制代码
function searchRotated(nums, target) {
    let left = 0, right = nums.length - 1;
    while (left <= right) {
        const middle = left + ((right - left) >> 1);
        if (nums[middle] === target) return middle;

        if (nums[left] <= nums[middle]) {
            if (nums[left] <= target && target < nums[middle]) right = middle - 1;
            else left = middle + 1;
        } else {
            if (nums[middle] < target && target <= nums[right]) left = middle + 1;
            else right = middle - 1;
        }
    }
    return -1;
}
// 每轮至少有一半区间有序,再判断 target 是否落在该区间
搜索二维矩阵(LC 74)⭐
javascript 复制代码
function searchMatrix(matrix, target) {
    if (!matrix.length || !matrix[0].length) return false;
    const rows = matrix.length, cols = matrix[0].length;
    let left = 0, right = rows * cols - 1;
    while (left <= right) {
        const middle = left + ((right - left) >> 1);
        const value = matrix[Math.floor(middle / cols)][middle % cols];
        if (value === target) return true;
        if (value < target) left = middle + 1;
        else right = middle - 1;
    }
    return false;
}
前 K 个高频元素(LC 347)⭐⭐
javascript 复制代码
function topKFrequent(nums, k) {
    const count = new Map();
    nums.forEach(num => count.set(num, (count.get(num) || 0) + 1));
    const buckets = Array.from({ length: nums.length + 1 }, () => []);
    count.forEach((frequency, num) => buckets[frequency].push(num));

    const result = [];
    for (let frequency = buckets.length - 1; frequency >= 0; frequency--) {
        for (const num of buckets[frequency]) {
            result.push(num);
            if (result.length === k) return result;
        }
    }
}
// 桶排序 O(n);追问时再补容量为 k 的最小堆方案
课程表(LC 207)⭐⭐
javascript 复制代码
function canFinish(numCourses, prerequisites) {
    const graph = Array.from({ length: numCourses }, () => []);
    const indegree = new Array(numCourses).fill(0);
    for (const [course, prerequisite] of prerequisites) {
        graph[prerequisite].push(course);
        indegree[course]++;
    }

    const queue = [];
    for (let i = 0; i < numCourses; i++) {
        if (indegree[i] === 0) queue.push(i);
    }

    let head = 0, learned = 0;
    while (head < queue.length) {
        const course = queue[head++];
        learned++;
        for (const next of graph[course]) {
            if (--indegree[next] === 0) queue.push(next);
        }
    }
    return learned === numCourses;
}
// 拓扑排序:最终无法处理完全部节点,说明图中存在环
第二优先级 DP

时间充足后再补以下三道,不挤占前面模板的复现时间:

  • LC 64 最小路径和:二维 DP
  • LC 139 单词拆分:完全背包/字符串 DP
  • LC 1143 最长公共子序列:二维序列 DP

五、框架原理

5.1 Vue 2 响应式(Object.defineProperty)

javascript 复制代码
var obj = {};
var val;
Object.defineProperty(obj, 'name', {
    set: function (newVal) {
        val = newVal;
        input.value = newVal;
        p.innerHTML = newVal;
    },
    get: function () { return val; }
});
input.addEventListener('input', function (e) {
    obj.name = e.target.value; // 触发set → 更新DOM
});
// 局限:无法检测属性的添加/删除、数组索引变化

5.2 Vue 3 响应式(Proxy)

javascript 复制代码
let obj = { text: 'vue3响应式' };
const list = new Set();
const newObj = new Proxy(obj, {
    get(target, key) {
        list.add(effect); // 依赖收集
        return target[key];
    },
    set(target, key, newVal) {
        target[key] = newVal;
        list.forEach(fn => fn()); // 派发更新
        return true;
    }
});
function effect() { document.getElementById('test').innerText = newObj.text; }
effect();
newObj.text = 'vue3变了'; // 自动触发DOM更新
// 优势:可以检测属性的添加、删除、数组变化

5.3 Vue vs React Diff 算法

对比项 Vue React
核心约束 同层比较,利用 key 识别节点 同层比较,利用 key 与元素类型复用 Fiber
Vue 2 双端指针比较 ---
Vue 3 最长递增子序列减少 DOM 移动 ---
React --- Reconciliation 标记插入、移动、删除等副作用
复杂度 常见列表场景约 O(n) 常见列表场景约 O(n)

不要简单表述为"Vue 就地复用、React 全部重建"。两者都会在满足条件时复用节点,

具体行为取决于节点类型、key、编译优化和协调过程。

5.4 浏览器渲染流程

  • JS 加载 :会阻塞 DOM 解析<script> 标签默认同步执行)
  • CSS 加载 :不会阻塞 DOM 树解析,但会阻塞渲染(render tree)JS 执行
  • 关键渲染路径:DOM → CSSOM → Render Tree → Layout → Paint

扩展常考知识点

  • defer:异步下载,DOMContentLoaded 前按顺序执行
  • async:异步下载,下载完立即执行(乱序)
  • 回流(Reflow):改变尺寸/位置 → 重新 Layout
  • 重绘(Repaint):改变颜色/背景 → 直接 Paint

六、公司真题精选

6.1 字节跳动/百度/拼多多------事件循环综合

javascript 复制代码
// 字节
for (var i = 0; i < 5; i++) {
    setTimeout(async () => { await console.log(i++); }, 1000);
} // 5 6 7 8 9(var没有块级作用域,1秒后i=5)

// B站
Promise.reject(0).catch(e => e).catch(e => console.log(e));
// Promise {<fulfilled>: 0} --- 第一个catch返回了0,第二个catch不触发

// 拼多多
Promise.resolve().then(() => {
    console.log('promise1');
    setTimeout(() => console.log('timer2'), 0);
});
setTimeout(() => {
    console.log('timer1');
    Promise.resolve().then(() => console.log('promise2'));
}, 0);
console.log('start');
// start → promise1 → timer1 → promise2 → timer2

6.2 字节------箭头函数 this

javascript 复制代码
const obj = { a: 1, func: () => { console.log(this.a); } };
obj.func(); // undefined(箭头函数this指向定义时的window)

// 综合 this 题
const objA = { value: 'a', fn: fn };
const objB = { value: 'b', fn: callArrowFn };
const objC = { value: 'c', fn: getArrowFn() }; // 箭头函数在全局定义,this=window
fn();        // undefined(全局调用)
objA.fn();   // 'a'
objB.fn();   // 'b'(callArrowFn普通函数,this=objB,内部箭头函数继承这个this)
objC.fn();   // undefined(getArrowFn在全局调用,返回的箭头函数this=window)

6.3 美团------变量提升 + 传参

javascript 复制代码
// var提升
var a = 1;
function f() {
    alert(a); // undefined(局部var a提升)
    a = 2; alert(a); // 2
    var a = 3; alert(a); // 3
}
f(); alert(a); // 1(全局a未变)

// 对象传参
function changeObj(o) {
    o.url = 'meituan';    // 修改了传入对象的属性
    o = new Object();     // o重新指向新对象,不影响外部
    o.url = 'baidu';
}
let test = new Object();
changeObj(test);
console.log(test.url); // 'meituan',不是'baidu'

6.4 其他公司真题速记

来源 关键点
typeof Person 百度 typeof class === 'function'
{}解构 百度 Object.create({x:1}) --- x在原型上,...rest 只取自有属性
构造函数 return 百度 构造函数返回非对象时忽略,返回this
冒泡排序 interview31 双重循环,每轮把最大元素移到最后
约瑟夫环变体 pp.js 循环删除,模4取余判断
水果最小成本 美团DP dp[i] = min(dp[i], dp[j-1] + cost),区间DP


七、计算机网络与浏览器 ✨ 新增

7.1 HTTP 缓存策略

类型 头字段 行为
强缓存 Cache-Control: max-age=3600 期限内直接用缓存,不发请求
Expires(HTTP 1.0) 绝对时间,有时钟偏差问题
协商缓存 ETag / If-None-Match 服务端对比资源hash,304/200
Last-Modified / If-Modified-Since 对比修改时间,精度秒级
复制代码
浏览器缓存流程:
请求 → 有强缓存且未过期?→ 直接用缓存(200 from disk/memory)
     → 无强缓存或已过期 → 发请求带 If-None-Match / If-Modified-Since
     → 服务端返回 304 → 更新缓存时间,用本地缓存
     → 服务端返回 200 → 返回新资源,更新缓存

7.2 跨域(CORS)

http 复制代码
// 简单请求:浏览器自动加 Origin 头
Origin: https://example.com

// 服务端返回:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true  // 允许携带 Cookie

// 非简单请求(如 content-type: application/json)先发预检请求:
OPTIONS /api HTTP/1.1
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

// 服务端返回:
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400  // 预检缓存时间

其他跨域方案:JSONP(只支持GET)、代理服务器(Nginx反向代理、webpack devServer proxy)

7.3 XSS 与 CSRF

攻击 原理 防御
XSS(跨站脚本) 注入恶意脚本到页面 输入过滤/转义、CSP、HttpOnly Cookie
CSRF(跨站请求伪造) 利用已登录态发伪造请求 CSRF Token、SameSite Cookie、Referer校验
javascript 复制代码
// XSS 防御:转义 HTML
function escapeHtml(str) {
    const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#x27;' };
    return str.replace(/[&<>"']/g, char => map[char]);
}

7.4 浏览器存储

特性 Cookie localStorage sessionStorage
容量 ~4KB ~5MB ~5MB
有效期 可设过期时间 永久(手动删除) 标签页关闭即清除
发送到服务端 每次请求自动带 不自动发送 不自动发送
作用域 同源+路径 同源 同源+同标签页

八、TypeScript 高频 ✨ 新增

8.1 常用工具类型

typescript 复制代码
// 面试中常要求口述或手写这些类型的实现

// Partial<T> --- 所有属性变为可选
type MyPartial<T> = { [K in keyof T]?: T[K] };

// Required<T> --- 所有属性变为必填
type MyRequired<T> = { [K in keyof T]-?: T[K] };

// Readonly<T> --- 所有属性变为只读
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

// Pick<T, K> --- 从 T 中选出 K 属性
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };

// Omit<T, K> --- 从 T 中排除 K 属性
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

// Record<K, V> --- 构造 K→V 的对象类型
type MyRecord<K extends keyof any, V> = { [P in K]: V };

// Exclude<T, U> --- 从联合类型中排除
type MyExclude<T, U> = T extends U ? never : T;

// Extract<T, U> --- 从联合类型中提取
type MyExtract<T, U> = T extends U ? T : never;

// ReturnType<T> --- 获取函数返回值类型
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never;

8.2 泛型约束

typescript 复制代码
// 基础泛型
function identity<T>(arg: T): T { return arg; }

// 泛型约束 --- 要求有 length 属性
function logLength<T extends { length: number }>(arg: T): number {
    return arg.length;
}

// 条件类型 --- 根据条件返回不同类型
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'

// keyof --- 获取对象所有 key 的联合类型
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

九、复习路线建议

9.1 使用原则

这份长文档是答案索引 ,不是每天从头阅读的教材。训练时先关掉答案,

在无代码补全的编辑器中完成;失败后再回到对应章节订正。

一道题达到"掌握"必须同时满足:

  1. 能先讲清输入、输出、边界和核心思路。
  2. 能在 25 分钟内独立写完,不依赖答案或代码补全。
  3. 能手动跑通至少 3 个测试,包括空值、单元素或重复元素。
  4. 能说出时间、空间复杂度以及一个替代方案。
  5. 在第 1、3、7 天再次随机抽中时仍能完成。

9.2 第一梯队:必须稳定写出

前端手写 15 项:

text 复制代码
Promise.all / allSettled / race
并发请求控制 / Scheduler / 异步串行与重试
防抖 / 节流 / 深拷贝
EventEmitter / LRU
数组转树 / call-apply-bind / new

算法核心题型:

text 复制代码
双指针与滑窗:两数之和、三数之和、最长无重复子串、接雨水
链表:反转链表、反转链表II、环入口、相交链表、重排链表
二叉树:层序、最大深度、验证BST、最近公共祖先、前中序构造
回溯与图:全排列、子集、括号生成、岛屿数量、课程表
动态规划:最长回文子串、最长递增子序列、最大子数组、零钱兑换
二分与排序:标准二分、左右边界、旋转数组、Top K、快速排序
其他:合并区间、LRU、螺旋矩阵、字符串相加、有效括号

第二梯队包括完整 Promise/A+、LazyMan、复杂类型体操和低频 CSS 手写。

第一梯队未稳定前,不增加第二梯队训练时间。

9.3 每日训练结构

每天算法时间约 3 小时,建议固定为:

text 复制代码
20分钟:随机抽一道旧题,模拟开场并口述
40分钟:一道新题,补覆盖面
60分钟:复现三道旧题(第1、3、7天间隔)
30分钟:随机一道中等题,25分钟计时编码
30分钟:随机两道前端手写题
20分钟:记录失败原因,修正后再次闭卷实现

新题最多一道。当天没有完成复现时,不通过增加新题制造"进度感"。

9.4 现场编码流程

拿到题目后不要立即写代码,先在纸上或注释中写四行:

text 复制代码
1. 状态/变量分别代表什么
2. 循环或递归过程始终保持什么条件
3. 空输入、单元素、重复值如何处理
4. 3至6行伪代码

编码时持续口述当前步骤。卡住超过 60 秒时,回到具体样例手动推演,

不要在脑中同时维护全部变量。

9.5 错题记录模板

markdown 复制代码
| 日期 | 题目 | 首次结果 | 失败类型 | 修正动作 | D+1 | D+3 | D+7 |
|------|------|----------|----------|----------|-----|-----|-----|
| 6/14 | LC 5 | 超时 | 边界混乱 | 先写 expand 定义 | ✅ | ⬜ | ⬜ |

失败类型只使用以下标签,便于一周后统计:

  • 无思路:没有识别题型或状态定义。
  • 代码组织:思路正确,但变量和流程无法落地。
  • 边界:空值、越界、重复值或终止条件错误。
  • API/语法:语言细节记忆错误。
  • 压力:模拟时明显慌乱或无法持续表达。

9.6 每周验收

每周至少进行两次 45 分钟模拟面试:

  • 前 5 分钟澄清问题并讲方案。
  • 25 分钟无补全编码。
  • 10 分钟测试、复杂度分析和追问。
  • 5 分钟复盘表达与卡顿点。

通过标准不是"这道题刷过",而是从第一梯队随机抽取 10 道,

至少 8 道能一次写对;前端手写随机抽取 5 道,至少 4 道通过。

9.7 概念题复习

  • 变量提升、闭包、this、事件循环:必须能逐行解释输出。
  • Promise、浏览器渲染、缓存、跨域:必须能从机制讲到实际排障。
  • Vue/React Diff:讲目标、约束、key 和各自策略,不背错误对比表。
  • 公司真题:只记录真实遇到的新题和失败原因,不继续无边界收集。

本文会持续根据真实面试反馈更新。建议将长文作为答案索引,

训练时先闭卷完成,再回来订正。

相关推荐
JustHappy10 小时前
古法编程秘籍(七):互联网到底是什么?把两台电脑怎么说话搞懂就够了
前端·后端·网络协议
老毛肚10 小时前
jeecg-boot-base-core 02 day
javascript·python
snow@li10 小时前
SEO-文章标题:写文章时候,分类+主标题+大纲+解释 作为标题 / 不点进去也知道全文覆盖什么 / 标题即架构
前端
kyriewen11 小时前
Git Commit 前自动修复代码风格?配置 Husky + lint-staged,从此 CR 只聊逻辑
前端·git·面试
小和尚同志11 小时前
AI 自动化测试探索(一):Playwright MCP
前端·人工智能·aigc
老马识途2.011 小时前
在AI的帮助下理解spring的启动过程
java·前端·spring
徐小夕12 小时前
Loop Engineering 深度解析与实战指南(全网最全)
前端·算法·github
运筹vivo@12 小时前
Python ContextVar 底层机制与内存模型拆解
前端·数据库·python
#麻辣小龙虾#13 小时前
基于vue3.0开发一款【固废与废气运维管理系统】(支持源码)
前端·vue.js·vue3