# 面试题
这套面试题涵盖了从基础知识到高级概念的全面内容,包括变量、数据类型、运算符、条件语句、循环、函数、作用域、数组、对象、字符串、日期、错误处理、调试技巧、DOM
操作、事件处理、异步编程、ES6+
特性、 BOM
、 JavaScript
库和框架、调试和性能优化、与其他技术整合等。按照计划逐步复习,可以系统地掌握 JavaScript
编程语言,并为进一步深入学习和应用打下坚实的基础。
# 介绍 JavaScript
的基本数据类型,并解释它们之间的区别。
Number
用于表示数值,包括整数和浮点数。String
用于表示文本数据,由一串字符组成。Boolean
表示真或假的逻辑值,只有两个可能的取值:true
和false
。Undefined
表示未定义的值,通常是声明了变量但未赋值时的默认值。Null
表示空值或不存在的对象。Symbol
表示唯一且不可变的值,通常用作对象属性的标识符。
# 如何判断一个变量的数据类型?
可以使用typeof
运算符来判断变量的数据类型,还可以使用instanceof
运算符、Object.prototype.toString.call()
方法进行类型判断。
# 什么是块级作用域?
块级作用域是指在一对花括号中定义的作用域范围,限定了变量的可见性仅限于当前的代码块内部。在 JavaScript
中,使用 let
和 const
关键字可以创建块级作用域,使变量的作用域仅限于当前代码块,而不是整个函数或全局作用域。块级作用域提供了更细粒度的变量控制,帮助避免变量污染和冲突,并提高代码的可读性和可维护性。
# 什么是变量作用域?
变量作用域是指在代码中定义的变量所能被访问的范围。它决定了变量的可见性和生命周期。全局作用域是指在整个代码中都可访问的变量,而局部作用域是指在特定代码块内部声明的变量,只能在其所在的代码块内部被访问。
# 什么是作用域链?
作用域链是 JavaScript
中的一种机制,用于查找和访问变量。它是由作用域的嵌套关系决定的,沿着嵌套的作用域逐级查找变量,直到找到变量或到达全局作用域。作用域链的顺序是从内部向外部逐级查找的。这种机制确保变量的访问按照作用域的层级关系进行,保证了变量的可见性和封装性。
# 什么是闭包?
闭包是指在函数内部创建的函数,它可以访问外部函数的变量和作用域,即使外部函数已经执行完毕,闭包仍然可以保留对外部函数作用域的引用。
- 保护变量:闭包可以创建一个封闭的环境,使内部函数可以访问外部函数的变量,同时又保护了这些变量不受外界干扰。这提供了一种封装数据的方式,防止全局污染。
- 保存状态:由于闭包可以保留对外部函数作用域的引用,因此可以通过闭包来保存变量的状态。每次调用外部函数时,都会创建一个新的闭包实例,因此可以在闭包中保存状态,并在每次调用内部函数时使用这些状态。
- 实现私有变量和方法:通过使用闭包,可以模拟私有变量和方法。外部函数内部的变量和函数只能通过内部函数进行访问,外部作用域无法直接访问,从而实现了一种封装和隐藏的效果。
- 延长变量的生命周期:由于闭包会保留对外部函数作用域的引用,使得外部函数的变量在内部函数中仍然可用,因此可以延长变量的生命周期,即使外部函数已经执行完毕。
# 什么是箭头函数?
箭头函数是一种简洁的函数定义语法,用于创建函数。它使用箭头 =>
来代替传统的 function
关键字,可以减少代码量并提供更简洁的函数定义方式。箭头函数自动绑定外部作用域的 this
值,适用于简单的函数表达式、回调函数和避免 this
问题的场景。
- 简洁的语法:箭头函数的语法更为简洁,通常可以用更少的代码量来定义函数。
- 自动绑定
this
值:箭头函数没有自己的this
值,它会继承外部作用域的this
值。这意味着在箭头函数内部使用的this
将自动指向外部函数的this
值,避免了传统函数中this
绑定的复杂性。 - 适用于回调函数和高阶函数:由于箭头函数的简洁性和对
this
的处理,它们特别适用于作为回调函数或高阶函数的参数,提供更简单和清晰的函数定义。
# 什么是立即执行函数?
立即执行函数是指在定义后立即执行的函数。它可以创建一个独立的作用域,避免全局变量的污染,还可以用于模块化和封装代码。
# 介绍常用的对象方法
Object.keys(obj)
返回一个包含给定对象的所有可枚举属性的数组。数组中的元素是对象的属性名。Object.values(obj)
返回一个包含给定对象的所有可枚举属性的值的数组。数组中的元素是对象的属性值。Object.entries(obj)
返回一个包含给定对象的所有可枚举属性的键值对的数组。数组中的元素是由键和值组成的数组。Object.assign(target, source)
用于将一个或多个源对象的属性复制到目标对象中。它将源对象的属性合并到目标对象,并返回目标对象。Object.getOwnPropertyNames(obj)
返回一个包含给定对象的所有属性名称(包括不可枚举属性)的数组。Object.hasOwnProperty(prop)
用于检查对象是否具有指定名称的自身属性。Object.freeze(obj)
用于冻结一个对象,阻止对其进行更改。被冻结的对象的属性无法被修改、删除或添加。Object.seal(obj)
用于封闭一个对象,阻止对其属性的添加和删除,但允许修改属性的值。
# 什么是原型链(Prototype Chain)?
每个对象(包括函数)都有一个 __proto__
属性,它指向该对象的原型。当我们访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript 引擎就会沿着原型链往上查找,直到找到对应的属性或方法或者到达原型链的顶端(即 Object.prototype
)。
这样,通过原型链,一个对象可以访问和继承其原型上的属性和方法。如果原型对象也有自己的原型,那么就会形成一个原型链,多个对象通过原型链相互关联。
# 如何使用原型链实现继承?
在 JavaScript 中,可以通过原型链来实现对象之间的继承关系。通过原型链,一个对象可以继承另一个对象的属性和方法。下面是使用原型链实现继承的一般步骤:
- 创建一个基础对象(父类)。
- 定义基础对象的属性和方法,将它们添加到基础对象的原型上。
- 创建一个派生对象(子类)。
- 将派生对象的原型指向基础对象,建立原型链关系。
// 基础对象(父类)
function Animal(name) {
this.name = name;
}
// 在基础对象的原型上添加方法
Animal.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name);
};
// 派生对象(子类)
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数,继承父类的属性
this.breed = breed;
}
// 将派生对象的原型指向基础对象,建立原型链关系
Dog.prototype = Object.create(Animal.prototype);
// 在派生对象的原型上添加方法
Dog.prototype.bark = function() {
console.log("Woof!");
};
// 创建派生对象实例
var dog = new Dog("Buddy", "Golden Retriever");
// 调用继承自基础对象的方法
dog.sayHello(); // 输出: Hello, I'm Buddy
// 调用派生对象自己的方法
dog.bark(); // 输出: Woof!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
在上述示例中,Animal
是基础对象(父类),它定义了 name
属性和 sayHello
方法,并将 sayHello
方法添加到原型上。Dog
是派生对象(子类),通过调用 Animal
的构造函数并使用 Object.create
方法将 Dog
的原型指向 Animal
的原型,建立了原型链关系。这样,Dog
实例就可以继承 Animal
的属性和方法。
# 什么是异步编程?
异步编程是一种编程模型,其中任务的执行顺序不是按照代码的顺序进行的。相反,任务是在后台执行,不会阻塞程序的其他部分。当任务完成时,通常会触发一个回调或返回一个 Promise
,以便处理任务的结果。
在 JavaScript
中,异步编程非常重要,因为 JavaScript
是一门单线程的语言。这意味着 JavaScript
一次只能执行一个任务,如果某个任务需要花费很长时间,会导致程序的其他部分无法执行,造成页面冻结和不响应的情况。
异步编程允许在执行耗时的操作(如网络请求、文件读写、数据库查询等)时,将控制权交还给 JavaScript
的运行环境,以便同时执行其他任务。当异步操作完成时,通过回调函数、Promise
或 async/await
等方式,处理异步操作的结果。
# 什么是 Promise
?
Promise
是 JavaScript
中用于处理异步操作的对象,表示一个异步操作的最终结果。它有三种状态:pending
(进行中)、fulfilled
(已完成)和 rejected
(已拒绝)。Promise
提供了链式的方法 .then()
来处理异步操作的结果,并解决了回调地狱问题。它在异步编程中的作用是简化代码编写、统一处理异步操作结果和支持错误处理。
# 什么是 async/await
?
async/await
是 JavaScript
中用于处理异步操作的特性。使用 async
关键字定义一个函数时,它会返回一个 Promise
对象。await
关键字可以暂停函数的执行,等待 Promise
对象解析,并获取其结果。async/await
简化了异步编程,使代码更直观、可读性更高,避免了回调地狱的问题,并提供了方便的错误处理和灵活的逻辑控制。
# 什么是回调函数?
回调函数是在 JavaScript
中用于处理异步操作的函数,具有以下特点:异步执行、作为参数传递、响应事件和错误处理。它们广泛用于定时器、AJAX
请求、事件处理和异步操作等场景。回调函数的使用使得 JavaScript
可以处理异步操作和事件驱动的编程。
# 什么是异步链式调用?
异步链式调用是一种通过连接多个异步操作的方式,以便按照特定顺序执行它们。使用 Promise
可以实现异步链式调用,通过 .then()
方法将多个 Promise
对象连接在一起,处理每个操作的结果,并返回新的 Promise
对象,以便继续添加更多操作。.catch()
方法用于处理链式调用中的错误。异步链式调用使得异步操作的顺序和依赖关系更清晰,避免了回调地狱问题。
# 什么是 Promise.all()
?
Promise.all()
是一个静态方法,用于并行执行多个 Promise
对象,并在所有 Promise
都成功解析后返回一个新的 Promise
对象。它接收一个包含多个 Promise
对象的数组作为参数,并返回一个 Promise
对象。当所有 Promise
都成功解析时,返回的 Promise
对象解析为一个包含所有解析值的数组。如果其中任何一个 Promise
被拒绝,返回的 Promise
对象将立即被拒绝,并传递拒绝原因。Promise.all()
可以同时触发多个异步操作,并等待它们全部完成后进行处理。
# 解释异步函数的工作原理和执行顺序
异步函数通过使用 async
和 await
关键字来处理异步操作。当调用异步函数时,它会立即返回一个 Promise
对象,并在后台开始执行。在异步函数内部,使用 await
关键字来等待异步操作的完成。当遇到 await
表达式时,异步函数会暂停执行,直到表达式中的 Promise
被解析或拒绝。异步函数的执行顺序是按顺序执行代码,遇到 await
表达式时暂停执行,并在 Promise
解析后恢复执行。
# 异步编程模式
异步编程模式提供了不同的方式来组织和管理异步代码,使其更具可读性、可维护性和扩展性。
发布/订阅模式是一种用于实现解耦的异步编程模式,也被称为事件模型或消息模型。它基于一个中心主题(或事件总线),允许发布者发布事件或消息,而订阅者可以订阅并接收这些事件或消息。
// 创建事件总线对象 const eventBus = { events: {}, // 存储事件和对应的订阅者回调函数 // 订阅事件 subscribe(eventType, callback) { if (!this.events[eventType]) { this.events[eventType] = []; // 如果事件不存在,则创建一个空的订阅者列表 } this.events[eventType].push(callback); // 将订阅者的回调函数添加到事件的订阅者列表中 }, // 发布事件 publish(eventType, data) { if (this.events[eventType]) { this.events[eventType].forEach((callback) => { callback(data); // 执行每个订阅者的回调函数,并传递数据 }); } }, }; // 订阅者A function subscriberA(data) { console.log('Subscriber A received:', data); } // 订阅者B function subscriberB(data) { console.log('Subscriber B received:', data); } // 订阅事件 eventBus.subscribe('message', subscriberA); eventBus.subscribe('message', subscriberB); // 发布事件 eventBus.publish('message', 'Hello, World!');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38观察者模式通常由一个主题和多个观察者组成,定义了一种对象之间的依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会得到通知并自动更新。
// 定义主题对象 class Subject { constructor() { this.observers = []; // 存储观察者列表 } // 添加观察者 addObserver(observer) { this.observers.push(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}`); } } // 创建主题和观察者对象 const subject = new Subject(); const observerA = new Observer('Observer A'); const observerB = new Observer('Observer B'); // 添加观察者到主题中 subject.addObserver(observerA); subject.addObserver(observerB); // 主题通知观察者 subject.notify('Hello, World!');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42生成器模式通过生成器函数
Generator Function
创建可暂停和恢复执行的迭代器Iterator
返回的是一个迭代器对象,通过yield
关键字暂停函数的执行,并返回一个中间结果,稍后可以从上次停止的地方继续执行。可以使用next()
方法逐步执行函数并获取每个中间结果。function* numberGenerator(start, end) { for (let i = start; i <= end; i++) { yield i; // 暂停函数的执行,并返回当前的值 } } // 创建迭代器对象 const iterator = numberGenerator(1, 5); // 使用迭代器遍历值 console.log(iterator.next().value); // 输出: 1 console.log(iterator.next().value); // 输出: 2 console.log(iterator.next().value); // 输出: 3 console.log(iterator.next().value); // 输出: 4 console.log(iterator.next().value); // 输出: 5 console.log(iterator.next().value); // 输出: undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Promise
是一种处理异步操作的对象,通过状态的改变进行后续处理,可以使用链式调用来处理连续的异步操作。
# 什么是异步任务队列 Event Loop
?
Event Loop
是 JavaScript
引擎的一部分,它负责处理异步任务的执行顺序。它包括宏任务队列和微任务队列,通过循环不断地从宏任务队列中选择任务执行,并在合适的时机执行微任务队列中的任务。Event Loop
的作用是确保异步任务按照正确的顺序执行,避免阻塞主线程,提高程序的性能和响应性。
# 使用 XMLHttpRequest
对象来发送请求
- 创建 XMLHttpRequest 对象。
- 使用
open()
方法设置请求的方法和 URL。 - 注册
onreadystatechange
事件处理程序。 - 使用
send()
方法发送请求。 - 监听
readyState
和status
属性的变化,检查请求状态和响应状态。 - 如果请求成功(
status
值为 200),使用responseText
或responseXML
属性获取响应数据。
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data", true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var responseData = xhr.responseText;
// 处理响应数据
console.log(responseData);
}
};
xhr.send();
2
3
4
5
6
7
8
9
10
11
12
# 使用 Fetch()
来发送请求
Fetch API
是一种现代的、基于 Promise
的网络请求 API
,用于进行网络通信和数据交换。具有语法简洁、基于 Promise
、内置 JSON
解析、更全面的错误处理、跨域请求处理、更强大的请求和响应控制等优势。
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
// 处理获取到的数据
console.log(data);
})
.catch(error => {
// 处理错误
console.error(error);
});
2
3
4
5
6
7
8
9
10
# 大型文件下载
fetch('https://example.com/largefile.pdf')
.then(response => {
if (!response.ok) {
throw new Error('下载文件失败');
}
return response.blob();
})
.then(blob => {
// 处理下载的文件
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'largefile.pdf';
link.click();
URL.revokeObjectURL(url);
})
.catch(error => {
console.error(error);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 大型文件分片上传
// 将文件分割为固定大小的块
function sliceFile(file, chunkSize) {
var chunks = [];
var offset = 0;
while (offset < file.size) {
var chunk = file.slice(offset, offset + chunkSize);
chunks.push(chunk);
offset += chunkSize;
}
return chunks;
}
// 上传文件块
function uploadChunk(url, chunk) {
return fetch(url, {
method: 'POST',
body: chunk
}).then(response => response.json());
}
// 分块上传大型文件
function uploadLargeFile(file, chunkSize, uploadUrl) {
var chunks = sliceFile(file, chunkSize);
var promises = [];
chunks.forEach(chunk => {
promises.push(uploadChunk(uploadUrl, chunk));
});
return Promise.all(promises);
}
// 选择文件并进行分块上传
var fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', function(event) {
var file = event.target.files[0];
var chunkSize = 1024 * 1024; // 1MB
var uploadUrl = 'https://example.com/upload';
uploadLargeFile(file, chunkSize, uploadUrl)
.then(responses => {
// 处理上传结果
console.log(responses);
})
.catch(error => {
console.error(error);
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 后台处理大文件
const fs = require('fs');
const path = require('path');
const { Readable } = require('stream');
const { createReadStream } = fs;
// 读取大型文件并返回 Blob 对象
function readFileAsBlob(filePath) {
const stream = createReadStream(filePath);
const chunks = [];
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => {
chunks.push(chunk);
});
stream.on('end', () => {
const fileData = Buffer.concat(chunks);
const blob = new Blob([fileData], { type: 'application/octet-stream' });
resolve(blob);
});
stream.on('error', (error) => {
reject(error);
});
});
}
// 示例:将文件转换为 Blob 对象并发送给前端
const filePath = path.join(__dirname, 'path/to/largefile.pdf');
readFileAsBlob(filePath)
.then((blob) => {
// 发送给前端
// 可以使用 Express 或其他 Node.js 框架发送响应
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename="largefile.pdf"');
res.send(blob);
})
.catch((error) => {
console.error(error);
// 处理错误
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# JavaScript中的深拷贝和浅拷贝有什么区别?
浅拷贝(Shallow Copy):
浅拷贝是创建一个新对象或数组,但仅复制引用而不复制实际的值。这意味着原始对象和新对象会共享相同的内部值,对其中一个对象所做的更改也会影响到另一个对象。
浅拷贝只复制了对象或数组的第一层,对于嵌套的对象或数组,仍然是共享引用。
使用 JavaScript 中的一些方法和运算符(如
Object.assign()
、Array.prototype.slice()
、扩展运算符等)可以实现浅拷贝。
深拷贝(Deep Copy):
深拷贝是创建一个全新的对象或数组,同时递归复制所有的嵌套对象和数组,确保原始对象和新对象之间没有任何引用关系。
深拷贝会复制所有层级的对象或数组,并创建它们的独立副本。
实现深拷贝通常需要自定义递归函数或使用第三方库(如 lodash 的
cloneDeep()
方法)来实现。
# 如何实现深拷贝?
function deepCopy(obj) {
if (obj === null || typeof obj !== 'object') {
return obj; // 非对象或 null,直接返回
}
let copy;
if (obj instanceof Array) {
copy = [];
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i]); // 递归拷贝数组元素
}
} else {
copy = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]); // 递归拷贝对象属性
}
}
}
return copy;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# HTTP
请求头
Host
:指定要访问的服务器主机名和端口号。User-Agent
:标识发送请求的客户端(浏览器、应用程序等)的相关信息。Accept
:指定客户端能够接受的响应内容类型。Content-Type
:指定请求体的媒体类型。Authorization
:提供身份验证凭据,用于访问受保护的资源。Cookie
:包含客户端的会话信息,用于与服务器进行状态管理。Referer
:指示请求的来源 URL。Cache-Control
:指定缓存机制的行为和规则。If-Modified-Since
:用于条件请求,检查资源是否已经被修改。Origin
:指示发起请求的源(用于跨域请求)。