js面试题集合

2024-08-02 22:48:13 118
JavaScript 方面的基础面试题集合,以及详细答案解析。

1. 什么是闭包(Closure)?请解释其工作原理。

回答: 闭包是指在一个函数内部定义的函数,能够访问其外部函数的作用域,即使外部函数已经执行完毕。

示例

function outerFunction() {
    let outerVariable = 'I am outside!';

    function innerFunction() {
        console.log(outerVariable);
    }

    return innerFunction;
}

const closure = outerFunction();
closure();  // 输出: I am outside!

解析

  • outerFunction 定义了一个内部函数 innerFunction,并返回它。
  • 即使 outerFunction 执行完毕,innerFunction 仍然可以访问 outerFunction 的变量 outerVariable,这就是闭包。

2. 解释事件委托的概念及其优点。

回答: 事件委托是一种利用事件冒泡机制,将事件处理器添加到父元素而不是每个子元素的方法。

示例

<ul id="parent">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>

<script>
document.getElementById('parent').addEventListener('click', function(event) {
    if (event.target.tagName === 'LI') {
        console.log(event.target.textContent);
    }
});
</script>

解析

  • 将点击事件处理器添加到父元素 #parent,利用事件冒泡机制,子元素 <li> 的点击事件会冒泡到父元素进行处理。
  • 优点包括减少内存消耗,动态添加或删除子元素时无需重新绑定事件处理器。

3. 解释 JavaScript 中的 varletconst 之间的区别。

回答

  • var

    • 变量提升:在声明之前可以使用,但值为 undefined
    • 函数作用域:仅在函数内有作用域。
  • let

    • 块作用域:只在块级作用域内有效。
    • 不存在变量提升:声明之前无法使用(暂时性死区)。
  • const

    • 块作用域:只在块级作用域内有效。
    • 声明后不可修改:必须初始化,并且不能重新赋值,但对象属性可以改变。

示例

if (true) {
    var x = 1;
    let y = 2;
    const z = 3;
}
console.log(x);  // 输出: 1
console.log(y);  // 报错: ReferenceError: y is not defined
console.log(z);  // 报错: ReferenceError: z is not defined

4. 如何深拷贝一个对象?

回答: 可以使用 JSON 方法或递归函数进行深拷贝。

JSON 方法

const original = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(original));
console.log(copy);  // 输出: { a: 1, b: { c: 2 } }

递归函数

function deepCopy(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }

    const copy = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = deepCopy(obj[key]);
        }
    }
    return copy;
}

const original = { a: 1, b: { c: 2 } };
const copy = deepCopy(original);
console.log(copy);  // 输出: { a: 1, b: { c: 2 } }

5. 什么是箭头函数(Arrow Function)?与传统函数有何不同?

回答: 箭头函数是 ES6 引入的简洁语法,允许以更简短的方式编写函数表达式。

示例

const add = (a, b) => a + b;
console.log(add(2, 3));  // 输出: 5

不同之处

  • 语法更简洁,无需 function 关键字和 return 语句(单行表达式)。
  • 没有自己的 this 绑定,this 由外层作用域决定。
  • 不能用作构造函数(没有 new 关键字)。
  • 没有 arguments 对象。

6. 如何处理 JavaScript 中的异步编程?

回答: JavaScript 中的异步编程可以通过回调函数、Promise 和 async/await 来处理。

回调函数

function fetchData(callback) {
    setTimeout(() => {
        callback('Data');
    }, 1000);
}

fetchData((data) => {
    console.log(data);  // 输出: Data
});

Promise

function fetchData() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('Data');
        }, 1000);
    });
}

fetchData().then((data) => {
    console.log(data);  // 输出: Data
});

async/await

function fetchData() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('Data');
        }, 1000);
    });
}

async function fetchAsyncData() {
    const data = await fetchData();
    console.log(data);  // 输出: Data
}

fetchAsyncData();

解析

  • 回调函数:通过回调处理异步操作结果,可能导致回调地狱。
  • Promise:通过链式

调用处理异步操作结果,避免回调地狱。

  • async/await:基于 Promise 的语法糖,使异步代码看起来像同步代码,更加直观和易读。

7. 解释 JavaScript 中的异步操作和事件驱动编程。

回答: JavaScript 是单线程的,但通过异步操作(如回调、Promise 和 async/await)和事件驱动机制,可以高效处理多个任务。事件驱动编程通过事件循环处理异步事件。

示例

console.log('Start');

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

console.log('End');

输出

Start
End
Timeout

解析

  • setTimeout 是一个异步操作,回调函数被推迟到事件循环的下一轮执行。
  • 事件驱动编程依赖于事件循环来处理异步事件,确保非阻塞执行。

8. 解释 JavaScript 中的深拷贝和浅拷贝的区别。

回答

  • 浅拷贝:复制对象的引用,而不是实际对象。改变拷贝后的对象也会影响原对象。

    const original = { a: 1, b: { c: 2 } };
    const shallowCopy = Object.assign({}, original);
    shallowCopy.b.c = 3;
    console.log(original.b.c);  // 输出: 3
    
  • 深拷贝:创建对象的完整副本,修改拷贝不会影响原对象。

    const deepCopy = JSON.parse(JSON.stringify(original));
    deepCopy.b.c = 4;
    console.log(original.b.c);  // 输出: 2
    

解析

  • 浅拷贝仅复制对象的第一层属性,而深拷贝复制所有嵌套的对象和数组。
  • 使用 Object.assign 或扩展运算符(...)实现浅拷贝,用 JSON 方法或递归函数实现深拷贝。

9. 解释 nullundefined 的区别。

回答

  • undefined:表示变量声明了但未赋值。

    let a;
    console.log(a);  // 输出: undefined
    
  • null:表示变量明确为空值。

    let b = null;
    console.log(b);  // 输出: null
    

解析

  • undefined 是 JavaScript 初始化变量的默认值。
  • null 是开发者用来表示空值的显式赋值。

10. 如何在 JavaScript 中实现防抖(Debounce)和节流(Throttle)?

回答

  • 防抖:在指定时间内不执行函数,直到停止触发一定时间后才执行。

    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }
    
    const debouncedFunction = debounce(() => {
        console.log('Debounced!');
    }, 2000);
    
    window.addEventListener('resize', debouncedFunction);
    
  • 节流:在指定时间内只执行一次函数。

    function throttle(func, limit) {
        let inThrottle;
        return function(...args) {
            if (!inThrottle) {
                func.apply(this, args);
                inThrottle = true;
                setTimeout(() => (inThrottle = false), limit);
            }
        };
    }
    
    const throttledFunction = throttle(() => {
        console.log('Throttled!');
    }, 2000);
    
    window.addEventListener('resize', throttledFunction);
    

解析

  • 防抖用于减少高频触发的事件,直到事件停止触发一定时间后才执行。
  • 节流用于限制高频触发的事件,在一定时间内只执行一次。

11. 解释 JavaScript 中的setTimeoutsetInterval 的区别。

回答

  • setTimeout:在指定时间后执行一次回调函数。

    setTimeout(() => {
        console.log('Executed after 1 second');
    }, 1000);
    
  • setInterval:每隔指定时间重复执行回调函数。

    setInterval(() => {
        console.log('Executed every 1 second');
    }, 1000);
    

解析

  • setTimeout 用于延迟执行代码。
  • setInterval 用于周期性执行代码,直到被清除。

12. 解释 try...catch 语句的用法及其作用。

回答try...catch 语句用于处理代码中的异常,防止程序崩溃,并提供错误处理逻辑。

示例

try {
    let result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error('An error occurred:', error.message);
}

解析

  • try 块包含可能会引发异常的代码。
  • catch 块包含处理异常的代码,当 try 块中发生异常时,程序控制流会转到 catch 块。

13. 解释 JavaScript 中的异步迭代器(Async Iterators)和 for-await-of 循环。

回答: 异步迭代器用于遍历异步可迭代对象,如异步生成器。for-await-of 循环用于异步遍历可迭代对象。

示例

async function* asyncGenerator() {
    let i = 0;
    while (i < 3) {
        yield new Promise(resolve => setTimeout(() => resolve(i++), 1000));
    }
}

(async () => {
    for await (const num of asyncGenerator()) {
        console.log(num);  // 输出: 0, 1, 2(每秒输出一个)
    }
})();

解析

  • async function* 定义异步生成器函数,yield 返回异步值。
  • for-await-of 循环用于等待并异步遍历可迭代对象的值。

14. 解释 JavaScript 中的 Symbol 类型及其用途。

回答Symbol 是 ES6 引入的一种原始数据类型,表示独一无二的标识符。它可以用于对象属性的唯一标识,避免属性名冲突。

示例

const sym1 = Symbol('description');
const sym2 = Symbol('description');

console.log(sym1 === sym2);  // 输出: false

const obj = {
    [sym1]: 'value1',
    [sym2]: 'value2'
};

console.log(obj[sym1]);  // 输出: value1
console.log(obj[sym2]);  // 输出: value2

解析

  • Symbol 创建唯一的标识符,即使描述相同,也不会相等。
  • Symbol 可以作为对象属性名,避免属性名冲突。

15. 解释 JavaScript 中的 MapSet

回答

  • Map:用于存储键值对,允许使用任何类型的值作为键。

    const map = new Map();
    map.set('key1', 'value1');
    map.set('key2', 'value2');
    
    console.log(map.get('key1'));  // 输出: value1
    console.log(map.size);  // 输出: 2
    
  • Set:用于存储唯一值,不允许重复的值。

    const set = new Set();
    set.add(1);
    set.add(2);
    set.add(1);  // 重复的值不会被添加
    
    console.log(set.has(1));  // 输出: true
    console.log(set.size);  // 输出: 2
    

解析

  • Map 提供了键值对的数据结构,键和值可以是任意类型。
  • Set 提供了一个包含唯一值的集合,自动去重。

16. 解释 JavaScript 中的模块化(Modules)。

回答: JavaScript 模块化是将代码分割成独立的、可重用的模块,每个模块只负责一个功能。ES6 引入了原生的模块化支持。

示例

  • 导出模块(export.js):

    export const name = 'Alice';
    export function greet() {
        console.log('Hello, ' + name);
    }
    
  • 导入模块(import.js):

    import { name, greet } from './export.js';
    console.log(name);  // 输出: Alice
    greet();  // 输出: Hello, Alice
    

解析

  • export 关键字用于导出模块成员。
  • import 关键字用于导入模块成员。

17. 解释 JavaScript 中的 asyncawait

回答asyncawait 是 ES8 引入的用于处理异步操作的语法糖,使代码看起来像同步代码。

示例

function fetchData() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('Data');
        }, 1000);
    });
}

async function fetchAsyncData() {
    const data = await fetchData();
    console.log(data);  // 输出: Data
}

fetchAsyncData();

解析

  • async 关键字用于定义一个返回 Promise 的异步函数。
  • await 关键字用于等待异步操作完成,并返回其结果。

18. 解释 JavaScript 中的事件捕获和事件冒泡。

回答: 事件捕获和事件冒泡是事件传播的两个阶段。

  • 事件捕获:事件从祖先元素向目标元素传播。
  • 事件冒泡:事件从目标元素向祖先元素传播。

示例

<div id="parent">
    <button id="child">Click me</button>
</div>

<script>
document.getElementById('parent').addEventListener('click', () => {
    console.log('Parent clicked');
}, true);  // 捕获阶段

document.getElementById('child').addEventListener('click', () => {
    console.log('Child clicked');
});
</script>

输出顺序(捕获阶段优先)

Parent clicked
Child clicked

解析

  • 事件捕获阶段:事件从外到内传播,先触发祖先元素的事件处理器。
  • 事件冒泡阶段:事件从内到外传播,先触发目标元素的事件处理器。

19. 解释 JavaScript 中的 Proxy 对象及其用途。

回答Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。

示例

const target = {
    message1: "hello",
    message2: "everyone"
};

const handler = {
    get: function(target, prop, receiver) {
        if (prop === "message2") {
            return "world";
        }
        return Reflect.get(...arguments);
    }
};

const proxy = new Proxy(target, handler);
console.log(proxy.message1);  // 输出: hello
console.log(proxy.message2);  // 输出: world

解析

  • Proxy 接受两个参数:目标对象和处理器对象。
  • 处理器对象定义了拦截目标对象操作的陷阱(trap),如 getset 等。

20. 解释 JavaScript 中的 Generator 函数。

回答Generator 函数是 ES6 引入的一种函数,允许函数在执行过程中暂停和恢复,提供生成器对象。

示例

function* generator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = generator();
console.log(gen.next().value);  // 输出: 1
console.log(gen.next().value);  // 输出: 2
console.log(gen.next().value);  // 输出: 3

解析

  • function* 定义生成器函数,yield 关键字用于暂停函数执行并返回值。
  • 生成器对象通过 next 方法恢复执行并返回下一个 yield 的值。