经常问的JavaScript面试问题和答案
在你的作品集中拥有JavaScript可以增加获得软件开发人员角色的机会。话虽如此,让我们来看看常见的JavaScript面试问题。
JavaScript是Web开发中使用最广泛的语言之一。现在它被用于开发几乎任何类型的应用程序。
在进入面试问题之前,让我们看一下学习JavaScript的优势。
JavaScript是一种轻量级、解释型或即时编译的编程语言。它是万维网的核心语言之一。你知道www的另外两种核心语言。如果你不知道的话,最好去搜索一下。
JavaScript主要是为Web开发而创建的。但现在它不仅仅是用于Web。借助像Node、Deno等环境的帮助,我们可以在几乎任何平台上运行它。
让我们看一下它的一些优势。
JavaScript的优势
- 易于入门。即使你没有任何编码知识,你也可以学会它。
- 周围有很大的社区。如果你在某个地方卡住了,你会得到所有你想要的帮助。
- 有很多使用JavaScript构建的库/框架,可以帮助更快地开发应用程序。
- 我们可以使用JavaScript开发前端、后端、Android、iOS等应用程序。我们几乎可以使用它创建任何类型的应用程序。但是,在Web开发中,它更加强大。
JavaScript中的数据类型有哪些?
数据类型用于存储不同类型的数据。数据类型会因为不同的programming language而有所不同。在JavaScript中,我们有8种数据类型。让我们逐一看一下。
- 数值(Number)
- 字符串(String)
- 布尔值(Boolean)
- 未定义(Undefined)
- 空(Null)
- 大整数(BigInt)
- 符号(Symbol)
- 对象(Object)
除了对象(Object)之外的所有数据类型都称为原始值。它们是不可变的。
JavaScript中的内置方法有哪些?
JavaScript中的内置方法对于每种数据类型都是不同的。我们可以使用相应的数据类型来访问这些内置方法。让我们看一些不同数据类型和数据结构的内置方法。
- 数值(Number)
- toFixed
- toString
- …
- 字符串(String)
- toLowerCase
- startsWith
- chartAt
- …
- 数组(Array)
- filter
- map
- forEach
- …
每种数据类型都有很多内置方法。你可以查阅不同数据类型和数据结构的所有内置方法的参考资料。
如何在JavaScript中创建数组?
数组是JavaScript中的核心数据结构之一。由于JavaScript是动态的,数组可以包含任何类型的数据。让我们看看如何在JavaScript中创建数组。
我们可以使用[]
括号来创建数组。这种方法简单快捷,用于创建对象。
// 空数组
const arr = [];
// 包含一些随机值的数组
const randomArr = [1, "One", true];
console.log(arr, randomArr);
我们可以使用Array
构造函数来创建数组。人们很少在一般项目中使用构造函数来创建数组。
// 空数组
const arr = new Array();
// 包含一些随机值的数组
const randomArr = new Array(1, "One", true);
console.log(arr, randomArr);
JavaScript数组是可变的,即我们创建后可以按需修改它们。
如何在JavaScript中创建对象?
除了数组外,对象是JavaScript中另一个核心数据结构。对象用于存储键值对。键必须是一个不可变的值,而值可以是任何内容。让我们看看如何在JavaScript中创建对象。
我们可以使用花括号{}
创建对象。这是一种简单和快速创建对象的方法。
// 空对象
const object = {};
// 添加一些随机值的对象
const randomObject = { 1: 2, one: "Two", true: false };
console.log(object, randomObject);
我们可以使用Object
构造函数创建对象。在一般项目中很少使用这种方法。
// 空对象
const object = new Object();
// 添加一些随机值的对象
const randomObject = new Object();
randomObject[1] = 2;
randomObject["one"] = "Two";
randomObject[true] = false;
console.log(object, randomObject);
JavaScript对象是可变的,也就是说,我们可以在创建后修改它们,就像你在第二个示例中看到的那样。
如何调试JavaScript代码?
调试代码并不简单。它因编程语言、项目等而异。让我们看看调试JavaScript常用的方法。
#1. 日志记录
我们可以在代码的多个位置使用console.log
语句来识别错误。当前一行代码中有错误时,代码将停止运行下一行代码。
日志记录是一种古老的调试方法,对于小型项目非常有效。它是任何编程语言的常见调试技术。
#2. 开发者工具
JavaScript主要用于开发Web应用程序。因此,几乎所有的浏览器现在都有开发者工具,可帮助调试JavaScript代码。
最常用的调试方法之一是在开发者工具中设置断点。断点会停止JavaScript的执行,并提供当前执行的所有信息。
我们可以在出现错误的位置周围设置多个断点,查看是什么导致了错误。这是调试JavaScript Web应用程序最有效的方式。
#3. IDEs
我们可以使用集成开发环境(IDEs)来调试JavaScript代码。VS Code支持使用断点进行调试。根据使用的IDE,调试功能可能会有所不同。但是,大多数IDE都会提供该功能。
如何在HTML文件中添加JavaScript代码?
我们可以使用script
标签在HTML文件中添加JavaScript代码。您可以查看下面的示例。
Geekflare
Geekflare
什么是Cookies?
Cookies是用于存储小型信息的键值对。信息可以是任何内容。我们可以设置Cookies的到期时间,在到期时间之后它们将被删除。它们被广泛用于存储用户信息。
即使刷新页面,Cookies也不会被清除,除非我们删除它们或它们过期。您可以通过打开开发者工具,在任何浏览器中检查任何Web应用程序/网页的Cookies。
如何读取Cookie?
我们可以使用JavaScript的document.cookie
来读取Cookie。它将返回我们创建的所有Cookie。
console.log("所有Cookie", document.cookie);
如果没有Cookie,它将返回一个空字符串。
如何创建和删除Cookie?
我们可以通过将键值对设置到document.cookie
来创建Cookie。让我们看一个示例。
document.cookie = "one=One;";
在上面的语法中,one
cookie的键和它的值是One
。我们可以给cookie添加更多的属性,比如域名、路径、过期时间等等;每个属性之间应该用一个分号(;)分隔开来。所有的属性都是可选的。
让我们看一个带有属性的例子。
document.cookie = "one=One;expires=Jan 31 2023;path=/;";
在上面的代码中,我们给cookie添加了一个到期日期和路径。如果没有提供到期日期,cookie会在会话结束后被删除。默认的路径将是文件路径。到期日期的格式应该是GMT格式。
让我们看一下如何创建多个cookie。
document.cookie = "one=One;expires=Jan 31 2023;path=/;";
document.cookie = "two=Two;expires=Jan 31 2023;path=/;";
document.cookie = "three=Three;expires=Jan 31 2023;path=/;";
如果在设置多个cookie时键或路径不同,它们不会被覆盖。如果键和路径相同,那么它将覆盖先前的cookie。请查看下面的示例,它将覆盖先前设置的cookie。
document.cookie = "one=One;expires=Jan 31 2023;path=/;";
document.cookie = "one=Two;path=/;";
我们已经从cookie中删除了到期日期并更改了值。
在测试代码时,将到期日期设置为将来的日期,以确保代码能够正确工作。如果在Jan 31 2023之后仍然保持相同的日期,cookie将不会被创建。
我们已经学习了如何创建和更新cookie。现在让我们看一下如何删除cookie。
删除cookie很简单。只需将cookie的到期日期更改为任何过去的日期。请查看下面的示例。
// 创建cookie
document.cookie = "one=One;expires=Jan 31 2023;path=/;";
document.cookie = "two=Two;expires=Jan 31 2023;path=/;";
document.cookie = "three=Three;expires=Jan 31 2023;path=/;";
// 删除最后一个cookie
document.cookie = "three=Three;expires=Jan 1 2023;path=/;";
在cookie中找不到最后一个cookie,因为它在代码的最后一行被删除了。这就是关于最小化cookie教程的全部内容。
有哪些不同的JavaScript框架?
有很多框架可供选择。React、Vue、Angular等等,用于UI开发。Express、Koa、Nest等等,用于服务器端开发。Gatsby、Next等等,用于静态站点生成。React Native、Ionic等等,用于移动应用开发。我们在这里提到了一些JavaScript框架。你可以找到更多框架,但需要花费很多时间来探索。在需要时进行探索。
JavaScript中的闭包
闭包是一个函数与其词法作用域及其父级词法环境一起捆绑的组合体。通过闭包,我们可以访问外部作用域的数据。闭包在函数创建时形成。
function outer() {
const a = 1;
function inner() {
// 我们可以在这里访问外部函数作用域的所有数据
// 即使我们在外部函数之外执行该函数,数据也仍然可用
// 因为在创建inner函数时形成了闭包
console.log("访问内部的a", a);
}
return inner;
}
const innerFn = outer();
innerFn();
闭包在JavaScript应用程序中被广泛使用。你可能以前在没有意识到是闭包的情况下使用过它们。关于闭包还有很多东西需要学习。确保你完全掌握了这个概念。
JavaScript中的变量提升
变量提升是JavaScript中的一个过程,在执行代码之前,变量、函数和类的声明会被移动到作用域的顶部。
如果你运行上面的代码,你不会看到任何错误。但在大多数语言中,你会得到错误。输出将为undefined
,因为提升只是将声明移到顶部,并且不会在第3行之前初始化它。
将var
更改为let
或const
,然后再次运行代码。
“`javascript
// 在声明之前访问`name`
console.log(name);
// 声明和初始化`name`
const name = “Geekflare”;
“`
现在,你会得到引用错误,表示我们无法在初始化变量之前访问它。
“`javascript
ReferenceError: Cannot access ‘name' before initialization
“`
所以,在这里,ES6引入了let
和const
,它们在初始化之前无法访问,正如错误所示。这是因为用let
或const
声明的变量将在暂时死区(TDZ)中,直到被初始化的那一行。我们无法从TDZ中访问变量。
JavaScript中的柯里化
柯里化是一种将具有多个参数的函数转换为具有较少参数的多个可调用函数的技术。通过它,我们可以将可调用函数add(a, b, c, d)转换为可调用函数add(a)(b)(c)(d)。让我们看一个如何实现它的例子。
“`javascript
function getCurryCallback(callback) {
return function (a) {
return function (b) {
return function (c) {
return function (d) {
return callback(a, b, c, d);
};
};
};
};
}
function add(a, b, c, d) {
return a + b + c + d;
}
const curriedAdd = getCurryCallback(add);
// 调用curriedAdd
console.log(curriedAdd(1)(2)(3)(4));
“`
我们可以泛化getCurryCallback函数,它将用于将不同函数转换为柯里化可调用函数。关于此更多详细信息,可以参考JavaScript Info。
document和window之间的区别
window
是浏览器中最顶层的对象。它包含有关浏览器窗口的所有信息,如历史记录、位置、导航器等。它在JavaScript中全局可用。我们可以直接在代码中使用它,无需任何导入。我们可以在不使用window.
的情况下访问window
对象的属性和方法。
document
是window
对象的一部分。加载在Web页面上的所有HTML都会转换为文档对象。文档对象指的是特殊的HTMLDocument元素,它具有不同的属性和方法,就像所有HTML元素一样。
window
对象表示浏览器窗口,document
表示加载在该浏览器窗口中的HTML文档。
客户端和服务器端的区别
客户端指的是使用应用程序的最终用户。服务器端指的是部署应用程序的Web服务器。
在前端术语中,我们可以将用户计算机上的浏览器称为客户端,将云服务称为服务器端。
innerHTML和innerText之间的区别
innerHTML
和innerText
都是HTML元素的属性。我们可以使用这些属性来更改HTML元素的内容。
我们可以将HTML字符串分配给innerHTML
属性,它将像正常的HTML一样呈现。请查看下面的示例。
“`javascript
const titleEl = document.getElementById(“title”);
titleEl.innerHTML = ‘Geekflare';
“`
在你的HTML中添加一个具有id title
的元素,并将上述脚本添加到JavaScript文件中。运行代码并查看输出。你将会看到橙色的Geekflare
。如果你检查该元素,它将在span
标签内。所以innerHTML
会将HTML字符串作为普通HTML渲染。
而另一方面,innerText
会将普通字符串作为原样渲染,不会渲染像innerHTML
那样的HTML。将上述代码中的innerHTML
更改为innerText
并检查输出。
const titleEl = document.getElementById("title");
titleEl.innerText = 'Geekflare';
现在,你将在网页上看到我们提供的确切字符串。
let和var的区别
let
和var
关键字用于在JavaScript中创建变量。let
关键字是在ES6中引入的。
let
是块级作用域,而var
是函数作用域。
{
let a = 2;
console.log("Inside block", a);
}
console.log("Outside block", a);
运行上述代码。你将在最后一行得到一个错误,因为我们无法在块外部访问let a
,因为它是块级作用域。现在,将其更改为var
并再次运行。
{
var a = 2;
console.log("Inside block", a);
}
console.log("Outside block", a);
你将不会得到任何错误,因为我们可以在块外部访问a
变量。现在,让我们用一个函数替换该块。
function sample() {
var a = 2;
console.log("Inside function", a);
}
sample();
console.log("Outside function", a);
如果运行上述代码,你将获得一个引用错误,因为我们无法在函数外部访问var a
,因为它是函数作用域。
我们可以使用var
关键字重新声明变量,但我们不能使用let
关键字重新声明变量。让我们看一个例子。
var a = "Geekflare";
var a = "Chandan";
console.log(a);
let a = "Geekflare";
let a = "Chandan";
console.log(a);
第一段代码不会抛出任何错误,变量a
的值将更改为最新分配的值。第二段代码将抛出错误,因为我们不能使用let
重新声明变量。
会话存储和本地存储之间的区别
会话存储和本地存储用于在用户的计算机上存储信息,可以在没有互联网的情况下访问。我们可以在会话存储和本地存储中存储键值对。如果提供其他数据类型或数据结构,键和值将转换为字符串。
会话存储在会话结束后(关闭浏览器时)被清除,而本地存储直到我们清除它才被清除。
我们可以使用sessionStorage
和localStorage
对象来访问、更新和删除会话存储和本地存储。
JavaScript中的NaN是什么?
NaN
代表不是一个数字。它表示JavaScript中的某些内容不是合法/有效的数字。有一些情况下我们会得到NaN
作为输出,例如0/0
,undefined * 2
,1 + undefined
,null * undefined
等。
什么是词法作用域?
词法作用域是指从其父作用域访问变量。假设我们有一个函数,其中包含两个内部函数。最内层的函数可以访问其两个父函数的作用域变量。类似地,第二级函数可以访问最外层函数的作用域。让我们看一个例子。
function outermost() {
let a = 1;
console.log(a);
function middle() {
let b = 2;
// 在这里可以访问`a`
console.log(a, b);
function innermost() {
let c = 3;
// 在这里可以访问`a`和`b`
console.log(a, b, c);
}
innermost();
}
middle();
}
outermost();
JavaScript使用作用域链来查找在代码中访问变量的地方的变量。首先,它将在当前作用域中检查变量,然后在父作用域中检查,直到全局作用域。
传值和传引用的区别是什么?
在JavaScript中,传值和传引用是将参数传递给函数的两种方式。
传值:它创建原始数据的副本并将其传递给函数。因此,在函数中进行任何更改都不会影响原始数据。请参考下面的示例。
function sample(a) {
// 更改`a`的值
a = 5;
console.log("函数内部", a);
}
let a = 3;
sample(a);
console.log("函数外部", a);
你会发现,尽管我们在函数内部更改了它,但变量a
的原始值并未发生变化。
传引用:它将数据的引用传递给函数。因此,在函数中进行任何更改都会改变原始数据。
function sample(arr) {
// 向数组中添加一个新值
arr.push(3);
console.log("函数内部", arr);
}
let arr = [1, 2];
sample(arr);
console.log("函数外部", arr);
你会发现,当我们在函数内部更改它时,变量arr
的原始值已经改变。
注意:所有原始数据类型都是按值传递的,而非原始数据类型是按引用传递的。
什么是记忆化?
记忆化是一种在缓存中存储计算值,并在需要时再次使用它们而不重新计算的技术。如果计算非常耗时,它将加速代码的执行。与时间相比,存储大小的权衡不是一个大问题。
const memo = {};
function add(a, b) {
const key = `${a}-${b}`;
// 检查我们是否已经计算过值
if (memo[key]) {
console.log("不再计算");
return memo[key];
}
// 将新计算的值添加到缓存中
// 这里的缓存是一个简单的全局对象
memo[key] = a + b;
return memo[key];
}
console.log(add(1, 2));
console.log(add(2, 3));
console.log(add(1, 2));
这是一个演示记忆化的简单示例。这里,两个数字相加并不是一个耗时的计算。这只是为了演示。
什么是剩余参数?
剩余参数用于收集函数中的所有剩余参数。假设我们有一个函数,它将接受至少2个参数,并且最多可以接受任意数量的参数。由于我们没有最大数量的参数,我们可以使用普通变量收集前2个参数,并使用剩余参数和剩余运算符收集所有其他参数。
function sample(a, b, ...rest) {
console.log("剩余参数", rest);
}
sample(1, 2, 3, 4, 5);
在上面的例子中,rest parameter将是最后三个参数的数组。通过这种方式,我们可以为函数设置任意数量的参数。
一个函数只能有一个rest参数,并且rest参数应该是参数顺序中的最后一个。
什么是对象解构?
对象解构用于访问对象中的变量,并将它们分配给与对象键相同名称的变量。让我们来看一个例子。
const object = { a: 1, b: 2, c: 3 };
// 对象解构
const { a, b, c } = object;
// 现在,a、b、c将被用作普通变量
console.log(a, b, c);
我们可以在同一行中更改解构变量的变量名,如下所示。
const object = { a: 1, b: 2, c: 3 };
// 更改`a`和`b`的名称
const { a: changedA, b: changedB, c } = object;
// 现在,changedA、changedB、c将被用作普通变量
console.log(changedA, changedB, c);
什么是数组解构?
数组解构用于访问数组中的变量并将它们分配给变量。让我们来看一个例子。
const array = [1, 2, 3];
// 数组解构
// 它基于数组的索引
const [a, b, c] = array;
// 现在,我们可以将a、b、c用作普通变量
console.log(a, b, c);
什么是事件捕获和事件冒泡?
事件捕获和事件冒泡是HTML DOM中事件传播的两种方式。假设有两个HTML元素,一个在另一个元素内部。并且在内部元素上发生了一个事件。现在,事件传播模式将决定这些事件的执行顺序。
事件冒泡:它首先在元素上运行事件处理程序,然后是其元素,然后一直到最顶层元素。这是所有事件的默认行为。
事件捕获:我们需要在事件中指定我们需要使用这种类型的事件传播。我们可以在添加事件监听器时指定它。如果启用了事件捕获,则事件将按以下顺序执行。
- 事件从最顶层元素开始执行,直到目标元素。
- 目标元素上的事件将再次执行。
- 冒泡事件传播将再次发生,直到最顶层元素。
我们可以通过调用event.stopPropogation
方法来停止事件传播。
JavaScript中的Promise是什么?
Promise
对象用于异步操作,这些操作将在将来以成功或失败状态完成。
Promise
可以具有以下状态之一。
pending
– 当操作仍在进行中时。fulfilled
– 当操作成功完成时。我们将在成功状态中有结果(如果有)。rejected
– 当操作以失败完成时。我们将知道失败的原因(错误)。
让我们看两个成功和失败的例子。
// 将英文翻译成简体中文,保留,及HTML标签。
// Promise which will complete successfully
const successPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ message: "已成功完成" });
}, 300);
});
successPromise
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
// Promise which will complete with failure state
const failurePromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("用于测试的Promise失败"));
}, 300);
});
failurePromise
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
如果需要,可以有多个then
链。上一个返回的数据将在下一个then
回调中被接受。
解释JavaScript中的不同作用域类型
JavaScript有两种作用域类型,即全局作用域
和局部作用域
。
你可能也听说过函数作用域和块级作用域。它们是var
和let
、const
的局部作用域。
什么是自执行函数?
自执行函数是匿名函数,在创建后立即执行。让我们看一些例子。
// 没有任何参数
(function sayHello() {
console.log("你好,世界!");
})();
// 带参数
(function add(a, b) {
console.log("和", a + b);
})(1, 2);
我们甚至可以将参数传递给自执行函数,就像你在示例中看到的那样。
什么是箭头函数?
箭头函数是对常规函数的一种语法糖,它们在大多数常规用法中表现得像普通函数。当我们需要回调函数时,箭头函数非常有用。让我们看一下它的语法。
// 如果没有大括号,箭头函数将默认返回
let add = (a, b) => a + b;
console.log(add(1, 2));
箭头函数和普通函数之间有一些区别。
- 箭头函数没有自己的
this
绑定。箭头函数内部的this
关键字引用其父级作用域的this
。 - 箭头函数不能用作构造函数。
什么是回调函数?
回调函数是传递给另一个函数并在该函数内部调用的函数。在JavaScript中,使用回调函数是很常见的。让我们看一个例子。
function sample(a, b, callback) {
const result = a + b;
callback(result);
}
function finished(result) {
console.log("完成,结果为", result);
}
sample(1, 2, finished);
函数finished
作为回调函数传递给sample
函数。在执行某些操作后,将使用结果调用finished
函数。你会在异步操作中经常看到回调函数的使用,比如在promises、setTimeout等中。
不同类型的错误有哪些?
让我们来看一些错误类型。
ReferenceError:如果我们访问的变量不存在,就会出现该错误。
TypeError:如果错误与其他类型的错误不匹配,JavaScript会抛出此错误。当我们尝试执行与数据不兼容的操作时,也会出现此错误。
SyntaxError:如果JavaScript语法不正确,就会出现此错误。
还有一些其他类型的错误。但这些是JavaScript中常见的错误类型。
JavaScript的变量有哪些作用域?
JavaScript中有两种变量的作用域。使用var
关键字声明的变量具有函数作用域,而使用let
和const
声明的变量具有块作用域。
有关这些变量作用域的详细信息,请参阅第17个问题。
JavaScript中的转义字符是什么?
反斜杠(backslash)是JavaScript中的转义字符。它用于打印一些我们通常无法打印的特殊字符。假设我们想在字符串中打印撇号 (')
,但正常情况下字符串会在第二个撇号处结束。在这种情况下,我们将使用转义字符来避免在那一点结束字符串。
const message = 'Hi, I'm Geekflare';
console.log(message);
我们可以通过用双引号替换外部单引号来实现上述输出,而不使用转义字符。但这只是一个使用转义字符的示例。对于其他需要转义字符的字符,例如n
、t
、等,我们肯定需要转义字符。
BOM和DOM是什么?
浏览器对象模型(BOM):所有浏览器都有代表当前浏览器窗口的BOM。它包含我们用于操作浏览器窗口的最顶层窗口对象。
文档对象模型(DOM):当HTML加载到树状结构中时,浏览器会创建DOM。我们可以使用DOM API来操作HTML元素。
什么是屏幕对象?
屏幕对象是全局窗口对象的属性之一。它包含了当前浏览器窗口渲染的屏幕的各种属性,例如宽度、高度、方向、像素深度等。
结论
对于上述所有问题,可能会有后续问题。因此,您需要准备好围绕上述所有问题的概念。
您还可以了解一些常见的Java interview问题和答案。
祝学习愉快 🙂