写在前面
由于目前现在求职市场竞争激烈,许多初学者和经验丰富的开发人员都面临着求职问题。因此,最好的方法是不断学习并提高自己的技能。
为了拿到心仪的offer,我们需要认真准备面试,因此,今天我为大家准备了53道面试题,我以“一问一答”的形式将这些前端面试题分享出来,希望能够帮助到你。
虽然这些面试题主要针对初级开发人员,但其中也有包括一些中级开发人员的题。
那么,我们现在开始吧。
1. JavaScript 中有哪些数据类型?
- Number — 数字
- String — 字符串
- Boolean — 布尔类型,true 或 false
- Object — JavaScript 对象
- null — 表示“无”、“空”或“未知值”的特殊值。
- undefined ——“值尚未被分配”。如果声明了变量但没有分配值,则分配此类型。
- Symbol— 一种唯一且不可变的数据类型,可用作对象属性的标识符。
- BigInt — 用于创建大数。
const bigInt = 1234567890123456789012345678901234567890n;
2.“==”和“===”有什么区别?
运算符“==”检查抽象相等,而“===”检查严格相等。换句话说,“==”运算符在比较之前执行必要的类型转换,而“===”则不执行类型转换。因此,如果两个值不是同一类型,则使用“===”运算符时将返回 false。
3. 变量的声明方式有哪些?
声明变量有 4 种方法:声明变量有 4 种方法:
foo =123;
var foo = 123;
let a = 123;
conest a = 123;
使用“var”关键字声明变量与第一种方法类似。 以这种方式声明的变量具有全局或函数作用域,但缺少块作用域,这是一个缺点。
“let”和“const”是声明变量的更好方法。 它们具有块作用域,这意味着在函数内部声明的变量在该函数外部将不可见。
“const”变量是不可变的,但如果它是一个对象,你可以改变它的属性,如果它是一个数组,你可以修改和添加元素。
4. null 和 undefined 有什么区别?
两个选项都代表空值。 如果我们初始化一个变量但不给它赋值,它就会被分配一个特殊的标记—undefined。Null 是手动分配的。Null 是一个特殊值,表示“无”、“空”或“未知值”。 如果我们需要清除变量的值,我们设置 foo = null。
5. 箭头函数以及与常规函数的区别。
- 箭头函数不能使用参数对象。
- 它们有不同的语法。
- 箭头函数没有自己的 this 上下文。 当引用 this 时,箭头函数从周围的作用域中获取上下文。
- 箭头函数不能用作构造函数。 换句话说,它们不能用 new 关键字调用。
6. 什么是闭包?以及为什么需要它们?
闭包是一个函数以及它可以访问的所有外部变量。 例如,有一个具有嵌套函数的函数,该函数将关闭并保留其父级的变量。
7. 什么是模板文字?
模板文字用反引号 (") 括起来,并允许多行字符串。 它们还允许在其中嵌入表达式。
8. 什么是Set和Map?
Map是一个集合,是一种按照键值对原理进行操作的数据结构,类似于Object。 然而,Map 和 Object 之间的主要区别在于 Map 允许使用任何类型的键。Set 是一种没有键的集合类型,是一个数组,其中每个值只能出现一次。 集合在其内部存储唯一的值。
9. 如何检查对象中是否存在属性?
第一种方法是使用 hasOwnProperty 函数,该函数适用于每个对象。第二种方法是使用 in 运算符。 但是,在使用 in 运算符时必须小心,因为它会检查链中的所有原型。
10. 如何访问对象属性?
第一种方式是静态的,使用点表示法:obj.a。
第二种方式是动态的,使用方括号:obj[‘a’]。
11. 使用数组的主要方法有哪些?
- forEach — 用于循环数组的迭代方法,不返回任何内容。 它提供了比常规 for 循环更优雅的替代方案。
- filter(callback, [args]) — 一种使用提供的函数过滤数组的方法。 它创建一个新数组,其中仅包含回调(item, i, arr) 函数返回 true 的原始数组中的元素。
- map(callback, [args]) — 一种转换数组的方法。 它创建一个新数组,其中包含为数组的每个元素调用回调(item, i, arr) 函数的结果。
- reduce(callback, [initValue]) — 一种按顺序处理数组的每个元素,同时维护中间结果的方法。
12.创建对象的方式有哪些?
使用构造函数:
使用对象文字表示法:
使用一个类:
使用创建函数:
13.什么是Promise?
Promise 是一个设计用于处理异步代码的对象。它维持自己的状态。最初,Promise 处于待处理状态,如果异步代码执行成功,则 Promise 转换为已完成状态;如果发生错误,则转换为拒绝状态。Promise 接受两个回调函数:
- onFulfilled,当Promise履行时触发。
- onRejected,当Promise被拒绝时触发。
使用模式如下:
- 需要异步执行某些操作的代码会创建一个 Promise 并返回它。
- 外部代码收到 Promise 后,将 onFulfilled 和 onRejected 回调函数传递给它。
- 该过程完成后,异步代码将 Promise 转换为已完成或已拒绝状态,并自动调用相应的回调函数。
14.什么是async/await以及如何使用它?
async/await 是一种处理 Promise 的特殊语法。
使用异步语法声明的函数始终返回 Promise。
关键字await使JavaScript解释器等待,直到await右侧的Promise完成后才继续执行。然后,它将返回结果,并且代码将继续执行。wait 不能在常规函数中使用。
15. 如何检查一个对象是否是数组?
要检查对象是否是数组,可以使用 Array.isArray() 方法。它接受一个对象作为输入,如果该对象是数组则返回 true,如果不是数组则返回 false。
16. 扩展运算符 (…) 的目的是什么?
扩展运算符 (…) 用于解包数组或对象。
它允许你扩展可迭代的元素,例如,数组和字符串。
它用在调用的预期参数数量为零或更多的函数中。
它用在数组文字或表达式中。
它用在对象文字中,其中键值对的数量应为零或更多。
17. 复制对象时如何避免引用依赖?
如果对象不包含嵌套对象,例如:
在这种情况下,你可以使用展开运算符或 Object.assign() 方法:
如果对象包含嵌套对象:
在这种情况下,你需要执行深复制。
解决方法虽然较慢,但是:
该方法适用于没有原型和函数的对象。
或者,你可以使用 lodash 库的 deepClone() 函数。
18. 如何改变函数的上下文?
使用bind()方法,该方法返回一个带有绑定上下文的新函数。
使用 call() 和 apply() 方法。主要区别在于 call() 接受参数序列,而 apply() 接受参数数组作为第二个参数。
19. 什么是三元运算符/条件运算符?
三元运算符是 if-else 语句的简写符号。运算符由问号和冒号表示。它被称为三元,因为它是唯一接受三个参数的运算符。
健康)状况 ?表达式_1:表达式_2
20.什么是解构?
解构是一种允许我们将数组和对象解包为多个变量的语法。
21.什么是DOM?
DOM 代表文档对象模型。它是将 HTML 文档表示为标签树。
例子
DOM 树中的每个节点都是一个对象。
HTML 文档的基本元素是标签。
根据文档对象模型 (DOM),每个 HTML 标签都是一个对象。嵌套标签是其父元素的“子元素”。标签内的文本也是一个对象。所有这些对象都可以使用 JavaScript 访问,我们可以使用它们来操作页面。
22.什么是事件循环?
事件循环——一种管理代码执行的机制。它以正确的顺序处理事件处理和任务执行。
事件循环的主要思想是JavaScript在单线程环境中运行但可以处理异步操作。当异步操作(例如服务器请求)完成时,它将相应的事件放入事件队列中。
事件循环以循环方式工作,按照事件到达的顺序处理这些事件。
它从队列中获取一个事件并将其传递以供执行。如果事件包含回调或处理程序,则会调用它,并执行与该事件关联的代码。
事件循环还处理其他任务,例如计时器和微任务(Promise)。它管理所有这些任务的执行顺序,以确保一致性并防止阻塞代码执行的主线程。
简而言之,JavaScript 中的事件循环通过处理队列中的事件并按正确的顺序执行相应的代码来管理异步操作。这使得 JavaScript 在处理异步操作时能够响应并有效地利用其资源。
23.什么是原型继承?
JavaScript 中的每个对象都有一个属性——原型。可以将方法和属性添加到原型中。可以根据原型创建其他对象。创建的对象自动继承其原型的方法和属性。如果对象中不存在某个属性,则将在原型中执行其搜索。
24.什么是可选链运算符?
可选链接运算符 ?。如果 ? 后面的部分停止计算并返回undefined。undefined或为null。
让我们考虑一个用户对象。大多数用户都有一个地址 user.address 和一个街道 user.address.street,但有些用户没有提供地址。在这种情况下,可选链接运算符可以帮助我们在尝试访问未在地址中指定街道的用户街道时避免错误。
25.什么是 Shadow DOM?
Shadow DOM 是一组 Web 标准,允许封装网页上元素的结构和样式。它代表 DOM 的一个特殊片段,位于元素内部并与页面的其余部分分开。Shadow DOM 用于创建具有独立且风格化内容的组件和小部件,这些内容与页面的整体结构不冲突。
26.什么是递归?如何使用它?
递归是一种解决问题的方法,其中函数通过在自己的函数体内重用自身来解决问题。简单来说,就是函数调用自身的时候。
递归函数包括:
- 终止条件或基本情况
- 递归步骤——一种将问题简化为更简单形式的方法。
基本情况是必要条件;否则,会因函数调用无限循环而导致堆栈溢出。
27. 函数表达式和函数声明有什么区别?
函数声明是声明函数的传统方式。
函数表达式:
通过函数声明,可以创建函数并将其分配给变量,就像任何其他值一样。本质上,函数如何定义并不重要,因为它是存储在变量“foo”中的值。
然而,函数声明是在执行代码块之前处理的,这意味着它们在整个代码块中都是可见的。另一方面,函数表达式仅在执行流到达时才会创建。
28.什么是构造函数?
构造函数是用于创建对象的常规函数。但是,使用它们有两个规则:
- 构造函数的名称应以大写字母开头。
- 应使用 new 运算符调用构造函数。
当使用 new 运算符创建构造函数时,会发生以下情况:
- 创建一个新的空对象并将其分配给该对象。
- 执行构造函数内的代码。通常,此代码将修改 this 对象并添加新属性。
- 返回此值。
29. 如何从对象中获取键列表和值列表?
你可以使用 Object.keys() 获取键列表,使用 Object.values() 获取值列表。
30. 提供 ES6 中新功能的示例。
最常见的:
- let和const。引入新关键字 let 和 const 用于声明具有块作用域的变量。
- 箭头函数。箭头函数的概念允许更简洁和清晰的函数定义。
- 默认参数。你可以定义函数参数的默认值。
- 展开运算符 (...)。扩展运算符允许解包函数参数的数组或对象元素或创建新的数组/对象。
- 解构。解构允许从数组或对象中提取值并将它们分配给变量。
31. ES6中如何进行类继承?
类继承是使用“extends”关键字后跟父类的名称来完成的。
32. JavaScript 中的微任务和宏任务是什么?
在 JavaScript 中,微任务和宏任务是指需要在事件循环中执行的任务类型。
微任务是在浏览器重新绘制页面之前需要在当前事件循环内执行的任务。它们通常使用 Promise.then()、process.nextTick()(在 Node.js 中)或 MutationObserver 等方法添加到执行队列中。
微任务的示例包括执行 Promise 处理程序和 DOM 突变。
另一方面,宏任务是在当前事件循环完成之后、在屏幕上呈现更改之前需要执行的任务。
这包括使用 setTimeout、setInterval、requestAnimationFrame 添加到事件队列的任务,以及处理输入事件和网络请求。
宏任务在当前事件循环中的所有微任务处理完毕后执行。
微任务和宏任务之间的区别很重要,因为它决定了执行顺序并允许管理 JavaScript 中不同任务的优先级。
微任务具有更高的优先级,并且在宏任务之前执行,这样可以更快地更新界面并防止阻塞主 JavaScript 执行线程。
33.什么是生成器?
生成器根据需要一个一个地生成一系列值。生成器可以很好地与对象配合使用,并且可以轻松创建数据流。
要声明生成器,需要使用一种特殊的语法——生成器函数。
next() 是生成器的主要方法。调用时,next() 开始执行代码,直到最近的yield 语句。该值可能不存在,在这种情况下它表示为未定义。当达到yield时,函数执行暂停,并将相应的值返回给外部代码。
34.浏览器中存储数据的方法有哪些?
在浏览器中存储数据有多种方法:
- LocalStorage 和 SessionStorage — 在浏览器中存储键值对。其中存储的数据在页面刷新后仍保留。两种存储选项都只能使用字符串作为键和值,因此需要使用 JSON.stringify() 转换对象。Cookie — 存储在浏览器中的小数据字符串。
- Cookie 通常由 Web 服务器使用 Set-Cookie 标头设置。然后,浏览器将使用 Cookie 标头自动将它们添加到几乎每个对同一域的请求中。一个 cookie 最多可以容纳 4kb 的数据。根据浏览器的不同,每个站点允许使用 20 个以上的 cookie。
- IndexedDB — 内置数据库,比 localStorage 更强大。它是一个键值存储,其中有多种类型的键可用,值几乎可以是任何东西。IndexedDB 支持事务以提高可靠性,支持键范围查询和索引,并允许存储比 localStorage 更多的数据。IndexedDB 是为离线应用程序设计的,可以与 Service Workers 等技术结合。
35、sessionStorage和localStorage有什么区别?
SessionStorage 和 localStorage 允许在浏览器中以键值格式存储对象。
主要区别是:
- localStorage 最多可以存储 10 MB 的数据,而 sessionStorage 最多可以存储 5 MB 的数据。
- localStorage 中的数据不会被删除,而 sessionStorage 中的数据会在浏览器选项卡关闭时被删除。
- localStorage 中的数据可以从任何窗口访问,而 sessionStorage 中的数据只能从同一浏览器窗口访问。
36.什么是正则表达式?
正则表达式是由特殊规则和模式定义的字符串。它们是一个强大的工具,可以检测和处理字符串中的复杂结构。
37.WeakSet和WeakMap是什么,它们与Map和Set有何不同?
WeakMap 和 Map 之间的第一个区别是 WeakMap 中的键必须是对象,而不是原始值。
第二个区别在于数据结构的内存存储。JavaScript 引擎将值保存在内存中,只要它们是可访问的,这意味着它们可以被使用。
通常,对象属性、数组元素或其他数据结构被认为是可访问的,并且只要数据结构存在,它们就会保留在内存中,即使没有其他对它们的引用。
对于 WeakMap 和 WeakSet 来说,它的工作方式不同。一旦对象变得不可访问,它将从数据结构中删除。
38. 为什么两个具有相同字段的对象比较时返回 false?
根据对内存区域的引用来比较对象。对于 JavaScript,test1 和 test2 对象是不同的,即使它们具有相同的字段。仅当对象是同一个对象时,它们才相等。
39. 为什么我们可以调用原始类型的方法?
JavaScript 允许使用原始数据类型(字符串、数字等),就像它们是对象一样。原始数据类型有方法。
为了使此功能可用,每个基本数据类型都有自己的包装对象:字符串、数字、布尔值和符号。由于这些包装对象,原始数据类型具有不同的方法集,例如 toLowerCase() 或 toUpperCase()。
40. 如何检查对象是从哪个类创建的?
你可以使用instanceof运算符检查对象是从哪个类创建的,同时考虑继承。
41. 编写代码,每 10 秒记录一次在网站上花费的时间(以秒为单位)。
42.什么是纯函数?
纯函数需要满足以下两个条件的函数:
- 每次使用相同的参数集调用该函数时,它都会返回相同的结果。
- 它没有副作用,这意味着它不会修改函数外部的变量。
43.什么是高阶函数?
高阶函数是接受另一个函数作为参数或返回一个函数作为结果的函数。
44. 如果我们可以使用回调来处理异步代码,为什么还需要 Promise?
如果我们想使用回调函数从服务器异步获取一些数据,则会导致以下结果:
这称为回调地狱,因为每个回调都嵌套在另一个回调中,并且每个内部回调都依赖于父函数。
使用Promises,我们可以重写上面的代码:
有了Promises,执行顺序就清晰了,让代码更具可读性。
45. 编写你自己的bind 方法的实现。
为了实现它,我们可以使用闭包和 apply() 方法将函数绑定到上下文。
46. 用加、减、乘、除和 get 方法编写一个计算器函数。该函数必须通过可选链来工作。
47. 编写一个 randomSort 函数,该函数接受一个数字数组并按随机顺序对数组进行排序。
你可以使用 sort() 方法和 Math.random() 来实现此目的。
48. 编写一个deleteGreatestValue 函数,该函数接受一个二维数字数组,并从每个嵌套数组中删除最大的数字。
我们应该迭代每个嵌套数组,获取每个嵌套数组的最大值并将其删除。
49. 编写一个 sortPeople 函数,它接受一个字符串名称数组和一个数字高度数组,其中名称[i] == heights[i]。它应该根据高度数组对名称数组进行排序。
50. 编写一个subsets 函数,它接受一个数字数组nums 并返回这些数字的所有可能的数组变体。
51.如何反转链表?
让我们创建一个函数reverseLinkedList,它将链表作为输入并返回该列表的反转版本。
方法:
- 它用 null 初始化结果变量,该变量将保存反转的列表。
- 它使用 head 初始化 root 变量,该变量指向列表的开头。
- 它进入一个 while 循环,一直持续到 root 变为 null,表示列表末尾。
- 在循环内部,它检查结果是否已经有元素。如果是,它会使用当前值 root.val 和指向下一个节点结果的指针创建一个新的列表节点。然后它用这个新节点更新结果。
- 如果 result 还没有任何元素,它将创建一个新的列表节点,其中当前值 root.val 和 null 作为指向下一个节点的指针。然后它用这个新节点更新结果。
- 更新结果后,通过将 root.next 分配给 root 来移动到列表中的下一个元素。
- while 循环完成后,它返回结果中存储的反转列表。
总之,该函数通过从头到尾迭代每个节点,为每个值创建一个新的列表节点并相应地更新指针来反转链表。
52.如何对链表进行排序?
让我们创建一个函数 sortList,它将链表作为输入并返回该列表的排序版本。
方法:
- 检查给定的链表是否为空。
- 遍历链表并将节点值存储到数组中。
- 使用内置的 sort() 方法对数组进行排序。
- 使用排序后的数组创建一个新的链表。
- 返回创建的链表的头。
53. Observables 和 Promise 之间有什么区别?
Observables 和 Promises 都用于处理 JavaScript 中的异步操作。
一个关键的区别是 Observables 可以随着时间的推移发出多个值。
它们适合处理数据流,例如用户交互、事件或来自 API 的随时间变化的数据。另一方面,承诺只能用单个值解析一次。
它们适合处理要么成功要么失败的单个异步操作。
总之,Promises 最适合处理具有单个结果的一次性异步操作,而 Observables 在处理持续的数据流、事件和复杂的数据处理管道时更强大。
它们之间的选择取决于特定的用例以及你正在处理的异步操作的性质。
写在最后
在准备这些面试题以及研究所涵盖的主题并查看相关资源的时候,相当于又把一些知识做了复习,对于之前没有记住的内容,通过对这些内容的掌握,可以提升你的面试成功通过机率。