JavaScript is a powerful language that is one of the primary building blocks of the web. This powerful language also has some of its quirks. For instance, did you know that 0 === -0
evaluates to true, or that Number("")
yields 0?
JavaScript 是一种功能强大的语言,是网络的主要构建模块之一。这种强大的语言也有一些怪癖。例如,你知道 0 === -0
的值为 true,或者Number("")
的值为 0 吗?
The thing is sometimes these quirks can leave you scratching your head or even questioning was Brendon Eich high, the day he was inventing JavaScript. Well, the point is not here that JavaScript is a bad programming language or it is evil as its critics say. All programming languages have some sort of weirdness associated with them and JavaScript is not an exception.
问题是,有时这些怪癖会让你摸不着头脑,甚至质疑 Brendon Eich 发明 JavaScript 的那一天是不是嗑药了。这里的重点不是说 JavaScript 是一门糟糕的编程语言或者像它的批评者所说的那样邪恶,所有编程语言都有某种与之相关的奇怪之处,JavaScript 也不例外。
In this blog post, we will see an in-depth explanation of some important JavaScript interview questions. My goal will be to explain these interview questions thoroughly so that we can understand the underlying concepts and hopefully solve other similar questions in interviews.
在本篇博文中,我们将深入讲解一些重要的 JavaScript 面试问题。我的目标是透彻地解释这些面试问题,以便我们能够理解其基本概念,并希望可以解决面试中其他类似的问题。
1-A Closer Look at the + and - Operators
1-仔细观察 + 和 - 运算符
**
console.log(1 + '1' - 1); log(1 + '1' - 1);
Can you guess the behaviour of JavaScript’s + and - operators in situations like the one above?
您能猜出 JavaScript 的 + 和 - 运算符在上述情况下的行为吗?
When JavaScript encounters 1 + '1'
, it processes the expression using the + operator. One interesting property of the + operator is that it prefers string concatenation when one of the operands is a string. In our case, ‘1’ is a string, so JavaScript implicitly coerces the numeric value 1 into a string. Consequently, 1 + '1'
becomes '1' + '1'
, resulting in the string '11'
.
当 JavaScript 遇到1 + '1'
时,它会使用 + 运算符处理表达式。 + 运算符的一个有趣的属性是,当操作数之一是字符串时,它更喜欢字符串连接。在我们的例子中,“1”是一个字符串,因此 JavaScript 隐式地将数值 1 强制转换为字符串。因此,1 + '1'
变为'1' + '1'
,结果是字符串 '11'
。
Now, our equation is '11' - 1
. The behaviour of the - operator is quite the opposite. It prioritizes numeric subtraction, regardless of the types of operands. When the operands are not of the number type, JavaScript performs implicit coercion to convert them into numbers. In this case, '11'
is converted to the numeric value 11, and the expression simplifies to 11 - 1
.
现在,我们的等式是'11' - 1
。 - 运算符的行为恰恰相反。无论操作数的类型如何,它都会优先考虑数字减法。当操作数不是数字类型时,JavaScript 会执行隐式强制转换,将其转换为数字。在本例中, '11'
被转换为数值 11,并且表达式简化为11 - 1
。
Putting it all together:
把它们放在一起来看:
'11' - 1 = 11 - 1 = 10
2-Duplicating Array Elements
2-复制数组元素
**
Consider the following JavaScript code and try to find any issues in this code:
考虑以下 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);
In this code snippet, we are required to create a new array containing the duplicated elements of the input array. Upon initial inspection, the code appears to create a new array newArr
, by duplicating each element from the original array arr
. However, a critical issue arises within the duplicate
function itself.
在这段代码片段中,我们需要创建一个新的数组,其中包含输入数组中的重复元素。初步检查代码时,看起来它通过复制原始数组 arr
中的每个元素来创建一个新的数组 newArr
。然而,在 duplicate
函数内部出现了一个关键问题。
The duplicate
function uses a loop to go through each item in the given array. But inside the loop, it is adding a new element at the end of the array, using the push()
method. This makes the array longer each time, creating a problem where the loop never stops. The loop condition (i < array.length
) always stays true because the array keeps getting bigger. This makes the loop go on forever, causing the program to get stuck.
duplicate
函数使用循环遍历给定数组中的每个元素。但是在循环内部,它使用push()
方法在数组末尾添加一个新元素。这使得数组每次都变得更长,导致循环永远不会停止。循环条件(i < array.length
)始终为真,因为数组不断增长。这使得循环无限进行下去,导致程序卡住。
To address the problem of the infinite loop caused by the growing array length, you can store the initial length of the array in a variable before entering the loop. Then, you can use this initial length as the limit for the loop iteration. This way, the loop will only run for the original elements in the array and won’t be affected by the array’s growth due to duplicates being added. Here is the modified version of the code:
为了解决由于数组长度增长而导致的无限循环问题,您可以在进入循环之前将数组的初始长度存储在一个变量中。然后,您可以将这个初始长度作为循环迭代的限制条件。这样,循环只会运行原始数组中的元素,并不会受到由于添加重复元素而导致的数组增长的影响。以下是代码的修改版本:
function duplicate(array) {
var initialLength = array.length; // Store the initial length
for (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);
The output will show the duplicated elements at the end of the array, and the loop won’t result in an infinite loop:
输出结果将显示数组末尾的重复元素,循环不会导致无限循环:
[1, 2, 3, 1, 2, 3]
3-Difference between prototype and proto
3-原型和__proto__的区别
**
The prototype
property is an attribute associated with constructor functions in JavaScript. Constructor functions are used to create objects in JavaScript. When you define a constructor function, you can also attach properties and methods to its prototype
property. These properties and methods then become accessible to all instances of objects created from that constructor. Thus, theprototype
property serves as a common repository for methods and properties that are shared among instances.
prototype
属性是 JavaScript 中与构造函数相关联的一个属性。构造函数用于在 JavaScript 中创建对象。当你定义一个构造函数时,你还可以将属性和方法附加到它的 prototype
属性上。这些属性和方法随后可以被从该构造函数创建的所有对象实例访问。因此,prototype
属性充当了一个共享存储库**,其中包含了在实例之间共享的方法和属性。**
Consider the following code snippet:
考虑以下代码片段:
// 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.
In this example, we have a constructor function named Person
. By extending the Person.prototype
with a method like sayHello
, we’re adding this method to the prototype chain of all Person
instances. This allows each instance of Person
to access and utilize the shared method. Instead of each instance having its own copy of the method.
在这个例子中,我们有一个名为 Person
的构造函数。通过将Person.prototype
扩展一个像sayHello
这样的方法,我们将这个方法添加到所有Person
实例的原型链中。这使得每个Person
实例都可以访问和利用这个共享方法。而不是每个实例都有自己的方法副本。
On the other hand, the __proto__
property, often pronounced as “dunder proto,” exists in every JavaScript object. In JavaScript, everything, except primitive types, can be treated as an object. Each of these objects has a prototype, which serves as a reference to another object. The __proto__
property is simply a reference to this prototype object. The prototype object is used as a fallback source for properties and methods when the original object doesn’t possess them. By default, when you create an object, its prototype is set to Object.prototype
.
另一方面,__proto__
属性,通常发音为"dunder proto",存在于每个JavaScript对象中。在JavaScript中,除了原始类型,其他所有东西都可以被视为对象。每个对象都有一个原型,它作为对另一个对象的引用。__proto__
属性只是对这个原型对象的引用。当原始对象没有某个属性或方法时,原型对象被用作备用来源。默认情况下,当你创建一个对象时,它的原型被设置为Object.prototype
.
When you attempt to access a property or method on an object, JavaScript follows a lookup process to find it. This process involves two main steps:
当你尝试访问对象的属性或方法时,JavaScript会按照一定的查找过程来寻找它。这个过程包括两个主要步骤:
- Object’s Own Properties: JavaScript first checks if the object itself directly possesses the desired property or method. If the property is found within the object, it’s accessed and used directly.
1.对象自身的属性:JavaScript首先检查对象本身是否直接拥有所需的属性或方法。如果属性在对象内找到,就直接访问和使用它。
- **Prototype Chain Lookup:**If the property is not found in the object itself, JavaScript looks at the object’s prototype (referenced by the
__proto__
property) and searches for the property there. This process continues recursively up the prototype chain until the property is found or until the lookup reaches theObject.prototype
.
2.原型链查找:如果属性在对象本身未找到,JavaScript会查看对象的原型(通过__proto__
属性引用),并在那里搜索该属性。这个过程会递归地沿着原型链向上继续,直到找到属性或者查找到达 Object.prototype
.
If the property is not found even in the Object.prototype
, JavaScript returns undefined
, indicating that the property does not exist.
如果即使在Object.prototype
中也找不到该属性,JavaScript会返回undefined
,表示该属性不存在。
4-Scopes
4-范围
**
When writing JavaScript code, it’s important to understand the concept of scope. Scope refers to the accessibility or visibility of variables within different parts of your code. Before proceeding with the example, if you’re unfamiliar with hoisting and how JavaScript code is executed, you can learn about it from this link. This will help you understand how JavaScript code works in more detail.
编写 JavaScript 代码时,理解作用域的概念很重要。范围是指代码不同部分中变量的可访问性或可见性。在继续该示例之前,如果您不熟悉提升以及 JavaScript 代码的执行方式,可以从此链接了解它。这将帮助您更详细地了解 JavaScript 代码的工作原理。
Let’s take a closer look at the code snippet:
让我们仔细看看代码片段:
function foo() {
console.log(a);
}
function bar() {
var a = 3;
foo();
}
var a = 5;
bar();
The code defines 2 functions foo()
and bar()
and a variable a
with a value of 5
. All these declarations happen in the global scope. Inside the bar()
function, a variable a
is declared and assigned the value 3
. So when thebar()
function is called, what value of a
do you think it will print?
这段代码定义了两个函数foo()
和bar()
,以及一个值为5
的变量a
。所有这些声明都发生在全局作用域内。在bar()
函数内部,声明了一个变量a
并赋值为3
。所以当调用bar()
函数时,你认为它会打印出什么值的a
?
When the JavaScript engine executes this code, the global variable a
is declared and assigned the value 5
. Then the bar()
function is called. Inside the bar()
function, a local variable a
is declared and assigned the value 3
. This local variable a
is distinct from the global variable a
. After that, the foo()
function is called from within the bar()
function.
当JavaScript引擎执行这段代码时,全局变量a
被声明并赋值为5
。然后调用bar()
函数。在bar()
函数内部,声明了一个局部变量a
并赋值为3
。这个局部变量a
与全局变量a
是不同的。之后,从bar()
函数内部调用了foo()
函数。
Inside the foo()
function, the console.log(a)
statement tries to log the value of a
. Since there is no local variable a
defined within the foo()
function’s scope, JavaScript looks up the scope chain to find the nearest variable named a
. The scope chain refers to all the different scopes that a function has access to when it’s trying to find and use variables.
在foo()
函数内部,console.log(a)
语句试图打印出变量a
的值。由于在foo()
函数的作用域内没有定义局部变量a
,JavaScript会沿着作用域链查找最近的名为a
的变量。作用域链指的是函数在查找和使用变量时可以访问的所有不同作用域。
Now, let’s address the question of where JavaScript will search for the variable a
. Will it look within the scope of the bar
function, or will it explore the global scope? As it turns out, JavaScript will search in the global scope, and this behaviour is driven by a concept called lexical scope.
现在,让我们来解答一下 JavaScript 将在哪里搜索变量 a
的问题。它会在 bar
函数的作用域内搜索吗?还是会探索全局作用域?事实证明,JavaScript 会在全局作用域中进行搜索,而这种行为是由一个叫做“词法作用域”的概念驱动的。
Lexical scope refers to the scope of a function or variable at the time it was written in the code. When we defined the foo
function, it was given access to both its own local scope and the global scope. This characteristic remains consistent no matter where we call the foo
function—whether inside the bar
function or if we export it to another module and run it there. Lexical scope is not determined where we call the function.
词法作用域指的是函数或变量在代码编写时的作用范围。当我们定义foo
函数时,它被赋予了访问自己的局部作用域和全局作用域的权限。无论我们在哪里调用foo
函数,无论是在bar
函数内部还是将其导出到另一个模块并在那里运行,这个特性始终保持一致。词法作用域并不是由我们调用函数的位置决定的。
The upshot of this is that the output will always be the same: the value of a
found in the global scope, which in this case is 5
.
这样的结果是输出始终都会是相同的:在全局作用域中找到的变量a
的值,这种情况下是5
。
However, if we had defined the foo
function within the bar
function, a different scenario emerges:
然而,如果我们在bar
函数内部定义了foo
函数,就会出现不同的情况。
function bar() {
var a = 3;
function foo() {
console.log(a);
}
foo();
}
var a = 5;
bar();
In this situation, the lexical scope of foo
would encompass three distinct scopes: its own local scope, the scope of the bar
function, and the global scope. Lexical scope is determined by where you place your code in the source code during compile time.
在这种情况下,foo
的词法作用域将包含三个不同的作用域:它自己的局部作用域、bar
函数的作用域和全局作用域。词法作用域是由编译时代码在源代码中的位置决定的。
When this code runs, foo
is situated within the bar
function. This arrangement alters the scope dynamics. Now, when foo
attempts to access the variable a
, it will first search within its own local scope. Since it doesn’t find a
there, it will broaden its search to the scope of the bar
function. Lo and behold, a
exists there with the value 3. As a result, the console statement would prints 3
.
当这段代码运行时,foo
位于bar
函数内部。这种安排改变了作用域的动态。现在,当foo
试图访问变量a
时,它首先会在自己的局部作用域中搜索。由于在那里找不到a
,它会扩大搜索范围到bar
函数的作用域。果然,a
在那里存在,并且值为3。因此,控制台会打印出3
。
5-Object Coercion
5-对象强制转换
const obj = {
valueOf: () => 42,
toString: () => 27
};
console.log(obj + '');
One intriguing facet to explore is how JavaScript handles the conversion of objects into primitive values, such as strings, numbers, or booleans. This is an interesting question that tests whether you know how coercion works with Objects.
一个有趣的方面值得探索的是JavaScript如何处理将对象转换为原始值,比如字符串、数字或布尔值。这是一个有趣的问题,可以测试你是否了解对象的强制转换方式。
This conversion is crucial when working with objects in scenarios like string concatenation or arithmetic operations. To achieve this, JavaScript relies on two special methods: valueOf
and toString
.
在像字符串拼接或算术运算这样的场景中,这种转换是非常关键的。为了实现这一点,JavaScript依赖于两个特殊的方法:valueOf
和 toString
。
The valueOf
method is a fundamental part of JavaScript’s object conversion mechanism. When an object is used in a context that requires a primitive value, JavaScript first looks for the valueOf
method within the object. In cases where the valueOf
method is either absent or doesn’t return an appropriate primitive value, JavaScript falls back to the toString
method. This method is responsible for providing a string representation of the object.
valueOf
方法是JavaScript对象转换机制的一个基本部分。当一个对象在需要原始值的上下文中使用时,JavaScript首先在对象内部查找valueOf
方法。在valueOf
方法不存在或者没有返回适当的原始值的情况下,JavaScript会回退到toString
方法。toString
方法负责提供对象的字符串表示形式。
Returning to our original code snippet:
回到我们的原始代码片段:
const obj = {
valueOf: () => 42,
toString: () => 27
};
console.log(obj + '');
When we run this code, the object obj
is converted to a primitive value. In this case, the valueOf
method returns 42
, which is then implicitly converted to a string due to the concatenation with an empty string. Consequently, the output of the code will be 42
.
当我们运行这段代码时,对象obj
会被转换为一个原始值。在这种情况下,valueOf
方法返回42
,然后由于与空字符串的连接而隐式地转换为字符串。因此,代码的输出将是42
。
However, in cases where the valueOf
method is either absent or doesn’t return an appropriate primitive value, JavaScript falls back to the toString
method. Let’s modify our previous example:
然而,在 valueOf
方法不存在或者没有返回适当的原始值的情况下,JavaScript会回退到toString
方法。让我们修改之前的例子:
const obj = {
toString: () => 27
};
console.log(obj + '');
Here, we’ve removed the valueOf
method, leaving only the toString
method, which returns the number 27
. In this scenario, JavaScript will resort to the toString
method for object conversion.
在这里,我们已经移除了valueOf
方法,只保留了toString
方法,它返回数字27
。在这种情况下,JavaScript会使用toString
方法进行对象转换。
6-Understanding Object Keys
6-理解对象键
**
When working with objects in JavaScript, it’s important to grasp how keys are treated and assigned within the context of other objects. Consider the following code snippet and take some time to guess the output:
在JavaScript中使用对象时,理解键在其他对象上下文中的处理和分配方式非常重要。考虑以下代码片段,并花些时间猜测输出结果:
let a = {};
let b = { key: 'test' };
let c = { key: 'test' };
a[b] = '123';
a[c] = '456';
console.log(a);
At first glance, it might seem like this code should produce an object a
with two distinct key-value pairs. However, the outcome is quite different due to JavaScript’s handling of object keys.
乍一看,这段代码似乎应该生成一个具有两个不同键值对的对象a
。然而,由于JavaScript对对象键的处理方式不同,结果却大不相同。
JavaScript employs the default toString()
method to convert object keys into strings. But why? In JavaScript, object keys are always strings(or symbols), or they are automatically converted to strings via implicit coercion. When you use any value other than a string (e.g., a number, object, or symbol) as a key in an object, JavaScript will internally convert that value to its string representation before using it as a key.
JavaScript使用默认的toString()
方法将对象键转换为字符串。但是为什么呢?在JavaScript中,对象键始终是字符串(或符号),或者它们通过隐式强制转换自动转换为字符串。当您在对象的键中使用除字符串(例如数字、对象或符号)以外的任何值时,JavaScript会在使用它作为键之前将该值内部转换为其字符串表示形式。
Consequently, when we use objects b
and c
as keys in object a
, both are transformed into the same string representation: [object Object]
. Due to this behaviour, the second assignment, a[b] = '123';
will overwrite the first assignment a[c] = '456';
. Let’s break down the code step by step:
因此,当我们将对象b
和c
作为对象a
的键时,它们都会被转换为相同的字符串表示:[object Object]
。由于这种行为,第二个赋值语句a[b] = '123';
会覆盖第一个赋值语句a[c] = '456';
。让我们逐步分解这段代码:
let a = {};
: Initializes an empty objecta
.let b = { key: 'test' };
: Creates an objectb
with a propertykey
having the value'test'
.let c = { key: 'test' };
: Defines another objectc
with the same structure asb
.a[b] = '123';
: Sets the value'123'
to the property with key[object Object]
in objecta
.a[c] = '456';
: Updates the value to'456'
for the same property with key[object Object]
in objecta
, replacing the previous value.
1. let a = {};
: 初始化一个空对象 a
.
2. let b = { key: 'test' };
:创建一个具有属性key
且值为'test'
的对象b
.
**3. let c = { key: 'test' };
: 定义另一个与b
具有相同结构的对象 c
.
a[b] = '123';
: 将值'123'
赋给a
对象中以[object Object]
为键的属性。a[c] = '456';
: 更新a
对象中以[object Object]
为键的属性的值为'456'
,替换之前的值。**
Both assignments utilize the identical key string [object Object]
. As a result, the second assignment overwrites the value set by the first assignment.
这两个赋值语句都使用了相同的键字符串 [object Object]
。因此,第二个赋值语句会覆盖第一个赋值语句设置的值。
When we log the object a
, we observe the following output:
当我们记录对象a
时,我们观察到以下输出:
{ '[object Object]': '456' }
7-The Double Equals Operator
7-双等号操作符
console.log([] == ![]);
This one is a bit complex. So, what do you think will be the output? Let’s evaluate this step by step. Let’s first start by seeing the types of both operands:
这个有点复杂。那么,您认为输出会是什么?让我们逐步评估一下。首先,让我们看看两个操作数的类型:
typeof([]) // "object"
typeof(![]) // "boolean"
For []
it is an object
, which is understandable. As everything in JavaScript is an object including arrays and functions. But how does the operand![]
has a type of boolean
? Let’s try to understand this. When you use !
with a primitive value, the following conversions happen:
对于[]来说,它是一个对象,这是可以理解的。因为在JavaScript中,包括数组和函数在内的所有东西都是对象。但是,操作数![]怎么会有一个布尔类型呢?让我们试着理解一下。当你使用!与原始值一起使用时,会发生以下转换:
-
Falsy Values: If the original value is a falsy value (such as
false
,0
,null
,undefined
,NaN
, or an empty string''
), applying!
will convert it totrue
. -
Falsy Values: 如果原始值是一个假值(比如
false
、0
、null
、undefined
、NaN
或者空字符串''
),应用!
运算符将会将其转换为true
。 -
Truthy Values: If the original value is a truthy value (any value that is not falsy), applying
!
will convert it tofalse
.
真值:如果原始值是一个真值(任何不是假值的值),应用!将把它转换为false。
In our case []
is an empty array, which is a truthy value in JavaScript. Since []
is truthy, ![]
becomes false
. So our expression becomes:
在我们的例子中,[]
是一个空数组,在JavaScript中是一个真值。由于 []
是真值,![]
变成了false
.。所以我们的表达式变成了:
[] == ![]
[] == false
Now let’s move ahead and understand the ==
operator. Whenever 2 values are compared using ==
operator, JavaScript performs the Abstract Equality Comparison Algorithm. The algorithm has the following steps:
现在让我们继续前进,了解一下 ==
运算符。每当使用 ==
运算符比较两个值时,JavaScript会执行抽象相等比较算法。该算法包含以下步骤:
As you can see this algorithm takes into account the types of the compared values and performs necessary conversions.
正如您所看到的,该算法考虑了比较值的类型并执行必要的转换。
For our case, let’s denote x
as []
and y
as ![]
. We inspected the types of x
and y
and found x
as an object and y
as boolean. Since y is a boolean and x is an object, condition 7from the abstract equality comparison algorithm is applied:
对于我们的情况,我们将x
示为[]
,y
表示为![]
. 我们检查了x
和y
的类型,发现x
是一个对象,而y
是布尔值。由于y是布尔值而x是对象,根据抽象相等比较算法的条件7进行比较:
If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
如果y的类型是布尔型,返回x == ToNumber(y)的比较结果。
Meaning if one of the types is a boolean, we need to convert it into a number before comparison. What’s the value of ToNumber(y)? As we saw,[]
is a truthy value, negating makes itfalse
. As a result,Number(false)
is0
.
如果其中一种类型是布尔值,我们需要在比较之前将其转换为数字。那么ToNumber(y)的值是多少呢?正如我们所见,[]是一个真值,取反后变为假。因此,Number(false)的值是0。
[] == false
[] == Number(false)
[] == 0
Now we have the comparison [] == 0
and this time condition 8 comes into play:
现在我们有了比较 [] == 0,而这次条件是8起作用:
If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
如果 Type(x) 是字符串或数字,并且 Type(y) 是对象,则返回比较 x == ToPrimitive(y) 的结果。
Based on this condition, if one of the operands is an object, we must convert it into a primitive value. This is where the ToPrimitive algorithm comes into the picture. We need to convertx
which is[]
to a primitive value. Arrays are objects in JavaScript. As we saw earlier that when converting objects to primitives,valueOf
andtoString
methods come into play. In this case,valueOf
returns the array itself which is not a valid primitive value. As a result, we move totoString
for an output. Applying thetoString
method to an empty array results in obtaining an empty string, which is a valid primitive:
根据这个条件,如果操作数之一是一个对象,我们必须将其转换为原始值。这就是ToPrimitive算法发挥作用的地方。我们需要将x,即[],转换为原始值。在JavaScript中,数组是对象。正如我们之前看到的,当将对象转换为原始值时,valueOf和toString方法会发挥作用。在这种情况下,valueOf返回的是数组本身,而不是有效的原始值。因此,我们转而使用toString方法进行输出。将toString方法应用于空数组将得到一个空字符串,这是一个有效的原始值。
[] == 0
[].toString() == 0
"" == 0
Converting the empty array to a string gives us an empty string, ""
and now we face the comparison: "" == 0
.
将空数组转换为字符串会得到一个空字符串“”,现在我们面临比较:“”== 0。
Now that one of the operands is of the type string
and the other one is of the type number
, condition 5 holds:
现在,其中一个操作数是字符串类型,另一个操作数是数字类型,则条件 5 成立:
If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
如果 Type(x) 是 String 并且 Type(y) 是 Number,则返回比较结果 ToNumber(x) == y。
Hence, we need to convert the empty string,""
to a number, which gives us a0
.
因此,我们需要将空字符串“”转换为数字,即为 0。
"" == 0
ToNumber("") == 0
0 == 0
Finally, both operands have the same type and condition 1 holds. As both have the same value, the final output is:
最后,两个操作数具有相同的类型并且条件 1 成立。由于两者具有相同的值,因此最终输出为:
0 == 0 // true
So far we made use of coercion in the last few questions we explored, which is an important concept in mastering JavaScript and tackling questions like this in an interview, which tend to be asked a lot. I really encourage you to check out my detailed blog post about coercion. It explains this concept in a clear and thorough way. Here’s the link.
到目前为止,我们在探索的最后几个问题中使用了强制转换,这是掌握 JavaScript 和在面试中解决此类问题的重要概念,这些问题往往会被问到很多次,我真的鼓励您查看我关于强制的详细博客文章。它以清晰而彻底的方式解释了这个概念这是链接。
8-Closures
8-闭包
**
This is one of the most famous interview questions asked related to closures:
这是一个与闭包相关的最著名的面试问题之一:
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log('Index: ' + i + ', element: ' + arr[i]);
}, 3000);
}
If you know the output, then well and good. So let’s try to understand this snippet. At the face value, it looks like this snippet would give us the output of:
如果你知道输出结果,那就很好。所以让我们试着理解这段代码片段。从表面上看,这段代码片段似乎会给我们输出结果:
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
But this is not the case over here. Due to the concept of closures and how JavaScript handles variable scope, the actual output will be different. When the setTimeout
callbacks are executed after the delay of 3000 milliseconds, they will all reference the same variable i
, which will have a final value of 4
after the loop has been completed. As a result, the output of the code will be:
但是在这里情况并非如此。由于闭包的概念以及JavaScript对变量作用域的处理方式,实际输出将会有所不同。当延迟3000毫秒后执行 setTimeout
回调函数时,它们都会引用同一个变量 i
,而该变量在循环完成后的最终值将为4
。因此,代码的输出结果将会是:
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
This behaviour occurs because the var
keyword does not have a block scope, and the setTimeout
callbacks capture the reference to the same i
variable. When the callbacks execute, they all see the final value of i
, which is 4
, and try to access arr[4]
, which is undefined
.
这种行为发生是因为 var
关键字没有块级作用域,而setTimeout
的回调函数捕获了同一个 i
变量的引用。当回调函数执行时,它们都看到了最终的 i
值,即4
,并尝试访问arr[4]
, 而这个值是undefined
。
To achieve the desired output, you can use the let
keyword to create a new scope for each iteration of the loop, ensuring that each callback captures the correct value of i
:
为了实现期望的输出,你可以使用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);
}
With this modification, you will get the expected output:
通过这个修改,您将获得预期的输出:
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
Using let
creates a new binding for i
in each iteration, ensuring that each callback refers to the correct value.
使用 let
会在每次迭代中为 i
创建新的绑定,确保每次回调都指向正确的值。
Often, developers have become familiar with the solution involving the let
keyword. However, interviews can sometimes take a step further and challenge you to solve the problem without using let
. In such cases, an alternative approach involves creating a closure by immediately invoking a function(IIFE) inside the loop. This way, each function call has its own copy of i
. Here’s how you can do it:
通常情况下,开发人员已经熟悉了使用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);
}
In this code, the immediately invoked function (function(index) { ... })(i);
creates a new scope for each iteration, capturing the current value of i
and passing it as the index
parameter. This ensures that each callback function gets its own separate index
value, preventing the closure-related issue and giving you the expected output:
在这段代码中,立即调用的函数 (function(index) { ... })(i);
为每次迭代创建了一个新的作用域,捕获了当前的 i
值并将其作为 index
参数传递。这确保了每个回调函数都拥有自己独立的 index
值,避免了闭包相关的问题,并给出了预期的输出结果。
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
【参考文献】
**
上述译文仅供参考,原文请查看下面链接,解释权归原作者所有
文章:《8 Advanced JavaScript Interview Questions for Senior Roles》
作者:Rabi Siddiqu
发表日期:2023年8月20日
⚠️:文章翻译上如有语法不准确或者内容纰漏,欢迎各位评论区指正。
【关于TalkX】
TalkX是一款基于GPT实现的 IDE智能开发插件,专注于编程领域,是开发者在日常编码中提高编码效率及质量的辅助工具,TalkX常用的功能包括但不限于:解释代码、中英翻译、性能检查、安全检查、样式检查、优化并改进、提高可读性、清理代码、生成测试用例等。
TalkX建立了全球加速网络,不需要考虑网络环境,响应速度快,界面效果和交互体验更流畅。并为用户提供了OpenAI的密钥,不需要ApiKey,不需要自备账号,不需要魔法。
TalkX产品支持:JetBrains (包括 IntelliJ IDEA、PyCharm、WebStorm、Android
Studio)、HBuilder、VS Code、Goland.
PS:大家最关心的问题来咯!TalkX功能免费使用, GPT4的完美平替!