如何用ps做网站网页,做影视网站引流,网站的创建历程怎么写,国家企业信用查询信息系统(全国)JavaScript 是一种功能强大的语言#xff0c;是网络的主要构建块之一。这种强大的语言也有一些怪癖。例如#xff0c;您是否知道 0 -0 的计算结果为 true#xff0c;或者 Number() 的结果为 0#xff1f; 问题是#xff0c;有时这些怪癖会让你摸不着头脑是网络的主要构建块之一。这种强大的语言也有一些怪癖。例如您是否知道 0 -0 的计算结果为 true或者 Number() 的结果为 0 问题是有时这些怪癖会让你摸不着头脑甚至质疑 Brendon Eich 发明 JavaScript 的那一天。好吧重点不在于 JavaScript 是一种糟糕的编程语言或者像它的批评者所说的那样它是邪恶的。所有编程语言都有某种与之相关的奇怪之处JavaScript 也不例外。
因此在今天这篇文章中我们将会看到一些重要的 JavaScript 面试问题的深入解释。我的目标是彻底解释这些面试问题以便我们能够理解基本概念并希望在面试中解决其他类似问题。
1、仔细观察 和 - 运算符
console.log(1 1 - 1);
您能猜出 JavaScript 的 和 - 运算符在上述情况下的行为吗
当 JavaScript 遇到 1 1 时它会使用 运算符处理表达式。 运算符的一个有趣的属性是当操作数之一是字符串时它更喜欢字符串连接。在我们的例子中“1”是一个字符串因此 JavaScript 隐式地将数值 1 强制转换为字符串。因此1 1 变为 1 1结果是字符串 11。
现在我们的等式是 11 - 1。- 运算符的行为恰恰相反。无论操作数的类型如何它都会优先考虑数字减法。当操作数不是数字类型时JavaScript 会执行隐式强制转换将其转换为数字。在本例中“11”被转换为数值 11并且表达式简化为 11 - 1。
把它们放在一起
11 - 1 11 - 1 10
2、复制数组元素
考虑以下 JavaScript 代码并尝试查找此代码中的任何问题
function duplicate(array) {for (var i 0; i array.length; i) {array.push(array[i]);}return array;
}const arr [1, 2, 3];
const newArr duplicate(arr);
console.log(newArr);
在此代码片段中我们需要创建一个包含输入数组的重复元素的新数组。初步检查后代码似乎通过复制原始数组 arr 中的每个元素来创建一个新数组 newArr。然而重复函数本身出现了一个关键问题。
重复函数使用循环来遍历给定数组中的每个项目。但在循环内部它使用 push() 方法在数组末尾添加一个新元素。这使得数组每次都变得更长从而产生循环永远不会停止的问题。循环条件 (i array.length) 始终保持为 true因为数组不断变大。这使得循环永远持续下去导致程序卡住。
为了解决数组长度不断增长导致无限循环的问题可以在进入循环之前将数组的初始长度存储在变量中。
然后您可以使用该初始长度作为循环迭代的限制。这样循环将仅针对数组中的原始元素运行并且不会因添加重复项而受到数组增长的影响。这是代码的修改版本
function duplicate(array) {var initialLength array.length; // Store the initial lengthfor (var i 0; i initialLength; i) {array.push(array[i]); // Push a duplicate of each element}return array;
}const arr [1, 2, 3];
const newArr duplicate(arr);
console.log(newArr);
输出将显示数组末尾的重复元素并且循环不会导致无限循环
[1, 2, 3, 1, 2, 3]
3、原型和__proto__之间的区别
原型属性是与 JavaScript 中的构造函数相关的属性。构造函数用于在 JavaScript 中创建对象。定义构造函数时还可以将属性和方法附加到其原型属性。
然后从该构造函数创建的对象的所有实例都可以访问这些属性和方法。因此prototype 属性充当在实例之间共享的方法和属性的公共存储库。
考虑以下代码片段
// Constructor function
function Person(name) {this.name name;
}// Adding a method to the prototype
Person.prototype.sayHello function() {console.log(Hello, my name is ${this.name}.);
};// Creating instances
const person1 new Person(Haider Wain);
const person2 new Person(Omer Asif);// Calling the shared method
person1.sayHello(); // Output: Hello, my name is Haider Wain.
person2.sayHello(); // Output: Hello, my name is Omer Asif.
在此示例中我们有一个名为 Person 的构造函数。通过使用 sayHello 之类的方法扩展 Person.prototype我们将此方法添加到所有 Person 实例的原型链中。这允许 Person 的每个实例访问和利用共享方法。而不是每个实例都有自己的方法副本。
另一方面 __proto__ 属性通常发音为“dunder proto”存在于每个 JavaScript 对象中。在 JavaScript 中除了原始类型之外所有东西都可以被视为对象。这些对象中的每一个都有一个原型用作对另一个对象的引用。__proto__ 属性只是对此原型对象的引用。当原始对象不具备属性和方法时原型对象用作属性和方法的后备源。默认情况下当您创建对象时其原型设置为 Object.prototype。
当您尝试访问对象的属性或方法时JavaScript 会遵循查找过程来查找它。这个过程涉及两个主要步骤
对象自己的属性JavaScript 首先检查对象本身是否直接拥有所需的属性或方法。如果在对象中找到该属性则直接访问和使用它。
原型链查找如果在对象本身中找不到该属性JavaScript 将查看该对象的原型由 __proto__ 属性引用并在那里搜索该属性。此过程在原型链上递归地继续直到找到属性或查找到达 Object.prototype。
如果即使在 Object.prototype 中也找不到该属性JavaScript 将返回 undefined表明该属性不存在。
4、范围
编写 JavaScript 代码时理解作用域的概念很重要。范围是指代码不同部分中变量的可访问性或可见性。在继续该示例之前如果您不熟悉提升以及 JavaScript 代码的执行方式可以从此链接了解它。这将帮助您更详细地了解 JavaScript 代码的工作原理。
让我们仔细看看代码片段
function foo() {console.log(a);
}function bar() {var a 3;foo();
}var a 5;
bar();
该代码定义了 2 个函数 foo() 和 bar() 以及一个值为 5 的变量 a。所有这些声明都发生在全局范围内。在 bar() 函数内部声明了一个变量 a 并赋值为 3。那么当调用 thebar() 函数时你认为它会打印 a 的值是多少
当 JavaScript 引擎执行此代码时声明全局变量 a 并为其赋值 5。然后调用 bar() 函数。在 bar() 函数内部声明了一个局部变量 a 并赋值为 3。该局部变量 a 与全局变量 a 不同。之后从 bar() 函数内部调用 foo() 函数。
在 foo() 函数内部console.log(a) 语句尝试记录 a 的值。由于 foo() 函数的作用域内没有定义局部变量 aJavaScript 会查找作用域链以找到最近的名为 a 的变量。作用域链是指函数在尝试查找和使用变量时可以访问的所有不同作用域。
现在我们来解决 JavaScript 将在哪里搜索变量 a 的问题。它会在 bar 函数的范围内查找还是会探索全局范围事实证明JavaScript 将在全局范围内进行搜索而这种行为是由称为词法范围的概念驱动的。
词法作用域是指函数或变量在代码中编写时的作用域。当我们定义 foo 函数时它被授予访问其自己的本地作用域和全局作用域的权限。无论我们在哪里调用 foo 函数无论是在 bar 函数内部还是将其导出到另一个模块并在那里运行这个特征都保持一致。词法范围不是由我们调用函数的位置决定的。
这样做的结果是输出始终相同在全局范围内找到的 a 值在本例中为 5。
但是如果我们在 bar 函数中定义了 foo 函数则会出现不同的情况
function bar() {var a 3;function foo() {console.log(a);}foo();
}var a 5;
bar();
在这种情况下 foo 的词法作用域将包含三个不同的作用域它自己的局部作用域、 bar 函数的作用域和全局作用域。词法范围由编译时将代码放置在源代码中的位置决定。
当此代码运行时foo 位于 bar 函数内。这种安排改变了范围动态。现在当 foo 尝试访问变量 a 时它将首先在其自己的本地范围内进行搜索。由于它在那里找不到 a因此它将搜索范围扩大到 bar 函数的范围。你瞧a 存在其值为 3。因此控制台语句将打印 3。
5、对象强制
const obj {valueOf: () 42,toString: () 27
};
console.log(obj );
值得探索的一个有趣的方面是 JavaScript 如何处理对象到原始值例如字符串、数字或布尔值的转换。这是一个有趣的问题测试您是否知道强制转换如何与对象一起使用。
在字符串连接或算术运算等场景中处理对象时这种转换至关重要。为了实现这一点JavaScript 依赖于两个特殊的方法valueOf 和 toString。
valueOf 方法是 JavaScript 对象转换机制的基本部分。当在需要原始值的上下文中使用对象时JavaScript 首先在对象中查找 valueOf 方法。
如果 valueOf 方法不存在或未返回适当的原始值JavaScript 将回退到 toString 方法。该方法负责提供对象的字符串表示形式。
回到我们原来的代码片段
const obj {valueOf: () 42,toString: () 27
};console.log(obj );
当我们运行此代码时对象 obj 被转换为原始值。在本例中valueOf 方法返回 42然后由于与空字符串连接而隐式转换为字符串。因此代码的输出将为 42。
但是如果 valueOf 方法不存在或未返回适当的原始值JavaScript 将回退到 toString 方法。让我们修改一下之前的例子
const obj {toString: () 27
};console.log(obj );
这里我们删除了 valueOf 方法只留下 toString 方法该方法返回数字 27。在这种情况下JavaScript 将诉诸 toString 方法进行对象转换。
6、理解对象键
在 JavaScript 中使用对象时了解如何在其他对象的上下文中处理和分配键非常重要。考虑以下代码片段并花一些时间猜测输出
let a {};
let b { key: test };
let c { key: test };a[b] 123;
a[c] 456;console.log(a);
乍一看这段代码似乎应该生成一个具有两个不同键值对的对象 a。然而由于 JavaScript 对对象键的处理方式结果完全不同。
JavaScript 使用默认的 toString() 方法将对象键转换为字符串。但为什么在 JavaScript 中对象键始终是字符串或符号或者它们通过隐式强制转换自动转换为字符串。当您使用字符串以外的任何值例如数字、对象或符号作为对象中的键时JavaScript 会在将该值用作键之前在内部将该值转换为其字符串表示形式。
因此当我们使用对象 b 和 c 作为对象 a 中的键时两者都会转换为相同的字符串表示形式[object Object]。由于这种行为第二个赋值 a[b] 123; 将覆盖第一个赋值 a[c] 456;。
现在让我们逐步分解代码
let a {};初始化一个空对象a。let b { key: test };: 创建一个对象 b其属性键值为 test。let c { key: test };: 定义另一个与 b 结构相同的对象 c。a[b] 123;将对象a中键为[object Object]的属性设置为值123。
a[c] 456;将对象 a 中键 [object Object] 相同属性的值更新为 456替换之前的值。
两个分配都使用相同的键字符串 [object Object]。结果第二个赋值会覆盖第一个赋值设置的值。
当我们记录对象 a 时我们观察到以下输出
{ [object Object]: 456 }
7、运算符
console.log([] ![]);
这个有点复杂。那么您认为输出会是什么让我们逐步评估一下。让我们首先看看两个操作数的类型
typeof([]) // object
typeof(![]) // boolean
对于[]来说它是一个对象这是可以理解的。JavaScript 中的一切都是对象包括数组和函数。但是操作数![]如何具有布尔类型呢让我们试着理解这一点。当你使用 ! 对于原始值会发生以下转换
假值如果原始值是假值例如 false、0、null、undefined、NaN 或空字符串 则应用 ! 会将其转换为 true。
真值如果原始值是真值任何非假值则应用会将其转换为 false。
在我们的例子中[] 是一个空数组它是 JavaScript 中的真值。由于 [] 为真所以 ![] 变为假。所以我们的表达式就变成了
[] ![]
[] false
现在让我们继续了解 运算符。每当使用 运算符比较 2 个值时JavaScript 就会执行抽象相等比较算法。
该算法有以下步骤 正如您所看到的该算法考虑了比较值的类型并执行必要的转换。
对于我们的例子我们将 x 表示为 []将 y 表示为 ![]。我们检查了 x 和 y 的类型发现 x 是对象y 是布尔值。由于 y 是布尔值x 是对象因此应用抽象相等比较算法中的条件 7
如果 Type(y) 为 Boolean则返回 x ToNumber(y) 的比较结果。
这意味着如果其中一种类型是布尔值我们需要在比较之前将其转换为数字。ToNumber(y) 的值是多少正如我们所看到的[] 是一个真值否定则使其为假。结果Number(false)为0。
[] false
[] Number(false)
[] 0
现在我们有了比较 [] 0这次条件 8 开始发挥作用
如果 Type(x) 是 String 或 Number 并且 Type(y) 是 Object则返回比较结果 x ToPrimitive(y)。
基于这个条件如果其中一个操作数是对象我们必须将其转换为原始值。这就是 ToPrimitive 算法发挥作用的地方。我们需要将 [] x 转换为原始值。数组是 JavaScript 中的对象。正如我们之前所看到的当将对象转换为基元时valueOf 和 toString 方法就会发挥作用。
在这种情况下 valueOf 返回数组本身它不是有效的原始值。因此我们转向 toString 进行输出。将 toString 方法应用于空数组会得到一个空字符串这是一个有效的原语
[] 0
[].toString() 00
将空数组转换为字符串会得到一个空字符串“”现在我们面临比较“” 0。
现在其中一个操作数是字符串类型另一个操作数是数字类型则条件 5 成立
如果 Type(x) 是 String 并且 Type(y) 是 Number则返回比较结果 ToNumber(x) y。
因此我们需要将空字符串“”转换为数字即为 0。 0
ToNumber() 0
0 0
最后两个操作数具有相同的类型并且条件 1 成立。由于两者具有相同的值因此最终输出为
0 0 // true
到目前为止我们在探索的最后几个问题中使用了强制转换这是掌握 JavaScript 和在面试中解决此类问题的重要概念这些问题往往会被问到很多。
8、闭包
这是与闭包相关的最著名的面试问题之一
const arr [10, 12, 15, 21];
for (var i 0; i arr.length; i) {setTimeout(function() {console.log(Index: i , element: arr[i]);}, 3000);
}
如果您知道输出那就好了。那么让我们尝试理解这个片段。从表面上看这段代码片段将为我们提供以下输出
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
但这里的情况并非如此。由于闭包的概念以及 JavaScript 处理变量作用域的方式实际的输出会有所不同。当延迟 3000 毫秒后执行 setTimeout 回调时它们都将引用同一个变量 i循环完成后该变量的最终值为 4。结果代码的输出将是
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
出现此行为的原因是 var 关键字没有块作用域并且 setTimeout 回调捕获对同一 i 变量的引用。当回调执行时它们都会看到 i 的最终值即 4并尝试访问未定义的 arr[4]。
为了实现所需的输出您可以使用 let 关键字为循环的每次迭代创建一个新范围确保每个回调捕获 i 的正确值
const arr [10, 12, 15, 21];
for (let i 0; i arr.length; i) {setTimeout(function() {console.log(Index: i , element: arr[i]);}, 3000);
}
通过此修改您将获得预期的输出
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
使用 let 在每次迭代中为 i 创建一个新的绑定确保每个回调引用正确的值。
通常开发人员已经熟悉涉及 let 关键字的解决方案。然而面试有时会更进一步挑战你在不使用 let 的情况下解决问题。在这种情况下另一种方法是通过立即调用循环内的函数IIFE来创建闭包。这样每个函数调用都有自己的 i 副本。您可以这样做
const arr [10, 12, 15, 21];
for (var i 0; i arr.length; i) {(function(index) {setTimeout(function() {console.log(Index: index , element: arr[index]);}, 3000);})(i);
}
在此代码中立即调用的函数 (function(index) { ... })(i); 为每次迭代创建一个新范围捕获 i 的当前值并将其作为索引参数传递。这确保每个回调函数都有自己单独的索引值防止与闭包相关的问题并为您提供预期的输出
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
总结
以上就是我今天这篇文章想与您分享的8个关于JS的前端面试题 我希望这篇文章对您的面试准备之旅有所帮助。
如果您还有任何疑问请在留言区给我们留言我们一起交流学习进步。
最后推荐一款应用开发神器
扯个嗓子关于目前低代码在技术领域很活跃 低代码是什么一组数字技术工具平台能基于图形化拖拽、参数化配置等更为高效的方式实现快速构建、数据编排、连接生态、中台服务等。通过少量代码或不用代码实现数字化转型中的场景应用创新。它能缓解甚至解决庞大的市场需求与传统的开发生产力引发的供需关系矛盾问题是数字化转型过程中降本增效趋势下的产物。 这边介绍一款好用的低代码平台——JNPF快速开发平台。近年在市场表现和产品竞争力方面表现较为突出采用的是最新主流前后分离框架SpringBootMybatis-plusAnt-DesignVue3。代码生成器依赖性低灵活的扩展能力可灵活实现二次开发。
以JNPF为代表的企业级低代码平台为了支撑更高技术要求的应用开发从数据库建模、Web API构建到页面设计与传统软件开发几乎没有差异只是通过低代码可视化模式减少了构建“增删改查”功能的重复劳动还没有了解过低代码的伙伴可以尝试了解一下。
应用https://www.jnpfsoft.com/?csdn
有了它开发人员在开发过程中就可以轻松上手充分利用传统开发模式下积累的经验。所以低代码平台对于程序员来说有着很大帮助。