跳到主要内容

JavaScript经典面试问题解答

前言

JavaScript是前端开发的核心语言,掌握其基础知识和进阶概念对于通过技术面试至关重要。本文收集了大量经典的JavaScript面试问题,并提供详细的解答和说明,帮助开发者更好地准备面试。

一、基础概念

1. JavaScript的数据类型有哪些?

JavaScript有8种数据类型,分为基本类型和引用类型:

基本类型(值类型):

  • Number:数字,包括整数和浮点数
  • String:字符串
  • Boolean:布尔值,true或false
  • Undefined:未定义
  • Null:空值
  • Symbol(ES6新增):唯一标识符
  • BigInt(ES10新增):任意精度整数

引用类型

  • Object:对象,包括普通对象、数组、函数、日期、正则表达式等
// 基本类型示例
const num = 42;
const str = 'hello';
const bool = true;
const undef = undefined;
const nil = null;
const sym = Symbol('symbol');
const bigInt = 9007199254740991n;

// 引用类型示例
const obj = { name: 'John' };
const arr = [1, 2, 3];
const func = function() {};
const date = new Date();
const regexp = /test/;

2. typeof操作符返回值有哪些?

typeof操作符用于检测变量的数据类型,返回以下字符串之一:

  • 'number'
  • 'string'
  • 'boolean'
  • 'undefined'
  • 'object'(对于null、数组、对象等)
  • 'function'
  • 'symbol'
  • 'bigint'

注意:typeof null返回'object'是JavaScript的一个历史遗留bug。

typeof 42; // 'number'
typeof 'hello'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof null; // 'object'(bug)
typeof {}; // 'object'
typeof []; // 'object'
typeof function() {}; // 'function'
typeof Symbol('sym'); // 'symbol'
typeof 1n; // 'bigint'

3. null和undefined的区别?

  • undefined:表示变量已声明但未赋值,或者访问对象不存在的属性。是JavaScript的原始值之一。
  • null:表示一个空值或"无",是一个被赋予的值。通常用于表示对象不存在。
let x;
console.log(x); // undefined
console.log({}.nonExistentProperty); // undefined

const y = null;
console.log(y); // null

4. ==和===的区别?

  • ==:抽象相等运算符,会进行类型转换后比较值是否相等。
  • ===:严格相等运算符,不进行类型转换,比较值和类型是否都相等。

建议:在大多数情况下,推荐使用===以避免类型转换带来的意外结果。

0 == ''; // true(类型转换后)
0 === ''; // false(类型不同)

null == undefined; // true
null === undefined; // false

1 == '1'; // true(类型转换后)
1 === '1'; // false(类型不同)

二、作用域与闭包

5. 什么是作用域?JavaScript有哪些作用域?

作用域是变量和函数可访问的范围,决定了代码中变量的可见性和生命周期。

JavaScript的作用域包括:

  • 全局作用域:在代码的任何地方都能访问的变量
  • 函数作用域:在函数内部定义的变量,只能在函数内部访问
  • 块级作用域:在内定义的变量(ES6引入let和const后),只能在块内访问
// 全局作用域
const globalVar = 'global';

function func() {
// 函数作用域
const funcVar = 'function';

if (true) {
// 块级作用域(ES6)
const blockVar = 'block';
let letVar = 'let';
// var不支持块级作用域
var varVar = 'var';
}

console.log(blockVar); // ReferenceError
console.log(letVar); // ReferenceError
console.log(varVar); // 'var'
}

6. 什么是闭包?闭包的用途是什么?

闭包是指函数可以访问其词法作用域之外的变量,即使该函数在其定义的作用域之外执行。

闭包的形成条件:

  1. 函数嵌套
  2. 内部函数引用外部函数的变量
  3. 内部函数被外部引用

闭包的用途:

  • 保存变量状态
  • 创建私有变量
  • 实现模块化
function createCounter() {
let count = 0;

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

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
// count变量对外部不可见,但通过闭包被保留

7. 什么是变量提升(Hoisting)?

变量提升是指JavaScript引擎在执行代码之前,会将变量和函数的声明提升到其作用域的顶部。

  • 函数声明:整个函数体都会被提升
  • var变量声明:只有变量声明被提升,赋值不会被提升
  • let和const:不会被提升,存在暂时性死区
// 函数声明提升
console.log(add(2, 3)); // 5
function add(a, b) {
return a + b;
}

// var变量提升
console.log(x); // undefined
var x = 5;

// let和const不存在提升
console.log(y); // ReferenceError
let y = 10;

三、函数与对象

8. 什么是原型链?

原型链是JavaScript中实现继承的机制。每个对象都有一个原型对象,对象从原型继承属性和方法。原型对象也可以有自己的原型,形成一条原型链,直到某个对象的原型为null。

// 构造函数
function Person(name) {
this.name = name;
}

// 添加方法到原型
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};

// 创建实例
const john = new Person('John');
john.sayHello(); // 'Hello, my name is John'

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

9. this关键字的指向规则是什么?

this的指向取决于函数的调用方式,主要有以下几种情况:

  1. 普通函数调用:this指向全局对象(非严格模式)或undefined(严格模式)
  2. 方法调用:this指向调用方法的对象
  3. 构造函数调用:this指向新创建的对象
  4. apply/call/bind调用:this指向指定的对象
  5. 箭头函数:this指向箭头函数定义时的外层作用域的this
// 1. 普通函数调用
function func() {
console.log(this);
}
func(); // 浏览器中是window,Node.js中是global

// 2. 方法调用
const obj = {
name: 'obj',
method: function() {
console.log(this.name);
}
};
obj.method(); // 'obj'

// 3. 构造函数调用
function Person(name) {
this.name = name;
}
const person = new Person('John');
console.log(person.name); // 'John'

// 4. apply/call/bind调用
function greet() {
console.log(`Hello, ${this.name}`);
}
const user = { name: 'Alice' };
greet.call(user); // 'Hello, Alice'

// 5. 箭头函数
const arrowFunc = () => {
console.log(this);
};
arrowFunc(); // 指向外层作用域的this

10. 什么是箭头函数?它与普通函数有什么区别?

箭头函数是ES6引入的一种新的函数语法,使用=>来定义函数。

箭头函数与普通函数的区别:

  1. 语法更简洁
  2. 没有自己的this,this指向外层作用域的this
  3. 不能用作构造函数,不能使用new关键字
  4. 没有arguments对象
  5. 没有prototype属性
  6. 不能用作Generator函数
// 箭头函数示例
const add = (a, b) => a + b;
const double = x => x * 2;
const greet = () => {
console.log('Hello');
};

// this指向的区别
const obj = {
name: 'obj',
regularMethod: function() {
console.log(this.name); // 'obj'
},
arrowMethod: () => {
console.log(this.name); // 可能是undefined或全局对象的name属性
}
};

11. 什么是高阶函数?举个例子。

高阶函数是指接受一个或多个函数作为参数,或者返回一个函数的函数。

常见的高阶函数包括:

  • Array.prototype.map()
  • Array.prototype.filter()
  • Array.prototype.reduce()
  • setTimeout()
  • setInterval()
// 自定义高阶函数示例
function withLogging(func) {
return function(...args) {
console.log(`Calling function with args: ${args}`);
const result = func.apply(this, args);
console.log(`Function returned: ${result}`);
return result;
};
}

const add = (a, b) => a + b;
const addWithLogging = withLogging(add);
console.log(addWithLogging(2, 3)); // 5

四、异步编程

12. 什么是异步编程?JavaScript中处理异步的方式有哪些?

异步编程是指不阻塞主线程,允许程序在等待某个操作完成的同时继续执行其他任务的编程方式。

JavaScript中处理异步的方式:

  1. 回调函数(Callbacks)
  2. 事件监听
  3. Promise(ES6)
  4. async/await(ES2017)
// 1. 回调函数
function fetchData(callback) {
setTimeout(() => {
callback('Data fetched');
}, 1000);
}

// 2. Promise
function fetchDataPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data fetched');
}, 1000);
});
}

// 3. async/await
async function fetchDataAsync() {
const data = await fetchDataPromise();
return data;
}

13. Promise有哪些状态?如何使用Promise?

Promise有三种状态:

  • pending:初始状态,既不是成功也不是失败
  • fulfilled:操作成功完成
  • rejected:操作失败

状态一旦改变,就不能再变。

Promise的使用方法:

const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(value);
} else {
reject(error);
}
});

promise
.then(value => {
// 处理成功的结果
})
.catch(error => {
// 处理错误
})
.finally(() => {
// 无论成功还是失败都会执行
});

14. async/await的工作原理是什么?

async/await是基于Promise的语法糖,使异步代码看起来更像同步代码。

  • async:声明一个异步函数,返回一个Promise
  • await:等待一个Promise解决并返回其结果,只能在async函数中使用
async function example() {
try {
const result1 = await promise1();
const result2 = await promise2(result1);
return result2;
} catch (error) {
console.error(error);
}
}

15. 什么是事件循环(Event Loop)?

事件循环是JavaScript处理异步操作的机制,负责协调执行代码、收集和处理事件以及执行队列中的子任务。

JavaScript的执行模型:

  1. 执行同步代码(宏任务)
  2. 执行所有微任务
  3. 渲染页面(如果需要)
  4. 从事件队列中取出下一个宏任务执行
  5. 重复上述过程

常见的宏任务和微任务:

  • 宏任务:setTimeout、setInterval、setImmediate、I/O操作、UI渲染
  • 微任务:Promise.then、Promise.catch、Promise.finally、process.nextTick、MutationObserver
console.log('Start');

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

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

console.log('End');

// 输出顺序:
// Start
// End
// Promise
// Timeout

五、ES6+新特性

16. let、const和var的区别?

  • 作用域
    • var:函数作用域
    • let和const:块级作用域
  • 变量提升
    • var:存在变量提升
    • let和const:不存在变量提升,有暂时性死区
  • 重复声明
    • var:允许重复声明
    • let和const:不允许重复声明
  • 修改
    • var和let:可以修改
    • const:声明常量,不能修改引用,但对象的属性可以修改
// 作用域示例
if (true) {
var x = 1; // 全局作用域
let y = 2; // 块级作用域
const z = 3; // 块级作用域
}
console.log(x); // 1
console.log(y); // ReferenceError
console.log(z); // ReferenceError

// const引用不可变,但属性可变
const obj = { name: 'John' };
obj.name = 'Alice'; // 允许
obj = {}; // 不允许,TypeError

17. 什么是解构赋值?

解构赋值是ES6引入的一种语法,可以从数组或对象中提取值,并赋给变量。

// 数组解构
const [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]

// 对象解构
const { name, age, ...other } = { name: 'John', age: 30, city: 'New York', job: 'Developer' };
console.log(name); // 'John'
console.log(age); // 30
console.log(other); // { city: 'New York', job: 'Developer' }

// 默认值
const { x = 10, y = 20 } = { x: 5 };
console.log(x); // 5
console.log(y); // 20

18. 什么是扩展运算符(...)?

扩展运算符用于展开数组或对象,可以用于以下场景:

  • 复制数组或对象
  • 合并数组或对象
  • 将数组作为函数参数传递
  • 将类数组对象转换为数组
// 复制数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1];

// 合并数组
const mergedArr = [...arr1, 4, 5, ...arr2];

// 复制对象
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1 };

// 合并对象
const mergedObj = { ...obj1, c: 3, ...obj2, d: 4 };

// 函数参数
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

19. 什么是模板字符串?

模板字符串是ES6引入的一种字符串字面量语法,使用反引号(`)包围,可以包含变量和表达式。

const name = 'John';
const age = 30;

// 基本用法
const greeting = `Hello, my name is ${name}.`;

// 多行字符串
const multiLine = `
This is a
multi-line
string
`;

// 表达式
const calculation = `${name} is ${age + 5} years old in 5 years.`;

// 函数调用
function formatName(n) {
return n.toUpperCase();
}
const upperName = `Name in uppercase: ${formatName(name)}`;

20. 什么是类(Class)?如何使用?

类是ES6引入的一种语法糖,提供了更接近传统面向对象编程语言的语法,用于创建对象和实现继承。

// 类定义
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}

// 实例方法
sayHello() {
console.log(`Hello, my name is ${this.name}.`);
}

// 静态方法
static createAdult(name) {
return new Person(name, 18);
}
}

// 类继承
class Student extends Person {
constructor(name, age, studentId) {
super(name, age); // 调用父类构造函数
this.studentId = studentId;
}

study() {
console.log(`${this.name} with ID ${this.studentId} is studying.`);
}
}

// 创建实例
const john = new Person('John', 30);
john.sayHello();

const alice = Student.createAdult('Alice');
alice.study();

六、进阶概念

21. 什么是深拷贝和浅拷贝?如何实现深拷贝?

  • 浅拷贝:创建一个新对象,新对象的属性值是原对象属性值的引用。如果属性是基本类型,拷贝的是值;如果是引用类型,拷贝的是引用地址。
  • 深拷贝:创建一个新对象,递归地拷贝原对象的所有属性,包括嵌套的对象。新对象和原对象完全独立,互不影响。

实现深拷贝的方法:

  1. JSON.parse(JSON.stringify())
  2. 递归实现
  3. 使用第三方库(如lodash的_.cloneDeep)
// 浅拷贝示例
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };
shallowCopy.b.c = 3;
console.log(original.b.c); // 3

// 深拷贝 - JSON方法
const deepCopy1 = JSON.parse(JSON.stringify(original));
// 注意:这种方法有局限性,不能处理函数、undefined、循环引用等

// 深拷贝 - 递归实现
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}

if (obj instanceof Date) {
return new Date(obj.getTime());
}

if (obj instanceof Array) {
return obj.map(item => deepClone(item));
}

const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}

return clonedObj;
}

const deepCopy2 = deepClone(original);

22. 什么是防抖(Debounce)和节流(Throttle)?它们的区别是什么?

  • 防抖:在事件触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。适用于输入框实时搜索、窗口大小调整等场景。
  • 节流:在规定的时间内,只能执行一次函数。适用于滚动事件、鼠标移动事件等场景。
// 防抖实现
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}

// 节流实现
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}

23. 什么是设计模式?JavaScript中常用的设计模式有哪些?

设计模式是解决软件设计中常见问题的可复用解决方案。

JavaScript中常用的设计模式:

  • 单例模式(Singleton)
  • 工厂模式(Factory)
  • 观察者模式(Observer)
  • 发布-订阅模式(Publish-Subscribe)
  • 策略模式(Strategy)
  • 代理模式(Proxy)
  • 装饰器模式(Decorator)
// 单例模式示例
const Singleton = (function() {
let instance;

function createInstance() {
const object = new Object('I am an instance');
return object;
}

return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();

// 观察者模式示例
class Subject {
constructor() {
this.observers = [];
}

subscribe(observer) {
this.observers.push(observer);
}

unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}

notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}

class Observer {
constructor(name) {
this.name = name;
}

update(data) {
console.log(`${this.name} received: ${data}`);
}
}

24. 什么是内存泄漏?JavaScript中常见的内存泄漏场景有哪些?

内存泄漏是指程序中已不再使用的内存没有被释放,导致内存占用不断增加。

JavaScript中常见的内存泄漏场景:

  1. 意外的全局变量
  2. 闭包导致的内存泄漏
  3. 未清理的DOM引用
  4. 未清理的定时器
  5. 事件监听器未移除
// 1. 意外的全局变量
function createGlobalVar() {
// 忘记使用var/let/const
globalVar = 'This is a global variable';
}

// 2. 闭包导致的内存泄漏
function createClosure() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log('I have access to largeData');
};
}
const closure = createClosure();

// 3. 未清理的DOM引用
const elements = [];
function createElement() {
const element = document.createElement('div');
elements.push(element);
return element;
}

// 4. 未清理的定时器
function startTimer() {
setInterval(() => {
console.log('Timer is running');
}, 1000);
}

// 5. 事件监听器未移除
function addListener(element) {
element.addEventListener('click', handleClick);
}

25. 什么是Web Worker?它的用途是什么?

Web Worker是HTML5提供的一种API,允许在后台线程中运行JavaScript代码,而不阻塞主线程。

Web Worker的用途:

  • 执行CPU密集型任务
  • 处理大量数据计算
  • 执行长时间运行的操作
  • 提高Web应用的响应性能
// 主线程代码
const worker = new Worker('worker.js');

worker.postMessage({ data: 'Hello from main thread' });

worker.onmessage = function(event) {
console.log('Message from worker:', event.data);
};

worker.onerror = function(error) {
console.error('Worker error:', error);
};

// worker.js文件
self.onmessage = function(event) {
const data = event.data;
// 执行计算密集型任务
const result = data.data.toUpperCase();
// 发送结果回主线程
self.postMessage({ result: result });
};

七、性能优化

26. JavaScript性能优化的方法有哪些?

  1. 减少DOM操作:DOM操作是昂贵的,应尽量减少
  2. 使用事件委托:通过事件冒泡处理多个元素的事件
  3. 避免使用eval():eval()会将字符串转换为代码执行,性能较差且不安全
  4. 使用适当的数据结构:根据需求选择合适的数据结构
  5. 避免不必要的闭包:闭包会持有外部作用域的变量,可能导致内存泄漏
  6. 使用防抖和节流:减少频繁触发的事件处理函数的执行次数
  7. 延迟加载:按需加载资源
  8. 代码分割:将代码分成多个小块,按需加载
  9. 压缩和混淆代码:减少文件大小
  10. 使用Web Workers:将CPU密集型任务移至后台线程
// 事件委托示例
document.getElementById('parent').addEventListener('click', function(event) {
if (event.target.classList.contains('child')) {
console.log('Child element clicked');
}
});

// 延迟加载示例
function lazyLoadImage() {
const images = document.querySelectorAll('img[data-src]');

images.forEach(img => {
if (isElementInViewport(img)) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
});
}

27. 什么是事件委托?为什么要使用事件委托?

事件委托是一种事件处理模式,利用事件冒泡原理,将事件监听器添加到父元素上,而不是添加到每个子元素上。

使用事件委托的好处:

  • 减少事件监听器的数量,提高性能
  • 动态添加的子元素也能触发事件处理函数
  • 代码更简洁,易于维护
// 不使用事件委托
const buttons = document.querySelectorAll('.button');
buttons.forEach(button => {
button.addEventListener('click', handleClick);
});

// 使用事件委托
const container = document.querySelector('.container');
container.addEventListener('click', function(event) {
if (event.target.classList.contains('button')) {
handleClick(event);
}
});

28. 什么是虚拟DOM?它的优势是什么?

虚拟DOM是一个轻量级的JavaScript对象,用于表示真实DOM的结构和属性。当状态发生变化时,先更新虚拟DOM,然后通过对比新旧虚拟DOM的差异(diff算法),最小化地更新真实DOM。

虚拟DOM的优势:

  • 减少真实DOM操作,提高性能
  • 支持跨平台渲染(如React Native)
  • 简化UI开发,提高开发效率
// 虚拟DOM示例(简化版)
const vnode = {
tag: 'div',
props: {
className: 'container',
id: 'app'
},
children: [
{
tag: 'h1',
props: {
style: { color: 'blue' }
},
children: ['Hello, World!']
},
{
tag: 'p',
props: {},
children: ['This is a paragraph']
}
]
};

八、安全性

29. JavaScript中常见的安全问题有哪些?如何防范?

JavaScript中常见的安全问题:

  1. XSS(跨站脚本攻击):攻击者注入恶意脚本到网页中
  2. CSRF(跨站请求伪造):攻击者诱导用户执行非本意的操作
  3. 点击劫持:攻击者通过透明层欺骗用户点击
  4. SQL注入:通过用户输入注入SQL代码
  5. 不安全的第三方库:使用存在漏洞的第三方库

防范措施:

  1. 对用户输入进行验证和转义
  2. 使用内容安全策略(CSP)
  3. 设置HttpOnly和Secure标志的Cookie
  4. 使用CSRF令牌
  5. 实施X-Frame-Options头部以防止点击劫持
  6. 定期更新第三方库
  7. 避免使用eval()和Function构造函数
// 输入验证示例
function sanitizeInput(input) {
const element = document.createElement('div');
element.textContent = input;
return element.innerHTML;
}

// 使用CSRF令牌示例
function submitForm() {
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data)
});
}

30. 什么是CORS(跨域资源共享)?如何解决跨域问题?

CORS(Cross-Origin Resource Sharing)是一种机制,允许网页从另一个域请求资源。浏览器的同源策略默认阻止跨域请求,而CORS提供了一种安全的方式来实现跨域访问。

解决跨域问题的方法:

  1. 使用CORS:服务器设置Access-Control-Allow-Origin头部
  2. JSONP:通过动态创建script标签实现跨域请求
  3. 代理服务器:在服务器端转发请求
  4. WebSocket:WebSocket不受同源策略限制
  5. PostMessage API:用于窗口间通信
// JSONP示例
function jsonp(url, callbackName, callback) {
const script = document.createElement('script');
script.src = `${url}?callback=${callbackName}`;
window[callbackName] = function(data) {
callback(data);
document.head.removeChild(script);
delete window[callbackName];
};
document.head.appendChild(script);
}

// 使用CORS示例(服务器端设置)
// Access-Control-Allow-Origin: *
// 或者指定域名
// Access-Control-Allow-Origin: https://example.com

九、浏览器相关

31. 什么是浏览器的渲染过程?

浏览器的渲染过程大致包括以下几个步骤:

  1. 解析HTML:生成DOM树
  2. 解析CSS:生成CSSOM树
  3. DOM和CSSOM合并:生成渲染树(Render Tree)
  4. 布局(Layout):计算元素的位置和大小
  5. 绘制(Painting):将元素绘制到屏幕上
  6. 合成(Compositing):将页面的各个部分合成为一个完整的图像

32. 什么是重排(Reflow)和重绘(Repaint)?如何减少重排和重绘?

  • 重排(Reflow):当DOM的结构或元素的尺寸、位置发生变化时,浏览器需要重新计算元素的几何属性和布局,这个过程称为重排。
  • 重绘(Repaint):当元素的样式发生变化,但不影响其几何属性时,浏览器只需要重新绘制元素的外观,这个过程称为重绘。

减少重排和重绘的方法:

  1. 合并多次DOM操作
  2. 使用DocumentFragment
  3. 避免频繁访问计算样式
  4. 使用CSS Transform和Opacity实现动画
  5. 避免使用table布局
  6. 使用虚拟DOM
// 合并DOM操作示例
const container = document.getElementById('container');

// 不好的做法 - 多次重排
for (let i = 0; i < 10; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
container.appendChild(div);
}

// 好的做法 - 减少重排
const fragment = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment);

33. localStorage、sessionStorage和Cookie的区别?

特性localStoragesessionStorageCookie
存储大小约5-10MB约5-10MB约4KB
过期时间永久,除非手动清除会话结束时可设置过期时间
发送到服务器是,每次请求都会携带
作用域同一域名下的所有标签页和窗口仅当前标签页可设置路径
// localStorage使用
localStorage.setItem('name', 'John');
const name = localStorage.getItem('name');
localStorage.removeItem('name');
localStorage.clear();

// sessionStorage使用
sessionStorage.setItem('sessionId', '12345');
const sessionId = sessionStorage.getItem('sessionId');

// Cookie使用
document.cookie = 'username=John; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/';
function getCookie(name) {
const cookieArr = document.cookie.split(';');
for (let i = 0; i < cookieArr.length; i++) {
const cookiePair = cookieArr[i].split('=');
if (name === cookiePair[0].trim()) {
return decodeURIComponent(cookiePair[1]);
}
}
return null;
}

总结

本文涵盖了JavaScript开发中常见的面试问题,从基础概念到进阶知识,包括作用域、闭包、异步编程、ES6+新特性、设计模式、性能优化、安全性等方面。通过掌握这些知识,开发者可以更好地准备面试,也能在实际工作中写出更优质的JavaScript代码。

需要注意的是,JavaScript是一门不断发展的语言,新的特性和最佳实践不断涌现。因此,开发者应该保持学习的态度,关注行业动态,不断提升自己的技术水平。