您当前的位置:首页 > 电脑百科 > 程序开发 > 前端

针对高级前端的八个级JavaScript面试问题

时间:2023-09-20 13:31:09  来源:微信公众号  作者:大迁世界

JAVAScript 是一种功能强大的语言,也是构建现代 Web 的基础之一。这种强大的语言也有一些自己的怪癖。例如,你知道 0 === -0 会计算为 true,或者 Number("") 会返回 0 吗?

有时候,这些怪癖会让你百思不得其解,甚至让你怀疑 Brendan 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。然而,在 duplicate 函数内部出现了一个严重的问题。

duplicate 函数使用循环来遍历给定数组中的每个项目。但在循环内部,它使用 push() 方法在数组末尾添加新元素。这导致数组每次都会变长,从而产生一个问题:循环永远不会停止。因为数组长度不断增加,循环条件(i < array.length)始终为真。这使得循环无限进行下去,导致程序陷入僵局。

为了解决由于数组长度增长而导致的无限循环问题,可以在进入循环之前将数组的初始长度存储在一个变量中。然后,可以使用这个初始长度作为循环迭代的限制。这样,循环只会针对数组中的原始元素进行,并不会受到由于添加重复项而导致数组增长的影响。以下是修改后的代码:

function duplicate(array) {
  var initialLength = array.length; // 存储初始长度
  for (var i = 0; i < initialLength; i++) {
    array.push(array[i]); // 推入每个元素的副本
  }
  return array;
}

const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);

输出将显示数组末尾的重复元素,并且循环不会导致无限循环:

[1, 2, 3, 1, 2, 3]

3- prototype 和 proto 的区别

prototype 属性是与 JavaScript 中的构造函数相关联的属性。构造函数用于在 JavaScript 中创建对象。当您定义一个构造函数时,还可以将属性和方法附加到其 prototype 属性上。这些属性和方法然后变得可以被该构造函数创建的所有对象实例访问。因此,prototype 属性充当共享方法和属性的通用存储库。

考虑以下代码片段:

// 构造函数
function Person(name) {
  this.name = name;
}

// 添加一个方法到 prototype
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}.`);
};

// 创建实例
const person1 = new Person("HAIder Wain");
const person2 = new Person("Omer Asif");

// 调用共享的方法
person1.sayHello();  // 输出:Hello, my name is Haider Wain.
person2.sayHello();  // 输出:Hello, my name is Omer Asif.

另一方面,__proto__ 属性,通常读作 "dunder proto",存在于每一个 JavaScript 对象中。在 JavaScript 中,除了原始类型外,一切都可以被视为对象。每个这样的对象都有一个原型,该原型作为对另一个对象的引用。__proto__ 属性简单地是对这个原型对象的引用。

当你试图访问对象上的一个属性或方法时,JavaScript 会进行查找过程来找到它。这个过程主要涉及两个步骤:

对象的自有属性:JavaScript 首先检查对象自身是否直接拥有所需的属性或方法。如果在对象内找到了该属性,则直接访问和使用。原型链查找:如果在对象自身没有找到该属性,JavaScript 将查看对象的原型(由 __proto__ 属性引用)并在那里搜索该属性。这个过程会递归地沿着原型链进行,直到找到该属性或直到查找达到 Object.prototype。如果在 Object.prototype 中甚至没有找到该属性,JavaScript 将返回 undefined,表示该属性不存在。

4-作用域

当编写 JavaScript 代码时,理解作用域的概念非常重要。作用域指的是变量在代码的不同部分的可访问性或可见性。下面我们通过一个代码片段来更仔细地了解这个概念:

function foo() {
  console.log(a);
}

function bar() {
  var a = 3;
  foo();
}

var a = 5;
bar();

代码定义了两个函数 foo() 和 bar(),以及一个值为5的变量 a。所有这些声明都发生在全局作用域中。在bar()函数内部,声明了一个变量a并赋值为 3。那么当bar()函数被调用时,你认为会输出哪个值的a

当JavaScript引擎执行这段代码时,全局变量a被声明并赋值为5。然后调用了bar()函数。在bar()函数内部,声明了一个局部变量a并赋值为3。这个局部变量a与全局变量a是不同的。之后,从bar()函数内部调用了foo()函数。

foo()函数内部,console.log(a)语句试图输出变量a的值。由于在foo()函数的作用域内没有定义局部变量a,JavaScript会查找作用域链以找到最近的名为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函数的作用域。果然,那里存在一个值为3a。因此,控制台语句将输出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 方法,只留下了返回数字27toString方法。在这种情况下,JavaScript 将依赖 toString 方法进行对象转换。

6-理解对象键(Object Keys)

当在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中,对象键总是字符串(或 symbols),或者通过隐式强制转换自动转换为字符串。当你在对象中使用除字符串之外的任何值(例如,数字、对象或符号)作为键时,JavaScript将在使用它作为键之前内部将该值转换为其字符串表示形式。

因此,当我们在对象a中使用对象bc作为键时,两者都转换为相同的字符串表示形式:[object Object]。由于这种行为,第二个赋值a[c] = '456';会覆盖第一个赋值a[b] = '123';

最终,当我们记录对象a时,我们观察到以下输出:

{ '[object Object]': '456' }

7-双等号运算符

console.log([] == ![]);

这个有点复杂。那么,你认为输出会是什么呢?

这个问题相当复杂。那么,你认为输出结果会是什么呢?让我们一步一步地来评估。首先,让我们看一下两个操作数的类型:

typeof([]) // "object"
typeof(![]) // "boolean"

对于 [],它是一个对象,这是可以理解的,因为在JavaScript中,包括数组和函数在内的一切都是对象。但操作数 ![] 是如何具有布尔类型的呢?让我们尝试理解一下。当你使用 ! 与一个原始值(primitive value)一起时,会发生以下转换:

  • Falsy Values(假值):如果原始值是一个假值(例如 false0nullundefinedNaN 或一个空字符串 ''),应用 ! 将把它转换为 true。
  • Truthy Values(真值):如果原始值是一个真值(即任何不是假值的值),应用 ! 将把它转换为 false。

在我们的案例中,[] 是一个空数组,这在JavaScript中是一个真值。因为 [] 是真值,![] 变成了 false。因此,我们的表达式变为:

[] == ![]
[] == false

现在,让我们继续了解 == 运算符。当使用 == 运算符比较两个值时,JavaScript会执行“抽象相等性比较算法(Abstract Equality Comparison Algorithm)”。这个算法会考虑比较值的类型并进行必要的转换。

图片

在我们的情况中,让我们把 x 记作 []y 记作 ![]。我们检查了 x 和 y 的类型,并发现 x 是对象,y 是布尔值。由于 y 是布尔值,x 是对象,算法的第7个条件被应用:

如果 Type(y) 是 Boolean,则返回 x == ToNumber(y) 的比较结果。

这意味着如果其中一个类型是布尔值,我们需要在比较之前将其转换为数字。ToNumber(y) 的值是多少呢?如我们所见,[] 是一个真值,取反使其变为 false。因此,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() == 0
"" == 0

将空数组转换为字符串给了我们一个空字符串 "",现在我们面对的比较是:"" == 0

现在其中一个操作数的类型是字符串,另一个是数字,算法的第5个条件成立:

如果 Type(x) 是 String,而 Type(y) 是 Number,则返回 ToNumber(x) == y 的比较结果。

因此,我们需要将空字符串 "" 转换为数字,这给了我们一个 0

"" == 0
ToNumber("") == 0
0 == 0

最后,两个操作数具有相同的类型和条件1成立。由于两者具有相同的值,最终的输出是:

0 == 0 // true

至此,我们已经利用了强制转换(coercion)来解决了我们探讨的最后几个问题,这是掌握JavaScript和解决面试中这类常见问题的重要概念。我强烈建议你查看我的关于强制转换的详细博客文章。它以清晰和彻底的方式解释了这个概念。这里是链接。



Tags:前端   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
20k级别前端是怎么使用LocalStorage的,想知道吗?
当咱们把咱们想缓存的东西,存在localStorage、sessionStorage中,在开发过程中,确实有利于咱们的开发,咱们想看的时候也是一目了然,点击Application就可以看到。前言大家好,我是林...【详细内容】
2024-03-26  Search: 前端  点击:(11)  评论:(0)  加入收藏
前端不存在了?盲测64%的人更喜欢GPT-4V的设计,杨笛一等团队新作
3 月 9 日央视的一档节目上,百度创始人、董事长兼 CEO 李彦宏指出,以后不会存在「程序员」这种职业了,因为只要会说话,人人都会具备程序员的能力。「未来的编程语言只会剩下两种...【详细内容】
2024-03-11  Search: 前端  点击:(9)  评论:(0)  加入收藏
前端开始“锈化”?Vue团队开源JS打包工具:基于Rust、速度极快、尤雨溪主导
Vue 团队已正式开源Rolldown &mdash;&mdash; 基于 Rust 的 JavaScrip 打包工具。Rolldown 是使用 Rust 开发的 Rollup 替代品,它提供与 Rollup 兼容的应用程序接口和插件接口...【详细内容】
2024-03-09  Search: 前端  点击:(11)  评论:(0)  加入收藏
两年前端经验还不会手写Promise?
什么是promise?当我们处理异步操作时,我们经常需要进行一系列的操作,如请求数据、处理数据、渲染UI等。在过去,这些操作通常通过回调函数来处理,但是回调函数嵌套过多会导致代码...【详细内容】
2024-03-07  Search: 前端  点击:(23)  评论:(0)  加入收藏
十个前端冷门但好用的前端工具函数库
本文推荐十个冷门但好用的前端工具函数仓库,它们可能没有很高的知名度,但却能为你解决实际问题,提高开发效率。在前端开发中,有时候我们会遇到一些常见的功能需求,但却不知道从哪...【详细内容】
2024-02-27  Search: 前端  点击:(21)  评论:(0)  加入收藏
前端开发:Visual Studio Code和Visual studio如何选?
Visual Studio Code和Visual studio都是微软的集成开发环境(IDE),那么在实际工作中该如何选择呢。贝格前端工场对二者做一番对比,帮助您决策一下。一、Visual Studio Code的介绍...【详细内容】
2024-02-27  Search: 前端  点击:(46)  评论:(0)  加入收藏
网站开发中的前端和后端开发有什么区别
前端开发和后端开发都是干什么的?有哪些区别?通俗地讲,前端干的工作是用户可以直接看得见的,而后端开发的工作主要在服务端,用户不太能直接看到。虽然前端开发和后端开发的工作有...【详细内容】
2024-02-21  Search: 前端  点击:(31)  评论:(0)  加入收藏
一段微信小程序前端与后端连接的代码,带注解
微信小程序的前端和后端连接通常涉及到使用微信小程序提供的网络请求API与后端服务器进行通信。以下是一个简单的示例,展示如何使用微信小程序的前端代码向后端发送请求并处...【详细内容】
2024-01-24  Search: 前端  点击:(55)  评论:(0)  加入收藏
如何优雅的实现前端国际化?
JavaScript 中每个常见问题都有许多成熟的解决方案。当然,国际化 (i18n) 也不例外,有很多成熟的 JavaScript i18n 库可供选择,下面就来分享一些热门的前端国际化库!i18nexti18ne...【详细内容】
2024-01-17  Search: 前端  点击:(68)  评论:(0)  加入收藏
JavaScript前端框架2024年展望
Angular、Next.js、React和Solid的维护者和创作者们展望2024年,分享了他们计划中的改进。译自2024 Predictions by JavaScript Frontend Framework Maintainers,作者 Loraine...【详细内容】
2024-01-05  Search: 前端  点击:(89)  评论:(0)  加入收藏
▌简易百科推荐
20k级别前端是怎么使用LocalStorage的,想知道吗?
当咱们把咱们想缓存的东西,存在localStorage、sessionStorage中,在开发过程中,确实有利于咱们的开发,咱们想看的时候也是一目了然,点击Application就可以看到。前言大家好,我是林...【详细内容】
2024-03-26  前端之神  微信公众号  Tags:前端   点击:(11)  评论:(0)  加入收藏
前端不存在了?盲测64%的人更喜欢GPT-4V的设计,杨笛一等团队新作
3 月 9 日央视的一档节目上,百度创始人、董事长兼 CEO 李彦宏指出,以后不会存在「程序员」这种职业了,因为只要会说话,人人都会具备程序员的能力。「未来的编程语言只会剩下两种...【详细内容】
2024-03-11  机器之心Pro    Tags:前端   点击:(9)  评论:(0)  加入收藏
前端开始“锈化”?Vue团队开源JS打包工具:基于Rust、速度极快、尤雨溪主导
Vue 团队已正式开源Rolldown &mdash;&mdash; 基于 Rust 的 JavaScrip 打包工具。Rolldown 是使用 Rust 开发的 Rollup 替代品,它提供与 Rollup 兼容的应用程序接口和插件接口...【详细内容】
2024-03-09  OSC开源社区    Tags:Vue   点击:(11)  评论:(0)  加入收藏
两年前端经验还不会手写Promise?
什么是promise?当我们处理异步操作时,我们经常需要进行一系列的操作,如请求数据、处理数据、渲染UI等。在过去,这些操作通常通过回调函数来处理,但是回调函数嵌套过多会导致代码...【详细内容】
2024-03-07  海燕技术栈  微信公众号  Tags:Promise   点击:(23)  评论:(0)  加入收藏
网站开发中的前端和后端开发有什么区别
前端开发和后端开发都是干什么的?有哪些区别?通俗地讲,前端干的工作是用户可以直接看得见的,而后端开发的工作主要在服务端,用户不太能直接看到。虽然前端开发和后端开发的工作有...【详细内容】
2024-02-21  CarryData    Tags:前端   点击:(31)  评论:(0)  加入收藏
网站程序开发中的前后端分离技术
随着互联网的快速发展和技术的不断创新,传统的网站开发模式已经难以满足日益增长的业务需求。为了提高开发效率、增强系统的可维护性和可扩展性,前后端分离技术逐渐成为了网站...【详细内容】
2024-01-31  网站建设派迪星航    Tags:前后端分离   点击:(23)  评论:(0)  加入收藏
如何优雅的实现前端国际化?
JavaScript 中每个常见问题都有许多成熟的解决方案。当然,国际化 (i18n) 也不例外,有很多成熟的 JavaScript i18n 库可供选择,下面就来分享一些热门的前端国际化库!i18nexti18ne...【详细内容】
2024-01-17  前端充电宝  微信公众号  Tags:前端   点击:(68)  评论:(0)  加入收藏
Vue中Scope是怎么做样式隔离的?
scope样式隔离在 Vue 中,样式隔离是通过 scoped 特性实现的。当在一个组件的 <style> 标签上添加 scoped 特性时,Vue 会自动为这个样式块中的所有选择器添加一个唯一的属性,以...【详细内容】
2024-01-04  海燕技术栈  微信公众号  Tags:Vue   点击:(80)  评论:(0)  加入收藏
vue3中 ref和 reactive的区别 ?
最近有朋友在面试过程中经常被问到这么一个问题,vue3 中的ref 和 reactive的区别在哪里,为什么 要定义两个API 一个 api不能实现 响应式更新吗??带着这个疑问 ,我们 接下来进行逐...【详细内容】
2024-01-03  互联网高级架构师  今日头条  Tags:vue3   点击:(37)  评论:(0)  加入收藏
React18 与 Vue3 全方面对比
1. 编程风格 & 视图风格1.1 编程风格 React 语法少、难度大;Vue 语法多,难度小例如指令:Vue<input v-model="username"/><ul> <li v-for="(item,index) in list" :key="inde...【详细内容】
2024-01-03  爱做梦的程序员  今日头条  Tags:Vue3   点击:(72)  评论:(0)  加入收藏
站内最新
站内热门
站内头条