职业发展

前端技术面试题库

精选前端面试高频题目,涵盖 JavaScript、React、性能优化等核心知识点

前端技术面试题库

本文精选了前端面试中的高频题目,涵盖 JavaScript 基础、框架原理、性能优化等核心知识点。

JavaScript 基础

1. 闭包

问题:什么是闭包?闭包的应用场景有哪些?

答案

闭包是指函数能够访问其词法作用域外的变量。

// 闭包示例
function createCounter() {
    let count = 0;

    return {
        increment() {
            count++;
            return count;
        },
        decrement() {
            count--;
            return count;
        },
        getCount() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2

应用场景

  1. 数据私有化
  2. 函数柯里化
  3. 模块化
  4. 防抖节流

注意事项

  • 闭包会导致内存占用
  • 注意避免内存泄漏

2. this 指向

问题:JavaScript 中 this 的指向规则是什么?

答案

this 的指向取决于函数的调用方式:

// 1. 默认绑定:指向全局对象(严格模式下是 undefined)
function foo() {
    console.log(this);
}
foo(); // window (浏览器) 或 global (Node.js)

// 2. 隐式绑定:指向调用对象
const obj = {
    name: 'Alice',
    sayName() {
        console.log(this.name);
    }
};
obj.sayName(); // 'Alice'

// 3. 显式绑定:call、apply、bind
function greet() {
    console.log(`Hello, ${this.name}`);
}
const person = { name: 'Bob' };
greet.call(person); // 'Hello, Bob'

// 4. new 绑定:指向新创建的对象
function Person(name) {
    this.name = name;
}
const p = new Person('Charlie');
console.log(p.name); // 'Charlie'

// 5. 箭头函数:继承外层作用域的 this
const obj2 = {
    name: 'David',
    sayName: () => {
        console.log(this.name);
    }
};
obj2.sayName(); // undefined(this 指向外层作用域)

优先级:new > 显式绑定 > 隐式绑定 > 默认绑定

3. 原型链

问题:解释 JavaScript 的原型链机制

答案

// 原型链示例
function Person(name) {
    this.name = name;
}

Person.prototype.sayName = function() {
    console.log(this.name);
};

const person = new Person('Alice');

// 原型链查找过程
person.sayName(); // 'Alice'
// 1. 在 person 对象上查找 sayName -> 没找到
// 2. 在 person.__proto__ (Person.prototype) 上查找 -> 找到
// 3. 执行 Person.prototype.sayName

// 原型链关系
console.log(person.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

// 完整的原型链
person -> Person.prototype -> Object.prototype -> null

继承实现

// ES5 继承
function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(`${this.name} is eating`);
};

function Dog(name, breed) {
    Animal.call(this, name); // 继承属性
    this.breed = breed;
}

// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
    console.log('Woof!');
};

const dog = new Dog('Buddy', 'Golden Retriever');
dog.eat();  // 'Buddy is eating'
dog.bark(); // 'Woof!'

// ES6 继承
class Animal {
    constructor(name) {
        this.name = name;
    }

    eat() {
        console.log(`${this.name} is eating`);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }

    bark() {
        console.log('Woof!');
    }
}

4. Promise

问题:实现一个简单的 Promise

答案

class MyPromise {
    constructor(executor) {
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn => fn());
            }
        };

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

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
        onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e };

        const promise2 = new MyPromise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolve(x);
                    } catch (error) {
                        reject(error);
                    }
                });
            }

            if (this.state === 'rejected') {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolve(x);
                    } catch (error) {
                        reject(error);
                    }
                });
            }

            if (this.state === 'pending') {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolve(x);
                        } catch (error) {
                            reject(error);
                        }
                    });
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolve(x);
                        } catch (error) {
                            reject(error);
                        }
                    });
                });
            }
        });

        return promise2;
    }

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

    static resolve(value) {
        return new MyPromise((resolve) => resolve(value));
    }

    static reject(reason) {
        return new MyPromise((_, reject) => reject(reason));
    }

    static all(promises) {
        return new MyPromise((resolve, reject) => {
            const results = [];
            let count = 0;

            promises.forEach((promise, index) => {
                Promise.resolve(promise).then(value => {
                    results[index] = value;
                    count++;
                    if (count === promises.length) {
                        resolve(results);
                    }
                }, reject);
            });
        });
    }

    static race(promises) {
        return new MyPromise((resolve, reject) => {
            promises.forEach(promise => {
                Promise.resolve(promise).then(resolve, reject);
            });
        });
    }
}

// 使用示例
const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => resolve('Success!'), 1000);
});

promise.then(value => {
    console.log(value); // 'Success!'
});

5. 事件循环

问题:解释 JavaScript 的事件循环机制

答案

// 事件循环示例
console.log('1');

setTimeout(() => {
    console.log('2');
}, 0);

Promise.resolve().then(() => {
    console.log('3');
});

console.log('4');

// 输出顺序:1, 4, 3, 2

// 解释:
// 1. 同步代码:console.log('1') 和 console.log('4')
// 2. 微任务:Promise.then -> console.log('3')
// 3. 宏任务:setTimeout -> console.log('2')

执行顺序

  1. 执行同步代码
  2. 执行微任务队列(Promise、MutationObserver)
  3. 执行宏任务队列(setTimeout、setInterval、I/O)
  4. 重复 2-3

复杂示例

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2() {
    console.log('async2');
}

console.log('script start');

setTimeout(() => {
    console.log('setTimeout');
}, 0);

async1();

new Promise(resolve => {
    console.log('promise1');
    resolve();
}).then(() => {
    console.log('promise2');
});

console.log('script end');

// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

React 相关

1. Virtual DOM

问题:React 的 Virtual DOM 是如何工作的?

答案

Virtual DOM 是对真实 DOM 的 JavaScript 对象表示。

工作流程

// 1. 创建 Virtual DOM
const vdom = {
    type: 'div',
    props: {
        className: 'container',
        children: [
            {
                type: 'h1',
                props: {
                    children: 'Hello'
                }
            },
            {
                type: 'p',
                props: {
                    children: 'World'
                }
            }
        ]
    }
};

// 2. Diff 算法比较新旧 Virtual DOM
function diff(oldVNode, newVNode) {
    // 比较节点类型
    if (oldVNode.type !== newVNode.type) {
        return { type: 'REPLACE', newVNode };
    }

    // 比较属性
    const propsPatches = diffProps(oldVNode.props, newVNode.props);

    // 比较子节点
    const childrenPatches = diffChildren(
        oldVNode.props.children,
        newVNode.props.children
    );

    return {
        type: 'UPDATE',
        propsPatches,
        childrenPatches
    };
}

// 3. 根据 Diff 结果更新真实 DOM
function patch(dom, patches) {
    switch (patches.type) {
        case 'REPLACE':
            const newDom = createElement(patches.newVNode);
            dom.parentNode.replaceChild(newDom, dom);
            break;
        case 'UPDATE':
            updateProps(dom, patches.propsPatches);
            patches.childrenPatches.forEach((patch, index) => {
                patch(dom.childNodes[index], patch);
            });
            break;
    }
}

优势

  • 减少 DOM 操作
  • 批量更新
  • 跨平台(React Native)

2. Hooks 原理

问题:React Hooks 是如何实现的?

答案

// 简化的 Hooks 实现
let currentComponent = null;
let currentHookIndex = 0;

function useState(initialValue) {
    const component = currentComponent;
    const hookIndex = currentHookIndex;

    // 初始化 hooks 数组
    if (!component.hooks) {
        component.hooks = [];
    }

    // 获取或初始化 hook
    if (component.hooks[hookIndex] === undefined) {
        component.hooks[hookIndex] = initialValue;
    }

    const setState = (newValue) => {
        component.hooks[hookIndex] = newValue;
        // 触发重新渲染
        render(component);
    };

    currentHookIndex++;
    return [component.hooks[hookIndex], setState];
}

function useEffect(callback, deps) {
    const component = currentComponent;
    const hookIndex = currentHookIndex;

    if (!component.hooks) {
        component.hooks = [];
    }

    const oldDeps = component.hooks[hookIndex];

    // 检查依赖是否变化
    const hasChanged = !oldDeps || deps.some((dep, i) => dep !== oldDeps[i]);

    if (hasChanged) {
        // 执行副作用
        callback();
        // 保存依赖
        component.hooks[hookIndex] = deps;
    }

    currentHookIndex++;
}

// 渲染组件
function render(component) {
    currentComponent = component;
    currentHookIndex = 0;
    component.render();
}

Hooks 规则

  1. 只在顶层调用 Hooks
  2. 只在 React 函数中调用 Hooks

3. Fiber 架构

问题:React Fiber 是什么?解决了什么问题?

答案

Fiber 是 React 16 引入的新的协调引擎,实现了可中断的渲染。

核心概念

// Fiber 节点结构
const fiber = {
    // 节点类型
    type: 'div',
    // 属性
    props: {},
    // 父节点
    return: parentFiber,
    // 第一个子节点
    child: childFiber,
    // 下一个兄弟节点
    sibling: siblingFiber,
    // 副作用标记
    effectTag: 'UPDATE',
    // 旧的 props
    alternate: oldFiber
};

// 工作循环
function workLoop(deadline) {
    let shouldYield = false;

    while (nextUnitOfWork && !shouldYield) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
        shouldYield = deadline.timeRemaining() < 1;
    }

    if (!nextUnitOfWork && wipRoot) {
        commitRoot();
    }

    requestIdleCallback(workLoop);
}

// 执行工作单元
function performUnitOfWork(fiber) {
    // 1. 处理当前 fiber
    if (!fiber.dom) {
        fiber.dom = createDom(fiber);
    }

    // 2. 创建子 fiber
    const elements = fiber.props.children;
    reconcileChildren(fiber, elements);

    // 3. 返回下一个工作单元
    if (fiber.child) {
        return fiber.child;
    }

    let nextFiber = fiber;
    while (nextFiber) {
        if (nextFiber.sibling) {
            return nextFiber.sibling;
        }
        nextFiber = nextFiber.return;
    }
}

解决的问题

  • 长时间渲染导致页面卡顿
  • 无法中断渲染过程
  • 无法设置任务优先级

优势

  • 可中断渲染
  • 优先级调度
  • 增量渲染

性能优化

1. 首屏优化

问题:如何优化首屏加载速度?

答案

// 1. 代码分割
import React, { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
    return (
        <Suspense fallback={<div>Loading...</div>}>
            <HeavyComponent />
        </Suspense>
    );
}

// 2. 路由懒加载
const routes = [
    {
        path: '/',
        component: lazy(() => import('./pages/Home'))
    },
    {
        path: '/about',
        component: lazy(() => import('./pages/About'))
    }
];

// 3. 图片懒加载
function LazyImage({ src, alt }) {
    const [imageSrc, setImageSrc] = useState(placeholder);
    const imgRef = useRef();

    useEffect(() => {
        const observer = new IntersectionObserver(entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    setImageSrc(src);
                    observer.unobserve(entry.target);
                }
            });
        });

        if (imgRef.current) {
            observer.observe(imgRef.current);
        }

        return () => observer.disconnect();
    }, [src]);

    return <img ref={imgRef} src={imageSrc} alt={alt} />;
}

// 4. 预加载关键资源
<link rel="preload" href="/critical.css" as="style" />
<link rel="preload" href="/critical.js" as="script" />

// 5. SSR/SSG
// Next.js
export async function getServerSideProps() {
    const data = await fetchData();
    return { props: { data } };
}

// 6. CDN 加速
const imageUrl = 'https://cdn.example.com/image.jpg';

// 7. 压缩资源
// webpack.config.js
module.exports = {
    optimization: {
        minimize: true,
        minimizer: [new TerserPlugin(), new CssMinimizerPlugin()]
    }
};

2. 运行时优化

问题:如何优化 React 应用的运行时性能?

答案

// 1. React.memo
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
    // 只有当 data 改变时才重新渲染
    return <div>{/* 复杂的渲染逻辑 */}</div>;
});

// 2. useMemo
function Component({ items }) {
    const expensiveValue = useMemo(() => {
        return items.reduce((sum, item) => sum + item.value, 0);
    }, [items]);

    return <div>{expensiveValue}</div>;
}

// 3. useCallback
function Parent() {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        setCount(c => c + 1);
    }, []);

    return <Child onClick={handleClick} />;
}

// 4. 虚拟滚动
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
    return (
        <FixedSizeList
            height={600}
            itemCount={items.length}
            itemSize={50}
            width="100%"
        >
            {({ index, style }) => (
                <div style={style}>{items[index]}</div>
            )}
        </FixedSizeList>
    );
}

// 5. 防抖节流
function useDebounce(value, delay) {
    const [debouncedValue, setDebouncedValue] = useState(value);

    useEffect(() => {
        const timer = setTimeout(() => {
            setDebouncedValue(value);
        }, delay);

        return () => clearTimeout(timer);
    }, [value, delay]);

    return debouncedValue;
}

// 6. Web Worker
// worker.js
self.addEventListener('message', (e) => {
    const result = heavyComputation(e.data);
    self.postMessage(result);
});

// main.js
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.onmessage = (e) => {
    console.log(e.data);
};

算法题

1. 两数之和

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 [];
}

2. 防抖

function debounce(func, wait) {
    let timeout;

    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };

        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

3. 节流

function throttle(func, limit) {
    let inThrottle;

    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

4. 深拷贝

function deepClone(obj, hash = new WeakMap()) {
    if (obj === null) return null;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== 'object') return obj;

    if (hash.has(obj)) return hash.get(obj);

    const cloneObj = new obj.constructor();
    hash.set(obj, cloneObj);

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key], hash);
        }
    }

    return cloneObj;
}

5. 数组扁平化

// 方法一:递归
function flatten(arr) {
    return arr.reduce((acc, val) => {
        return Array.isArray(val)
            ? acc.concat(flatten(val))
            : acc.concat(val);
    }, []);
}

// 方法二:迭代
function flatten(arr) {
    const result = [];
    const stack = [...arr];

    while (stack.length) {
        const item = stack.pop();

        if (Array.isArray(item)) {
            stack.push(...item);
        } else {
            result.unshift(item);
        }
    }

    return result;
}

// 方法三:flat
const arr = [1, [2, [3, [4]]]];
arr.flat(Infinity); // [1, 2, 3, 4]

总结

面试准备要点:

  • ✅ 理解核心概念和原理
  • ✅ 能够手写常见算法
  • ✅ 掌握性能优化技巧
  • ✅ 了解框架底层实现
  • ✅ 多做题多练习

记住:理解原理比死记硬背更重要!

参考资源


持续学习,不断进步!