中级
高级知识
充分准备你的下一个JavaScript面试,增强信心!
无论你是老手还是刚进入技术行业,这份2024年必备资源都将帮助你复习核心概念,从基本语言特性到高级主题。
在本文中,我汇总了30个最关键的JavaScript面试题以及详细的答案和代码示例。
深入探索这宝贵的收藏,以确保JavaScript面试的顺利进行,在当今竞争激烈的技术行业保持领先地位。
JavaScript是单线程的吗?
解释JavaScript引擎的主要组成部分及其工作原理。
解释JavaScript中的事件循环及其如何帮助异步编程。
var、let和const的区别是什么?
JavaScript中的不同数据类型?
什么是回调函数和回调地狱?
什么是Promise和Promise链?
什么是async/await?
= =和= = =运算符有什么区别?
在Javascript中创建对象的不同方法?
什么是rest和spread运算符?
什么是高阶函数?
什么是闭包?闭包的用例有哪些?
解释JavaScript中的变量提升概念。
什么是暂时性死区?
什么是原型链?以及Object.create()方法?
Call、Apply和Bind方法的区别是什么?
什么是lambda或箭头函数?
什么是柯里化函数?
ES6的特性有哪些?
什么是执行上下文、执行堆栈、变量对象和作用域链?
回调函数、Promise、setTimeout和process.nextTick()的执行优先级是什么?
什么是工厂函数和生成器函数?
对象的浅拷贝和深拷贝的不同方法?
如何使对象不可变?(封装和冻结方法)
什么是事件、事件流、事件冒泡和事件捕获?
什么是事件委托?
什么是服务端推送事件?
web worker或服务工作线程是什么?
如何在javascript中比较两个JSON对象?
是的,JavaScript是一种单线程语言。这意味着它只有一个调用堆栈和一个内存堆。一次只执行一组指令。
此外,Javascript本质上是同步和阻塞的。这意味着代码逐行执行,一个任务必须在下一个任务开始之前完成。
然而,JavaScript也具有异步功能,允许独立于主执行线程执行某些操作。这通常通过回调、promise、async/await和事件侦听器等机制来实现。这些异步功能使得JavaScript能够处理数据获取、用户输入和I/O操作等任务,而不会阻塞主线程,因此适合构建响应迅速和交互性强的Web应用程序。
每个浏览器都有一个JavaScript引擎,它执行javascript代码并将其转换为机器代码。
当执行JavaScript代码时,解析器首先读取代码并生成AST,并将其存储在内存中。然后解释器处理这个AST并生成字节码或机器代码,由计算机执行。
分析器是JavaScript引擎的一个组件,用于监控代码的执行。
字节码与性能分析数据一起用于优化编译器。”优化编译器“或即时(JIT)编译器根据分析数据的假设生成高度优化的机器代码。
有时,“优化”假设不正确,然后它通过“取消优化”阶段(这实际上成为我们的开销)返回到以前的版本。
JS引擎通常会优化“热函数”,并使用内联缓存技术来优化代码。
在此过程中,调用堆栈跟踪当前正在执行的函数,内存堆用于内存分配。
最后,垃圾回收器开始发挥作用,通过回收未使用对象的内存来管理内存。
Google Chrome V8引擎:
事件循环是JavaScript运行时环境的核心组件。它负责调度和执行异步任务。事件循环通过持续监控两个队列来工作:调用堆栈和事件队列。
调用堆栈是一个堆栈(LIFO)数据结构,用于存储当前正在执行的函数调用的所有执行上下文(存储代码执行期间创建的执行上下文)。
Web API是异步操作(setTimeout、fetch请求、promise)及其回调正在等待完成的地方。它从线程池中借用线程在后台完成任务,而不会阻塞主线程。
**作业队列(或微任务)是一个FIFO(先进先出)结构,用于保存异步/等待的回调函数、promise、process.nextTick()**等准备执行的回调。例如,fulfilled promise的resolve或reject回调会进入作业队列。
**任务队列(或宏任务)是一个FIFO(先进先出)结构,用于保存异步操作回调(计时器像setInterval、setTimeout)**等准备执行的回调。例如,超时的setTimeout()
回调准备执行,会进入任务队列。
事件循环会永久监控调用堆栈是否为空。如果调用堆栈为空,事件循环会查看作业队列或任务队列,并将任何准备执行的回调出队到调用堆栈中。
在浏览器中,window对象是浏览器窗口,HTML树中的顶级结构。使用var全局声明的变量会附加到window对象上。在浏览器控制台中键入var dog = ‘bowser’,然后键入window.dog.。值“bowser”出现了!这使得控制变量作用域变得更加困难。相比之下,let和const没有附加到window对象上。
JavaScript是一种动态和松散类型化的语言,也称为鸭子类型语言。这意味着我们不需要指定变量的类型,因为JavaScript引擎会根据其值动态确定变量的数据类型。
JavaScript中的基本数据类型是最基本的数据类型,用于表示单个值。它们是**不可变的(不能更改)**并直接包含特定的值。
在JavaScript中,Symbol是一个原始数据类型,在ECMAScript 6(ES6)中引入,代表一个唯一和不可变的值。它通常用作对象属性的标识符,以避免名称冲突。
const mySymbol = Symbol('key');
const obj = {
[mySymbol]: 'value'
};
当Symbol用作属性键时,它与其他属性键(包括字符串键)不会发生冲突。
在JavaScript中,回调函数通常用于处理异步操作。
回调函数是一个作为参数传递给另一个函数的函数,旨在在完成特定任务或在给定时间后执行。
function fetchData(url, callback) {
// 模拟从服务器获取数据
setTimeout(() => {
const data = 'Some data from the server';
callback(data);
}, 1000);
}
function processData(data) {
console.log('Processing data:', data);
}
fetchData('https://example.com/data', processData);
在这个例子中,fetchData函数有两个参数:URL和一个回调函数。在从服务器获取数据(使用setTimeout模拟)后,它调用回调函数并将检索到的数据传递给它。
回调地狱,也称为**“金字塔”,是一个术语,用于描述在异步函数中使用多个嵌套回调**的情况。
它发生在异步操作取决于以前的异步操作结果的情况下,导致代码嵌套深度增加,通常难以阅读。
回调地狱是一个反模式,其中有多个嵌套的回调,这使得代码在处理异步逻辑时难以阅读和调试。
fs.readFile('file1.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
fs.readFile('file2.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
fs.readFile('file3.txt', 'utf8', function (err, data) {
if (err) {
console.error(err);
} else {
// Continue with more nested callbacks...
}
});
}
});
}
});
在这个例子中,我们使用fs.readFile
函数顺序读取三个文件,每个文件读取操作都是异步的。因此,我们必须将回调嵌套在另一个回调中,创建一个回调金字塔。
**要避免回调地狱,现代的 JavaScript 提供了 Promises 和 async/await 等替代方案。**下面是使用 Promises 的相同代码:
const readFile = (file) => {
return new Promise((resolve, reject) => {
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
};
readFile('file1.txt')
.then((data1) => {
return readFile('file2.txt');
})
.then((data2) => {
return readFile('file3.txt');
})
.then((data3) => {
// Continue with more promise-based code...
})
.catch((err) => {
console.error(err);
});
Promise: Promise是JavaScript中的一个对象,用于异步计算。它表示异步操作的结果,该结果可能被解析或拒绝。
Promise有三种状态:
Promise构造函数有两个参数**(resolve, reject),这些参数都是函数**。如果异步任务已成功完成且没有错误,则使用消息或获取的数据调用resolve函数来解析promise。
如果发生错误,请调用reject函数并将错误传递给它。
我们可以使用.then()处理程序访问promise的结果。
我们可以在.catch()处理程序中捕获错误。
// 创建一个Promise
const fetchData = new Promise((resolve, reject) => {
// 模拟从服务器获取数据
setTimeout(() => {
const data = 'Some data from the server';
// 使用检索到的数据解析Promise
resolve(data);
// 使用错误拒绝Promise
// reject(new Error('Failed to fetch data'));
}, 1000);
});
// 使用Promise
fetchData
.then((data) => {
console.log('Data fetched:', data);
})
.catch((error) => {
console.error('Error fetching data:', error);
});
Promise链: 按特定顺序执行一系列异步任务的过程称为Promise链。
它涉及将多个.then()
方法链到Promise上以按特定顺序执行一系列任务。
new Promise(function (resolve, reject) {
setTimeout(() => resolve(1), 1000);
})
.then(function (result) {
console.log(result); // 1
return result * 2;
})
.then(function (result) {
console.log(result); // 2
return result * 3;
})
.then(function (result) {
console.log(result); // 6
return result * 4;
});
Async/await是现代方法来处理JavaScript中的异步代码,它提供了更简洁和易读的方式来使用Promise和异步操作,有效避免“回调地狱”,并改善异步代码的整体结构。
在JavaScript中,使用async关键字来定义一个异步函数,它返回一个Promise。
在async函数内部,使用await关键字可以暂停函数的执行,直到Promise被解析,从而实现同步代码的效果,同时处理异步操作。
async function fetchData() {
try {
const data = await fetch('https://example.com/data');
const jsonData = await data.json();
return jsonData;
} catch (error) {
throw error;
}
}
// 使用异步函数
fetchData()
.then((jsonData) => {
// 处理检索到的数据
})
.catch((error) => {
// 处理错误
});
在这个例子中,fetchData函数被定义为一个异步函数,它使用await关键字暂停执行并等待fetch和json操作完成,有效地以类似同步代码的方式处理Promise。
==
(松散相等运算符): 该运算符执行类型强制转换,这意味着它会将操作数转换为相同类型后再进行比较。它检查值是否相等而不考虑它们的数据类型。例如,1 == '1'
将返回true
,因为JavaScript会将字符串'1'
转换为数字后再进行比较。
===
(严格相等运算符): 该运算符执行严格比较而不进行类型强制转换。它会检查值和数据类型是否都相等。例如,1 === '1'
将返回false
,因为数据类型不同(数字和字符串)。
总结起来,==
在类型强制转换后检查相等性,而===
在检查严格相等性时会考虑值和数据类型。
与===
语句相比,==
的执行会更快。
下面是一些示例,涵盖了上述情况:
0 == false // true
0 === false // false
1 == "1" // true
1 === "1" // false
null == undefined // true
null === undefined // false
'0' == false // true
'0' === false // false
[]==[] 或 []===[] //false,引用内存中的不同对象
{}=={} 或 {}==={} //false,引用内存中的不同对象
在JavaScript中,有几种创建对象的方法。 一些常见的对象创建方法包括:
a)对象字面量: 最直接的创建对象的方法是使用对象字面量,在花括号中以逗号分隔的列表定义对象的属性和方法。
let person = {
firstName: 'John',
lastName: 'Doe',
greet: function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
};
b)构造函数: 可以使用构造函数和new关键字创建对象的多个实例。 在构造函数内部,可以将属性和方法分配给this关键字。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.greet = function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
};
}
let person1 = new Person('John', 'Doe');
let person2 = new Person('Jane', 'Smith');
c)Object.create(): Object.create()方法允许您使用指定的原型对象创建新对象。 此方法可对新创建对象的原型提供更多控制。
let personProto = {
greet: function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
};
let person = Object.create(personProto);
person.firstName = 'John';
person.lastName = 'Doe';
d)类语法(ES6): 在ES6中,JavaScript支持使用class关键字定义对象的类语法。 这为以更熟悉和更结构化的方式创建对象及定义其属性和方法提供了一种方法。
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
greet() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
}
let person = new Person('John', 'Doe');
e)工厂函数: 工厂函数是返回对象的函数。 此方法允许您封装对象创建过程,并轻松地使用自定义属性创建多个实例。
function createPerson(firstName, lastName) {
return {
firstName: firstName,
lastName: lastName,
greet: function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
};
}
let person1 = createPerson('John', 'Doe');
let person2 = createPerson('Jane', 'Smith');
f)Object.setPrototypeOf(): Object.setPrototypeOf()方法可用于为指定对象设置原型。 这为在创建对象之后设置对象原型提供了另一种方法。
let personProto = {
greet: function() {
return 'Hello, ' + this.firstName + ' ' + this.lastName;
}
};
let person = {};
person.firstName = 'John';
person.lastName = 'Doe';
Object.setPrototypeOf(person, personProto);
g)Object.assign(): Object.assign()方法可用于通过从一个或多个源对象复制所有可枚举自有属性的值来创建新对象。 这在合并对象或创建浅表副本时特别有用。
let target = { a: 1, b: 2 };
let source = { b: 3, c: 4 };
let mergedObject = Object.assign({}, target, source);
h)原型继承: JavaScript使用原型继承,允许对象从其他对象继承属性和方法。 您可以通过利用原型继承并使用构造函数或类的prototype属性来定义共享行为来创建对象。
function Animal(name) {
this.name = name;
}
Animal.prototype.greet = function() {
return 'Hello, I am ' + this.name;
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
let myDog = new Dog('Max', 'Poodle');
i)单例模式: 单例模式用于将对象限制为单个实例。 它可以通过闭包和立即调用的函数表达式(IIFE)的组合在JavaScript中实现。 这可确保只创建对象的一个实例。
let singleton = (() => {
let instance;
function createInstance() {
return {
// properties and methods
};
}
return {
getInstance: () => {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
rest运算符,表示为三个点(...
),用于在函数参数中收集可变数量的参数到一个数组中。 它允许您将任意数量的参数传递给函数,而不需要将它们显式定义为命名参数。
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 输出 10
spread运算符,也表示为三个点(...
),用于将数组或对象的元素展开到另一个数组或对象中。 它允许您轻松克隆数组、连接数组和合并对象。
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = [...array1, ...array2];
// mergedArray 是 [1, 2, 3, 4, 5, 6]
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const mergedObject = { ...obj1, ...obj2 };
// mergedObject 是 { a: 1, b: 3, c: 4 }
JavaScript中的高阶函数是一个函数,它要么接受一个或多个函数作为参数,要么返回一个函数作为其结果。换句话说,它对函数进行操作,无论是将它们作为参数,返回它们还是两者兼有。
function operationOnArray(arr, operation) {
let result = [];
for (let element of arr) {
result.push(operation(element));
}
return result;
}
function double(x) {
return x * 2;
}
let numbers = [1, 2, 3, 4];
let doubledNumbers = operationOnArray(numbers, double);
console.log(doubledNumbers); // 输出:[2, 4, 6, 8]
它们支持函数组合、柯里化和基于回调的异步操作等强大技术。理解高阶函数对于编写表达性强和函数式的JavaScript代码至关重要。
一元函数(即单参数函数)是一个只接受一个参数的函数。 它代表一个函数接受的单个参数。