- Published on
BigFrontEnd JavaScript Quizzes
- Authors
- Name
- Yinhuan Yuan
Introduction
This blog post summarizes JavaScript Quizzes on BigFrontEnd.Dev.
- 1. Hoisting
- 2.Scope
- 3. Implicit Coercion
- 4.Closure
- 5.Prototype
- 6.this
- 7.Symbol
- 8.Promise
- 1.Promise order
- 2. Promise executor
- 3. Promise then callbacks
- 4. Promise then callbacks II
- 18. Promise executor II
- 23. Promise.all()
- 36. Promise.prototype.finally()
- 47. Promise Order II
- 98. Promise executor IV
- 103. Promise Order III
- 106. Promise.all() II
- 50. async await
- 130. async
- 131. return await
- 60. postMessage
- 61. onClick
- 62. MessageChannel
- 9.Iterator and Generator
- 10.Operator
- 11.Function
- 12. Array
- 13.time
- 14.Object
- 15.String
- 16.Proxy
- 17.Other
1. Hoisting
Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their respective scopes during the compilation phase, before the code is executed
- Variable declaration
var
x is hoisted to the top of its scope, but the initialization is not. Trying to access it befor initlialization will return aundefined
- Function declarations and implementation are also hoisted.
const
andlet
: While they are hoisted, they are not initialized, and trying to access them before their declaration will result in aReferenceError
.- Function declarations inside blocks: In non-strict mode, function declarations inside blocks are hoisted to the top of the enclosing function or global scope. This can lead to unexpected behavior. In strict mode (introduced in ES5), function declarations inside blocks are block-scoped, meaning they are not hoisted outside of the block.
window.foo
always refers to the globalfoo
(in a browser environment).- Variable declaration
var
is not limited in block. - In IIFE, the function definition outside can not be used inside.
27. Hoisting I
27.https://bigfrontend.dev/quiz/Hoisting-I
const a = 1
console.log(a)
var b
console.log(b)
b = 2
console.log(c)
var c = 3
console.log(d)
let d = 2
Solution: To explain what's happening in this code snippet, let's go through it line by line:
const a = 1
console.log(a) // Output: 1
We declare a constant a
and assign it the value 1. When we log it, it prints 1.
var b
console.log(b) // Output: undefined
We declare a variable b
using var
, but don't assign it a value. When we log it, it prints undefined
.
b = 2
console.log(c) // Output: undefined
We assign the value 2 to b
. Then we try to log c
, which hasn't been declared yet. However, due to hoisting (explained below), this doesn't throw an error, but logs undefined
.
var c = 3
console.log(d) // Throws a ReferenceError
We declare c
and assign it the value 3. Then we try to log d
, which hasn't been declared yet. This throws a ReferenceError.
let d = 2
We declare d
using let
and assign it the value 2. This line is never reached due to the error on the previous line.
Key points:
- Variables declared with
var
are hoisted, meaning their declarations (but not assignments) are moved to the top of their scope. That's why we can usec
before its declaration without an error. - Variables declared with
let
orconst
are not hoisted in the same way. Trying to use them before declaration results in a ReferenceError. const
variables must be assigned a value when declared and cannot be reassigned.- If a variable is declared but not assigned a value, it has the value
undefined
.
1
undefined
undefined
Error
28. Hoisting II
28.https://bigfrontend.dev/quiz/Hoisting-II
const func1 = () => console.log(1)
func1()
func2()
function func2() {
console.log(2)
}
func3()
var func3 = function func4() {
console.log(3)
}
Solution:
First, we define
func1
as an arrow function that logs the number 1.We call
func1()
, which will output1
to the console.We then call
func2()
. Even thoughfunc2
is defined after this call in the code, it works because of function hoisting. Function declarations are hoisted to the top of their scope, sofunc2
is available throughout the entire script.The
func2
function is defined, which logs the number 2.We try to call
func3()
. This will throw a TypeError because at this point,func3
is undefined. Even thoughvar func3
is hoisted, only the declaration is hoisted, not the assignment.Finally, we assign a function expression to
func3
. This function is namedfunc4
, but this name is only available inside the function itself (for recursion, for example). Outside the function, it's only known asfunc3
.
The key points to understand here are:
- Function declarations (like
func2
) are hoisted entirely and can be used before their actual position in the code. - Variables declared with
var
(likefunc3
) have their declarations hoisted, but not their assignments. Sofunc3
exists but isundefined
when we try to call it. - Function expressions (like the one assigned to
func3
) are not hoisted. They're only available after the line where they're defined.
If this code were to run, it would output:
1
2
And then throw a TypeError when trying to call func3()
.
const func1 = () => console.log(1)
func1() // 1
func2() // 2
// Function declarations are hoisted to the top of their scope
function func2() {
console.log(2)
}
func3() // Error
var func3 = function func4() {
console.log(3)
}
29. Hoisting III
29.https://bigfrontend.dev/quiz/Hoisting-III
var a = 1
function func() {
a = 2
console.log(a)
var a
}
func()
console.log(a)
if (!('b' in window)) {
var b = 1
}
console.log(b)
Solution:
We start by declaring a global variable
a
and assigning it the value 1.We define a function
func()
.Inside
func()
:- We assign 2 to
a
- We log the value of
a
- We declare
var a
(but this declaration is hoisted to the top of the function)
- We assign 2 to
We call
func()
We log the value of the global
a
We check if
'b'
is not a property of thewindow
objectIf
'b'
is not inwindow
, we declarevar b
and assign it 1We log the value of
b
Now, let's explain the output and behavior:
When
func()
is called:- Due to variable hoisting, the
var a
inside the function is moved to the top of the function scope. This creates a new locala
that shadows the globala
. - The assignment
a = 2
andconsole.log(a)
work with this locala
. - So, the first
console.log(a)
insidefunc()
outputs2
.
- Due to variable hoisting, the
After
func()
is called, weconsole.log(a)
again. This refers to the globala
, which was never changed. So it outputs1
.The
if
statement checks if'b'
is not a property ofwindow
. In a browser environment,var
declarations at the global scope create properties on thewindow
object. However, due to hoisting,var b
is already declared before this check, so'b' in window
would be true, and theif
block would not execute.The final
console.log(b)
would outputundefined
. This is becausevar b
was hoisted to the top of its scope (global in this case), but since theif
block didn't execute, it never got assigned a value.
So, the final output of this code would be:
2
1
undefined
Key points to understand:
- Variable hoisting with
var
- Function scope vs global scope
- Shadowing of global variables by local variables
- How
var
at the global scope interacts with thewindow
object in a browser environment
var a = 1
function func() {
a = 2
console.log(a)
var a // Move to above a = 2 and shadow the a outside
}
func() // 2
console.log(a) // 1
if (!('b' in window)) {
var b = 1 // var b is moved to the top as var a = 1
// the if condition is false. b is not assigned.
}
console.log(b) // undefined
32. Hoisting IIII
32.https://bigfrontend.dev/quiz/Hoisting-IIII
var a = 1
function a() {}
console.log(typeof a)
var b
function b() {}
b = 1
console.log(typeof b)
function c() {}
var c = 1
console.log(typeof c)
var d = 1
;(function () {
d = '2'
console.log(typeof d)
function d() {}
})()
console.log(typeof d)
var e = 1
const f = function e() {}
console.log(typeof e)
Solution:
This code explores variable and function declarations, hoisting, and scope in JavaScript. Let's break it down section by section:
var a = 1
function a() {}
console.log(typeof a) // 'number'
Here, both var a
and function a()
are hoisted. The function declaration initially takes precedence, but then a
is reassigned to 1. So typeof a
is 'number'.
var b
function b() {}
b = 1
console.log(typeof b) // 'number'
Similar to a
, but the reassignment happens after both declarations. The final value of b
is 1, so typeof b
is 'number'.
function c() {}
var c = 1
console.log(typeof c) // 'number'
The function declaration is hoisted and then c
is reassigned to 1. So typeof c
is 'number'.
var d = 1
;(function () {
d = '2'
console.log(typeof d) // 'string'
function d() {}
})()
console.log(typeof d) // 'number'
This is trickier. Inside the IIFE (Immediately Invoked Function Expression):
function d()
is hoisted within the function scope.d = '2'
reassigns the locald
to a string.- So inside the IIFE,
typeof d
is 'string'. Outside the IIFE, the globald
remains unchanged, sotypeof d
is still 'number'.
var e = 1
const f = function e() {}
console.log(typeof e) // 'number'
Here, the e
in function e() {}
is only scoped inside the function itself. It doesn't affect the outer e
. So typeof e
remains 'number'.
Key points to understand:
- Function declarations are hoisted and initially take precedence over variable declarations.
- Reassignments happen after hoisting, potentially changing the type of a variable.
- Function expressions (like in the last example) don't create a variable in the outer scope.
- In function scopes, local variables take precedence over global ones.
- The
typeof
operator returns a string indicating the type of the unevaluated operand.
This code demonstrates some of the more complex behaviors of JavaScript regarding variable declarations, function declarations, and scope. It's a good reminder of why it's generally recommended to use let
and const
for clearer scoping rules, and to avoid reusing names for different purposes. Would you like me to elaborate on any specific part of this explanation?
38. Hoisting IV
38.https://bigfrontend.dev/quiz/Hoisting-IV
let foo = 10
function func1() {
console.log(foo)
var foo = 1
}
func1()
function func2() {
console.log(foo)
let foo = 1
}
func2()
Solution: This code demonstrates the difference between variable hoisting with var
and the temporal dead zone (TDZ) with let
in JavaScript. Let's break it down:
let foo = 10
- This declares a variable
foo
in the global scope with a value of 10.
- This declares a variable
function func1() { ... }
- This defines a function
func1
.
- This defines a function
Inside
func1
:console.log(foo) var foo = 1
- Due to hoisting, this is equivalent to:
var foo console.log(foo) foo = 1
- The
var
declaration is hoisted to the top of the function scope, but the initialization isn't. - So when
console.log(foo)
is executed,foo
exists in the function scope but isundefined
.
- Due to hoisting, this is equivalent to:
func1()
- This calls
func1
, which will logundefined
.
- This calls
function func2() { ... }
- This defines a function
func2
.
- This defines a function
Inside
func2
:console.log(foo) let foo = 1
- Unlike
var
,let
declarations are not hoisted. - The
foo
variable is in the temporal dead zone from the start of the block until the point of declaration. - Accessing
foo
before its declaration results in aReferenceError
.
- Unlike
func2()
- This calls
func2
, which will throw aReferenceError
.
- This calls
Output:
undefined
Uncaught ReferenceError: Cannot access 'foo' before initialization
Key points:
- Variables declared with
var
are hoisted to the top of their scope. The declaration is hoisted, but not the initialization. - Variables declared with
let
(andconst
) are not hoisted. They exist in a "temporal dead zone" from the start of their block until the point where they are declared. - Accessing a
let
variable before its declaration results in aReferenceError
, while accessing avar
variable before its declaration results inundefined
. - This code demonstrates why
let
can help catch potential errors by making it impossible to accidentally use a variable before it's declared.
42. Hoisting V
42.https://bigfrontend.dev/quiz/hoisting-v
;(() => {
if (!fn) {
function fn() {
console.log('2')
}
}
fn()
})()
function fn() {
console.log('1')
}
// another one
function fn1() {
console.log('3')
}
;(() => {
if (!fn1) {
function fn1() {
console.log('4')
}
}
fn1()
})()(
// another one !
() => {
if (false) {
function fn3() {
console.log('5')
}
}
fn3()
}
)()
Solution:
In IIFE (Immediately Invoked Function Expression), the function definition outside can not be used inside. (!fn)
and (!fn1)
return true .
'2'
'4'
Error
45. Hoisting VI
45.https://bigfrontend.dev/quiz/Hoisting-VI
var foo = 1
;(function () {
console.log(foo)
foo = 2
console.log(window.foo)
console.log(foo)
var foo = 3
console.log(foo)
console.log(window.foo)
})()
Solution: This code demonstrates variable hoisting, scope, and the difference between local and global variables in JavaScript. Let's break it down line by line:
var foo = 1;
- This declares a global variable
foo
and assigns it the value 1.
- This declares a global variable
(function () { ... })()
- This is an Immediately Invoked Function Expression (IIFE). It creates a new scope.
Inside the IIFE:
a.
console.log(foo);
- Due to hoisting, the local
var foo
declaration (line 7) is moved to the top of the function scope, but not its initialization. - At this point,
foo
isundefined
in the local scope. - Output:
undefined
b.
foo = 2;
- This assigns 2 to the local
foo
, not the global one.
c.
console.log(window.foo);
- This explicitly references the global
foo
(assuming this is run in a browser). - The global
foo
is still 1. - Output:
1
d.
console.log(foo);
- This refers to the local
foo
, which is now 2. - Output:
2
e.
var foo = 3;
- This is the actual declaration and initialization of the local
foo
. - Remember, the declaration part was hoisted, but the initialization happens here.
f.
console.log(foo);
- The local
foo
is now 3. - Output:
3
g.
console.log(window.foo)
- Again, this explicitly references the global
foo
, which is still 1. - Output:
1
- Due to hoisting, the local
So, the complete output will be:
undefined
1
2
3
1
Key points:
- Variable declarations with
var
are hoisted to the top of their scope. - Function scopes create their own context for variables.
- When a variable is declared inside a function with
var
, it's local to that function. - Assigning a value to a variable that hasn't been declared with
var
in the current scope will create or modify a global variable (if not in strict mode). window.foo
always refers to the globalfoo
(in a browser environment).
This code illustrates the complexity that can arise from variable hoisting and the importance of understanding scope in JavaScript.
39. var
39.https://bigfrontend.dev/quiz/var
function foo() {
console.log(i)
for (var i = 0; i < 3; i++) {
console.log(i)
}
}
foo()
Solution: Let's break down this code and explain its behavior:
function foo() { ... }
- This defines a function named
foo
.
- This defines a function named
Inside
foo()
:console.log(i)
- This line tries to log the value of
i
before the for loop. - Due to hoisting of
var
declarations, this is equivalent to:var i console.log(i)
- At this point,
i
exists but isundefined
.
- This line tries to log the value of
The
for
loop:for (var i = 0; i < 3; i++) { console.log(i) }
- This is a standard for loop that initializes
i
to 0, runs whilei
is less than 3, and incrementsi
after each iteration. - Inside the loop, it logs the current value of
i
.
- This is a standard for loop that initializes
foo()
- This calls the
foo
function.
- This calls the
When we run this code, the output will be:
undefined
0
1
2
Explanation of the output:
- The first
undefined
comes from loggingi
before it's initialized in the for loop. - The subsequent 0, 1, and 2 come from logging
i
inside the for loop for each iteration.
Key points:
- Variables declared with
var
are hoisted to the top of their function scope. - Hoisting moves the declaration, but not the initialization.
- This is why we can access
i
before the for loop without getting a ReferenceError, but its value isundefined
. - Inside the for loop,
i
behaves as expected, taking on the values 0, 1, and 2.
This quiz demonstrates the concept of variable hoisting in JavaScript and how it can lead to potentially unexpected behavior when variables are accessed before their apparent declaration in the code.
67. if
67.https://bigfrontend.dev/quiz/if
if (true) {
function foo() {
console.log('BFE')
}
}
if (false) {
function bar() {
console.log('dev')
}
}
foo()
bar()
Solution:
This code demonstrates the behavior of function declarations within block statements in JavaScript. Let's break it down:
First
if
block:if (true) { function foo() { console.log('BFE') } }
- This block will always execute because the condition is
true
. - It declares a function
foo
.
- This block will always execute because the condition is
Second
if
block:if (false) { function bar() { console.log('dev') } }
- This block will never execute because the condition is
false
. - It declares a function
bar
, but this declaration is inside a block that doesn't run.
- This block will never execute because the condition is
foo()
- This attempts to call the function
foo
.
- This attempts to call the function
bar()
- This attempts to call the function
bar
.
- This attempts to call the function
The behavior of this code can vary depending on the JavaScript environment and whether it's running in strict mode or not. Let's consider two scenarios:
Scenario 1: Non-strict mode in most browsers:
foo()
will execute and log 'BFE'.bar()
will throw a ReferenceError becausebar
is not defined.
Scenario 2: Strict mode or in some modern JavaScript environments:
foo()
will execute and log 'BFE'.bar()
will throw a ReferenceError becausebar
is not defined.
The key points are:
Function declarations in blocks (block-scoped function declarations) are not part of the ECMAScript specification before ES6 and their behavior was historically inconsistent across browsers.
In modern JavaScript (ES6+), function declarations in blocks are supposed to be block-scoped, meaning they should only be accessible within the block they're declared in.
However, for compatibility reasons, many browsers still hoist function declarations in blocks to the containing function or global scope in non-strict mode.
In strict mode, block-scoped function declarations behave as they're supposed to in ES6+: they're only accessible within their block.
The function
bar
is never actually created because it's in a block that doesn't execute.
This quiz tests understanding of:
- Function declarations in block statements
- The effect of conditions on whether code in blocks is executed
- Potential differences between strict and non-strict mode
- The concept of hoisting and block scope
It's a good example of why it's generally safer to use function expressions with const
or let
instead of function declarations in blocks, to avoid these ambiguities and ensure consistent behavior across different JavaScript environments.
68. if II
68.https://bigfrontend.dev/quiz/if-II
if (
function foo() {
console.log('BFE')
}
) {
console.log('dev')
}
foo()
Solution:
This code demonstrates some interesting behavior in JavaScript related to function declarations, expressions, and hoisting. Let's break it down:
The
if
statement:if ( function foo() { console.log('BFE') } ) { console.log('dev') }
- The condition of the
if
statement is a function expression. - Function expressions are truthy values in JavaScript.
- Therefore, the condition always evaluates to
true
. - The body of the
if
statement will always execute, logging 'dev'.
- The condition of the
Inside the
if
condition:- The function expression
function foo(){ console.log('BFE') }
is created but not executed. - This function is not assigned to any variable or property.
- The function expression
foo()
- This attempts to call a function named
foo
.
- This attempts to call a function named
Now, let's consider the behavior:
The
if
statement condition is true, so 'dev' will be logged to the console.The function expression in the
if
condition does not create a variable namedfoo
in the outer scope. It only exists within the scope of theif
statement.When
foo()
is called outside theif
block, it will result in a ReferenceError becausefoo
is not defined in this scope.
The output and behavior will be:
dev
Uncaught ReferenceError: foo is not defined
Key points:
- Function expressions in conditions are truthy.
- Function expressions do not create variables in the surrounding scope, unlike function declarations.
- The function
foo
is not hoisted or made available outside theif
statement.
This quiz tests understanding of:
- The difference between function declarations and function expressions
- Scoping rules in JavaScript
- How JavaScript evaluates truthy/falsy values in conditions
- Common misconceptions about function availability
It's a tricky example that highlights the importance of understanding how function expressions work in JavaScript, especially when used in unusual contexts like if
statement conditions.
70. function
70.https://bigfrontend.dev/quiz/function
function foo() {
console.log(1)
}
var foo = 2
function foo() {
console.log(3)
}
foo()
Solution: This code demonstrates function hoisting, variable hoisting, and function overwriting in JavaScript. Let's break it down step by step:
First function declaration:
function foo() { console.log(1) }
Variable declaration and assignment:
var foo = 2
Second function declaration:
function foo() { console.log(3) }
Function call:
foo()
Now, let's consider how JavaScript interprets this code:
Function declarations are hoisted to the top of their scope. Both function declarations for
foo
are hoisted.Variable declarations (but not assignments) are also hoisted. The
var foo
declaration is hoisted, but not its assignment.The second function declaration overwrites the first one.
After hoisting and before execution, the code is effectively interpreted like this:
function foo() { console.log(1) } function foo() { console.log(3) } var foo foo = 2 foo()
When the assignment
foo = 2
is executed, it overwrites the functionfoo
with the number 2.When
foo()
is called,foo
is no longer a function, it's the number 2.
Therefore, when this code is executed:
- It will throw a TypeError: foo is not a function
The error occurs because we're trying to call foo
as a function, but at that point, foo
is the number 2.
Key points:
- Function declarations are hoisted and can overwrite each other.
- Variable declarations are hoisted, but not their assignments.
- Assignments can overwrite functions.
- Trying to call a non-function value as a function results in a TypeError.
This quiz tests understanding of:
- Function and variable hoisting
- The order of precedence in declarations and assignments
- How variables can overwrite functions
- The importance of the final state of a variable/function before it's used
It's a tricky example that highlights some of the potential pitfalls in JavaScript when mixing function declarations, variable declarations, and assignments with the same name.
89. let
89.https://bigfrontend.dev/quiz/let
let a = 1
;(function () {
let foo = () => a
let a = 2
console.log(foo())
})()
Solution: This code demonstrates lexical scoping and the temporal dead zone in JavaScript. Let's break it down step by step:
let a = 1;
- Declares a variable
a
in the global scope and initializes it to 1.
- Declares a variable
(function() { ... }())
- This is an Immediately Invoked Function Expression (IIFE). It creates a new scope.
Inside the IIFE:
let foo = () => a
- Declares a function
foo
that returns the value ofa
. - The
a
here refers to thea
in its lexical scope, which is the IIFE, not the globala
.
- Declares a function
let a = 2;
- Declares a new variable
a
inside the IIFE, shadowing the globala
. - This
a
is initialized to 2.
- Declares a new variable
console.log(foo())
- Calls
foo()
and logs its result.
- Calls
Now, here's the tricky part:
- When
foo
is defined, it captures thea
from its lexical environment (the IIFE). - However, at the point where
foo
is defined, the locala
exists but hasn't been initialized yet. This is known as the Temporal Dead Zone (TDZ). - When
foo()
is called, it tries to access this locala
, which is now initialized to 2.
Therefore, the output will be:
2
Key points:
- Lexical scoping: The function
foo
captures thea
from its immediate scope (the IIFE), not the global scope. - Temporal Dead Zone: Variables declared with
let
exist from the start of their block, but can't be accessed until after their declaration. - Variable shadowing: The
a
inside the IIFE shadows the globala
. - Closure:
foo
forms a closure over its lexical environment, which includes the locala
.
This code demonstrates why it's important to be careful with variable declarations and their placement, especially when dealing with closures and nested scopes. It's a classic example used to test understanding of JavaScript's scoping rules and the behavior of let
declarations.
116. strict
116.https://bigfrontend.dev/quiz/strict
function a() {
'use strict'
dev = 'BFE'
console.log(dev)
}
a()
Solution:
I'd be happy to explain that code snippet. Let's break it down line by line:
function a() {
'use strict'
dev = 'BFE'
console.log(dev)
}
a()
function a() {
: This declares a function nameda
with no parameters.'use strict'
: This enables strict mode for the function. Strict mode helps catch common coding errors and prevents the use of certain error-prone features.dev = 'BFE'
: This line attempts to assign the string 'BFE' to a variable nameddev
. However, there's novar
,let
, orconst
declaration fordev
.console.log(dev)
: This would attempt to print the value ofdev
to the console.}
: This closes the function definition.a()
: This calls the functiona
.
The key issue in this code is on line 3. In strict mode, assigning to an undeclared variable throws an error. So when this code runs, it will throw a ReferenceError before it reaches the console.log statement.
If you want this code to work as intended, you should declare the variable dev
using let
, const
, or var
(though var
is generally discouraged in modern JavaScript). For example:
function a() {
'use strict'
let dev = 'BFE'
console.log(dev)
}
a()
This version would successfully log 'BFE' to the console when the function is called.
2.Scope
Scope
defines the visibility and accessibility of variables within your code. Variables can have either global scope (accessible from anywhere in the code) or local scope (accessible only within a specific function or block).
The scope chain
is a mechanism in JavaScript that determines the order in which variable lookups are performed. When a variable is referenced, JavaScript searches for it starting from the innermost scope and moving outward until it finds the variable.
The lexical environment
consists of all the variables and functions that are in scope at a particular point in code. It contains two components: 1. Environment Record: 2. Reference to the Outer Lexical Environment:
- var is hoisted out of the block and the let is hoisted within the block.
const a = b = c = 1
isc=1;b=c;const a = b
.
5. scope
5.https://bigfrontend.dev/quiz/block-scope-1
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0)
}
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0)
}
Solution: This code contains two for
loops, each using a different variable declaration (var
and let
) and both setting up setTimeout
callbacks. These callbacks log the value of i
after a delay of 0 milliseconds. The key difference lies in how var
and let
handle scope, particularly within asynchronous callbacks like those in setTimeout
.
- First Loop: Using
var
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0)
}
Explanation:
var
is function-scoped (or globally scoped if not in a function). It means that the variablei
is the same for every iteration of the loop, and its value is shared across all thesetTimeout
callbacks.- The
setTimeout
function is asynchronous and runs after the loop has completed. By the time thesetTimeout
callbacks are executed, the loop has already incrementedi
to5
. - As a result, each
setTimeout
logs the value ofi
after the loop has finished, so all of them log5
.
Output:
5
5
5
5
5
- Second Loop: Using
let
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0)
}
Explanation:
let
is block-scoped. Each iteration of the loop creates a new block-scopedi
variable. Therefore, eachsetTimeout
callback captures its own distincti
value.- When the
setTimeout
callbacks are executed, each callback logs the value ofi
that was current at the time of that particular iteration. - As a result, the values logged are
0
,1
,2
,3
, and4
, corresponding to each iteration of the loop.
Output:
0
1
2
3
4
3.Summary:
- With
var
: The loop variablei
is shared across all iterations, so allsetTimeout
callbacks log the final value ofi
, which is5
. - With
let
: Each iteration creates a newi
variable that is unique to that iteration, so eachsetTimeout
callback logs the correct value ofi
from that particular iteration.
76. const
76.https://bigfrontend.dev/quiz/const
function func() {
const a = (b = c = 1)
}
func()
console.log(typeof a, typeof b, typeof c)
Solution: Certainly! This code demonstrates some interesting behavior related to variable declaration and scope in JavaScript. Let's break it down:
Function Definition:
function func() { const a = (b = c = 1) }
This function contains a single line that looks like it's declaring three variables, but it's not doing exactly what it might seem.
Function Call:
func()
This executes the function.
Console Log:
console.log(typeof a, typeof b, typeof c)
This attempts to log the types of
a
,b
, andc
.
Now, let's analyze what's happening:
Inside the function:
const a = b = c = 1
is actually equivalent to:c = 1 b = c const a = b
c
andb
are created as global variables (if not in strict mode) because they don't have a declaration keyword.a
is declared as a constant, but only within the function's scope.
After the function call:
a
is not accessible outside the function because it's block-scoped (due toconst
).b
andc
are global variables (in non-strict mode) and retain their values.
The
console.log
statement:typeof a
is "undefined" becausea
is not defined in the global scope.typeof b
is "number" becauseb
is a global variable with the value 1.typeof c
is "number" becausec
is also a global variable with the value 1.
Therefore, the output will be:
undefined number number
Key points:
- Only
a
is truly declared as a local variable (withconst
). b
andc
become global variables due to the lack of declaration keywords.- This behavior can be a source of bugs and is one reason why "use strict" is recommended.
- In strict mode, this would actually throw an error because
b
andc
would be considered undeclared.
This code demonstrates the importance of properly declaring variables and understanding scope in JavaScript. It's generally a good practice to always use let
, const
, or var
when declaring variables to avoid unintentional global variable creation.
3. Implicit Coercion
<, >, <=, >=
convert thenull
andundefined
to Number.==
does not conver them and only return true for comparingnull
andundefined
.
9. null and undefined
9.https://bigfrontend.dev/quiz/null-and-undefined
console.log(JSON.stringify([1, 2, null, 3]))
console.log(JSON.stringify([1, 2, undefined, 3]))
console.log(null === undefined)
console.log(null == undefined)
console.log(null == 0)
console.log(null < 0)
console.log(null > 0)
console.log(null <= 0)
console.log(null >= 0)
console.log(undefined == 0)
console.log(undefined < 0)
console.log(undefined > 0)
console.log(undefined <= 0)
console.log(undefined >= 0)
Solution: Let's go through this code line by line:
console.log(JSON.stringify([1,2,null,3]))
- JSON.stringify converts null to null in the JSON string.
- Output:
'[1,2,null,3]'
console.log(JSON.stringify([1,2,undefined,3]))
- JSON.stringify omits undefined values in arrays.
- Output:
'[1,2,null,3]'
console.log(null === undefined)
- Strict equality check. null and undefined are different types.
- Output:
false
console.log(null == undefined)
- Loose equality. null and undefined are considered equal.
- Output:
true
console.log(null == 0)
- Loose equality. null is not considered equal to 0.
- Output:
false
console.log(null < 0)
- Comparison converts null to 0. 0 is not less than 0.
- Output:
false
console.log(null > 0)
- Comparison converts null to 0. 0 is not greater than 0.
- Output:
false
console.log(null <= 0)
- Comparison converts null to 0. 0 is less than or equal to 0.
- Output:
true
console.log(null >= 0)
- Comparison converts null to 0. 0 is greater than or equal to 0.
- Output:
true
console.log(undefined == 0)
- Loose equality. undefined is not considered equal to 0.
- Output:
false
console.log(undefined < 0)
- Comparison with undefined returns false.
- Output:
false
console.log(undefined > 0)
- Comparison with undefined returns false.
- Output:
false
console.log(undefined <= 0)
- Comparison with undefined returns false.
- Output:
false
console.log(undefined >= 0)
- Comparison with undefined returns false.
- Output:
false
Key points:
- JSON.stringify treats null and undefined differently in arrays.
- null and undefined are loosely equal (==) but not strictly equal (===).
- In comparisons (
<, >, <=, >=
), null is converted to 0. - undefined is not equal to 0 and all comparisons with undefined return false.
- These examples demonstrate the quirks of JavaScript's type coercion and comparison rules, which can sometimes lead to unexpected results if not well understood.
10. Equal
10.https://bigfrontend.dev/quiz/Equal-1
console.log(0 == false)
console.log('' == false)
console.log([] == false)
console.log(undefined == false)
console.log(null == false)
console.log('1' == true)
console.log(1n == true)
console.log(' 1 ' == true)
Solution:
This code demonstrates JavaScript's type coercion rules when using the loose equality operator (==). Let's go through each line:
console.log(0 == false)
- false is coerced to 0
- 0 == 0 is true
- Output:
true
console.log('' == false)
- false is coerced to 0
- '' is coerced to 0
- 0 == 0 is true
- Output:
true
console.log([] == false)
- false is coerced to 0
- [] is first converted to a primitive, which results in ''
- '' is then coerced to 0
- 0 == 0 is true
- Output:
true
console.log(undefined == false)
- undefined is not coerced to a number in this case
- undefined == false is always false
- Output:
false
console.log(null == false)
- null is not coerced to a number in this case
- null == false is always false
- Output:
false
console.log('1' == true)
- true is coerced to 1
- '1' is coerced to 1
- 1 == 1 is true
- Output:
true
console.log(1n == true)
- true is coerced to 1
- 1n (BigInt) is compared to 1
- 1n == 1 is true
- Output:
true
console.log(' 1 ' == true)
- true is coerced to 1
- ' 1 ' is trimmed and coerced to 1
- 1 == 1 is true
- Output:
true
Key points:
- The == operator performs type coercion before comparison.
- false is typically coerced to 0, and true to 1.
- Empty strings and arrays are coerced to 0 when compared with booleans.
- undefined and null are not coerced to numbers when compared with booleans.
- Strings containing numbers (even with whitespace) are coerced to their numeric values.
These examples highlight why it's often recommended to use the strict equality operator (===) instead of ==, as it avoids these sometimes unintuitive type coercions.
30. Equal II
30.https://bigfrontend.dev/quiz/Equal-II
What does the code snippet to the right output by console.log?
ref to the The Abstract Equality Comparison Algorithm
console.log([1] == 1)
console.log([1] == '1')
console.log(['1'] == '1')
console.log(['1'] == 1)
console.log([1] == ['1'])
console.log(new Boolean(true) == 1)
console.log(new Boolean(true) == new Boolean(true))
console.log(Boolean(true) == '1')
console.log(Boolean(false) == [0])
console.log(new Boolean(true) == '1')
console.log(new Boolean(false) == [0])
console.log(null == undefined)
Solution:
Let's break down each comparison:
[1] == 1
: true. The array[1]
is converted to a primitive, which becomes'1'
, then to a number 1.[1] == '1'
: true.[1]
is converted to'1'
, so it's'1' == '1'
.['1'] == '1'
: true. Similar to #2,['1']
becomes'1'
.['1'] == 1
: true.['1']
becomes'1'
, then'1'
is converted to number 1.[1] == ['1']
: false. When comparing two objects (arrays are objects), it checks if they're the same object in memory, which they're not.new Boolean(true) == 1
: true. The Boolean object is converted to its primitive valuetrue
, which is then converted to number 1.new Boolean(true) == new Boolean(true)
: false. These are two different objects in memory.Boolean(true) == '1'
: true.Boolean(true)
is justtrue
, which is converted to number 1, then'1'
is also converted to 1.Boolean(false) == [0]
: true.[0]
is converted to'0'
, then to number 0, which equalsfalse
.new Boolean(true) == '1'
: true. The Boolean object is converted to its primitive valuetrue
, which is then converted to number 1.new Boolean(false) == [0]
: false. The Boolean object is converted to its primitive valuetrue
, which is then converted to number 1.null == undefined
: true. This is a special case in JavaScript wherenull
andundefined
are considered loosely equal.
Key points:
- The
==
operator performs type coercion. - Arrays are typically converted to strings, then to numbers if necessary.
Boolean
objects (new Boolean()
) behave differently from boolean primitives.- When comparing objects, it checks if they're the same object in memory.
null
andundefined
are loosely equal to each other, but not to any other values.
console.log([1] == 1) // true
console.log([1] == '1') // true
console.log(['1'] == '1') // true
console.log(['1'] == 1) // true
console.log([1] == ['1']) // false
console.log(new Boolean(true) == 1) // true
console.log(new Boolean(true) == new Boolean(true)) // false
console.log(Boolean(true) == '1') // true
console.log(Boolean(false) == [0]) // true
console.log(new Boolean(true) == '1') // true
console.log(new Boolean(false) == [0]) // false
console.log(null == undefined) // true
79. Equal III
79.https://bigfrontend.dev/quiz/equal-iii
console.log(((2.0 == '2') == new Boolean(true)) == '1')
Solution: This code demonstrates JavaScript's type coercion and the behavior of the loose equality operator (==). Let's break it down step by step:
2.0 == "2" == new Boolean(true) == "1"
This is evaluated from left to right:
First comparison:
2.0 == "2"
- The string "2" is coerced to a number
- 2.0 == 2 is true
Second comparison:
true == new Boolean(true)
- The result of the previous comparison (true) is compared with the Boolean object
- When comparing a primitive to an object with ==, the object is converted to a primitive
- new Boolean(true) is converted to the primitive true
- true == true is true
Third comparison:
true == "1"
- The boolean true is coerced to a number (1)
- The string "1" is coerced to a number (1)
- 1 == 1 is true
Therefore, the entire expression evaluates to true.
The output will be:
true
Key points:
- The loose equality operator (==) performs type coercion.
- Numbers and strings are compared numerically after coercion.
- Boolean objects are converted to primitive booleans when compared with ==.
- true is coerced to 1 when compared with a string or number.
- The expression is evaluated left to right, with each comparison resulting in a boolean that's used in the next comparison.
This code is a tricky example that highlights the complexity of JavaScript's type coercion rules. It's generally recommended to use the strict equality operator (===) to avoid unexpected behavior due to type coercion, unless you specifically need the coercion behavior of ==.
101. Equal IV
101.https://bigfrontend.dev/quiz/Equal-IV
console.log([0] == '')
console.log([0] == 0)
Solution: Let's break down this code and explain what's happening:
console.log([0] == '')
console.log([0] == 0)
These lines are using the loose equality operator (==
) to compare an array containing a single element 0
with an empty string and the number 0
. The key to understanding this is knowing how JavaScript performs type coercion when using ==
.
Let's examine each comparison:
[0] == ''
When comparing an array to a primitive value using
==
, JavaScript first tries to convert the array to a primitive. It does this by calling the array'stoString()
method.[0].toString()
returns"0"
(a string containing "0")- Now we're comparing
"0" == ''
- A non-empty string is not equal to an empty string
- Therefore, this comparison returns
false
[0] == 0
Again, the array
[0]
is converted to a primitive:[0].toString()
returns"0"
(a string containing "0")- Now we're comparing
"0" == 0
- When comparing a string to a number, JavaScript attempts to convert the string to a number
Number("0")
is0
0 == 0
is true- Therefore, this comparison returns
true
So, the output of this code will be:
false
true
This example demonstrates several important concepts in JavaScript:
- Type coercion: How JavaScript converts between types when using the
==
operator. - Array to primitive conversion: How arrays are converted to primitives (using
toString()
). - String to number conversion: How strings are converted to numbers in certain comparisons.
It's worth noting that these kinds of comparisons can lead to unexpected results, which is why it's often recommended to use the strict equality operator (===
) instead, which doesn't perform type coercion.
8. Implicit Coercion I
8.https://bigfrontend.dev/quiz/Implicit-Conversion-1
console.log(Boolean('false'))
console.log(Boolean(false))
console.log('3' + 1)
console.log('3' - 1)
console.log('3' - ' 02 ')
console.log('3' * ' 02 ')
console.log(Number('1'))
console.log(Number('number'))
console.log(Number(null))
console.log(Number(false))
console.log(Number(undefined))
Solution: Let's go through this code line by line:
console.log(Boolean('false'))
- This converts the string 'false' to a boolean.
- Any non-empty string is truthy in JavaScript.
- Output:
true
console.log(Boolean(false))
- This is already a boolean false value.
- Output:
false
console.log('3' + 1)
- When using the + operator with a string, JavaScript performs string concatenation.
- The number 1 is converted to a string.
- Output:
'31'
console.log('3' - 1)
- For the - operator, JavaScript tries to convert the string to a number.
- '3' is converted to the number 3, then 1 is subtracted.
- Output:
2
console.log('3' - ' 02 ')
- Both strings are converted to numbers.
- '3' becomes 3, ' 02 ' becomes 2 (leading/trailing spaces are ignored).
- Output:
1
console.log('3' * ' 02 ')
- Similar to subtraction, both are converted to numbers for multiplication.
- Output:
6
console.log(Number('1'))
- Converts the string '1' to a number.
- Output:
1
console.log(Number('number'))
- Tries to convert 'number' to a number, which isn't possible.
- Output:
NaN
(Not a Number)
console.log(Number(null))
- null is converted to 0 when using the Number() function.
- Output:
0
console.log(Number(false))
- false is converted to 0 when using the Number() function.
- Output:
0
console.log(Number(undefined))
undefined
is converted toNaN
when using the Number() function.- Output:
NaN
This code demonstrates various type coercions in JavaScript, particularly:
- How Boolean() handles different values
- How JavaScript handles operations between strings and numbers
- How the Number() function converts different types to numbers
These examples highlight the importance of understanding type coercion in JavaScript to avoid unexpected results in operations.
11. Implicit Coercion II
11.https://bigfrontend.dev/quiz/Implicit-Conversion-II
console.log([] + [])
console.log([] + 1)
console.log([[]] + 1)
console.log([[1]] + 1)
console.log([[[[2]]]] + 1)
console.log([] - 1)
console.log([[]] - 1)
console.log([[1]] - 1)
console.log([[[[2]]]] - 1)
console.log([] + {})
console.log({} + {})
console.log({} - {})
Solution:
Certainly. This code demonstrates some of JavaScript's more unusual type coercion rules, especially when dealing with arrays, objects, and the + and - operators. Let's break it down:
console.log([] + [])
- Both arrays are converted to empty strings
- "" + "" results in an empty string
- Output:
""
console.log([] + 1)
- [] is converted to an empty string
- "" + 1 coerces 1 to a string
- Output:
"1"
console.log([[]] + 1)
- [[]] is converted to ""
- "" + 1 coerces 1 to a string
- Output:
"1"
console.log([[1]] + 1)
- [[1]] is converted to "1"
- "1" + 1 coerces 1 to a string
- Output:
"11"
console.log([[[[2]]]] + 1)
- [[[[2]]]] is converted to "2"
- "2" + 1 coerces 1 to a string
- Output:
"21"
console.log([] - 1)
- [] is converted to 0 for subtraction
- 0 - 1 is performed
- Output:
-1
console.log([[]] - 1)
- [[]] is converted to 0 for subtraction
- 0 - 1 is performed
- Output:
-1
console.log([[1]] - 1)
- [[1]] is converted to 1 for subtraction
- 1 - 1 is performed
- Output:
0
console.log([[[[2]]]] - 1)
- [[[[2]]]] is converted to 2 for subtraction
- 2 - 1 is performed
- Output:
1
console.log([] + {})
- [] becomes ""
- becomes "[object Object]"
- Output:
"[object Object]"
console.log({} + {})
- Both become "[object Object]"
- Output:
"[object Object][object Object]"
console.log({} - {})
- Both are converted to NaN for subtraction
- NaN - NaN is NaN
- Output:
NaN
Key points:
- The + operator with arrays tends to convert them to strings.
- The - operator with arrays tends to convert them to numbers.
- Empty arrays are treated as 0 in numeric contexts and "" in string contexts.
- Nested arrays are flattened to their innermost value when converted to primitives.
- Objects, when converted to primitives, typically become "[object Object]".
- These examples show why it's important to be cautious when performing operations on mixed types in JavaScript, as the results can be unintuitive.
Reference: https://bigfrontend.dev/quiz/Implicit-Conversion-II/discuss
35. Implicit Coercion III
35.https://bigfrontend.dev/quiz/Implicit-Conversion-III
console.log([] + {})
console.log(+{})
console.log(+[])
console.log({} + [])
console.log({} + [])
console.log({} + [])
console.log({} + [])
console.log({} + +[])
console.log({} + +[] + {})
console.log({} + +[] + {} + [])
Solution: This code demonstrates some of JavaScript's type coercion rules and the behavior of the +
operator with different types. Let's go through each line:
console.log( [] + {} )
- Output:
"[object Object]"
[]
is coerced to an empty string ""{}
is coerced to "[object Object]"- The result is string concatenation
- Output:
console.log( + {} )
- Output:
NaN
- The unary
+
attempts to convert{}
to a number, which results inNaN
- Output:
console.log( + [] )
- Output:
0
- The unary
+
converts an empty array to 0
- Output:
console.log( {} + [])
- Output:
"[object Object]"
- In most browsers, this is interpreted the same as
({} + [])
{}
is coerced to "[object Object]" and[]
to an empty string
- Output:
console.log( ({}) + [])
- Output:
"[object Object]"
- Same as #4, but the parentheses ensure
{}
is treated as an object literal
- Output:
console.log( ({}) + [])
- Output:
"[object Object]"
- Identical to #5
- Output:
console.log( ({}) + [])
- Output:
"[object Object]"
- Identical to #5 and #6
- Output:
console.log( {} + + [])
- Output:
"[object Object]0"
+[]
evaluates to 0- In most browsers, this is interpreted as
({}+0)
, which coerces{}
to "[object Object]" - However, some browsers might interpret this as
{}; +[]
, which would output 0
- Output:
console.log( {} + + [] + {} )
- Output:
"[object Object]0[object Object]"
+[]
evaluates to 0{}
is coerced to "[object Object]"- So it's effectively
"[object Object]" + 0 + "[object Object]"
- Output:
console.log( {} + + [] + {} + [])
- Output:
"[object Object]0[object Object]"
- Similar to #9, but the final
+ []
adds an empty string - It's effectively
"[object Object]" + 0 + "[object Object]" + ""
- Output:
Key points:
- Empty arrays
[]
are often coerced to empty strings or 0, depending on the context - Objects
{}
are typically coerced to "[object Object]" when used with the+
operator - The unary
+
operator attempts to convert its operand to a number - The behavior of
{} + ...
at the start of a line can be ambiguous and may be interpreted differently in some JavaScript environments - These examples demonstrate why it's important to be explicit about type conversions and to avoid relying on implicit coercion in JavaScript
46. Implicit Coercion IV
46.https://bigfrontend.dev/quiz/implicit-coersion-2
const foo = [0]
if (foo) {
console.log(foo == true)
} else {
console.log(foo == false)
}
Solution: This code demonstrates some interesting aspects of JavaScript's type coercion and truthiness. Let's break it down:
const foo = [0]
- This creates a constant
foo
that is an array containing a single element, the number 0.
- This creates a constant
if (foo) { ... } else { ... }
- In JavaScript, when a non-boolean value is used in a boolean context (like an if statement), it is coerced to a boolean.
- Arrays, even empty ones, are always truthy in JavaScript.
- So,
foo
(an array) is coerced totrue
for the purpose of the if condition.
Inside the if block:
console.log(foo == true)
- This uses the loose equality operator
==
, which allows type coercion. - When comparing an array to a boolean using
==
, JavaScript follows these steps: a. It tries to convert the array to a primitive value. For arrays, this means calling itstoString()
method. b.[0].toString()
results in the string "0". c. The string "0" is then compared totrue
. d. When comparing a string to a boolean, both are converted to numbers. e. "0" becomes the number 0, and true becomes the number 1. f. 0 is not equal to 1. - Therefore,
foo == true
evaluates tofalse
.
- This uses the loose equality operator
The output of this code will be:
false
Key points:
- Arrays are truthy in boolean contexts (like if statements), regardless of their contents.
- The loose equality operator
==
performs type coercion, which can lead to unexpected results. - When comparing different types with
==
, JavaScript follows a complex set of rules to coerce the values. - In this case, even though
foo
is truthy in the if statement, it's not loosely equal totrue
.
This code highlights the difference between:
- The truthiness of a value (used in boolean contexts like if statements)
- The result of loosely comparing a value to
true
orfalse
It's a good example of why it's often recommended to use strict equality ===
in JavaScript to avoid unexpected type coercion issues.
24. Equality & Sameness
24.https://bigfrontend.dev/quiz/Equality-Sameness
console.log(0 == '0')
console.log(0 === '0')
console.log(Object.is(0, '0'))
console.log(0 == 0)
console.log(0 === 0)
console.log(Object.is(0, 0))
console.log(0 == -0)
console.log(0 === -0)
console.log(Object.is(0, -0))
console.log(NaN == NaN)
console.log(NaN === NaN)
console.log(Object.is(NaN, NaN))
console.log(0 == false)
console.log(0 === false)
console.log(Object.is(0, false))
Solution: This code demonstrates different comparison methods in JavaScript: loose equality (==
), strict equality (===
), and Object.is()
. Each of these methods has its own rules for determining equality. Let's break down the output and explain each comparison.
Let's go through each comparison:
console.log(0 == '0')
Output:true
- Loose equality performs type coercion. The string '0' is coerced to a number.
console.log(0 === '0')
Output:false
- Strict equality checks both value and type. 0 is a number, '0' is a string.
console.log(Object.is(0, '0'))
Output:false
Object.is()
behaves similarly to===
in this case.
console.log(0 == 0)
Output:true
- Same value and type.
console.log(0 === 0)
Output:true
- Same value and type.
console.log(Object.is(0, 0))
Output:true
- Same value and type.
console.log(0 == -0)
Output:true
- In JavaScript, 0 and -0 are considered equal under
==
.
- In JavaScript, 0 and -0 are considered equal under
console.log(0 === -0)
Output:true
- Even strict equality considers 0 and -0 to be the same.
console.log(Object.is(0, -0))
Output:false
- This is where
Object.is()
differs. It distinguishes between 0 and -0.
- This is where
console.log(NaN == NaN)
Output:false
NaN
is not equal to anything, including itself, under==
.
console.log(NaN === NaN)
Output:false
NaN
is not equal to anything, including itself, even under===
.
console.log(Object.is(NaN, NaN))
Output:true
Object.is()
considersNaN
equal to itself.
console.log(0 == false)
Output:true
- Loose equality coerces
false
to 0.
- Loose equality coerces
console.log(0 === false)
Output:false
- Strict equality checks type. 0 is a number,
false
is a boolean.
- Strict equality checks type. 0 is a number,
console.log(Object.is(0, false))
Output:false
Object.is()
behaves like===
in this case.
Key takeaways:
- Loose equality (
==
) performs type coercion, which can lead to unexpected results. - Strict equality (
===
) compares both value and type, but still considers 0 and -0 to be equal. Object.is()
is the strictest form of equality:- It distinguishes between 0 and -0
- It considers NaN equal to itself
- For other comparisons, it generally behaves like
===
- NaN is not equal to itself under
==
or===
, but is underObject.is()
. - When comparing with booleans,
==
performs coercion while===
andObject.is()
do not.
These examples highlight the importance of understanding different equality comparisons in JavaScript, especially when dealing with edge cases like -0, NaN, or type coercion scenarios.
console.log(0 == '0') // true
console.log(0 === '0') // false
console.log(Object.is(0, '0')) // false
console.log(0 == 0) // true
console.log(0 === 0) // true
console.log(Object.is(0, 0)) // true
console.log(0 == -0) // true
console.log(0 === -0) // true
console.log(Object.is(0, -0)) // false
console.log(NaN == NaN) // false
console.log(NaN === NaN) // false
console.log(Object.is(NaN, NaN)) // true
console.log(0 == false) // true
console.log(0 === false) // false
console.log(Object.is(0, false)) // false
26. true or false
26.https://bigfrontend.dev/quiz/true-or-false
console.log([] == 0)
console.log([] == false)
console.log(!![])
console.log([1] == 1)
console.log(!![1])
console.log(Boolean([]))
console.log(Boolean(new Boolean([])))
console.log(Boolean(new Boolean(false)))
Solution:
This code demonstrates some of JavaScript's type coercion rules and the behavior of the Boolean constructor. Let's break it down line by line:
console.log([] == 0)
Output:true
Explanation: When comparing an array to a number using==
, JavaScript first converts the array to a primitive. An empty array converts to an empty string, which then converts to 0.console.log([] == false)
Output:true
Explanation: Both[]
andfalse
are coerced to the number 0 when using==
.console.log(!![])
Output:true
Explanation: The!
operator first converts[]
to a boolean (which istrue
for any non-empty object), then negates it. The second!
negates it again, resulting intrue
.console.log([1] == 1)
Output:true
Explanation: The array[1]
is first converted to a primitive, which results in the string "1", which is then converted to the number 1.console.log(!![1])
Output:true
Explanation: Similar to!![]
, this converts[1]
to a boolean (which istrue
), negates it, then negates it again.console.log(Boolean([]))
Output:true
Explanation: When converted to a boolean, any non-empty object (including empty arrays) becomestrue
.console.log(Boolean(new Boolean([])))
Output:true
Explanation:new Boolean([])
creates a Boolean object. Any object, when passed toBoolean()
, returnstrue
.console.log(Boolean(new Boolean(false)))
Output:true
Explanation: Although we're passingfalse
to the Boolean constructor,new Boolean(false)
creates a Boolean object. As before, any object passed toBoolean()
returnstrue
.
Key takeaways:
Empty arrays (
[]
) are truthy when converted to boolean, but can be equal to0
orfalse
when using loose equality (==
) due to type coercion.The
!!
operator is a common way to convert a value to its boolean equivalent.When comparing arrays to primitives using
==
, the array is first converted to a primitive value (often a string), which can lead to unexpected results.Boolean()
function converts its argument to a boolean primitive:- Objects (including arrays and Boolean objects) always convert to
true
. - Empty strings,
0
,null
,undefined
, andNaN
convert tofalse
. - All other values convert to
true
.
- Objects (including arrays and Boolean objects) always convert to
new Boolean()
creates a Boolean object, which is always truthy, regardless of its content.
These examples highlight the importance of understanding JavaScript's type coercion rules and the difference between primitive boolean values and Boolean objects. It's generally recommended to use strict equality (===
) and explicit type conversion to avoid unexpected behavior from implicit type coercion.
console.log([] == 0) // true
console.log([] == false) // true
console.log(!![]) // true
console.log([1] == 1) // true
console.log(!![1]) // true
console.log(Boolean([])) // true
console.log(Boolean(new Boolean([]))) // true
console.log(Boolean(new Boolean(false))) // true
40. RegExp.prototype.test
40.https://bigfrontend.dev/quiz/RegExp-prototype-test
console.log(/^4\d\d$/.test('404'))
console.log(/^4\d\d$/.test(404))
console.log(/^4\d\d$/.test(['404']))
console.log(/^4\d\d$/.test([404]))
Solution:
This code demonstrates how JavaScript's test()
method of regular expressions behaves with different types of input. Let's break it down line by line:
console.log(/^4\d\d$/.test('404'))
- Output:
true
- The regular expression
^4\d\d$
matches:^
start of the string4
the digit 4\d\d
two digits$
end of the string
- '404' is a string that perfectly matches this pattern.
- Output:
console.log(/^4\d\d$/.test(404))
- Output:
true
- Even though 404 is a number, JavaScript automatically converts it to a string '404' before applying the regex test.
- The result is the same as the first test.
- Output:
console.log(/^4\d\d$/.test(['404']))
- Output:
true
- When
test()
is called with an array, JavaScript converts the array to a string. - ['404'].toString() results in '404'.
- This string matches the regex pattern.
- Output:
console.log(/^4\d\d$/.test([404]))
- Output:
true
- Similar to the previous case, [404] is converted to a string.
- [404].toString() results in '404'.
- This string matches the regex pattern.
- Output:
Key points:
- The
test()
method returnstrue
if the regex matches any part of the string, andfalse
otherwise. - When
test()
is called with a non-string argument, JavaScript automatically converts the argument to a string before testing. - For arrays, the
toString()
method is called, which joins array elements with commas. However, in these cases, there's only one element, so no commas are involved. - Numbers are converted to their string representation.
- This behavior can sometimes lead to unexpected results if you're not aware of these automatic type conversions.
This code demonstrates the importance of understanding both regular expressions and JavaScript's type coercion rules when working with the test()
method.
56. to primitive
56.https://bigfrontend.dev/quiz/primitive
// case 1
const obj1 = {
valueOf() {
return 1
},
toString() {
return '100'
},
}
console.log(obj1 + 1)
console.log(parseInt(obj1))
// case 2
const obj2 = {
[Symbol.toPrimitive]() {
return 200
},
valueOf() {
return 1
},
toString() {
return '100'
},
}
console.log(obj2 + 1)
console.log(parseInt(obj2))
// case 3
const obj3 = {
toString() {
return '100'
},
}
console.log(+obj3)
console.log(obj3 + 1)
console.log(parseInt(obj3))
// case 4
const obj4 = {
valueOf() {
return 1
},
}
console.log(obj4 + 1)
console.log(parseInt(obj4))
// case 5
const obj5 = {
[Symbol.toPrimitive](hint) {
return hint === 'string' ? '100' : 1
},
}
console.log(obj5 + 1)
console.log(parseInt(obj5))
Solution: This code demonstrates various aspects of type coercion and object-to-primitive conversion in JavaScript. Let's break it down case by case:
Case 1:
const obj1 = {
valueOf() {
return 1
},
toString() {
return '100'
},
}
console.log(obj1 + 1) // Output: 2
console.log(parseInt(obj1)) // Output: 100
- For addition,
valueOf
is called first, returning 1. - For
parseInt
,toString
is called, returning '100'.
Case 2:
const obj2 = {
[Symbol.toPrimitive]() {
return 200
},
valueOf() {
return 1
},
toString() {
return '100'
},
}
console.log(obj2 + 1) // Output: 201
console.log(parseInt(obj2)) // Output: 200
Symbol.toPrimitive
takes precedence overvalueOf
andtoString
.
Case 3:
const obj3 = {
toString() {
return '100'
},
}
console.log(+obj3) // Output: 100
console.log(obj3 + 1) // Output: 1001
console.log(parseInt(obj3)) // Output: 100
- Without
valueOf
,toString
is used for all conversions. - Unary plus (+) converts '100' to number 100.
- Addition concatenates '100' and '1'.
Case 4:
const obj4 = {
valueOf() {
return 1
},
}
console.log(obj4 + 1) // Output: 2
console.log(parseInt(obj4)) // Output: NaN
valueOf
is used for addition.parseInt
tries to usetoString
, which defaults to "[object Object]", resulting in NaN.
Case 5:
const obj5 = {
[Symbol.toPrimitive](hint) {
return hint === 'string' ? '100' : 1
},
}
console.log(obj5 + 1) // Output: 2
console.log(parseInt(obj5)) // Output: 100
Symbol.toPrimitive
allows different results based on the conversion hint.- For addition, the numeric result (1) is used.
- For
parseInt
, the string result ('100') is used.
Key points:
- The order of precedence for object-to-primitive conversion is:
Symbol.toPrimitive
>valueOf
>toString
- Different operations may prefer numeric or string conversion.
parseInt
always converts its argument to a string first.- The unary plus operator (+) forces numeric conversion.
- Addition (+) can result in either numeric addition or string concatenation, depending on the operands.
This quiz tests deep understanding of JavaScript's type coercion rules and object-to-primitive conversion mechanisms.
4.Closure
A closure in JavaScript is created when a function is defined within another function. It allows the inner function to access the variables and parameters of the outer function, even after the outer function has finished executing.
- Encapsulation with Closures:
// Outer function definition
function createCounter() {
// Variable declaration in the outer function's scope
let count = 0
// Returning an object with methods
return {
// Increment method
increment: function () {
count++
},
// Decrement method
decrement: function () {
count--
},
// Get count method
getCount: function () {
return count
},
}
}
// Create a counter instance
const counter = createCounter()
// Increment the counter twice
counter.increment()
counter.increment()
// Log the count to the console
console.log(counter.getCount()) // Output: 2
// Decrement the counter
counter.decrement()
// Log the count to the console
console.log(counter.getCount()) // Output: 1
- Function Factories:
// Define createMultiplier
function createMultiplier(multiplier) {
// Return a new function
return function (number) {
return number * multiplier // Multiply input number by multiplier
}
}
const double = createMultiplier(2) // Function to double a number
const triple = createMultiplier(3) // Function to triple a number
console.log(double(5)) // 10
console.log(triple(5)) // 15
- Unintended Closures:
// Define fetchData
function fetchData(apiUrl, callback) {
// Simulate an asynchronous operation
setTimeout(() => {
const data = { name: 'John Doe', age: 30 } // Simulated API response
callback(data) // Call the provided callback with the data
}, 1000) // Simulate network delay
}
// Define processData
function processData(data) {
console.log('Received data:', data) // Process and log the data
}
fetchData('https://api.example.com/user', processData) // Fetch data and process it
- Callback Functions:
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i) // Log the value of i
}, i * 1000) // Delay based on i
}
// Output: 6, 6, 6, 6, 6
for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i) // Log the value of i
}, i * 1000) // Delay based on i
}
// Output: 1, 2, 3, 4, 5
- Memory Leaks:
// Define createClosure:
function createClosure() {
// Declare a large array largeArray
let largeArray = new Array(1000000).fill('x') // Large array
// Return a closure
return function () {
console.log(largeArray.length) // Log the length of the array
largeArray = null // Free up memory
}
}
// Create a closure instance
const closure = createClosure()
// Invoke the closure
closure() // Output: 1000000
- Memoization:
// Define memoize Function
function memoize(fn) {
const cache = {} // Private cache object
return function (...args) {
const key = JSON.stringify(args) // Create a key from arguments
if (cache[key]) {
return cache[key] // Return cached result
}
const result = fn(...args)
cache[key] = result // Store result in cache
return result
}
}
// Example usage:
const fibonacci = memoize(function (n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
})
console.log(fibonacci(10)) // 55
- Event Handling:
function createEventListener(element, eventType) {
return function (callback) {
element.addEventListener(eventType, callback)
}
}
// Example usage:
const button = document.getElementById('myButton')
const onClick = createEventListener(button, 'click')
onClick(function () {
console.log('Button clicked!')
})
- Private Members in Constructors:
function Person(name, age) {
const privateData = { secret: 'I have a secret!' } // Private data
this.name = name
this.age = age
this.getSecret = function () {
return privateData.secret
}
}
// Example usage:
const john = new Person('John', 30)
console.log(john.name) // 'John'
console.log(john.getSecret()) // 'I have a secret!'
console.log(john.privateData) // undefined (private)
- Managing Dependencies:
function createModule(dependency) {
// Private dependency
const privateDependency = dependency
// Public methods
return {
useDependency: function () {
console.log(privateDependency)
},
}
}
// Example usage:
const module = createModule('Dependency')
module.useDependency() // Output: 'Dependency'
- Currying
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args)
} else {
return function (...moreArgs) {
return curried(...args, ...moreArgs)
}
}
}
}
// Example usage:
function add(a, b, c) {
return a + b + c
}
const curriedAdd = curry(add)
console.log(curriedAdd(1)(2)(3)) // Output: 6
- Promises and Asynchronous Operations:
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => response.json())
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
}
// Example usage:
const url = 'https://api.example.com/data'
fetchData(url)
.then((data) => {
console.log('Data fetched:', data)
})
.catch((error) => {
console.error('Error fetching data:', error)
})
- Iterators and Generators
function createIterator(arr) {
let index = 0 // Private variable
return {
next: function () {
return index < arr.length ? { value: arr[index++], done: false } : { done: true }
},
}
}
// Example usage:
const iterator = createIterator(['a', 'b', 'c'])
console.log(iterator.next()) // Output: { value: 'a', done: false }
console.log(iterator.next()) // Output: { value: 'b', done: false }
console.log(iterator.next()) // Output: { value: 'c', done: false }
console.log(iterator.next()) // Output: { done: true }
- Functional Programming
function compose(...fns) {
return function (result) {
return fns.reduceRight((acc, fn) => fn(acc), result)
}
}
// Example usage:
const add1 = (x) => x + 1
const multiply2 = (x) => x * 2
const add1ThenMultiply2 = compose(multiply2, add1)
console.log(add1ThenMultiply2(5)) // Output: 12 (5 + 1 = 6, 6 * 2 = 12)
- Timer Functions
function debounce(fn, delay) {
let timeoutID // Private variable
return function (...args) {
clearTimeout(timeoutID) // Clear previous timeout
timeoutID = setTimeout(() => {
fn(...args)
}, delay)
}
}
// Example usage:
const handleResize = debounce(() => {
console.log('Window resized')
}, 300)
window.addEventListener('resize', handleResize)
Reference: https://dev.to/jahid6597/mastering-javascript-closures-a-comprehensive-guide-1gd7
99. closure
99.https://bigfrontend.dev/quiz/closure-1
let dev = 'bfe'
function a() {
let dev = 'BFE'
return function () {
console.log(dev)
}
}
dev = 'bigfrontend'
a()()
Solution: Let's break down this code step by step:
let dev = 'bfe'
function a() {
let dev = 'BFE'
return function () {
console.log(dev)
}
}
dev = 'bigfrontend'
a()()
We start by declaring a global variable
dev
and assigning it the value 'bfe'.We define a function
a()
. Inside this function:- We declare a new local variable
dev
and assign it the value 'BFE'. - We return an anonymous function that, when called, will log the value of
dev
.
- We declare a new local variable
We reassign the global
dev
variable to 'bigfrontend'.Finally, we call
a()()
. This is actually two function calls:- First, we call
a()
, which returns the inner anonymous function. - Then we immediately call that returned function.
- First, we call
Now, the key to understanding this code is the concept of closures in JavaScript. When a function is created, it retains access to the variables in its lexical scope, even if it's executed in a different scope.
So, when the inner anonymous function is created inside a()
, it "closes over" the local dev
variable, which has the value 'BFE'.
Even though we later change the global dev
to 'bigfrontend', this doesn't affect the dev
that the inner function has access to.
Therefore, when we execute a()()
, it logs the value of the dev
variable that was captured in its closure, which is 'BFE'.
The output of this code will be:
BFE
This example demonstrates:
- Variable scoping: The
dev
insidea()
is a different variable from the globaldev
. - Closures: The inner function maintains access to the
dev
variable from its containing scope. - The difference between variable declaration and assignment.
It's a common interview question that tests understanding of these fundamental JavaScript concepts.
5.Prototype
48. Prototype
48.https://bigfrontend.dev/quiz/prototype
function Foo() {}
Foo.prototype.bar = 1
const a = new Foo()
console.log(a.bar)
Foo.prototype.bar = 2
const b = new Foo()
console.log(a.bar)
console.log(b.bar)
Foo.prototype = { bar: 3 }
const c = new Foo()
console.log(a.bar)
console.log(b.bar)
console.log(c.bar)
Solution: This code demonstrates the concept of prototypal inheritance in JavaScript and how changes to the prototype affect instances of an object. Let's break it down step by step:
function Foo() { }
- This defines a constructor function
Foo
.
- This defines a constructor function
Foo.prototype.bar = 1
- This adds a property
bar
with value 1 toFoo
's prototype.
- This adds a property
const a = new Foo()
- This creates a new instance of
Foo
calleda
.
- This creates a new instance of
console.log(a.bar)
- This logs 1, as
a
inheritsbar
fromFoo
's prototype.
- This logs 1, as
Foo.prototype.bar = 2
- This changes the value of
bar
inFoo
's prototype to 2.
- This changes the value of
const b = new Foo()
- This creates another instance of
Foo
calledb
.
- This creates another instance of
console.log(a.bar)
- This logs 2, as
a
still references the same prototype object, which now hasbar
set to 2.
- This logs 2, as
console.log(b.bar)
- This also logs 2, as
b
references the same prototype object asa
.
- This also logs 2, as
Foo.prototype = {bar: 3}
- This completely replaces
Foo
's prototype with a new object that hasbar
set to 3.
- This completely replaces
const c = new Foo()
- This creates a third instance of
Foo
calledc
, which will use the new prototype.
- This creates a third instance of
console.log(a.bar)
- This still logs 2, because
a
still references the old prototype object.
- This still logs 2, because
console.log(b.bar)
- This also logs 2, because
b
also still references the old prototype object.
- This also logs 2, because
console.log(c.bar)
- This logs 3, because
c
was created after the prototype was replaced and thus uses the new prototype object.
- This logs 3, because
The output will be:
1
2
2
2
2
3
Key points:
- Objects created with
new
inherit properties from their constructor's prototype. - Changing a property on the prototype affects all instances that refer to that prototype.
- Replacing the entire prototype only affects instances created afterwards; existing instances still reference the old prototype.
- This demonstrates why it's generally better to modify existing prototypes rather than replacing them entirely, as replacing can break the prototype chain for existing instances.
This quiz tests understanding of JavaScript's prototypal inheritance model and how changes to prototypes affect object instances.
53. Prototype 2
53.https://bigfrontend.dev/quiz/prototype2
function F() {
this.foo = 'bar'
}
const f = new F()
console.log(f.prototype)
Solution:
This code demonstrates some fundamental concepts of JavaScript's object-oriented programming and prototypes. Let's break it down:
function F() { this.foo = 'bar' }
- This defines a constructor function
F
. - When called with
new
, it creates an object and sets itsfoo
property to 'bar'.
- This defines a constructor function
const f = new F()
- This creates a new instance of
F
and assigns it to the variablef
. - The
new
keyword does several things: a. Creates a new empty object. b. Sets this new object's internal[[Prototype]]
(i.e.,__proto__
) to the object referenced byF.prototype
. c. CallsF
withthis
set to the new object. d. Returns the new object (unless the constructor returns a different object).
- This creates a new instance of
console.log(f.prototype)
- This attempts to log the
prototype
property off
. - The output will be:
undefined
- This attempts to log the
The key point here is that prototype
is a property of constructor functions, not of instances created from those constructors.
F.prototype
exists and is an object (by default, it's an empty object unless explicitly set to something else).f
is an instance created fromF
. It doesn't have its ownprototype
property.f
does have an internal[[Prototype]]
(accessible via__proto__
in most environments), which referencesF.prototype
.
If we wanted to access the prototype of f
, we could use:
Object.getPrototypeOf(f)
- or
f.__proto__
(though__proto__
is deprecated for use in production code)
Both of these would give us the same object that F.prototype
refers to.
This quiz tests the understanding of:
- The difference between a constructor function's
prototype
and an instance's[[Prototype]]
. - How the
new
keyword works with constructor functions. - The fact that regular objects (instances) don't have a
prototype
property, even though they do have a prototype in their prototype chain.
51. method
51.https://bigfrontend.dev/quiz/method
// This is a trick question
// case 1
const obj1 = {
foo() {
console.log(super.foo())
},
}
Object.setPrototypeOf(obj1, {
foo() {
return 'bar'
},
})
obj1.foo()
// case 2
const obj2 = {
foo: function () {
console.log(super.foo())
},
}
Object.setPrototypeOf(obj2, {
foo() {
return 'bar'
},
})
obj2.foo()
Solution: This code demonstrates the behavior of the super
keyword in JavaScript, specifically in object literals and method definitions. It's indeed a tricky question because it highlights a subtle difference in how super
works in different contexts. Let's break it down:
Case 1:
const obj1 = {
foo() {
// method
console.log(super.foo())
},
}
Object.setPrototypeOf(obj1, {
foo() {
return 'bar'
},
})
obj1.foo()
In this case:
obj1
is created with a methodfoo
using the shorthand method syntax.- The prototype of
obj1
is set to an object with its ownfoo
method. - When
obj1.foo()
is called, it successfully usessuper.foo()
to call thefoo
method from its prototype. - The output will be:
bar
Case 2:
const obj2 = {
foo: function () {
// property
console.log(super.foo())
},
}
Object.setPrototypeOf(obj2, {
foo() {
return 'bar'
},
})
obj2.foo()
In this case:
obj2
is created with afoo
property assigned a function expression.- The prototype of
obj2
is set to an object with its ownfoo
method. - When
obj2.foo()
is called, it throws aSyntaxError
becausesuper
is not allowed in this context.
The key difference:
- In Case 1, the method is defined using the shorthand syntax
foo() {...}
, which allows the use ofsuper
. - In Case 2, the method is defined as a property with a function expression
foo: function() {...}
, which does not allow the use ofsuper
.
The output will be: For Case 1: bar
For Case 2: SyntaxError: 'super' keyword unexpected here
This quiz highlights that:
- The
super
keyword can be used in object literals, but only in methods defined using the shorthand syntax. - Function expressions assigned to object properties cannot use
super
, even if they're used as methods. - The ability to use
super
depends on how a method is defined, not just on whether it's a method of an object.
This is a nuanced aspect of JavaScript that even experienced developers might not be aware of, making it an excellent "trick question" for testing deep language knowledge.
104. Inheritance
104.https://bigfrontend.dev/quiz/Inheritance
class A {
a = 'a'
}
A.prototype.c = 'c'
class B extends A {
b = 'b'
}
const a = new A()
const b = new B()
console.log(a.a)
console.log(a.b)
console.log(a.c)
console.log(b.a)
console.log(b.b)
console.log(b.c)
Solution:
Let's break down this code and explain what's happening:
class A {
a = 'a'
}
A.prototype.c = 'c'
class B extends A {
b = 'b'
}
const a = new A()
const b = new B()
console.log(a.a)
console.log(a.b)
console.log(a.c)
console.log(b.a)
console.log(b.b)
console.log(b.c)
This code demonstrates class inheritance and prototype chain in JavaScript. Let's go through it step by step:
We define class
A
with a propertya
set to 'a'.We add a property
c
toA
's prototype, setting it to 'c'.We define class
B
that extendsA
, with its own propertyb
set to 'b'.We create an instance of
A
calleda
.We create an instance of
B
calledb
.We then try to access various properties on both instances.
Now, let's look at each console.log statement:
console.log(a.a)
:a
is an instance ofA
, which has the propertya
.- This logs
'a'
.
console.log(a.b)
:a
is an instance ofA
, which doesn't have a propertyb
.- This logs
undefined
.
console.log(a.c)
:a
doesn't have its own propertyc
, but it can accessc
fromA.prototype
.- This logs
'c'
.
console.log(b.a)
:b
is an instance ofB
, which extendsA
. It inherits the propertya
fromA
.- This logs
'a'
.
console.log(b.b)
:b
is an instance ofB
, which has its own propertyb
.- This logs
'b'
.
console.log(b.c)
:b
doesn't have its own propertyc
, but it can accessc
fromA.prototype
through inheritance.- This logs
'c'
.
Therefore, the output will be:
'a'
undefined
'c'
'a'
'b'
'c'
This code demonstrates several important concepts in JavaScript:
- Class definition and inheritance (
class
andextends
keywords). - Instance properties (defined in the class body with
=
). - Prototype properties (added to
A.prototype
). - Property lookup through the prototype chain.
- How subclasses inherit both instance and prototype properties from their parent class.
It's a great example to understand how JavaScript handles object-oriented programming concepts like inheritance and the prototype chain.
129. Function II
129.https://bigfrontend.dev/quiz/function-ii
console.log(Function.prototype.__proto__ === Object.prototype) // true
console.log(Function.__proto__ === Object.__proto__) // true
console.log(Function.__proto__.__proto__ === Object.prototype) // true
console.log(Object.constructor.prototype === Object.prototype) // false
console.log(Function.constructor === Function) // true
console.log(Object.constructor === Object) // false
console.log(Array.__proto__ === Function.__proto__) // true
console.log(Array.constructor === Function) // true
console.log(Object.__proto__ === Function) // false
console.log(Function.__proto__ === Function.prototype) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Map instanceof Map) // false
Solution: This code explores the prototype chain and constructor relationships in JavaScript. Let's break it down line by line:
console.log(Function.prototype.__proto__ === Object.prototype)
- True. The prototype of Function.prototype is Object.prototype.
console.log(Function.__proto__ === Object.__proto__)
- True. Both Function and Object are functions, so they share the same prototype (Function.prototype).
console.log(Function.__proto__.__proto__ === Object.prototype)
- True. Function.proto is Function.prototype, and its prototype is Object.prototype.
console.log(Object.constructor.prototype === Object.prototype)
- False. Object.constructor is Function, so this compares Function.prototype to Object.prototype.
console.log(Function.constructor === Function)
- True. Function is its own constructor.
console.log(Object.constructor === Object)
- False. Object's constructor is Function, not Object itself.
console.log(Array.__proto__ === Function.__proto__)
- True. Both Array and Function are functions, so they share the same prototype (Function.prototype).
console.log(Array.constructor === Function)
- True. The constructor of Array is Function.
console.log(Object.__proto__ === Function)
- False. Object.proto is Function.prototype, not Function itself.
console.log(Function.__proto__ === Function.prototype)
- True. Function's prototype is Function.prototype.
console.log(Object instanceof Object)
- True. The prototype chain of Object eventually leads to Object.prototype.
console.log(Function instanceof Function)
- True. Function is an instance of itself.
console.log(Map instanceof Map)
- False. Map is not an instance of itself; it's an instance of Function.
Key points:
- In JavaScript, functions are objects, and they have a prototype chain.
- The
__proto__
property (or[[Prototype]]
internal slot) points to the prototype of an object. - The
prototype
property is used for instances created withnew
. Function
is a special object that is its own constructor.- The prototype chain can be circular, which is why some of these comparisons are true.
This code demonstrates the complex relationships between objects, functions, and prototypes in JavaScript's object-oriented system.
133. toString
132.https://bigfrontend.dev/quiz/tostring
const obj = {}
const fun = () => {}
console.log(obj.toString === Object.toString)
console.log(fun.toString === Function.toString)
console.log(obj.toString === Object.prototype.toString)
console.log(fun.toString === Function.prototype.toString)
console.log(Object.toString === Object.prototype.toString)
console.log(Function.toString === Function.prototype.toString)
Solution:
Certainly! Let's break down this code and explain each comparison:
const obj = {}
const fun = () => {}
console.log(obj.toString === Object.toString)
console.log(fun.toString === Function.toString)
console.log(obj.toString === Object.prototype.toString)
console.log(fun.toString === Function.prototype.toString)
console.log(Object.toString === Object.prototype.toString)
console.log(Function.toString === Function.prototype.toString)
Let's go through each comparison:
console.log(obj.toString === Object.toString)
- Result:
false
obj.toString
refers toObject.prototype.toString
Object.toString
refers toFunction.prototype.toString
(becauseObject
is a function)
- Result:
console.log(fun.toString === Function.toString)
- Result:
true
fun.toString
refers toFunction.prototype.toString
Function.toString
also refers toFunction.prototype.toString
,- Since all functions in JavaScript inherit from
Function.prototype
,fun.toString
is equivalent to the same method defined onFunction.prototype
. Thus, it logs true
- Result:
console.log(obj.toString === Object.prototype.toString)
- Result:
true
obj.toString
is inherited fromObject.prototype.toString
- Result:
console.log(fun.toString === Function.prototype.toString)
- Result:
true
fun.toString
is inherited fromFunction.prototype.toString
- Result:
console.log(Object.toString === Object.prototype.toString)
- Result:
false
Object.toString
refers toFunction.prototype.toString
(becauseObject
is a function)Object.prototype.toString
is a different method
- Result:
console.log(Function.toString === Function.prototype.toString)
- Result:
true
Function.toString
looks for thetoString
method on theFunction
object itself. If its not found there, it'll look up the prototype chain and find it onFunction.prototype
. Therefore, both expressions essentially point to the same method. Logs true
- Result:
Key points to understand:
Objects inherit methods from their prototype chain. Regular objects inherit from
Object.prototype
, while functions inherit fromFunction.prototype
.Object
,Function
, and other built-in constructors are themselves functions, so they inherit fromFunction.prototype
.The
toString
method on most objects comes fromObject.prototype.toString
.The
toString
method on functions comes fromFunction.prototype.toString
.When you access a method directly on a constructor (like
Object.toString
orFunction.toString
), you're getting the method fromFunction.prototype
, not from the constructor's own prototype.Even when two references point to the same method implementation, they might not be strictly equal (
===
) if they're accessed through different paths in the prototype chain.
This code demonstrates the nuances of method inheritance and the prototype chain in JavaScript, particularly how built-in objects and functions relate to each other.
134. Object.groupBy
132.https://bigfrontend.dev/quiz/object-groupby
const items = [
{
id: 'a',
},
{
id: 'b',
},
]
const groups = Object.groupBy(items, ({ id }) => id)
console.log(items.toString())
console.log(groups.toString())
Solution:
Certainly! Let's break down this code and explain what's happening:
const items = [
{
id: 'a',
},
{
id: 'b',
},
]
const groups = Object.groupBy(items, ({ id }) => id)
console.log(items.toString())
console.log(groups.toString())
Let's go through this step by step:
We start with an array
items
containing two objects, each with anid
property.Object.groupBy(items, ({id}) => id)
:- This is using the
Object.groupBy()
method, which is a relatively new addition to JavaScript (as of my last update, it was still at the proposal stage). - It groups the elements of the
items
array based on the return value of the callback function. - The callback function
({id}) => id
extracts theid
property from each object and uses it as the grouping key. - The result is an object where the keys are the
id
values, and the values are arrays of objects with thatid
.
- This is using the
console.log(items.toString())
:- When you call
toString()
on an array, it callstoString()
on each element and joins the results with commas. - For objects, the default
toString()
method returns"[object Object]"
. - So this will output:
[object Object],[object Object]
- When you call
console.log(groups.toString())
:groups
is a null-prototype object with properties for all groups, each assigned to an array containing the elements of the associated group.- null-prototype object lacks the
Object.prototype.toString()
method and will trigger error.
Therefore, the output will be:
[object Object],[object Object]
TypeError: groups.toString is not a function
Key points to understand:
Object.groupBy()
is a powerful method for grouping array elements. It creates a null-prototype object and lacks the theObject.prototype.toString()
method.The
toString()
method doesn't provide a very informative representation of objects or complex data structures. It's generally more useful to useJSON.stringify()
or custom logging for these kinds of structures.When called on an array,
toString()
gives a string representation of the array's elements.When called on an object,
toString()
simply returns"[object Object]"
unless it's overridden.
this
6.6. Arrow Function
6.https://bigfrontend.dev/quiz/6-Arrow-Function
const obj = {
dev: 'bfe',
a: function () {
return this.dev
},
b() {
return this.dev
},
c: () => {
return this.dev
},
d: function () {
return (() => {
return this.dev
})()
},
e: function () {
return this.b()
},
f: function () {
return this.b
},
g: function () {
return this.c()
},
h: function () {
return this.c
},
i: function () {
return () => {
return this.dev
}
},
}
console.log(obj.a())
console.log(obj.b())
console.log(obj.c())
console.log(obj.d())
console.log(obj.e())
console.log(obj.f()())
console.log(obj.g())
console.log(obj.h()())
console.log(obj.i()())
Solution: Let's break it down step by step:
We have an object
obj
with a propertydev
and several methods.obj.a()
andobj.b()
: These are regular function methods. When called,this
refers to the object, so they return'bfe'
.obj.c()
: This is an arrow function. Arrow functions don't have their ownthis
context, sothis.dev
here refers to the global (or module) scope, which doesn't have adev
property. It will returnundefined
.obj.d()
: This is a regular function that returns the result of an immediately invoked arrow function. The arrow function captures thethis
from the outer function, so it returns'bfe'
.obj.e()
: This callsthis.b()
, which is equivalent to callingobj.b()
, so it returns'bfe'
.obj.f()
: This returns the functionb
itself (without calling it).obj.f()()
then calls this returned function, and since it's called without a context,this
will beundefined
orglobal
in non-strict mode. It will likely returnundefined
.obj.g()
: This callsthis.c()
, which is equivalent toobj.c()
. As explained earlier, this will returnundefined
.obj.h()
: This returns the arrow functionc
itself.obj.h()()
then calls this returned arrow function. Arrow functions keep their originalthis
binding, which in this case is the global context, so it will returnundefined
.obj.i()
: This returns a new arrow function. When this returned function is called withobj.i()()
, it uses thethis
from where it was defined (which is theobj
), so it returns'bfe'
.
So, the output will be:
'bfe'
'bfe'
undefined
'bfe'
'bfe'
undefined
undefined
undefined
'bfe'
This code demonstrates various behaviors of this
in different function types and calling contexts in JavaScript.
102. Arrow Function II
102.https://bigfrontend.dev/quiz/Arrow-Function-II
class Site {
name = 'BFE'
getHandle() {
return {
name: 'bigfrontend',
getName1() {
return this.name
},
getName2: () => {
return this.name
},
getName3: function () {
return this.name
},
}
}
}
const site = new Site()
console.log(site.getHandle().getName1())
console.log(site.getHandle().getName2())
console.log(site.getHandle().getName3())
Solution:
Let's break down this code and explain what's happening:
class Site {
name = 'BFE'
getHandle() {
return {
name: 'bigfrontend',
getName1() {
return this.name
},
getName2: () => {
return this.name
},
getName3: function () {
return this.name
},
}
}
}
const site = new Site()
console.log(site.getHandle().getName1())
console.log(site.getHandle().getName2())
console.log(site.getHandle().getName3())
This code demonstrates different ways of defining methods and how they affect the this
context. Let's go through it step by step:
We define a
Site
class with a propertyname
set to 'BFE'.The
getHandle()
method returns an object with its ownname
property and three methods to get the name.We create an instance of
Site
calledsite
.We call each of the getName methods through
site.getHandle()
.
Now, let's look at each method call:
site.getHandle().getName1()
getName1
is defined as a method using the shorthand syntax.- When called,
this
refers to the object it's called on (the object returned bygetHandle()
). - This object has
name: 'bigfrontend'
, so it returns'bigfrontend'
.
site.getHandle().getName2()
getName2
is defined as an arrow function.- Arrow functions do not have their own
this
context. They inheritthis
from the enclosing scope. - In this case, the enclosing scope is the
Site
instance, wherename
is 'BFE'. - So this returns
'BFE'
.
site.getHandle().getName3()
getName3
is defined as a regular function.- Like
getName1
, when called,this
refers to the object it's called on (the object returned bygetHandle()
). - This object has
name: 'bigfrontend'
, so it returns'bigfrontend'
.
Therefore, the output will be:
bigfrontend
BFE
bigfrontend
This code demonstrates several important concepts in JavaScript:
- Method definitions and their effect on
this
binding. - The behavior of arrow functions with regard to
this
. - The difference between the
this
context in methods and in the class itself.
It's a great example to understand how this
works in different contexts and how arrow functions can be used to maintain the outer this
context.
this
19. 19.https://bigfrontend.dev/quiz/this
const obj = {
a: 1,
b: function () {
console.log(this.a)
},
c() {
console.log(this.a)
},
d: () => {
console.log(this.a)
},
e: (function () {
return () => {
console.log(this.a)
}
})(),
f: function () {
return () => {
console.log(this.a)
}
},
}
console.log(obj.a)
obj.b()
obj.b()
const b = obj.b
b()
obj.b.apply({ a: 2 })
obj.c()
obj.d()
obj.d()
obj.d.apply({ a: 2 })
obj.e()
obj.e()
obj.e.call({ a: 2 })
obj.f()()
obj.f()()
obj.f().call({ a: 2 })
Solution:
Let's analyze each line of output:
console.log(obj.a)
Output:1
Explanation: This simply logs the value of thea
property ofobj
.obj.b()
Output:1
Explanation: When called as a method,this
insideb
refers toobj
.;(obj.b)()
Output:1
Explanation: The parentheses don't change the context, sothis
still refers toobj
.const b = obj.b; b()
Output:undefined
Explanation: Whenb
is called as a standalone function,this
becomes the global object (orundefined
in strict mode), which doesn't have ana
property.obj.b.apply({a: 2})
Output:2
Explanation:apply
explicitly setsthis
to the provided object{a: 2}
.obj.c()
Output:1
Explanation: Method shorthand syntax behaves the same asb()
.obj.d()
Output:undefined
Explanation: Arrow functions capturethis
from their enclosing lexical scope at the time of their creation. Here,this
refers to the global object (orundefined
in modules).;(obj.d)()
Output:undefined
Explanation: Same as above, the arrow function'sthis
is not affected by how it's called.obj.d.apply({a:2})
Output:undefined
Explanation:apply
doesn't affect arrow functions'this
binding.obj.e()
Output:undefined
Explanation:e
is an immediately invoked function expression (IIFE) that returns an arrow function. The IIFE'sthis
is the global object, so the returned arrow function'sthis
is also the global object.;(obj.e)()
Output:undefined
Explanation: Same as above, parentheses don't change the behavior.obj.e.call({a:2})
Output:undefined
Explanation:call
doesn't affect the arrow function'sthis
binding.obj.f()()
Output:1
Explanation:f
is a regular function, so itsthis
isobj
. It returns an arrow function that captures thisthis
, so it logsobj.a
which is1
.;(obj.f())()
Output:1
Explanation: Same as above, parentheses don't change the behavior.obj.f().call({a:2})
Output:1
Explanation: The arrow function returned byf
has already capturedthis
asobj
, socall
doesn't affect itsthis
binding.
Key takeaways:
- Regular functions and method shorthand syntax determine
this
based on how they're called. - Arrow functions capture
this
from their enclosing lexical scope and are not affected by how they're called. - The
apply
,call
, andbind
methods can changethis
for regular functions but not for arrow functions. - Be careful when using arrow functions as object methods if you need
this
to refer to the object.
this
II
33. 33.https://bigfrontend.dev/quiz/this-II
const obj = {
a: 1,
b() {
return this.a
},
}
console.log(obj.b())
console.log((true ? obj.b : a)())
console.log((true, obj.b)())
console.log((3, obj['b'])())
console.log(obj.b())
console.log((obj.c = obj.b)())
Solution:
Here we define an object obj
with a property a
set to 1 and a method b
that returns this.a
.
Now let's look at each console.log statement:
console.log(obj.b())
Output:1
When called as a method onobj
,this
insideb()
refers toobj
, sothis.a
is 1.console.log((true ? obj.b : a)())
Output:undefined
The ternary operator returnsobj.b
, but it's then called without any context. This is equivalent tolet temp = obj.b; temp()
. In this case,this
becomesundefined
in strict mode or the global object in non-strict mode. Either way,this.a
is undefined.console.log((true, obj.b)())
Output:undefined
The comma operator evaluates both operands and returns the last one. Similar to #2, this detachesb
fromobj
before calling it, sothis
is lost.console.log((3, obj['b'])())
Output:undefined
This is the same as #3, just using bracket notation to accessb
. The function is still called without context.console.log((obj.b)())
Output:1
Parentheses don't affect thethis
binding.obj.b
is called as a method onobj
, sothis
isobj
.console.log((obj.c = obj.b)())
Output:undefined
This assignsobj.b
to a new propertyc
onobj
, but then immediately calls the result of this assignment. The assignment expression returns the assigned value (the function), but detached fromobj
. So it's called without context, similar to #2 and #3.
Key points to understand:
- When a function is called as a method (like
obj.b()
),this
inside the function refers to the object it was called on. - When a function is called standalone (like
let f = obj.b; f()
),this
is undefined (in strict mode) or the global object (in non-strict mode). - The way a function is called matters more than how it's defined when determining
this
. - Parentheses around a method call don't affect
this
binding. - Operations like the ternary operator, comma operator, or assignment can detach a method from its object, causing it to lose its
this
binding when called.
This code demonstrates some of the trickier aspects of this
in JavaScript, which can be a source of bugs if not well understood. It's one reason why arrow functions, which don't have their own this
context, are often used in modern JavaScript. Would you like me to elaborate on any part of this explanation?
const obj = {
a: 1,
b() {
return this.a
},
}
console.log(obj.b()) // 1
console.log((true ? obj.b : a)()) // undefined
console.log((true, obj.b)()) // undefined
console.log((3, obj['b'])()) // undefined
console.log(obj.b()) // 1
console.log((obj.c = obj.b)()) // undefined
this
III
41. 41.https://bigfrontend.dev/quiz/this-III
const obj = {
a: 1,
b: this.a + 1,
c: () => this.a + 1,
d() {
return this.a + 1
},
e() {
return (() => this.a + 1)()
},
}
console.log(obj.b)
console.log(obj.c())
console.log(obj.d())
console.log(obj.e())
Solution:
This code explores the behavior of this
in different contexts within an object. Let's break it down:
const obj = { ... }
- This creates an object with properties a, b, c, d, and e.
a: 1
- Simple property assignment.
b: this.a + 1
- In this context,
this
refers to the global object (or undefined in strict mode), not toobj
. this.a
is undefined, sothis.a + 1
evaluates to NaN.
- In this context,
c: () => this.a + 1
- This is an arrow function. Arrow functions capture
this
from their surrounding lexical scope. - Here,
this
is the same as in the global scope (likely window in a browser, or global in Node.js). this.a
is undefined, so this will return NaN when called.
- This is an arrow function. Arrow functions capture
d() { return this.a + 1 }
- This is a method. When called as
obj.d()
,this
will refer toobj
. - It will return
obj.a + 1
, which is 2.
- This is a method. When called as
e() { return (() => this.a + 1)() }
- This method returns the result of an immediately invoked arrow function.
- The arrow function captures
this
from thee
method, wherethis
refers toobj
. - It will return
obj.a + 1
, which is 2.
Now, let's look at the console.log statements:
console.log(obj.b)
- Outputs:
NaN
- This is the value of
b
computed when the object was created.
- Outputs:
console.log(obj.c())
- Outputs:
NaN
- The arrow function
c
usesthis
from the global scope, wherea
is undefined.
- Outputs:
console.log(obj.d())
- Outputs:
2
- When called as a method,
this
refers toobj
, so it returns1 + 1
.
- Outputs:
console.log(obj.e())
- Outputs:
2
- The arrow function inside
e
capturesthis
from the method scope, which isobj
.
- Outputs:
In summary, the output will be:
NaN
NaN
2
2
This quiz demonstrates the complexity of this
in JavaScript, especially:
- How
this
behaves differently in arrow functions vs. regular functions - The importance of how a function is called (as a method or not)
- How
this
is determined at the time of object creation vs. function execution
this
IV
49. 49.https://bigfrontend.dev/quiz/this-4
var bar = 1
function foo() {
return this.bar++
}
const a = {
bar: 10,
foo1: foo,
foo2: function () {
return foo()
},
}
console.log(a.foo1.call())
console.log(a.foo1())
console.log(a.foo2.call())
console.log(a.foo2())
Solution:
This code demonstrates the behavior of this
in JavaScript and how it changes based on how a function is called. Let's break it down step by step:
var bar = 1
- This declares a global variable
bar
with value 1.
- This declares a global variable
function foo() { return this.bar++ }
- This defines a function
foo
that increments and returnsthis.bar
.
- This defines a function
const a = { ... }
- This creates an object
a
with propertiesbar
,foo1
, andfoo2
.
- This creates an object
Now, let's look at each console.log statement:
console.log(a.foo1.call())
call()
without arguments setsthis
to the global object (or undefined in strict mode).- In non-strict mode, this increments and returns the global
bar
(1), then sets it to 2. - Output: 1
console.log(a.foo1())
- This calls
foo1
as a method ofa
, sothis
refers toa
. - It increments and returns
a.bar
(10), then sets it to 11. - Output: 10
- This calls
console.log(a.foo2.call())
call()
is used onfoo2
, butfoo2
internally callsfoo()
without any context.- In
foo()
,this
refers to the global object again. - It increments and returns the global
bar
(2), then sets it to 3. - Output: 2
console.log(a.foo2())
- This calls
foo2
as a method ofa
, butfoo2
internally callsfoo()
without any context. - In
foo()
,this
again refers to the global object. - It increments and returns the global
bar
(3), then sets it to 4. - Output: 3
- This calls
So, the final output will be:
1
10
2
3
Key points:
- The value of
this
is determined by how a function is called, not where it's defined. - Using
call()
without arguments or withnull
/undefined
setsthis
to the global object in non-strict mode. - When a function is called as a method (
object.method()
),this
refers to the object. - When a function is called without any context (like inside
foo2
),this
defaults to the global object in non-strict mode. - The global
bar
anda.bar
are separate variables that are modified independently.
This quiz tests understanding of this
binding in JavaScript, which is a crucial and often confusing concept for developers.
this
V
97. 97.https://bigfrontend.dev/quiz/this-V
const obj = {
prefix: 'BFE',
list: ['1', '2', '3'],
log() {
this.list.forEach(function (item) {
console.log(this.prefix + item)
})
},
}
obj.log()
Solution: Certainly, I'll explain this code in detail:
const obj = {
prefix: 'BFE',
list: ['1', '2', '3'],
log() {
this.list.forEach(function (item) {
console.log(this.prefix + item)
})
},
}
obj.log()
Let's break it down:
We define an object
obj
with three properties:prefix
: a string with value 'BFE'list
: an array of strings ['1', '2', '3']log
: a method
The
log
method usesthis.list.forEach()
to iterate over thelist
array.Inside
forEach
, there's an anonymous function that takes eachitem
as an argument.This function attempts to log
this.prefix + item
for each item in the list.Finally, we call
obj.log()
.
Now, here's the tricky part and the likely point of the quiz:
The forEach
method creates a new execution context for its callback function. In this new context, this
is not bound to obj
, but instead defaults to the global object (or undefined
in strict mode).
As a result, when the code runs, it will try to access this.prefix
inside the forEach
callback, but this
doesn't refer to obj
in this context. So, this.prefix
will be undefined
.
The output will be:
undefined1
undefined2
undefined3
To fix this and make it work as intended, you could use an arrow function, which doesn't create its own this
context:
log() {
this.list.forEach((item) => {
console.log(this.prefix + item);
});
}
Or you could explicitly bind this
to the forEach
callback:
log() {
this.list.forEach(function (item) {
console.log(this.prefix + item);
}.bind(this));
}
Both of these solutions would correctly output:
BFE1
BFE2
BFE3
This quiz tests understanding of this
context in JavaScript, particularly how it behaves differently in regular functions versus arrow functions, and within methods of objects.
this
VI
107. 107.https://bigfrontend.dev/quiz/this-VI
const a = {
dev: 'BFE.dev',
update: (name) => {
this.dev = name
},
}
a.update('bigfrontend.dev')
console.log(a.dev)
Solution:
Let's break down this code and explain what's happening:
const a = {
dev: 'BFE.dev',
update: (name) => {
this.dev = name
},
}
a.update('bigfrontend.dev')
console.log(a.dev)
This code demonstrates a common misconception about how this
works with arrow functions in JavaScript. Let's go through it step-by-step:
We define an object
a
with two properties:dev
: a string with the value 'BFE.dev'update
: an arrow function that takes aname
parameter and attempts to updatethis.dev
We call
a.update('bigfrontend.dev')
, attempting to update thedev
property.We then log
a.dev
to the console.
Now, here's the key point: The arrow function update
does not behave as you might expect. Here's why:
Arrow functions do not have their own
this
context. Instead, they inheritthis
from the enclosing lexical scope at the time they are defined.In this case, the arrow function is defined in the global scope (or whatever scope
a
is defined in), not as a method ofa
.Therefore, when
this.dev = name
is executed inside the arrow function, it's not updating thedev
property ofa
. Instead, it's either:- In non-strict mode: creating or updating a
dev
property on the global object (e.g.,window
in a browser). - In strict mode: throwing a TypeError because
this
isundefined
.
- In non-strict mode: creating or updating a
The original
dev
property ofa
remains unchanged.
As a result, when we log a.dev
, we still get the original value:
BFE.dev
To fix this and make it work as intended, you could use a regular function instead of an arrow function:
const a = {
dev: 'BFE.dev',
update: function (name) {
this.dev = name
},
}
Or use the method shorthand syntax:
const a = {
dev: 'BFE.dev',
update(name) {
this.dev = name
},
}
Both of these would correctly update a.dev
when called.
This code demonstrates several important concepts in JavaScript:
- The behavior of
this
in different function types. - How arrow functions inherit
this
from their enclosing lexical scope. - The difference between arrow functions and regular functions when used as object methods.
- The importance of understanding
this
binding for proper object manipulation.
It's a common pitfall when working with objects and arrow functions in JavaScript, making it a valuable example for understanding these concepts.
this
VII
117. 117.https://bigfrontend.dev/quiz/this-VII
class A {
static dev = 'BFE'
dev = 'bigfrontend'
}
class B extends A {
log() {
console.log(this.dev)
}
static log() {
console.log(this.dev)
}
}
B.log()
new B().log()
Solution: Certainly! Let's break down this code and explain what's happening:
class A {
static dev = 'BFE'
dev = 'bigfrontend'
}
class B extends A {
log() {
console.log(this.dev)
}
static log() {
console.log(this.dev)
}
}
B.log()
new B().log()
Class A:
- It has a static property
dev
set to 'BFE'. - It also has an instance property
dev
set to 'bigfrontend'.
- It has a static property
Class B:
- It extends class A, inheriting its properties and methods.
- It has an instance method
log()
that logsthis.dev
. - It also has a static method
log()
that logsthis.dev
.
B.log()
:- This calls the static
log()
method on class B. - In a static method,
this
refers to the class itself. - Class B inherits the static
dev
property from class A. - So, this will log 'BFE' to the console.
- This calls the static
new B().log()
:- This creates a new instance of class B and calls its
log()
method. - In an instance method,
this
refers to the instance itself. - The instance inherits the instance property
dev
from class A. - So, this will log 'bigfrontend' to the console.
- This creates a new instance of class B and calls its
To summarize the output:
BFE
bigfrontend
Key points:
- Static properties and methods are associated with the class itself, not instances.
- Instance properties and methods are associated with instances of the class.
- When a class extends another, it inherits both static and instance members.
- The
this
keyword behaves differently in static and instance contexts.
64. reference type
64.https://bigfrontend.dev/quiz/reference-type
const obj = {
msg: 'BFE',
foo() {
console.log(this.msg)
},
bar() {
console.log('dev')
},
}
obj.foo()
obj.foo()
;(obj.foo || obj.bar)()
Solution: This code demonstrates the behavior of function calls and how the this
context is determined in JavaScript. Let's break it down line by line:
Object definition:
const obj = { msg: 'BFE', foo() { console.log(this.msg) }, bar() { console.log('dev') }, }
This creates an object with a property
msg
and two methodsfoo
andbar
.obj.foo();
- This is a straightforward method call on the object.
this
insidefoo
refers toobj
.- Output:
'BFE'
(obj.foo)();
- The parentheses around
obj.foo
don't change anything here. - It's still a method call on the object.
this
insidefoo
still refers toobj
.- Output:
'BFE'
- The parentheses around
(obj.foo || obj.bar)();
- This is where things get tricky.
obj.foo || obj.bar
is evaluated first. Sinceobj.foo
exists and is truthy, it's selected.- However, this expression results in the function itself, detached from the object.
- The function is then called without any context.
- In non-strict mode,
this
will be the global object (e.g.,window
in browsers). - In strict mode,
this
will beundefined
. - Either way,
this.msg
will beundefined
. - Output:
undefined
So, the final output will be:
BFE
BFE
undefined
Key points:
- Method calls (like
obj.foo()
) setthis
to the object the method is called on. - Parentheses around a method reference (
(obj.foo)()
) don't affect thethis
binding. - When a method is passed around or used in an expression (like
obj.foo || obj.bar
), it loses itsthis
binding. - Functions called without any context (like in the third case) have
this
set to the global object in non-strict mode, orundefined
in strict mode.
This quiz tests understanding of how this
works in JavaScript, particularly the difference between method calls and function calls, and how the context can be lost when methods are passed around or used in expressions.
73. window name
73.https://bigfrontend.dev/quiz/window-name
var a = 1
;(function () {
console.log(a + this.a)
var a = '2'
console.log(a + this.a)
})()
var name = 1
;(function () {
console.log(name + this.name)
var name = '2'
console.log(name + this.name)
})()
Solution: This code demonstrates several important concepts in JavaScript, including variable hoisting, scope, and the behavior of this
in different contexts. Let's break it down:
- First IIFE (Immediately Invoked Function Expression):
var a = 1
;(function () {
console.log(a + this.a)
var a = '2'
console.log(a + this.a)
})()
var a = 1
sets a global variablea
to 1.- Inside the IIFE,
var a
is hoisted, but its assignment is not. this
in a regular function call (not a method call) refers to the global object (orundefined
in strict mode).- First
console.log
:- Local
a
isundefined
due to hoisting this.a
is 1 (globala
)undefined + 1
results inNaN
- Local
a = '2'
assigns the locala
- Second
console.log
:- Local
a
is now '2' this.a
is still 1- '2' + 1 results in '21' (string concatenation)
- Local
- Second IIFE:
var name = 1
;(function () {
console.log(name + this.name)
var name = '2'
console.log(name + this.name)
})()
This behaves similarly to the first IIFE, but with a key difference:
- In many environments,
name
is a special property of the global object. var name = 1
might not create a new global variable, but instead set the existing globalname
property.- implicitly coerced
name
to string "1" The output will be:
NaN
"21"
"undefined1"
"21"
Key points:
- Variable declarations with
var
are hoisted, but initializations are not. this
in a regular function call refers to the global object (in non-strict mode).- Global variables become properties of the global object.
- The
name
property is special and may behave differently from other global variables.
This quiz tests understanding of variable hoisting, scope, this
context, and some nuances of global variables in JavaScript.
var
vs let
100. 100.https://bigfrontend.dev/quiz/var-vs-let
var a = 'BFE'
let b = 'bigfrontend'
console.log(this.a)
console.log(this.b)
Solution: Let's break down this code and explain its behavior:
var a = 'BFE'
let b = 'bigfrontend'
console.log(this.a)
console.log(this.b)
var a = 'BFE'
This declares a variablea
usingvar
and assigns it the value 'BFE'.let b = 'bigfrontend'
This declares a variableb
usinglet
and assigns it the value 'bigfrontend'.console.log(this.a)
This attempts to log the value ofa
as a property ofthis
.console.log(this.b)
This attempts to log the value ofb
as a property ofthis
.
The key to understanding the output of this code lies in how var
and let
behave differently in terms of creating properties on the global object, and what this
refers to in different contexts. Let's break it down:
Variables declared with
var
in the global scope become properties of the global object (which iswindow
in browsers orglobal
in Node.js).Variables declared with
let
(orconst
) do not become properties of the global object, even when declared in the global scope.The behavior of
this
depends on where this code is run:- In a browser's global scope,
this
refers to thewindow
object. - In Node.js at the top level of a module,
this
is equivalent tomodule.exports
. - In strict mode or in a module,
this
may beundefined
.
- In a browser's global scope,
Given these points, here's what would happen in different environments:
In a browser (non-strict mode, global scope):
BFE
undefined
In Node.js (at the top level of a module):
undefined
undefined
The reason for this difference is that in a browser, var a
creates a property on the global window
object, which this
refers to. But let b
does not create such a property. In Node.js, neither var
nor let
create properties on this
at the module level.
This code demonstrates the differences between var
and let
in terms of their interaction with the global object, as well as the behavior of this
in different JavaScript environments. It's a good example for understanding variable scoping and the global execution context in JavaScript.
114. constructor
114.https://bigfrontend.dev/quiz/constructor
function A() {
this.dev1 = 'BFE'
this.dev2 = 'dev'
return {
dev1: 'bigfrontend',
}
}
const a = new A()
console.log(a.dev1)
console.log(a.dev2)
Solution:
Let's break down this code and explain what's happening:
function A() {
this.dev1 = 'BFE'
this.dev2 = 'dev'
return {
dev1: 'bigfrontend',
}
}
const a = new A()
console.log(a.dev1)
console.log(a.dev2)
We define a function
A()
. In JavaScript, functions can be used as constructors when called with thenew
keyword.Inside the function, we set two properties on
this
:this.dev1 = 'BFE'
this.dev2 = 'dev'
However, the function then explicitly returns an object:
return { dev1: 'bigfrontend', }
We create a new instance of
A
using thenew
keyword:const a = new A()
Now, here's the key point: When a constructor function returns an object, that object becomes the result of the
new
expression, instead of thethis
object that was being built up.So,
a
is not the object that haddev1: 'BFE'
anddev2: 'dev'
. Instead,a
is the object that was explicitly returned:{ dev1: 'bigfrontend' }
.console.log(a.dev1)
will output'bigfrontend'
, because that's thedev1
property of the returned object.console.log(a.dev2)
will outputundefined
, because the returned object doesn't have adev2
property.
The this.dev1 = 'BFE'
and this.dev2 = 'dev'
lines effectively do nothing in this case, because the object they're modifying is discarded in favor of the explicitly returned object.
This behavior is a bit unusual and not typically how constructor functions are used. Normally, constructor functions modify this
and implicitly return this
, rather than returning a new object.
7.Symbol
112. Symbol
112.https://bigfrontend.dev/quiz/Symbol
const symbol = Symbol('BFE')
const a = {
[symbol]: 'BFE',
BFE: 'BFE',
}
console.log(symbol in a)
console.log('BFE' in a)
console.log(Object.keys(a).length)
Solution:
Let's break down this code and explain what's happening:
const symbol = Symbol('BFE')
This line creates a new Symbol with the description 'BFE'. Symbols are unique identifiers in JavaScript.
const a = {
[symbol]: 'BFE',
BFE: 'BFE',
}
Here, we're creating an object a
with two properties:
- A property with the Symbol as its key and 'BFE' as its value.
- A property with the string 'BFE' as its key and 'BFE' as its value.
Now, let's look at the console.log statements:
console.log(symbol in a)
This will output true
. The in
operator checks if a property exists in an object or its prototype chain. Since we added the Symbol as a property to a
, this check returns true.
console.log('BFE' in a)
This will also output true
. We added a property with the key 'BFE' to the object, so this check returns true.
console.log(Object.keys(a).length)
This will output 1
. Object.keys()
returns an array of a given object's own enumerable property names (as strings). Symbol properties are not included in this array. So, only the 'BFE' string property is counted, resulting in a length of 1.
In summary:
- Symbol properties are valid object properties and can be checked with the
in
operator. - String properties behave as expected.
Object.keys()
does not include Symbol properties, which is why the length is 1 instead of 2.
This code demonstrates some of the unique behaviors of Symbols in JavaScript, particularly how they differ from string keys when it comes to object property enumeration.
8.Promise
Key points:
- Promise executors run synchronously.
.then
callbacks are added to the microtask queue andsetTimeout
callbacks are added to the macrotask queue. The microtask is executed firstly before the macrotask. .then
only takes the callback as parameter. Other type parameter will be ignored. If the.then
callback does not return, aundefined
will be passed forward..finally
callbacks do not carry any value.
1.Promise order
1.https://bigfrontend.dev/quiz/1-promise-order
console.log(1)
const promise = new Promise((resolve) => {
console.log(2)
resolve()
console.log(3)
})
console.log(4)
promise
.then(() => {
console.log(5)
})
.then(() => {
console.log(6)
})
console.log(7)
setTimeout(() => {
console.log(8)
}, 10)
setTimeout(() => {
console.log(9)
}, 0)
Solution: This code demonstrates the concept of JavaScript's event loop, promises, and the order of execution. Let's break it down step by step:
console.log(1)
This logs 1 immediately.
const promise = new Promise((resolve) => {
console.log(2)
resolve()
console.log(3)
})
This creates a new Promise. The executor function runs synchronously, so it logs 2, calls resolve(), and then logs 3.
console.log(4)
This logs 4 immediately after the Promise is created.
promise
.then(() => {
console.log(5)
})
.then(() => {
console.log(6)
})
This adds two callbacks to the microtask queue. They won't run yet, as the main script is still executing.
console.log(7)
This logs 7 immediately.
setTimeout(() => {
console.log(8)
}, 10)
This schedules a callback to be added to the macrotask queue after 10ms.
setTimeout(() => {
console.log(9)
}, 0)
This schedules a callback to be added to the macrotask queue as soon as possible (but after the current script finishes).
Now, let's look at the order of execution:
- 1 is logged (synchronous)
- 2 is logged (synchronous, inside Promise executor)
- 3 is logged (synchronous, inside Promise executor)
- 4 is logged (synchronous)
- 7 is logged (synchronous)
- The main script finishes, so the event loop checks the microtask queue
- 5 is logged (from the first .then() callback)
- 6 is logged (from the second .then() callback)
- The event loop checks the macrotask queue
- 9 is logged (from the setTimeout with 0ms delay)
- After at least 10ms, 8 is logged (from the setTimeout with 10ms delay)
So the final output order will be:
1
2
3
4
7
5
6
9
8
Key points to understand:
- Synchronous code runs immediately.
- Promise executors run synchronously.
- .then() callbacks are added to the microtask queue.
- setTimeout callbacks are added to the macrotask queue.
- Microtasks are processed before macrotasks.
- Among macrotasks, the order is determined by the specified delay (0ms runs before 10ms).
This code effectively demonstrates the complex interplay between synchronous code, promises, and the event loop in JavaScript.
2. Promise executor
2.https://bigfrontend.dev/quiz/2-promise-executor
new Promise((resolve, reject) => {
resolve(1)
resolve(2)
reject('error')
}).then(
(value) => {
console.log(value)
},
(error) => {
console.log('error')
}
)
Solution:
Here's what's going on step by step:
We create a new Promise. The executor function (the function passed to the Promise constructor) runs immediately.
Inside the executor function:
resolve(1)
is called first. This sets the state of the promise to "fulfilled" with a value of 1.resolve(2)
is called next. This has no effect because the promise has already been resolved.reject('error')
is called last. This also has no effect because the promise has already been resolved.
The key point here is that once a promise is settled (either resolved or rejected), its state cannot change. The first
resolve
orreject
call determines the promise's final state and value.We chain a
.then()
method to the promise. This method takes two arguments:- The first is a function to handle the fulfilled state (when the promise is resolved).
- The second is a function to handle the rejected state (when the promise is rejected).
Since our promise was resolved (with the value 1), the first function passed to
.then()
will be called.This function receives the resolved value (1) as its argument and logs it to the console.
So, the output of this code will be:
1
Key points to understand:
A promise can only be settled once. Once it's resolved or rejected, subsequent calls to
resolve()
orreject()
are ignored.The first
resolve()
orreject()
call determines the promise's fate.In this case,
resolve(1)
is the first call, so it sets the promise's state to fulfilled with a value of 1.The
.then()
method's first argument (the fulfillment handler) is called because the promise was fulfilled, not rejected.The rejection handler (the second argument to
.then()
) is not called, so we never see 'error' logged.
This example demonstrates how promises maintain their state once settled, which is a crucial aspect of their design for managing asynchronous operations.
3. Promise then callbacks
3.https://bigfrontend.dev/quiz/3-promise-then-callbacks
Promise.resolve(1)
.then(() => 2)
.then(3)
.then((value) => value * 3)
.then(Promise.resolve(4))
.then(console.log)
Solution:
Step-by-Step Breakdown:
Promise.resolve(1)
:- This creates a resolved promise with the value
1
.
- This creates a resolved promise with the value
First
.then(() => 2)
:- The first
then
callback doesn't use the value1
from the resolved promise. Instead, it returns2
. - The promise chain now has the value
2
.
- The first
Second
.then(3)
:- This
then
is passed3
, which is a value, not a function. - When you pass a non-function value to
.then
, it doesn't do anything useful. The previous value (2
) is passed through unchanged. - The promise chain still has the value
2
.
- This
Third
.then((value) => value * 3)
:- This
then
takes the value2
from the previous step and multiplies it by3
. - The promise chain now has the value
6
.
- This
Fourth
.then(Promise.resolve(4))
:- Similar to the second
then
, passing a promise (Promise.resolve(4)
) directly to.then
doesn't do what you might expect. - Since
Promise.resolve(4)
is not a function, it does nothing with the value from the previousthen
. The value6
is passed through unchanged. - The promise chain still has the value
6
.
- Similar to the second
Fifth
.then(console.log)
:- This logs the final value in the promise chain to the console.
- The final value is
6
.
Result:
// Output
6
Explanation:
- The final output is
6
because thethen(3)
andthen(Promise.resolve(4))
do not alter the value of the promise chain. Only the operations on the values in the first and third.then
calls affect the final result.
4. Promise then callbacks II
4.https://bigfrontend.dev/quiz/4-Promise-then-callbacks-II
Promise.resolve(1)
.then((val) => {
console.log(val)
return val + 1
})
.then((val) => {
console.log(val)
})
.then((val) => {
console.log(val)
return Promise.resolve(3).then((val) => {
console.log(val)
})
})
.then((val) => {
console.log(val)
return Promise.reject(4)
})
.catch((val) => {
console.log(val)
})
.finally((val) => {
console.log(val)
return 10
})
.then((val) => {
console.log(val)
})
Solution: Let's break down the code step by step to understand its behavior:
Promise.resolve(1)
.then((val) => {
console.log(val) // Step 1: Logs 1
return val + 1 // Returns 2
})
.then((val) => {
console.log(val) // Step 2: Logs 2
})
.then((val) => {
console.log(val) // Step 3: Logs undefined (previous .then didn't return anything)
return Promise.resolve(3).then((val) => {
console.log(val) // Step 4: Logs 3
})
})
.then((val) => {
console.log(val) // Step 5: Logs undefined (previous .then didn't return anything)
return Promise.reject(4) // Returns a rejected promise with value 4
})
.catch((val) => {
console.log(val) // Step 6: Logs 4 (handles the rejection)
})
.finally((val) => {
console.log(val) // Step 7: Logs undefined (finally doesn't receive any value)
return 10 // This return value is ignored, but next .then will receive 10
})
.then((val) => {
console.log(val) // Step 8: Logs undefined (Unable to receive from the resolved promise after finally)
})
Step-by-Step Explanation:
First
.then((val) => {...})
:- The resolved value from
Promise.resolve(1)
is1
. - It logs
1
. - Returns
val + 1
, which is2
.
- The resolved value from
Second
.then((val) => {...})
:- Receives the value
2
from the previous step. - Logs
2
. - Doesn't return anything, so the next
val
isundefined
.
- Receives the value
Third
.then((val) => {...})
:- Receives
undefined
because the previous.then
didn't return anything. - Logs
undefined
. - Returns a promise from
Promise.resolve(3)
. - Inside this promise,
3
is logged. - The returned promise resolves to
undefined
(since the innermost.then
doesn't return anything).
- Receives
Fourth
.then((val) => {...})
:- Receives
undefined
from the previous.then
. - Logs
undefined
. - Returns a rejected promise with the value
4
usingPromise.reject(4)
.
- Receives
.catch((val) => {...})
:- Catches the rejection from the previous step.
- Logs
4
.
.finally((val) => {...})
:.finally
doesn't receive any argument, soval
isundefined
.- Logs
undefined
. - Returns
10
, but this value is ignored for the purposes of thefinally
block itself. - The next
.then
receives the resolved value10
.
Last
.then((val) => {...})
:- Unable to receive the value
10
from the resolved promise after thefinally
. - Logs
undefined
.
- Unable to receive the value
Final Output:
1
2
undefined
3
undefined
4
undefined
undefined
18. Promise executor II
18.https://bigfrontend.dev/quiz/Promise-executor-II
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => resolve(p1))
const p3 = Promise.resolve(p1)
const p4 = p2.then(() => new Promise((resolve) => resolve(p3)))
const p5 = p4.then(() => p4)
console.log(p1 == p2)
console.log(p1 == p3)
console.log(p3 == p4)
console.log(p4 == p5)
Solution:
When Promise.resolve()
receives a Promise
object, it doesn't wrap it in a new Promise
. Instead, it returns the input Promise
itself. By following const p3 = Promise.resolve(p1)
, p1
is as same as p3
.
false
true
false
false
23. Promise.all()
23.https://bigfrontend.dev/quiz/Promise-all
;(async () => {
await Promise.all([]).then(
(value) => {
console.log(value)
},
(error) => {
console.log(error)
}
)
await Promise.all([1, 2, Promise.resolve(3), Promise.resolve(4)]).then(
(value) => {
console.log(value)
},
(error) => {
console.log(error)
}
)
await Promise.all([1, 2, Promise.resolve(3), Promise.reject('error')]).then(
(value) => {
console.log(value)
},
(error) => {
console.log(error)
}
)
})()
Solution: The code is wrapped in an Immediately Invoked Async Function Expression (IIAFE). Let's examine each Promise.all()
call:
First case:
await Promise.all([]).then( (value) => { console.log(value) }, (error) => { console.log(error) } )
Output:
[]
Explanation:
Promise.all([])
resolves immediately with an empty array when given an empty array.- The
then
block is executed with this empty array.
Second case:
await Promise.all([1, 2, Promise.resolve(3), Promise.resolve(4)]).then( (value) => { console.log(value) }, (error) => { console.log(error) } )
Output:
[1, 2, 3, 4]
Explanation:
Promise.all()
waits for all promises to resolve.- Non-promise values (1 and 2) are treated as already resolved promises.
- The result is an array of all resolved values in the original order.
Third case:
await Promise.all([1, 2, Promise.resolve(3), Promise.reject('error')]).then( (value) => { console.log(value) }, (error) => { console.log(error) } )
Output:
error
Explanation:
- If any promise in the array rejects,
Promise.all()
immediately rejects with that error. - The rejection handler (second argument to
then
) is called with the rejection reason. - Other promises that may still be pending are not affected, but their results are ignored.
- If any promise in the array rejects,
Key points to remember about Promise.all()
:
- It takes an iterable (usually an array) of promises as input.
- It returns a single Promise that resolves to an array of the results of the input promises.
- The returned promise resolves when all input promises have resolved, or rejects if any of the input promises reject.
- If the input array is empty, it resolves immediately with an empty array.
- Non-promise values in the input array are treated as already resolved promises.
- If any promise rejects,
Promise.all()
rejects immediately with that reason, ignoring the results of other promises.
This example demonstrates how Promise.all()
is useful for handling multiple asynchronous operations concurrently and collecting their results, while also showing how it behaves with different types of inputs and in error scenarios.
;[][(1, 2, 3, 4)]
;('error')
36. Promise.prototype.finally()
36.https://bigfrontend.dev/quiz/Promise-prototype-finally
Promise.resolve(1)
.finally((data) => {
console.log(data)
return Promise.reject('error')
})
.catch((error) => {
console.log(error)
throw 'error2'
})
.finally((data) => {
console.log(data)
return Promise.resolve(2).then(console.log)
})
.then(console.log)
.catch(console.log)
Solution:
This code demonstrates the behavior of Promise chaining, particularly focusing on the finally
, catch
, and then
methods. Let's break it down step by step:
Promise.resolve(1)
- Creates a Promise that immediately resolves with the value 1.
.finally((data) => { ... })
finally
is called, but its callback doesn't receive any data (data is undefined).- It logs
undefined
to the console. - It returns a rejected Promise with 'error'.
- The return value of
finally
is ignored unless it's a rejected Promise.
.catch((error) => { ... })
- Catches the 'error' from the rejected Promise in the
finally
. - Logs 'error' to the console.
- Throws a new error 'error2'.
- Catches the 'error' from the rejected Promise in the
.finally((data) => { ... })
- Again,
finally
is called with no data (data is undefined). - Logs
undefined
to the console. - Returns a new Promise that resolves to 2 and logs it.
- This return value is ignored as it's not a rejected Promise.
- Again,
.then(console.log)
- This
then
would log the result of the previous operation, but there isn't one because the previousfinally
doesn't pass anything along.
- This
.catch(console.log)
- Catches the 'error2' thrown in the earlier
catch
block. - Logs 'error2' to the console.
- Catches the 'error2' thrown in the earlier
The output will be:
undefined // from the first finally
error // from the catch
undefined // from the second finally
2 // from the Promise.resolve(2) in the second finally
error2 // from the final catch
Key points:
finally
callbacks don't receive any parameters, which is whydata
isundefined
in both cases.- The return value of a
finally
block is generally ignored, unless it's a rejected Promise. catch
blocks can throw new errors, which will be caught by subsequentcatch
blocks.- Promises chain sequentially, with each step potentially modifying the data or error being passed along.
- This example demonstrates how complex Promise chains can lead to potentially confusing behavior, emphasizing the importance of careful error handling and understanding of Promise mechanics.
47. Promise Order II
47.https://bigfrontend.dev/quiz/promise-order-II
console.log(1)
setTimeout(() => {
console.log(2)
}, 10)
setTimeout(() => {
console.log(3)
}, 0)
new Promise((_, reject) => {
console.log(4)
reject(5)
console.log(6)
})
.then(() => console.log(7))
.catch(() => console.log(8))
.then(() => console.log(9))
.catch(() => console.log(10))
.then(() => console.log(11))
.then(console.log)
.finally(() => console.log(12))
console.log(13)
Solution:
This code demonstrates the concept of JavaScript's event loop, promise chaining, and the order of execution for different types of operations. Let's break it down step by step:
console.log(1)
- This is synchronous and executes immediately.
First
setTimeout
with 10ms delay- This is queued to run after 10ms.
Second
setTimeout
with 0ms delay- This is queued to run in the next event loop iteration.
Promise creation and execution
console.log(4)
runs synchronously.- The promise is rejected with value 5.
console.log(6)
runs synchronously.- The
then
callback is not executed because the promise was rejected. - The
catch
callback is queued as a microtask.
console.log(13)
- This runs synchronously.
After all synchronous code, microtasks are processed:
- The
catch
callback logs 8. - The following
then
logs 9. - The next
then
logs 11. - The next
then
logsundefined
. finally
logs 12.
- The
After microtasks, the event loop moves to macrotasks:
- The 0ms setTimeout callback runs, logging 3.
- The 10ms setTimeout callback runs, logging 2.
So, the final output order will be:
1
4
6
13
8
9
11
undefined
12
3
2
Key points:
- Synchronous code runs first.
- Promise callbacks (then, catch, finally) are microtasks and run after all synchronous code but before the next event loop iteration.
- setTimeout callbacks are macrotasks and run in subsequent event loop iterations.
- Even a 0ms setTimeout runs after microtasks.
- In a promise chain, if a promise is rejected, it skips
then
callbacks until it finds acatch
. - After a
catch
, the promise chain continues normally. finally
always runs, regardless of whether the promise was fulfilled or rejected.
This quiz tests understanding of JavaScript's event loop, promise mechanics, and the order of execution for different types of asynchronous operations.
98. Promise executor IV
98.https://bigfrontend.dev/quiz/Promise-executor-IV
const promise = new Promise((resolve, reject) => {
const promise2 = Promise.reject('error').then(
() => {
console.log(1)
},
() => {
console.log(2)
}
)
resolve(promise2)
})
promise.then(console.log)
Solution: Let's break down this code step by step:
- We create a new Promise called
promise
:
const promise = new Promise((resolve, reject) => {
// ... inner code ...
})
- Inside this Promise, we create another Promise called
promise2
:
const promise2 = Promise.reject('error').then(
() => {
console.log(1)
},
() => {
console.log(2)
}
)
This promise2
is created by immediately rejecting a Promise with the value 'error', and then chaining a .then()
method to it. The .then()
method has two callback functions:
- The first callback (for resolved promises) logs 1
- The second callback (for rejected promises) logs 2
- We resolve the outer
promise
withpromise2
:
resolve(promise2)
- Finally, we chain a
.then()
method to the outerpromise
:
promise.then(console.log)
Now, let's see what happens when this code runs:
promise2
is created. Since it starts withPromise.reject('error')
, it will immediately trigger the rejection handler in its.then()
method.This rejection handler logs 2 to the console.
After logging 2, the rejection is considered handled, and
promise2
resolves successfully (with no value, which means it resolves toundefined
).The outer
promise
is resolved withpromise2
. Sincepromise2
is already settled (resolved toundefined
), this effectively meanspromise
resolves toundefined
.The
.then(console.log)
on the outerpromise
will therefore logundefined
.
So, the output of this code will be:
2
undefined
The 2
comes from the rejection handler of promise2
, and the undefined
comes from the final .then(console.log)
on the outer promise
.
This code demonstrates several important concepts in JavaScript Promises:
- Promise chaining
- How rejection handlers in
.then()
can "recover" from rejections - How resolving a Promise with another Promise works
103. Promise Order III
103.https://bigfrontend.dev/quiz/Promise-Order-III
const createPromise = () => Promise.resolve(1)
function func1() {
createPromise().then(console.log)
console.log(2)
}
async function func2() {
await createPromise()
console.log(3)
}
console.log(4)
func1()
func2()
Solution:
Let's break down this code and explain its execution:
const createPromise = () => Promise.resolve(1)
function func1() {
createPromise().then(console.log)
console.log(2)
}
async function func2() {
await createPromise()
console.log(3)
}
console.log(4)
func1()
func2()
Let's go through this step-by-step:
We define
createPromise()
, which returns a Promise that immediately resolves with the value 1.We define
func1()
, which creates a promise and attaches athen
handler to log the result, then immediately logs 2.We define
func2()
, an async function that awaits the promise fromcreatePromise()
, then logs 3.We log 4.
We call
func1()
.We call
func2()
.
Now, let's look at the execution order:
console.log(4)
executes first, logging 4.func1()
is called:- It creates a promise and attaches a
then
handler to log the result. - This
then
handler is scheduled to run in the next event loop iteration. - It immediately logs 2.
- It creates a promise and attaches a
func2()
is called:- It creates a promise and awaits it.
- The rest of
func2()
(logging 3) is scheduled to run after the promise resolves, in a future event loop iteration.
The main script finishes executing.
In the next event loop iteration:
- The
then
handler fromfunc1()
runs, logging 1. - The remainder of
func2()
runs, logging 3.
- The
Therefore, the output will be:
4
2
1
3
This code demonstrates several important concepts in JavaScript:
- Asynchronous execution and the event loop.
- The difference between
then()
callbacks andasync/await
syntax. - How promises are scheduled and resolved.
- The order of execution between synchronous and asynchronous code.
It's a great example to understand how JavaScript handles asynchronous operations and the order in which different types of code (synchronous, promise-based, and async/await) are executed.
106. Promise.all() II
106.https://bigfrontend.dev/quiz/Promise-all-II
const promise1 = Promise.resolve(1)
const promise2 = Promise.resolve(2)
const promise3 = Promise.resolve(3)
const promise4 = Promise.reject(4)
const promiseAll = async () => {
const group1 = await Promise.all([promise1, promise2])
const group2 = await Promise.all([promise3, promise4])
return [group1, group2]
}
promiseAll().then(console.log).catch(console.log)
Solution: Let's break down this code and explain what's happening:
const promise1 = Promise.resolve(1)
const promise2 = Promise.resolve(2)
const promise3 = Promise.resolve(3)
const promise4 = Promise.reject(4)
const promiseAll = async () => {
const group1 = await Promise.all([promise1, promise2])
const group2 = await Promise.all([promise3, promise4])
return [group1, group2]
}
promiseAll().then(console.log).catch(console.log)
Let's go through this step-by-step:
We create four promises:
promise1
,promise2
, andpromise3
are immediately resolved with values 1, 2, and 3 respectively.promise4
is immediately rejected with value 4.
We define an async function
promiseAll
:- It uses
Promise.all()
to wait for two groups of promises. group1
waits forpromise1
andpromise2
to resolve.group2
waits forpromise3
andpromise4
to resolve.- The function attempts to return an array containing both groups.
- It uses
We call
promiseAll()
and chain.then(console.log)
and.catch(console.log)
to handle the result or error.
Now, let's consider the execution:
group1 = await Promise.all([promise1, promise2])
:- This will succeed because both promises resolve.
group1
will be[1, 2]
.
group2 = await Promise.all([promise3, promise4])
:- This will fail because
promise4
is rejected. - When any promise in
Promise.all()
is rejected, the entirePromise.all()
is immediately rejected. - This rejection will be caught by the
catch
block of the async function.
- This will fail because
Because of the rejection in step 2, the function will not reach the
return
statement.The rejection from
Promise.all([promise3, promise4])
will be propagated to thecatch
method chained topromiseAll()
.
Therefore, the output will be:
4
This is because:
- The rejection value of
promise4
(which is 4) is caught by thecatch
method. console.log
in thecatch
method prints this rejection value.
Key points demonstrated by this code:
Promise.all()
resolves only if all promises in the array resolve, and rejects if any promise rejects.- In an async function, a rejected promise will cause the function to immediately return a rejected promise, skipping any code after the rejection.
- Error handling in promise chains: rejections are caught by
catch
methods. - The difference between resolved and rejected promises in
Promise.all()
.
This example is excellent for understanding how Promise.all()
works with mixed resolved and rejected promises, and how async/await handles promise rejections.
50. async await
50.https://bigfrontend.dev/quiz/async-await
async function async1() {
console.log(1)
await async2()
console.log(2)
}
async function async2() {
console.log(3)
}
console.log(4)
setTimeout(function () {
console.log(5)
}, 0)
async1()
new Promise(function (resolve) {
console.log(6)
resolve()
}).then(function () {
console.log(7)
})
console.log(8)
Solution:
This code demonstrates the execution order of different types of operations in JavaScript, including synchronous code, asynchronous functions, Promises, and the event loop. Let's break it down step by step:
console.log(4)
- This is synchronous and executes immediately.
setTimeout(function(){ console.log(5) }, 0)
- This schedules a callback to run in the next event loop iteration (macrotask).
async1()
is calledconsole.log(1)
runs synchronously inside async1.await async2()
is encountered:async2()
is called, which logs 3 synchronously.- The rest of
async1()
is suspended and scheduled as a microtask.
New Promise creation
console.log(6)
runs synchronously inside the Promise executor.resolve()
is called, scheduling thethen
callback as a microtask.
console.log(8)
- This runs synchronously.
After all synchronous code, microtasks are processed:
- The suspended part of
async1()
resumes, logging 2. - The Promise's
then
callback runs, logging 7.
- The suspended part of
After microtasks, the event loop moves to macrotasks:
- The setTimeout callback runs, logging 5.
So, the final output order will be:
4
1
3
6
8
2
7
5
Key points:
- Synchronous code runs first, in the order it appears.
async
functions run synchronously until the firstawait
, then the rest is scheduled as a microtask.- Promise callbacks (then, catch, finally) are microtasks and run after all synchronous code but before the next event loop iteration.
- setTimeout callbacks are macrotasks and run in subsequent event loop iterations, after all microtasks are completed.
- Even a 0ms setTimeout runs after microtasks.
This quiz tests understanding of JavaScript's event loop, async/await mechanics, promise execution, and the order of execution for different types of operations. It's a comprehensive test of JavaScript's asynchronous behavior.
130. async
130.https://bigfrontend.dev/quiz/async
const promise = Promise.resolve()
function f() {
return promise
}
async function a() {
return f()
}
async function b() {
return await f()
}
function c() {
return f()
}
console.log(a() === b())
console.log(b() === c())
console.log(a() === c())
Solution: Certainly! Let's break down this code and explain what's happening:
const promise = Promise.resolve()
function f() {
return promise
}
async function a() {
return f()
}
async function b() {
return await f()
}
function c() {
return f()
}
console.log(a() === b())
console.log(b() === c())
console.log(a() === c())
Let's go through each part:
const promise = Promise.resolve()
creates a resolved promise.function f()
returns this promise.async function a() { return f() }
:- This is an async function that returns the result of
f()
. - When an async function returns a value that is not a promise, it automatically wraps it in a promise.
- However, since
f()
already returns a promise, it doesn't get wrapped again.
- This is an async function that returns the result of
async function b() { return await f() }
:- This async function awaits the result of
f()
before returning. - The
await
keyword unpacks the promise returned byf()
, then repacks it into a new promise (because it's an async function).
- This async function awaits the result of
function c() { return f() }
:- This is a regular function that simply returns the result of
f()
, which is the original promise.
- This is a regular function that simply returns the result of
Now, let's look at the comparisons:
console.log(a() === b())
- This logs
false
. - Both
a()
andb()
return promises, but they are different promise objects. - Even though they resolve to the same value, they are not the same promise instance.
- This logs
console.log(b() === c())
- This logs
false
. - For the same reason as above,
b()
returns a new promise, whilec()
returns the original promise.
- This logs
console.log(a() === c())
- This logs
false
. a()
returns a new promise (because it's an async function), whilec()
returns the original promise.
- This logs
Key points to understand:
- Async functions always return promises. If the return value is not a promise, it gets wrapped in one.
- The
await
keyword unwraps a promise, and then the async function rewraps the result in a new promise. - Regular functions (like
c
) that return promises don't create new promise objects; they just pass along the existing promise. - Promise equality (
===
) compares object identity, not the resolved values of the promises. - Each call to an async function creates a new promise object, even if it's returning the same underlying promise each time.
This code demonstrates some subtle but important aspects of how promises and async functions work in JavaScript.
131. return await
131.https://bigfrontend.dev/quiz/return-await
async function a() {
try {
return await Promise.reject(1)
} catch (e) {
console.log(e)
}
}
async function b() {
try {
return Promise.reject(2)
} catch (e) {
console.log(e)
}
}
async function start() {
await a()
await b()
}
start()
Solution: Let's break down what this code does, focusing on the differences between using await
with Promise.reject
inside an async
function and returning Promise.reject
directly.
async
Function a
async function a() {
try {
return await Promise.reject(1)
} catch (e) {
console.log(e)
}
}
await Promise.reject(1)
: This line creates a rejected promise with the value1
. Sinceawait
is used, JavaScript waits for the promise to resolve or reject. In this case, it rejects, and control immediately jumps to thecatch
block.catch (e)
: The rejection (1
) is caught here, ande
is logged, so1
will be printed to the console.- Return value: After the
catch
block executes, the function completes without returning anything, which means it returnsundefined
.
async
Function b
async function b() {
try {
return Promise.reject(2)
} catch (e) {
console.log(e)
}
}
return Promise.reject(2)
: Here, a rejected promise is returned without usingawait
. The rejection is not immediately caught by thecatch
block withinb
. Instead, the rejected promise is returned, and any code that callsb()
is responsible for handling the rejection.catch (e)
: Thecatch
block will not be executed because the rejection happens after the return statement, meaning the error is not caught within the functionb
.- Return value: The function returns a promise that is rejected with the value
2
.
async
Function start
async function start() {
await a()
await b()
}
await a()
: This callsa()
. Sincea()
handles its rejection internally and returnsundefined
, thisawait
will resolve withundefined
without throwing an error.await b()
: This callsb()
, which returns a rejected promise. Sincestart()
doesn't have atry...catch
around this call, the rejected promise will propagate, and thestart()
function will return a rejected promise with the value2
.
start()
Execution
start()
start()
begins by callinga()
. This results in1
being logged to the console, asa()
catches the rejection.- Next,
b()
is called. Sinceb()
returns a rejected promise and doesn't catch the error,start()
's promise will be rejected with2
. If there is no external error handling (e.g., anothertry...catch
orcatch
block chained to the promise returned bystart()
), this rejection will result in an unhandled promise rejection.
Summary:
- Output:
1
will be printed from thecatch
block ina()
.- Nothing will be printed for
b()
because the rejection is not caught withinb()
.
- Behavior:
a()
handles the rejection internally, sostart()
continues execution.b()
doesn't handle the rejection, sostart()
results in an unhandled promise rejection with the value2
, unless caught elsewhere.
To summarize, await
combined with try...catch
inside a()
captures and handles the rejection, while b()
's rejection is not caught within the function, leading to a propagated rejected promise.
60. postMessage
60.https://bigfrontend.dev/quiz/postMessage
console.log(1)
window.onmessage = () => {
console.log(2)
}
Promise.resolve().then(() => {
console.log(3)
})
setTimeout(() => {
console.log(4)
}, 0)
console.log(5)
window.postMessage('')
console.log(6)
Solution: This code demonstrates the order of execution in JavaScript, including synchronous code, asynchronous operations, and the event loop. Let's break it down step by step:
console.log(1)
- This runs immediately, outputting 1.
window.onmessage = () => { console.log(2) }
- This sets up an event listener for the 'message' event.
Promise.resolve().then(() => { console.log(3) })
- This creates a microtask that will log 3.
setTimeout(() => { console.log(4) }, 0)
- This schedules a macrotask to log 4 after a minimum delay.
console.log(5)
- This runs immediately, outputting 5.
window.postMessage('')
- This posts a message to the window, which will trigger the onmessage event handler.
- However, the event is added to the macrotask queue.
console.log(6)
- This runs immediately, outputting 6.
Now, let's consider the execution order:
All synchronous code runs first, so we see outputs: 1, 5, 6.
After synchronous code, microtasks are processed. The Promise's then callback is a microtask, so 3 is logged next.
After microtasks, the event loop moves to macrotasks. There are two macrotasks queued:
- The setTimeout callback
- The message event triggered by postMessage
The order of these can vary between browsers, but typically, the message event is processed before the setTimeout callback.
So, the final output order will likely be:
1
5
6
3
2
4
Key points:
- Synchronous code runs first.
- Microtasks (like Promise callbacks) run after all synchronous code but before the next event loop iteration.
- Macrotasks (like setTimeout callbacks and most DOM events) run in subsequent event loop iterations.
- postMessage triggers an event asynchronously, even if called with a delay of 0.
- The exact order of macrotasks can sometimes vary between browsers, especially for events triggered in the same synchronous code block.
This quiz tests understanding of JavaScript's event loop, the difference between microtasks and macrotasks, and how various asynchronous operations are scheduled and executed.
UPDATE: Apparently, this behavior is different in different browsers.
Chrome: 1 -> 5 -> 6 -> 3 -> 4 -> 2
Safari: 1 -> 5 -> 6 -> 3 -> 2 -> 4
61. onClick
61.https://bigfrontend.dev/quiz/messsage-channel-is-async
console.log(1)
document.body.addEventListener('click', () => {
console.log(2)
})
Promise.resolve().then(() => {
console.log(3)
})
setTimeout(() => {
console.log(4)
}, 0)
console.log(5)
document.body.click()
console.log(6)
Solution: This code demonstrates the order of execution in JavaScript, including synchronous code, asynchronous operations, microtasks, macrotasks, and event handling. Let's break it down step by step:
console.log(1)
- This runs immediately, outputting 1.
document.body.addEventListener('click', () => { console.log(2) })
- This sets up an event listener for the 'click' event on the body.
Promise.resolve().then(() => { console.log(3) })
- This creates a microtask that will log 3.
setTimeout(() => { console.log(4) }, 0)
- This schedules a macrotask to log 4 after a minimum delay.
console.log(5)
- This runs immediately, outputting 5.
document.body.click()
- This triggers the click event on the body synchronously.
console.log(6)
- This runs immediately, outputting 6.
Now, let's consider the execution order:
All synchronous code runs first, so we see outputs: 1, 5, 6.
The
document.body.click()
is a synchronous method callAfter synchronous code, microtasks are processed. The Promise's then callback is a microtask, so 3 is logged next.
After microtasks, the event loop moves to macrotasks. There are two macrotasks queued:
- The setTimeout callback
So, the final output order will be:
1
5
2
6
3
4
Key points:
- Synchronous code runs first.
- Microtasks (like Promise callbacks) run after all synchronous code but before the next event loop iteration.
- Macrotasks (like setTimeout callbacks and most DOM events) run in subsequent event loop iterations.
This quiz tests understanding of JavaScript's event loop, the difference between microtasks and macrotasks, how events are handled, and the order of execution for various types of operations. It's a comprehensive test of JavaScript's asynchronous behavior.
62. MessageChannel
62.https://bigfrontend.dev/quiz/message-channel
console.log(1)
const mc = new MessageChannel()
mc.port1.onmessage = () => {
console.log(2)
}
Promise.resolve().then(() => {
console.log(3)
})
setTimeout(() => {
console.log(4)
}, 0)
console.log(5)
mc.port2.postMessage('')
console.log(6)
Solution:
This code demonstrates the interplay between different types of asynchronous operations in JavaScript, including Promises, MessageChannel, and setTimeout. Let's break it down step by step:
console.log(1)
- This runs immediately, outputting 1.
const mc = new MessageChannel()
- This creates a new MessageChannel with two ports: port1 and port2.
mc.port1.onmessage = () => { console.log(2) }
- This sets up a message event handler on port1.
Promise.resolve().then(() => { console.log(3) })
- This creates a microtask that will log 3.
setTimeout(() => { console.log(4) }, 0)
- This schedules a macrotask to log 4 after a minimum delay.
console.log(5)
- This runs immediately, outputting 5.
mc.port2.postMessage('')
- This posts a message to port2, which will trigger the onmessage event handler on port1.
- However, this operation is asynchronous and will be queued as a task.
console.log(6)
- This runs immediately, outputting 6.
Now, let's consider the execution order:
All synchronous code runs first, so we see outputs: 1, 5, 6.
After synchronous code, microtasks are processed. The Promise's then callback is a microtask, so 3 is logged next.
After microtasks, the event loop moves to tasks. There are two tasks queued:
- The setTimeout callback
- The MessageChannel message event
The MessageChannel message event is typically processed after the setTimeout callback.
So, the final output order will be:
1
5
6
3
4
2
Key points:
- Synchronous code runs first.
- Microtasks (like Promise callbacks) run after all synchronous code but before the next event loop iteration.
- MessageChannel operations are asynchronous and are queued as tasks.
- Tasks (like setTimeout callbacks and MessageChannel events) run in subsequent event loop iterations.
- MessageChannel events typically run after setTimeout callbacks, even with a 0ms delay.
This quiz tests understanding of JavaScript's event loop, the difference between microtasks and tasks, how MessageChannel operates, and the order of execution for various types of operations. It's a comprehensive test of JavaScript's asynchronous behavior, including some less commonly used APIs like MessageChannel.
9.Iterator and Generator
71. two-way generator
71.https://bigfrontend.dev/quiz/generator-2-way
function* gen() {
yield 2 * (yield 100)
}
const generator = gen()
console.log(generator.next().value)
console.log(generator.next(1).value)
console.log(generator.next(1).value)
Solution: Certainly! This code demonstrates the behavior of generator functions in JavaScript. Let's break it down step by step:
Generator Function Definition:
function* gen() { yield 2 * (yield 100) }
This defines a generator function that uses the
yield
keyword twice.Creating a Generator Instance:
const generator = gen()
This creates an instance of the generator.
First
next()
call:console.log(generator.next().value)
- This starts the generator and runs until the first
yield
. - It yields 100 and pauses.
- Output:
100
- This starts the generator and runs until the first
Second
next()
call:console.log(generator.next(1).value)
- This resumes the generator, passing 1 as the value for the previous
yield
. - The expression becomes
2 * 1
. - It yields 2 and pauses.
- Output:
2
- This resumes the generator, passing 1 as the value for the previous
Third
next()
call:console.log(generator.next(1).value)
- This resumes the generator, but there's nothing left to execute.
- It returns
undefined
as the value property of the result object. - Output:
undefined
So, the final output will be:
100
2
undefined
Key points:
- The first
next()
call starts the generator and runs until the firstyield
. - Subsequent
next()
calls resume the generator from where it last paused. - The value passed to
next()
becomes the result of theyield
expression where the generator last paused. - When there's nothing left to yield,
next()
returns an object withvalue: undefined
anddone: true
.
This quiz tests understanding of:
- Generator functions and their behavior
- How
yield
works in generators - The relationship between
next()
calls andyield
expressions - The lifecycle of a generator
It's a good example of how generators can maintain state between yields and how values can be passed into the generator through next()
calls.
119. return in Generator
119.https://bigfrontend.dev/quiz/return-in-generator
function* gen() {
try {
yield 1
yield 2
return 3
yield 4
} finally {
yield 5
return 6
yield 7
}
}
console.log([...gen()])
Solution: Certainly! Let's break down this generator function and explain its behavior:
function* gen() {
try {
yield 1
yield 2
return 3
yield 4
} finally {
yield 5
return 6
yield 7
}
}
console.log([...gen()])
Here's what's happening:
function* gen()
defines a generator function. Generator functions useyield
to pause execution and return a value.Inside the
try
block:yield 1
returns 1 and pauses the generator.yield 2
returns 2 and pauses the generator.return 3
would normally end the generator, but because there's afinally
block, execution continues there.yield 4
is never reached due to thereturn
statement before it.
The
finally
block:- This block always executes, even if there's a
return
in thetry
block. yield 5
returns 5 and pauses the generator.return 6
ends the generator.yield 7
is never reached due to thereturn
statement before it.
- This block always executes, even if there's a
console.log([...gen()])
:- The spread operator
...
is used with the generator. - This creates an array with all yielded values from the generator.
- The spread operator
Now, let's look at the output:
;[1, 2, 5]
Here's why:
- The generator yields 1, then 2.
- The
return 3
in thetry
block doesn't add to the array (spread ignoresreturn
values). - The
finally
block executes, yielding 5. - The
return 6
in thefinally
block ends the generator, but its value is not included in the array.
Key points:
yield
pauses the generator and provides a value to the iterator.return
in a generator ends the generator's execution.- The
finally
block always executes, even after areturn
. - The spread operator
...
collects all yielded values but ignores returned values. - Any code after a
return
statement in a block is not executed.
This example demonstrates the complex interplay between generators, try
/finally
blocks, and how the spread operator interacts with generators.
121. Generator return()
121.https://bigfrontend.dev/quiz/generator-return
function* gen() {
yield 1
try {
yield 2
yield 3
} finally {
yield 4
}
yield 5
}
const g = gen()
console.log(g.next().value)
console.log(g.next().value)
console.log(g.return(6).value)
console.log(g.next().value)
console.log(g.next().value)
Solution: Let's break down this generator function and its usage step by step:
function* gen() {
yield 1
try {
yield 2
yield 3
} finally {
yield 4
}
yield 5
}
const g = gen()
console.log(g.next().value)
console.log(g.next().value)
console.log(g.return(6).value)
console.log(g.next().value)
console.log(g.next().value)
Here's what happens:
const g = gen()
creates a generator object.console.log(g.next().value)
:- This yields 1.
- Output:
1
console.log(g.next().value)
:- This enters the try block and yields 2.
- Output:
2
console.log(g.return(6).value)
:return(6)
is called, which attempts to terminate the generator.- However, because we're inside a try block with a finally clause, the finally block executes before the generator terminates.
- The finally block yields 4.
- The generator yields the value 4 from the finally block, ignoring 6 at this stage.
- Output:
4
console.log(g.next().value)
:- The next call to
g.next()
continues after the finally block. - Since the generator has already been finalized, it immediately returns the value 6 that was passed to
g.return(6)
. - Output:
6
- The next call to
console.log(g.next().value)
:- The generator is now completed, so the next call to
g.next()
returnsundefined
. - Output:
undefined
- The generator is now completed, so the next call to
So, the final output will be:
1
2
4
6
undefined
Key points to note:
- The
yield 3
andyield 5
are never reached due to thereturn(6)
call. - The
finally
block always executes, even whenreturn()
is called on the generator. - After
return()
is called, subsequentnext()
calls always return{value: undefined, done: true}
. - The
return(6)
call causes the generator to terminate, but not before executing thefinally
block. - The value passed to
return()
(6 in this case) becomes thevalue
property of the returned object, overriding anyyield
in thefinally
block.
This example demonstrates the complex interplay between generators, try
/finally
blocks, and the return()
method of generators.
122. Iterable
122.https://bigfrontend.dev/quiz/iterable
const entries = [1, 2, 3, 4].entries()
for (const [, item] of entries) {
console.log(item)
break
}
for (const [, item] of entries) {
console.log(item)
break
}
Solution:
Let's break down this code and explain what's happening:
const entries = [1, 2, 3, 4].entries()
for (const [, item] of entries) {
console.log(item)
break
}
for (const [, item] of entries) {
console.log(item)
break
}
Let's go through it step by step:
const entries = [1,2,3,4].entries()
- The
entries()
method returns an iterator that gives array entries in the form of[index, value]
pairs. entries
is now an iterator, not an array.
- The
First
for...of
loop:for (const [, item] of entries)
starts iterating over the entries.- The destructuring
[, item]
ignores the index (first element) and assigns the value toitem
. console.log(item)
prints the first item, which is1
.break
immediately exits the loop after the first iteration.
Second
for...of
loop:- This loop starts where the previous one left off because
entries
is an iterator that keeps its state. - It gets the next entry, which is
[1, 2]
. console.log(item)
prints2
.break
exits the loop after the first iteration.
- This loop starts where the previous one left off because
The output of this code will be:
1
2
Key points to understand:
entries()
returns an iterator, not an array. Iterators maintain their state across usage.- The destructuring
[, item]
in thefor...of
loop ignores the index and only uses the value. break
in each loop ensures only one item is processed per loop.- The second loop continues from where the first loop left off in the iterator.
- If there were more loops, they would continue through the remaining entries (3 and 4) before the iterator is exhausted.
This code demonstrates how iterators maintain state across multiple uses and how destructuring can be used to select specific parts of each entry in an iterator.
123. return in Generator 2
123.https://bigfrontend.dev/quiz/return-gen
function* g() {
console.log(1)
try {
console.log(2)
yield 2
console.log(3)
throw new Error('error')
} finally {
console.log(4)
}
}
const obj = g()
obj.next()
obj.return()
Solution: Let's break down this code and explain its behavior step by step:
function* g() {
console.log(1)
try {
console.log(2)
yield 2
console.log(3)
throw new Error('error')
} finally {
console.log(4)
}
}
const obj = g()
obj.next()
obj.return()
Here's what happens:
const obj = g()
- This creates a generator object but doesn't start executing the generator function yet.
obj.next()
- This starts the execution of the generator function.
console.log(1)
executes, printing1
.- Enters the try block.
console.log(2)
executes, printing2
.- Hits
yield 2
, which pauses the generator and returns{value: 2, done: false}
to the caller. - The function is paused at this point, waiting for the next
next()
call.
obj.return()
- This is called instead of
next()
, which attempts to terminate the generator prematurely. - Because there's a
finally
block, it must be executed before the generator can fully terminate. - The
finally
block runs, executingconsole.log(4)
and printing4
. - The generator then terminates.
- This is called instead of
The output of this code will be:
1
2
4
Key points to note:
The
console.log(3)
and thethrow new Error('error')
never execute becauseobj.return()
is called before reaching that point.The
finally
block always executes, even when the generator is terminated early withreturn()
.If
obj.next()
had been called again instead ofobj.return()
, it would have continued execution, printed 3, then thrown an error.The
return()
method on a generator object is used to terminate the generator prematurely. It will still triggerfinally
blocks if they exist.After
obj.return()
, the generator is in a completed state. Any further calls tonext()
would return{value: undefined, done: true}
.
This example demonstrates how generators behave with try
/finally
blocks, and how the return()
method interacts with this structure, ensuring that cleanup code in finally
blocks is always executed, even when a generator is terminated early.
110. yield
110.https://bigfrontend.dev/quiz/yield
function* genA() {
yield [1, 2, 3]
}
function* genB() {
yield* [1, 2, 3]
}
console.log(genA().next().value)
console.log(genB().next().value)
Solution:
Let's break down this code and explain what's happening:
function* genA() {
yield [1, 2, 3]
}
function* genB() {
yield* [1, 2, 3]
}
console.log(genA().next().value)
console.log(genB().next().value)
This code demonstrates the difference between yield
and yield*
in generator functions. Let's go through it step-by-step:
We define two generator functions,
genA()
andgenB()
.genA()
usesyield [1, 2, 3]
:- This yields the entire array
[1, 2, 3]
as a single value.
- This yields the entire array
genB()
usesyield* [1, 2, 3]
:- The
yield*
expression delegates to another iterable object (in this case, the array[1, 2, 3]
). - It yields each value in the iterable one by one.
- The
We call
genA().next().value
:- This creates an instance of the
genA
generator, immediately callsnext()
on it, and accesses thevalue
property of the resulting object. - Since
genA
yields the entire array at once, this will return[1, 2, 3]
.
- This creates an instance of the
We call
genB().next().value
:- This creates an instance of the
genB
generator, immediately callsnext()
on it, and accesses thevalue
property of the resulting object. - Since
genB
usesyield*
, it yields the first value from the array, which is1
.
- This creates an instance of the
Therefore, the output will be:
[1, 2, 3]
1
Key points to understand:
yield
:- Yields a single value.
- When used with an array, it yields the entire array as one value.
yield*
:- Delegates to another iterable.
- When used with an array, it yields each element of the array individually.
- It's equivalent to writing a loop that yields each value in the iterable.
Generator's
next()
method:- Returns an object with
value
(the yielded value) anddone
(boolean indicating if the generator is finished) properties.
- Returns an object with
Accessing
.value
on the result ofnext()
:- Gives us the yielded value directly.
This code demonstrates:
- The difference between
yield
andyield*
in generator functions. - How generators handle different types of yields.
- The behavior of the
next()
method on generators.
It's a great example to understand the nuances of generator functions in JavaScript, particularly how they can yield single values or delegate to other iterables. This concept is useful for creating complex sequences or processing streams of data.
10.Operator
7. Increment Operator
7.https://bigfrontend.dev/quiz/Increment-Operator
let a = 1
const b = ++a
const c = a++
console.log(a) // 3
console.log(b) // 2
console.log(c) // 2
Solution:
let a = 1
This initializes a variablea
with the value 1.const b = ++a
This uses the pre-increment operator (++a). It incrementsa
by 1 before assigning the value tob
. So:a
becomes 2b
is assigned the new value ofa
, which is 2
const c = a++
This uses the post-increment operator (a++). It assigns the current value ofa
toc
, then incrementsa
by 1. So:c
is assigned the current value ofa
, which is 2a
is then incremented to 3
console.log(a) // 3
At this point,a
has been incremented twice, so its value is 3.console.log(b) // 2
b
was assigned the value ofa
after the first increment, so its value is 2.console.log(c) // 2
c
was assigned the value ofa
before the second increment, so its value is also 2.
The key difference to note here is between the pre-increment (++a) and post-increment (a++) operators:
- Pre-increment (++a) increments the value and then returns it.
- Post-increment (a++) returns the value and then increments it.
This is why b
and c
both end up with the value 2, even though a
ends up as 3.
13. Operator precedence
13.https://bigfrontend.dev/quiz/operator-precedence
console.log((0 == 1) == 2)
console.log((2 == 1) == 0)
console.log(0 < 1 < 2)
console.log(1 < 2 < 3)
console.log(2 > 1 > 0)
console.log(3 > 2 > 1)
Solution: Let's break it down line by line:
console.log(0 == 1 == 2)
- First,
0 == 1
is evaluated, which isfalse
- Then,
false == 2
is evaluated.false
is coerced to 0, so it's0 == 2
- This is
false
- Output:
false
- First,
console.log(2 == 1 == 0)
- First,
2 == 1
is evaluated, which isfalse
- Then,
false == 0
is evaluated.false
is coerced to 0, so it's0 == 0
- This is
true
- Output:
true
- First,
console.log(0 < 1 < 2)
- First,
0 < 1
is evaluated, which istrue
- Then,
true < 2
is evaluated.true
is coerced to 1, so it's1 < 2
- This is
true
- Output:
true
- First,
console.log(1 < 2 < 3)
- First,
1 < 2
is evaluated, which istrue
- Then,
true < 3
is evaluated.true
is coerced to 1, so it's1 < 3
- This is
true
- Output:
true
- First,
console.log(2 > 1 > 0)
- First,
2 > 1
is evaluated, which istrue
- Then,
true > 0
is evaluated.true
is coerced to 1, so it's1 > 0
- This is
true
- Output:
true
- First,
console.log(3 > 2 > 1)
- First,
3 > 2
is evaluated, which istrue
- Then,
true > 1
is evaluated.true
is coerced to 1, so it's1 > 1
- This is
false
- Output:
false
- First,
Key points:
- JavaScript evaluates these expressions from left to right.
- Each comparison returns either
true
orfalse
, which is then used in the next comparison. - When
true
orfalse
is used in a comparison with a number,true
is coerced to 1 andfalse
to 0. - This chaining behavior can lead to unexpected results, especially when mixing different comparison operators.
- It's generally safer and more readable to use explicit boolean logic (e.g.,
0 < 1 && 1 < 2
) rather than chaining comparisons like this.
14. Addition vs Unary Plus
14.https://bigfrontend.dev/quiz/Addition-vs-Unary-Plus
What does the code snippet to the right output by console.log?
There is a difference between Addition Operator(+) and Unary plus operator(+), even though they use the same '+'.
console.log(1 + 2)
console.log(1 + +2)
console.log(1 + +(+2))
console.log(1 + '2')
console.log(1 + +'2')
console.log('1' + 2)
console.log('1' + +2)
console.log(1 + true)
console.log(1 + +true)
console.log('1' + true)
console.log('1' + +true)
console.log(1 + null)
console.log(1 + +null)
console.log('1' + null)
console.log('1' + +null)
console.log(1 + undefined)
console.log(1 + +undefined)
console.log('1' + undefined)
console.log('1' + +undefined)
console.log('1' + +(+undefined))
Solution:
This code explores various arithmetic and string concatenation operations in JavaScript, demonstrating type coercion and the behavior of the unary plus operator. Let's go through each line:
console.log(1 + 2)
- Simple addition: 1 + 2 = 3
- Output:
3
console.log(1 + + 2)
- The second + is a unary plus, which doesn't change 2
- 1 + 2 = 3
- Output:
3
console.log(1 + + + 2)
- Multiple unary plus operators, still doesn't change 2
- 1 + 2 = 3
- Output:
3
console.log(1 + '2')
- Number + String, coerces to string concatenation
- Output:
'12'
console.log(1 + + '2')
- The unary plus converts '2' to number 2
- 1 + 2 = 3
- Output:
3
console.log('1' + 2)
- String + Number, coerces to string concatenation
- Output:
'12'
console.log('1' + + 2)
- The unary plus doesn't affect 2 here
- String + Number, still string concatenation
- Output:
'12'
console.log(1 + true)
- true is coerced to 1
- 1 + 1 = 2
- Output:
2
console.log(1 + + true)
- The unary plus doesn't change true's coercion to 1
- 1 + 1 = 2
- Output:
2
console.log('1' + true)
- String concatenation with true
- Output:
'1true'
console.log('1' + + true)
- The unary plus converts true to 1
- String concatenation: '1' + 1
- Output:
'11'
console.log(1 + null)
- null is coerced to 0
- 1 + 0 = 1
- Output:
1
console.log(1 + + null)
- The unary plus converts null to 0
- 1 + 0 = 1
- Output:
1
console.log('1' + null)
- String concatenation with null
- Output:
'1null'
console.log('1' + + null)
- The unary plus converts null to 0
- String concatenation: '1' + 0
- Output:
'10'
console.log(1 + undefined)
- undefined in addition results in NaN
- Output:
NaN
console.log(1 + + undefined)
- The unary plus on undefined results in NaN
- 1 + NaN = NaN
- Output:
NaN
console.log('1' + undefined)
- String concatenation with undefined
- Output:
'1undefined'
console.log('1' + + undefined)
- The unary plus on undefined results in NaN
- String concatenation: '1' + NaN
- Output:
'1NaN'
console.log('1' + + + undefined)
- Multiple unary plus operators on undefined still result in NaN
- String concatenation: '1' + NaN
- Output:
'1NaN'
Key points:
- The unary plus operator attempts to convert its operand to a number.
- String concatenation takes precedence over addition when a string is involved.
- true is coerced to 1, null to 0, and undefined results in NaN in numeric operations.
- These examples demonstrate the importance of understanding type coercion in JavaScript to avoid unexpected results.
15. instanceOf
15.https://bigfrontend.dev/quiz/instanceOf
console.log(typeof null)
console.log(null instanceof Object)
console.log(typeof 1)
console.log(1 instanceof Number)
console.log(1 instanceof Object)
console.log(Number(1) instanceof Object)
console.log(new Number(1) instanceof Object)
console.log(typeof true)
console.log(true instanceof Boolean)
console.log(true instanceof Object)
console.log(Boolean(true) instanceof Object)
console.log(new Boolean(true) instanceof Object)
console.log([] instanceof Array)
console.log([] instanceof Object)
console.log((() => {}) instanceof Object)
Solution: Certainly. This code explores the behavior of the typeof
operator and the instanceof
operator in JavaScript, as well as the differences between primitive values and their object wrapper counterparts. Let's go through each line:
console.log(typeof null)
- Output:
'object'
- This is a known quirk in JavaScript.
null
is not actually an object, buttypeof null
returns 'object' due to a historical bug.
- Output:
console.log(null instanceof Object)
- Output:
false
- Despite the
typeof
quirk,null
is not an instance ofObject
.
- Output:
console.log(typeof 1)
- Output:
'number'
- 1 is a primitive number.
- Output:
console.log(1 instanceof Number)
- Output:
false
- Primitive numbers are not instances of the
Number
object.
- Output:
console.log(1 instanceof Object)
- Output:
false
- Primitive numbers are not instances of
Object
.
- Output:
console.log(Number(1) instanceof Object)
- Output:
false
Number(1)
returns a primitive number, not aNumber
object.
- Output:
console.log(new Number(1) instanceof Object)
- Output:
true
new Number(1)
creates aNumber
object, which is an instance ofObject
.
- Output:
console.log(typeof true)
- Output:
'boolean'
true
is a primitive boolean.
- Output:
console.log(true instanceof Boolean)
- Output:
false
- Primitive booleans are not instances of the
Boolean
object.
- Output:
console.log(true instanceof Object)
- Output:
false
- Primitive booleans are not instances of
Object
.
- Output:
console.log(Boolean(true) instanceof Object)
- Output:
false
Boolean(true)
returns a primitive boolean, not aBoolean
object.
- Output:
console.log(new Boolean(true) instanceof Object)
- Output:
true
new Boolean(true)
creates aBoolean
object, which is an instance ofObject
.
- Output:
console.log([] instanceof Array)
- Output:
true
- An array is an instance of
Array
.
- Output:
console.log([] instanceof Object)
- Output:
true
- Arrays are also instances of
Object
(inheritance).
- Output:
console.log((() => {}) instanceof Object)
- Output:
true
- Functions are instances of
Object
in JavaScript.
- Output:
Key points:
typeof
returns a string indicating the type of the unevaluated operand.instanceof
tests whether the prototype property of a constructor appears anywhere in the prototype chain of an object.- Primitive values (numbers, booleans, strings) are not instances of their corresponding object types or
Object
. - Object wrappers created with
new
(likenew Number(1)
) are instances of both their type andObject
. - Arrays and functions are objects in JavaScript.
- The behavior of
null
withtypeof
is a historical quirk in JavaScript. - Understanding these concepts is crucial for proper type checking and understanding object relationships in JavaScript.
87. instanceOf 2
87.https://bigfrontend.dev/quiz/instanceOf-2
console.log(Function instanceof Object)
console.log(Object instanceof Function)
console.log(Function instanceof Function)
console.log(Object instanceof Object)
Solution: This code explores the relationship between Function
, Object
, and the instanceof
operator in JavaScript. Let's break it down line by line:
console.log(Function instanceof Object)
- Output:
true
- In JavaScript, functions are objects.
Function
is an instance ofObject
because all functions inherit fromObject.prototype
.
- Output:
console.log(Object instanceof Function)
- Output:
true
Object
is a constructor function.- All constructor functions in JavaScript are instances of
Function
.
- Output:
console.log(Function instanceof Function)
- Output:
true
Function
is a constructor function for creating function objects.- It's also an instance of itself, as it's a function object.
- Output:
console.log(Object instanceof Object)
- Output:
true
Object
is a function object (constructor function).- All function objects are instances of
Object
.
- Output:
Key points to understand:
In JavaScript, functions are first-class objects. They are instances of
Object
.Function
is the constructor for all functions, including itself andObject
.Object
is a constructor function, which makes it an instance ofFunction
.The
instanceof
operator checks if the prototype property of the constructor appears anywhere in the prototype chain of the object.This creates a circular relationship:
Function
is an instance ofObject
Object
is an instance ofFunction
- Both are instances of themselves
This circular nature is a unique aspect of JavaScript's object model and prototype system. It's important to note that while this behavior is consistent across JavaScript environments, it's more of a curiosity than something you'd typically rely on in everyday programming.
These relationships demonstrate the deep interconnection between objects and functions in JavaScript, highlighting how the language blurs the line between these concepts in ways that other object-oriented languages typically don't.
25. zero
25.https://bigfrontend.dev/quiz/zero
console.log(1 / 0)
console.log(-1 / 0)
console.log(0 / 0)
console.log(0 === -0)
console.log(Object.is(0, -0))
console.log(Object.is(0, Math.round(-0.5)))
console.log(Object.is(0, Math.round(0.5)))
console.log(0 * Infinity)
console.log(Infinity / Infinity)
console.log(Object.is(0, Math.sign(0)))
console.log(Object.is(0, Math.sign(-0)))
console.log(1 / -0)
console.log(1 / 0)
console.log(1n / 0n)
Solution: This code explores some interesting and sometimes counterintuitive behaviors in JavaScript, particularly around division by zero, negative zero, and the Infinity
value. Let's break it down line by line:
console.log(1 / 0)
Output:Infinity
- Division by zero results in
Infinity
in JavaScript.
- Division by zero results in
console.log(-1 / 0)
Output:-Infinity
- Negative number divided by zero gives
-Infinity
.
- Negative number divided by zero gives
console.log(0 / 0)
Output:NaN
- Zero divided by zero is undefined, so JavaScript returns
NaN
(Not a Number).
- Zero divided by zero is undefined, so JavaScript returns
console.log(0 === -0)
Output:true
- JavaScript treats 0 and -0 as equal under normal comparison.
console.log(Object.is(0, -0))
Output:false
Object.is()
can distinguish between 0 and -0.
console.log(Object.is(0, Math.round(-0.5)))
Output:false
Math.round(-0.5)
returns -0, whichObject.is()
distinguishes from 0.
console.log(Object.is(0, Math.round(0.5)))
Output:false
Math.round(0.5)
returns 1, not 0.
console.log(0 * Infinity)
Output:NaN
- Multiplying 0 by Infinity is indeterminate, so it results in
NaN
.
- Multiplying 0 by Infinity is indeterminate, so it results in
console.log(Infinity / Infinity)
Output:NaN
- Dividing Infinity by Infinity is indeterminate, resulting in
NaN
.
- Dividing Infinity by Infinity is indeterminate, resulting in
console.log(Object.is(0, Math.sign(0)))
Output:true
Math.sign(0)
returns +0, which is indistinguishable from 0 even withObject.is()
.
console.log(Object.is(0, Math.sign(-0)))
Output:false
Math.sign(-0)
returns -0, whichObject.is()
can distinguish from +0.
console.log(1 / -0)
Output:-Infinity
- Division by negative zero results in negative Infinity.
console.log(1 / 0)
Output:Infinity
- Division by positive zero results in positive Infinity.
console.log(1n / 0n)
Output: Uncaught RangeError: Division by zero- BigInt division by zero throws an error, unlike regular number division.
Key takeaways:
- Division by zero with regular numbers results in
Infinity
or-Infinity
, not an error. - JavaScript distinguishes between +0 and -0 in some operations, but treats them as equal in comparisons.
Object.is()
can distinguish between +0 and -0, unlike===
.- Operations involving
Infinity
can result inNaN
when the result is indeterminate. Math.sign()
preserves the sign of zero, which can be detected byObject.is()
.- BigInt division behaves differently from regular number division, throwing an error on division by zero.
These examples highlight some of the nuances and special cases in JavaScript's handling of numbers, especially around zero, infinity, and numerical comparisons. Understanding these behaviors is crucial for writing robust JavaScript code, especially when dealing with mathematical operations or precise numerical comparisons.
console.log(1 / 0) // Infinity
console.log(-1 / 0) // -Infinity
console.log(0 / 0) // NaN
console.log(0 === -0) // true
console.log(Object.is(0, -0)) // false
console.log(Object.is(0, Math.round(-0.5))) // false
console.log(Object.is(0, Math.round(0.5))) // false
console.log(0 * Infinity) // NaN
console.log(Infinity / Infinity) // NaN
console.log(Object.is(0, Math.sign(0))) // true
console.log(Object.is(0, Math.sign(-0))) // false
console.log(1 / -0) // -Infinity
console.log(1 / 0) // Infinity
console.log(1n / 0n) // Error
34. precedence
34.https://bigfrontend.dev/quiz/precedence
let a = 1
console.log(a++ + a)
let b = 1
console.log(b + +(+b))
let c = 1
console.log(c-- - c)
let d = 1
console.log(d - -(-d))
Solution:
This code demonstrates some interesting behavior with JavaScript's increment/decrement operators and the unary plus operator. Let's break it down line by line:
let a = 1
console.log(a +++ a)
- Output:
3
- This is parsed as
(a++) + a
- First,
a++
is evaluated. It returns 1 (the original value ofa
) and then incrementsa
to 2. - Then, we add the new value of
a
(which is 2) - So, 1 + 2 = 3
- Output:
let b = 1
console.log(b + + + b)
- Output:
2
- This is parsed as
b + (+(+b))
- The rightmost
+b
is the unary plus operator, which doesn't change the value ofb
- So it's equivalent to
b + (+b)
- Both
b
and+b
evaluate to 1 - Therefore, 1 + 1 = 2
- Output:
let c = 1
console.log(c --- c)
- Output:
1
- This is parsed as
(c--) - c
- First,
c--
is evaluated. It returns 1 (the original value ofc
) and then decrementsc
to 0. - Then, we subtract the new value of
c
(which is 0) - So, 1 - 0 = 1
- Output:
let d = 1
console.log(d - - - d)
- Output:
0
- This is parsed as
d - (-(-d))
- The rightmost
-d
negatesd
, resulting in -1 - The next
-
negates that, resulting in 1 - So it's equivalent to
d - (1)
- Therefore, 1 - (1) = 0
- Output:
Key points:
- In
a +++ a
, the++
is treated as a postfix increment operator, and the last+
as a binary addition operator. - In
b + + + b
, all+
are treated as unary plus operators, except the leftmost one which is binary addition. - In
c --- c
, the--
is treated as a postfix decrement operator, and the last-
as a binary subtraction operator. - In
d - - - d
, all-
are treated as unary negation operators, except the leftmost one which is binary subtraction.
These examples demonstrate how JavaScript parses consecutive +
and -
operators, and the difference between unary operators (like ++
, --
, unary +
and -
) and binary operators (like addition and subtraction). It's a good reminder to use parentheses or spaces to make your intentions clear when using multiple operators in succession.
31. Math
31.https://bigfrontend.dev/quiz/Math
console.log(1 / 0)
console.log(0 / 0)
console.log(-1 / 0)
console.log((1 / 0) * 0)
console.log((1 / 0) * 1)
console.log((1 / 0) * -1)
console.log((1 / 0) * 1 + (1 / 0) * 1)
console.log((1 / 0) * 1 - (1 / 0) * 1)
console.log((1 / 0) * 1 * ((1 / 0) * 1))
console.log(((1 / 0) * 1) / ((1 / 0) * 1))
console.log(0 / Infinity)
console.log(0 * Infinity)
Solution:
Let's break down each operation:
1 / 0
: In JavaScript, this results inInfinity
.0 / 0
: This is an undefined mathematical operation, resulting inNaN
(Not a Number).-1 / 0
: This results in-Infinity
.1 / 0 * 0
:Infinity * 0
is undefined mathematically, so it results inNaN
.1 / 0 * 1
:Infinity * 1
is stillInfinity
.1 / 0 * -1
: This is equivalent to-Infinity
.1 / 0 * 1 + 1 / 0 * 1
: This isInfinity + Infinity
, which is stillInfinity
.1 / 0 * 1 - 1 / 0 * 1
: This isInfinity - Infinity
, which is undefined mathematically, resulting inNaN
.1 / 0 * 1 * (1 / 0 * 1)
: This isInfinity * Infinity
, which isInfinity
.1 / 0 * 1 / (1 / 0 * 1)
: This isInfinity / Infinity
, which is undefined mathematically, resulting inNaN
.0 / Infinity
: In mathematics, this is interpreted as a limit approaching 0, so JavaScript returns0
.0 * Infinity
: This is undefined mathematically, resulting inNaN
.
Key points to understand:
- Division by zero results in
Infinity
or-Infinity
, depending on the sign of the numerator. 0 / 0
and other undefined mathematical operations result inNaN
.- Operations with
Infinity
follow mathematical rules where possible, but undefined operations (likeInfinity - Infinity
) result inNaN
. - JavaScript has a built-in
Infinity
value that can be used directly. NaN
is the result of any operation that doesn't produce a meaningful numeric value.
This code demonstrates JavaScript's handling of edge cases in mathematical operations, which can be important to understand when dealing with numerical computations or when validating numerical inputs.
console.log(1 / 0) // Infinity
console.log(0 / 0) // NaN
console.log(-1 / 0) // -Infinity
console.log((1 / 0) * 0) // NaN => Infinity * 0 = NaN
console.log((1 / 0) * 1) // Infinity(NaN) => Infinity * 1 = Infinity
console.log((1 / 0) * -1) // -Infinity(NaN) => Infinity * -1 = -Infinity
console.log((1 / 0) * 1 + (1 / 0) * 1) // Infinity(NaN) => Infinity + Infinity = Infinity
console.log((1 / 0) * 1 - (1 / 0) * 1) // NaN => Infinity - Infinity = NaN
console.log((1 / 0) * 1 * ((1 / 0) * 1)) // Infinity(NaN) => Infinity * Infinity = Infinity
console.log(((1 / 0) * 1) / ((1 / 0) * 1)) // NaN => Infinity / Infinity = NaN
console.log(0 / Infinity) // 0
console.log(0 * Infinity) // NaN
75. meaningless calculation
75.https://bigfrontend.dev/quiz/meaningless-calculation
const num = +(~~!+[] + ~~!+[] + [] + ~~!+[])
console.log(num)
Solution: This code is a complex example of JavaScript type coercion and bitwise operations. Let's break it down step by step:
+[]
:- An empty array coerced to a number is 0.
!+[]
:!0
istrue
.
~~!+[]
:~~true
is 1. The double tilde (~~
) is a bitwise NOT operation performed twice, effectively coercing to an integer.
(~~!+[])+(~~!+[])
:- This is 1 + 1 = 2.
(~~!+[])+(~~!+[])+[]
:- 2 + [] results in string concatenation: "2" (the array is coerced to an empty string).
(~~!+[])+(~~!+[])+[]+(~~!+[])
:- "2" + 1 = "21" (string concatenation)
+((~~!+[])+(~~!+[])+[]+(~~!+[]))
:- The outer
+
coerces the string "21" back to a number.
- The outer
So, the final result is the number 21.
Therefore, console.log(num)
will output:
21
Key points:
+
can be used for both numeric addition and string concatenation, depending on the types of its operands.!
negates a value and returns a boolean.~~
is a shorthand for converting a value to an integer (similar to Math.floor() for positive numbers).- Empty arrays coerce to 0 when used as numbers, and to "" when used as strings.
- The outer
+
at the beginning of the expression coerces the final result to a number.
This code is an example of obfuscation and is not recommended for practical use. It's hard to read and maintain. It's primarily used as a puzzle or to demonstrate JavaScript's type coercion rules and bitwise operations in an extreme way.
83. Plus Plus
83.https://bigfrontend.dev/quiz/Plus-Plus
console.log(1 + 1)
console.log(1 + +1)
console.log(1 + +1 + 1)
console.log(1 + +1 + +1)
console.log(1 + +(+1))
console.log(1 + +'1' + +'1')
console.log('1' + +'1' + +'1')
console.log('a' + +'b')
console.log('a' + +'b' + 'c')
console.log('a' + +'b' + +'c')
Solution: This code demonstrates various uses of the addition operator (+) and the unary plus operator (+) in JavaScript. Let's break it down line by line:
console.log(1 + 1)
- Simple addition: 1 + 1 = 2
- Output: 2
console.log(1 + + 1)
- The second + is a unary plus, which doesn't change 1
- Equivalent to 1 + 1 = 2
- Output: 2
console.log(1 + + 1 + 1)
- Equivalent to 1 + 1 + 1 = 3
- Output: 3
console.log(1 + + 1 + + 1)
- Equivalent to 1 + 1 + 1 = 3
- Output: 3
console.log(1 + + + 1)
- Multiple unary plus operators, still doesn't change 1
- Equivalent to 1 + 1 = 2
- Output: 2
console.log(1 + + '1' + + '1')
- Unary plus converts '1' to number 1
- Equivalent to 1 + 1 + 1 = 3
- Output: 3
console.log('1' + + '1' + + '1')
- First + is string concatenation
- Unary plus converts '1' to number 1
- Equivalent to '1' + 1 + 1 = '111'
- Output: '111'
console.log('a' + + 'b')
- Unary plus tries to convert 'b' to a number, resulting in NaN
- Equivalent to 'a' + NaN = 'aNaN'
- Output: 'aNaN'
console.log('a' + + 'b' + 'c')
- Similar to previous, but with 'c' concatenated
- Equivalent to 'a' + NaN + 'c' = 'aNaNc'
- Output: 'aNaNc'
console.log('a' + + 'b' + + 'c')
- Both 'b' and 'c' are attempted to be converted to numbers, resulting in NaN
- Equivalent to 'a' + NaN + NaN = 'aNaNNaN'
- Output: 'aNaNNaN'
Key points:
- The unary plus (+) attempts to convert its operand to a number.
- When used with numbers, unary plus doesn't change the value.
- When used with strings that represent numbers, it converts them to actual numbers.
- When used with strings that don't represent numbers, it results in NaN.
- String concatenation takes precedence over addition when a string is involved.
This code demonstrates the intricacies of JavaScript's type coercion and operator behavior, particularly the interaction between numeric operations and string concatenation.
92. NaN
92.https://bigfrontend.dev/quiz/NaN
console.log(NaN == NaN)
console.log(NaN === NaN)
console.log(Object.is(NaN, NaN))
console.log([NaN].indexOf(NaN))
console.log([NaN].includes(NaN)) // true
console.log(Math.max(NaN, 1))
console.log(Math.min(NaN, 1))
console.log(Math.min(NaN, Infinity))
Solution: This code demonstrates various comparisons and operations involving NaN
(Not-a-Number) in JavaScript. Let's break it down line by line:
console.log(NaN == NaN)
- Output:
false
NaN
is not equal to itself when using loose equality (==).
- Output:
console.log(NaN === NaN)
- Output:
false
NaN
is not equal to itself even when using strict equality (===).
- Output:
console.log(Object.is(NaN, NaN))
- Output:
true
Object.is()
considersNaN
equal to itself.
- Output:
console.log([NaN].indexOf(NaN))
- Output:
-1
indexOf()
uses strict equality (===), so it can't findNaN
in the array.
- Output:
console.log([NaN].includes(NaN))
- Output:
true
includes()
usesSameValueZero
algorithm, which considersNaN
equal to itself.
- Output:
console.log(Math.max(NaN, 1))
- Output:
NaN
- If any argument to
Math.max()
isNaN
, the result isNaN
.
- Output:
console.log(Math.min(NaN, 1))
- Output:
NaN
- If any argument to
Math.min()
isNaN
, the result isNaN
.
- Output:
console.log(Math.min(NaN, Infinity))
- Output:
NaN
- Same as above,
NaN
as an argument results inNaN
.
- Output:
Key points:
NaN
is the only value in JavaScript that is not equal to itself using == or ===.Object.is()
andArray.prototype.includes()
use a different comparison algorithm that considersNaN
equal to itself.indexOf()
uses strict equality and thus can't findNaN
in an array.- Any mathematical operation involving
NaN
(including comparisons inMath.max()
andMath.min()
) results inNaN
.
This code highlights the special behavior of NaN
in JavaScript, which can be a source of confusion and bugs if not properly understood. It's important to use appropriate methods when working with NaN
, especially in comparisons and array operations.
109. isNaN()
109.https://bigfrontend.dev/quiz/isNaN
const a = 'BFE.dev'
const b = 1
console.log(Number.isNaN(a))
console.log(Number.isNaN(b))
console.log(isNaN(a))
console.log(isNaN(b))
Solution: Certainly! Let's break down this code and explain what's happening:
const a = 'BFE.dev'
const b = 1
console.log(Number.isNaN(a))
console.log(Number.isNaN(b))
console.log(isNaN(a))
console.log(isNaN(b))
This code demonstrates the difference between Number.isNaN()
and the global isNaN()
function. Let's go through each line:
- We define
a
as a string 'BFE.dev'. - We define
b
as a number 1.
Now, let's look at each console.log
statement:
console.log(Number.isNaN(a))
Number.isNaN()
checks if the passed value is exactly theNaN
value.- The string 'BFE.dev' is not
NaN
, so this returnsfalse
.
console.log(Number.isNaN(b))
- 1 is a number, but it's not
NaN
, so this returnsfalse
.
- 1 is a number, but it's not
console.log(isNaN(a))
- The global
isNaN()
function first attempts to convert its argument to a number. - 'BFE.dev' cannot be converted to a valid number, so it becomes
NaN
when converted. - Therefore, this returns
true
.
- The global
console.log(isNaN(b))
- 1 is already a number and it's not
NaN
, so this returnsfalse
.
- 1 is already a number and it's not
Therefore, the output will be:
false
false
true
false
Key points to understand:
Number.isNaN()
:- Introduced in ECMAScript 6 (ES6).
- It does not attempt to convert the argument to a number.
- It returns
true
only if the argument is exactly theNaN
value.
Global
isNaN()
:- It's an older function, available since the early days of JavaScript.
- It first attempts to convert its argument to a number.
- It returns
true
if the argument isNaN
or if it becomesNaN
after conversion to a number.
This code demonstrates:
- The difference between
Number.isNaN()
and the globalisNaN()
function. - How type coercion works in the global
isNaN()
function. - The behavior of these functions with different types of values (strings and numbers in this case).
It's a good example to understand the nuances of NaN
checking in JavaScript and how different methods handle type conversion. In general, Number.isNaN()
is considered more reliable for strictly checking for NaN
, while the global isNaN()
can be useful when you want to check if a value is not a valid number after conversion.
95. number format
95.https://bigfrontend.dev/quiz/number-format
console.log(017 - 011)
console.log(018 - 011)
console.log(019 - 011)
Solution: This code demonstrates the behavior of octal numbers in JavaScript and how they interact with decimal numbers. Let's break it down line by line:
console.log(017 - 011)
- In JavaScript, numbers starting with
0
are interpreted as octal (base-8) numbers, but only if all digits are 0-7. 017
in octal is 15 in decimal (1*8 + 7 = 15)011
in octal is 9 in decimal (1*8 + 1 = 9)- So this operation is equivalent to 15 - 9 = 6
- Output:
6
- In JavaScript, numbers starting with
console.log(018 - 011)
018
is not a valid octal number because 8 is not a valid octal digit.- In this case, JavaScript treats
018
as the decimal number 18. 011
is still interpreted as octal (9 in decimal)- So this operation is equivalent to 18 - 9 = 9
- Output:
9
console.log(019 - 011)
- Similar to the previous case,
019
is not a valid octal number. - JavaScript treats
019
as the decimal number 19. 011
is still interpreted as octal (9 in decimal)- So this operation is equivalent to 19 - 9 = 10
- Output:
10
- Similar to the previous case,
Key points:
- In JavaScript, numbers starting with
0
are interpreted as octal, but only if all digits are valid octal digits (0-7). - If a number starts with
0
but contains 8 or 9, it's treated as a decimal number. - Octal literals (starting with
0
) are considered legacy and are not allowed in strict mode or in ECMAScript 6 modules. - In modern JavaScript, it's recommended to use the prefix
0o
for octal numbers (e.g.,0o17
) to avoid confusion.
This behavior can lead to unexpected results and bugs if not properly understood. It's generally best to avoid using leading zeros for decimal numbers to prevent any ambiguity or unintended octal interpretation.
96. comparison
96.https://bigfrontend.dev/quiz/comparison
console.log(10 > 9)
console.log(10 > '9')
console.log('10' > 9)
console.log('10' > '9')
Solution: Let's break it down line by line:
console.log(10 > 9)
This compares two numbers. 10 is greater than 9, so this will output true
.
console.log(10 > '9')
Here we're comparing a number (10) to a string ('9'). JavaScript will try to convert the string to a number before comparing. '9' becomes 9, and 10 is still greater than 9, so this outputs true
.
console.log('10' > 9)
This compares a string ('10') to a number (9). Again, JavaScript converts the string to a number for comparison. '10' becomes 10, which is greater than 9, so this outputs true
.
console.log('10' > '9')
This compares two strings. When comparing strings, JavaScript uses lexicographic (dictionary) order. In this order, '10' comes after '9', so it's considered "greater". This will also output true
.
It's worth noting that while these all output true
, the comparisons are happening in different ways. The first is a simple numeric comparison, the middle two involve type coercion (converting strings to numbers), and the last is a string comparison.
105. typeof
105.https://bigfrontend.dev/quiz/typeof
const a = 'BFE.dev'
if (!typeof a === 'string') {
console.log('string')
} else {
console.log('not a string')
}
Solution: Let's break down this code and explain what's happening:
const a = 'BFE.dev'
if (!typeof a === 'string') {
console.log('string')
} else {
console.log('not a string')
}
This code is attempting to check if a
is not a string, but there's a subtle issue with the condition. Let's analyze it step by step:
We declare a constant
a
and assign it the string value 'BFE.dev'.The
if
statement condition is!typeof a === 'string'
.The
typeof
operator has higher precedence than the logical NOT (!
) operator, so this condition is evaluated as follows:- First,
typeof a
is evaluated. Sincea
is a string,typeof a
returns 'string'. - Then, the NOT operator is applied:
!('string')
. - The NOT operator converts its operand to a boolean and then negates it. Any non-empty string is truthy, so
!'string'
evaluates tofalse
. - Finally, we have:
false === 'string'
- First,
false === 'string'
is alwaysfalse
, because we're comparing a boolean to a string using strict equality.Since the condition evaluates to
false
, theelse
block is executed.
Therefore, the output will be:
not a string
This is counterintuitive because a
is indeed a string, but due to the incorrect use of the NOT operator, the condition always evaluates to false
.
To correctly check if a
is not a string, the condition should be written as:
if (typeof a !== 'string') {
console.log('not a string')
} else {
console.log('string')
}
This code demonstrates several important concepts in JavaScript:
- Operator precedence:
typeof
has higher precedence than!
. - Type coercion: how the
!
operator coerces its operand to a boolean. - Strict equality (
===
): no type coercion is performed. - The importance of parentheses in complex conditions to ensure correct evaluation order.
It's a tricky example that highlights how easy it is to write conditions that don't behave as intended due to operator precedence and type coercion rules in JavaScript.
111. falsy
111.https://bigfrontend.dev/quiz/falsy
console.log(!!'')
console.log(!!{})
console.log(!![])
Solution: Let's break down this code and explain what's happening:
console.log(!!'')
console.log(!!{})
console.log(!![])
This code demonstrates the concept of double negation (!!
) in JavaScript, which is often used as a shorthand way to convert a value to its boolean equivalent. Let's go through each line:
console.log(!!'')
:- The empty string
''
is a falsy value in JavaScript. - The first
!
negates this falsy value, resulting intrue
. - The second
!
negatestrue
, resulting infalse
. - So,
!!''
evaluates tofalse
.
- The empty string
console.log(!!{})
:- An empty object
{}
is a truthy value in JavaScript. - The first
!
negates this truthy value, resulting infalse
. - The second
!
negatesfalse
, resulting intrue
. - So,
!!{}
evaluates totrue
.
- An empty object
console.log(!![])
:- An empty array
[]
is a truthy value in JavaScript. - The first
!
negates this truthy value, resulting infalse
. - The second
!
negatesfalse
, resulting intrue
. - So,
!![]
evaluates totrue
.
- An empty array
Therefore, the output will be:
false
true
true
Key points to understand:
Truthy and Falsy values in JavaScript:
- Falsy values include:
false
,0
,''
(empty string),null
,undefined
, andNaN
. - All other values are considered truthy, including empty objects
{}
and empty arrays[]
.
- Falsy values include:
The
!
operator:- It negates a value, converting it to a boolean and then inverting it.
- When applied to a truthy value, it returns
false
. - When applied to a falsy value, it returns
true
.
Double negation
!!
:- It's equivalent to converting a value to its boolean representation.
- It's often used as a shorthand for
Boolean(x)
.
This code demonstrates:
- How different types of empty values (
''
,{}
,[]
) are treated in boolean contexts. - The use of double negation as a technique to convert values to booleans.
- The difference between primitives (like strings) and objects (including arrays) in terms of their "truthiness".
It's a concise way to illustrate some fundamental concepts about type coercion and boolean logic in JavaScript. Understanding these concepts is crucial for writing clear and predictable conditional logic in JavaScript programs.
132. banana
131.https://bigfrontend.dev/quiz/banana
console.log(('b' + 'a' + +'b' + 'a').toLowerCase())
Solution: Let's break down this expression step by step:
console.log(('b' + 'a' + +'b' + 'a').toLowerCase())
First, let's look at the expression inside the parentheses:
'b' + 'a' + + 'b' + 'a'
Breaking it down from left to right:
'b' + 'a'
concatenates to'ba'
- Now we have:
'ba' + + 'b' + 'a'
The key part is
+ 'b'
:- The
+
before'b'
is the unary plus operator - It attempts to convert
'b'
to a number 'b'
can't be converted to a number, so it becomesNaN
(Not a Number)
- The
So now we have:
'ba' + NaN + 'a'
When we add
NaN
to a string, it gets converted to the string'NaN'
'ba' + 'NaN' + 'a'
This all concatenates to:
'baNaNa'
Finally, we call
.toLowerCase()
on this string:'banana'
Therefore, console.log(('b' + 'a' + + 'b' + 'a').toLowerCase())
outputs:
banana
Key points:
- The unary
+
operator attempts to convert its operand to a number NaN
when used in string concatenation becomes the string'NaN'
- This is a tricky bit of JavaScript that relies on type coercion and the behavior of the
+
operator in different contexts
This is often used as a JavaScript joke or puzzle because it unexpectedly spells "banana" despite not having all the letters explicitly in the code.
11.Function
12. arguments
12.https://bigfrontend.dev/quiz/arguments
function log(a, b, c, d) {
console.log(a, b, c, d)
arguments[0] = 'bfe'
arguments[3] = 'dev'
console.log(a, b, c, d)
}
log(1, 2, 3)
Solution: Certainly. This code demonstrates some interesting behavior of the arguments
object in JavaScript. Let's break it down:
First, we define a function
log
that takes four parameters:a
,b
,c
, andd
.Inside the function, we have two
console.log
statements and some manipulations of thearguments
object.We call the function
log(1,2,3)
, passing only three arguments.
Now, let's go through what happens when we run this code:
console.log(a,b,c,d)
- This will output:
1 2 3 undefined
a
,b
, andc
are assigned the values 1, 2, and 3 respectively.d
is undefined because no fourth argument was provided.
- This will output:
arguments[0] = 'bfe'
- This changes the first argument to 'bfe'.
- In non-strict mode, this also changes the value of
a
to 'bfe'.
arguments[3] = 'dev'
- This assigns 'dev' to the fourth position in the arguments object.
- However, it doesn't create a new parameter or affect the
d
parameter.
console.log(a,b,c,d)
- This will output:
'bfe' 2 3 undefined
a
is now 'bfe' due to the change inarguments[0]
.b
andc
remain unchanged.d
is still undefined, despitearguments[3]
being set.
- This will output:
Key points:
- In non-strict mode, changing
arguments[i]
also changes the corresponding named parameter, and vice versa (fori
< number of parameters). - Adding values to
arguments
beyond the original number of arguments doesn't create new parameters or change undefined parameters. - The
arguments
object is array-like but not an actual array. - This behavior can be confusing and is one reason why using the
arguments
object is generally discouraged in modern JavaScript. Instead, rest parameters (...args) are often preferred for functions that need to handle a variable number of arguments.
16. parseInt
16.https://bigfrontend.dev/quiz/parseInt
console.log(['0'].map(parseInt))
console.log(['0', '1'].map(parseInt))
console.log(['0', '1', '1'].map(parseInt))
console.log(['0', '1', '1', '1'].map(parseInt))
Solution: This code demonstrates an interesting and often misunderstood interaction between the map()
function and parseInt()
in JavaScript. Let's break it down:
console.log(['0'].map(parseInt))
- Output:
[0]
- Here,
parseInt
is called with just one argument '0', so it works as expected.
- Output:
console.log(['0','1'].map(parseInt))
- Output:
[0, NaN]
- This is where things get tricky.
map
passes three arguments to the callback function: the current value, the index, and the array. - So it's effectively doing:
parseInt('0', 0, ['0','1'])
-> 0 (parsed in base 10, as 0 is not a valid radix)parseInt('1', 1, ['0','1'])
-> NaN (1 is not a valid radix)
- Output:
console.log(['0','1','1'].map(parseInt))
- Output:
[0, NaN, 1]
- This is doing:
parseInt('0', 0, ['0','1','1'])
-> 0parseInt('1', 1, ['0','1','1'])
-> NaN (1 is not a valid radix)parseInt('1', 2, ['0','1','1'])
-> 1 (valid binary digit)
- Output:
console.log(['0','1','1','1'].map(parseInt))
- Output:
[0, NaN, 1, 1]
- This is doing:
parseInt('0', 0, ['0','1','1','1'])
-> 0parseInt('1', 1, ['0','1','1','1'])
-> NaN (1 is not a valid radix)parseInt('1', 2, ['0','1','1','1'])
-> 1 (valid binary digit)parseInt('1', 3, ['0','1','1','1'])
-> 1 (valid ternary digit)
- Output:
Key points:
parseInt(string, radix)
parses a string and returns an integer of the specified radix (base).map(callback(currentValue[, index[, array]]))
calls the provided function once for each element in an array, in order.- When
parseInt
is used as the callback formap
, it receives not just the string to parse, but also the index as the radix. - This leads to unexpected results because the index is being used as the radix for
parseInt
. - To correctly use
parseInt
withmap
, you should wrap it in a function that only passes the string argument:This would correctly return;['0', '1', '1', '1'].map((num) => parseInt(num))
[0, 1, 1, 1]
.
This example highlights the importance of understanding how functions work when passed as callbacks, especially when they accept multiple parameters. It's a common pitfall in JavaScript.
77. parseInt 2
77.https://bigfrontend.dev/quiz/parseInt-2
console.log(parseInt(0.00001))
console.log(parseInt(0.000001))
console.log(parseInt(0.0000001))
console.log(parseInt('0x12'))
console.log(parseInt('1e2'))
Solution: This code demonstrates the behavior of JavaScript's parseInt()
function with different types of input. Let's break it down line by line:
console.log(parseInt(0.00001))
- Output:
0
parseInt()
converts its argument to a string first, so this becomesparseInt("0.00001")
.parseInt()
stops parsing at the first non-digit character (the decimal point).- Therefore, it returns 0.
- Output:
console.log(parseInt(0.000001))
- Output:
0
- Same reasoning as above.
- Output:
console.log(parseInt(0.0000001))
- Output:
1
- This number in JavaScript is represented in scientific notation: "1e-7".
- When converted to a string, it becomes "1e-7".
parseInt()
stops at the 'e', so it parses just the '1'.
- Output:
console.log(parseInt("0x12"))
- Output:
18
- The "0x" prefix is recognized as a hexadecimal number.
- 12 in hexadecimal is 18 in decimal.
- Output:
console.log(parseInt("1e2"))
- Output:
1
parseInt()
stops parsing at the first non-digit character ('e').- So it only parses the '1'.
- Output:
Key points:
parseInt()
first converts its argument to a string if it's not already a string.- It parses from left to right and stops at the first character it can't parse as part of the number.
- It recognizes and correctly parses hexadecimal numbers with the "0x" prefix.
- It does not recognize scientific notation (e.g., "1e2") as a single number.
- Very small floating-point numbers may be represented in scientific notation internally, which can lead to unexpected results when parsed.
This quiz demonstrates the importance of understanding how parseInt()
works, especially with edge cases like very small numbers or numbers in different formats. It's a good reminder to always be cautious when parsing numerical input and to consider using other methods (like Number()
or parseFloat()
) when appropriate.
115. parseInt II
115.https://bigfrontend.dev/quiz/parseInt-II
console.log(parseInt(' 1'))
console.log(parseInt(' 00001'))
console.log(parseInt(' 0100'))
console.log(parseInt(' 1e2 '))
Solution:
Let's break down each of these parseInt()
calls:
console.log(parseInt(' 1'))
- Result:
1
- Explanation:
parseInt()
ignores leading whitespace. It then parses the string until it encounters a character that isn't part of a valid integer. Here, it simply parses the '1'.
- Result:
console.log(parseInt(' 00001'))
- Result:
1
- Explanation: Again, leading whitespace is ignored. Leading zeros are also ignored in decimal interpretation. The function parses this as a decimal number (base 10 by default) and returns 1.
- Result:
console.log(parseInt(' 0100'))
- Result:
100
- Explanation: Leading whitespace and zeros are ignored. The function reads this as the decimal number 100.
- Result:
console.log(parseInt(' 1e2 '))
- Result:
1
- Explanation:
parseInt()
stops parsing when it encounters a character it can't interpret as part of an integer. In this case, it parses the '1', but stops at 'e' because 'e' is not a valid digit. The scientific notation 'e2' is not recognized byparseInt()
.
- Result:
Key points to remember about parseInt()
:
- It ignores leading whitespace.
- It parses until it encounters a character that can't be part of the number in the given base (10 by default).
- It ignores leading zeros in decimal interpretation.
- It doesn't recognize scientific notation (like 1e2) - for that, you'd use
parseFloat()
or the unary plus operator.
If you need to parse numbers in different bases or with specific formatting, you might need to use additional parameters or different methods.
17. reduce
17.https://bigfrontend.dev/quiz/reduce
;[1, 2, 3].reduce((a, b) => {
console.log(a, b)
})
;[1, 2, 3].reduce((a, b) => {
console.log(a, b)
}, 0)
Solution:
This code demonstrates the behavior of the reduce()
method in JavaScript, specifically highlighting the difference between using reduce()
with and without an initial value. Let's break it down:
- First
reduce()
call:
;[1, 2, 3].reduce((a, b) => {
console.log(a, b)
})
This will output:
1 2
undefined 3
Explanation:
- When
reduce()
is called without an initial value, the first element of the array (1) becomes the initial accumulator value. - In the first iteration,
a
is 1 (the first element) andb
is 2 (the second element). - In the second iteration,
a
isundefined
(because the previous iteration didn't return anything) andb
is 3. - The function doesn't return anything, so the final result of
reduce()
isundefined
.
- Second
reduce()
call:
;[1, 2, 3].reduce((a, b) => {
console.log(a, b)
}, 0)
This will output:
0 1
undefined 2
undefined 3
Explanation:
- Here,
reduce()
is called with an initial value of 0. - In the first iteration,
a
is 0 (the initial value) andb
is 1 (the first element). - In the second iteration,
a
isundefined
(because the previous iteration didn't return anything) andb
is 2. - In the third iteration,
a
is againundefined
andb
is 3. - The function doesn't return anything, so the final result of
reduce()
isundefined
.
Key points:
- When
reduce()
is called without an initial value, the first element of the array is used as the initial value, and reduction starts from the second element. - When an initial value is provided,
reduce()
starts from the first element of the array. - In both cases, because the callback function doesn't return a value,
undefined
is used as the accumulator in subsequent iterations. - Typically, you'd want to return a value from each iteration to properly accumulate results. For example:This would log each step and correctly sum the array.
;[1, 2, 3].reduce((a, b) => { console.log(a, b) return a + b }, 0)
This example highlights the importance of understanding how reduce()
works, particularly with regard to the initial value and the return value of the callback function.
20. name for Function expression
20.https://bigfrontend.dev/quiz/name-for-Function-expression
function a() {}
const b = function () {}
const c = function d() {
console.log(typeof d)
d = 'e'
console.log(typeof d)
}
console.log(typeof a)
console.log(typeof b)
console.log(typeof c)
console.log(typeof d)
c()
Solution:
Let's go through the code and its output:
Function Definitions:
function a() {} const b = function () {} const c = function d() { console.log(typeof d) d = 'e' console.log(typeof d) }
a
is a function declaration.b
is an anonymous function expression assigned to a variable.c
is a named function expression assigned to a variable. The function's internal name isd
.
Console Output:
console.log(typeof a) // Output: "function" console.log(typeof b) // Output: "function" console.log(typeof c) // Output: "function" console.log(typeof d) // Output: "undefined"
a
,b
, andc
are all functions, sotypeof
returns "function" for each.d
is not defined in the global scope, sotypeof d
returns "undefined".
Function
c
Execution:c()
This calls the function stored in
c
. Inside this function:- First
console.log(typeof d)
outputs: "function" This is becaused
refers to the function itself within its own scope. d = 'e'
attempts to reassignd
, but this doesn't affect the function name.- Second
console.log(typeof d)
still outputs: "function" The assignmentd = 'e'
doesn't change the type ofd
within the function scope.
- First
Key points to understand:
Function declarations (
a
) and function expressions (b
andc
) are all of type "function".In a named function expression (
function d() {...}
), the function name (d
in this case) is only accessible within the function's own scope. It's not added to the surrounding scope, which is whytypeof d
outside the function returns "undefined".Inside the named function expression, the function name (
d
) is read-only. Attempts to reassign it (liked = 'e'
) are ignored in strict mode or fail silently in non-strict mode. This is why the type ofd
remains "function" even after the attempted reassignment.The variable
c
holds a reference to the function. The internal named
doesn't create a variable in the outer scope.
This code demonstrates some subtle but important aspects of function definitions, scope, and the immutability of function names in JavaScript. It's a good example of why understanding these concepts is crucial for avoiding unexpected behavior in more complex scenarios.
22.min max
22.https://bigfrontend.dev/quiz/min-max
console.log(Math.min())
console.log(Math.max())
console.log(Math.min(1))
console.log(Math.max(1, 2))
console.log(Math.min([1, 2, 3]))
Solution: Let's break it down line by line.
console.log(Math.min())
Output:Infinity
Explanation: When
Math.min()
is called with no arguments, it returnsInfinity
. This might seem counterintuitive, but it's designed this way so that any number you compare with this result will be considered smaller.console.log(Math.max())
Output:-Infinity
Explanation: Similarly, when
Math.max()
is called with no arguments, it returns-Infinity
. This ensures that any number compared with this result will be considered larger.console.log(Math.min(1))
Output:1
Explanation: When
Math.min()
is called with a single argument, it simply returns that argument.console.log(Math.max(1,2))
Output:2
Explanation: This is the standard use case.
Math.max()
compares the given arguments and returns the largest one.console.log(Math.min([1,2,3]))
Output:NaN
Explanation: This is a tricky one.
Math.min()
doesn't automatically unpack arrays. When given an array as an argument, it tries to convert the array to a number, which results inNaN
(Not a Number).
Key points to remember:
Math.min()
andMath.max()
with no arguments returnInfinity
and-Infinity
respectively.These functions work as expected when given multiple numeric arguments.
They don't work directly with arrays. To use an array, you need to spread its elements:
console.log(Math.min(...[1, 2, 3])) // This would correctly output 1
Both functions can handle any number of arguments:
Math.min(5, 2, 8, 1, 9) // returns 1 Math.max(5, 2, 8, 1, 9) // returns 9
If any argument is
NaN
or can't be converted to a number, the result isNaN
:Math.min(1, 'hello', 3) // returns NaN
These behaviors highlight the importance of understanding how JavaScript functions handle different types of inputs and edge cases, especially when working with mathematical operations.
Infinity - Infinity
1
2
NaN
43. JSON.stringify()
43.https://bigfrontend.dev/quiz/json-stringify
// attention that for console.log('"a"'), you should enter ""a""
// please refer to format guide
console.log(JSON.stringify(['false', false]))
console.log(JSON.stringify([NaN, null, Infinity, undefined]))
console.log(JSON.stringify({ a: null, b: NaN, c: undefined }))
Solution:
This code demonstrates how JSON.stringify()
handles different JavaScript values. Let's break it down line by line:
console.log(JSON.stringify(['false', false]))
Output:'["false",false]'
- The string 'false' is encoded as a JSON string.
- The boolean false is encoded as the JSON literal false.
- Both are kept in the array structure.
console.log(JSON.stringify([NaN, null, Infinity, undefined]))
Output:'[null,null,null,null]'
- NaN is converted to null in JSON.
- null remains as null.
- Infinity is converted to null in JSON.
- undefined is converted to null when it's in an array.
console.log(JSON.stringify({a: null, b: NaN, c: undefined}))
Output:'{"a":null,"b":null}'
- The property a with value null is kept as-is.
- The property b with value NaN is converted to null.
- The property c with value undefined is omitted entirely from the JSON output.
Key points about JSON.stringify():
- Strings are always valid JSON and are enclosed in double quotes.
- Booleans (true and false) are valid JSON literals.
- Numbers are generally valid JSON, but with some exceptions:
- NaN and Infinity are converted to null.
- null is a valid JSON literal.
- undefined is handled differently depending on context:
- In arrays, it's converted to null.
- As an object property value, the entire property is omitted.
- Object properties with undefined values are omitted from the JSON output.
- JSON doesn't have a representation for JavaScript's NaN or Infinity, so they're converted to null.
This behavior ensures that the resulting JSON string is always valid according to the JSON specification, even if it means losing some information from the original JavaScript value. It's important to be aware of these conversions when working with data that might contain special values like NaN, Infinity, or undefined.
127. JSON.stringify() II
127.https://bigfrontend.dev/quiz/json-stringify-ii
console.log(JSON.stringify(undefined))
console.log(JSON.stringify([undefined]))
Solution:
Let's break down this code and explain what's happening:
console.log(JSON.stringify(undefined))
console.log(JSON.stringify([undefined]))
Let's examine each line separately:
console.log(JSON.stringify(undefined))
JSON.stringify()
is a method that converts a JavaScript value to a JSON string.- When
undefined
is passed directly toJSON.stringify()
, it returnsundefined
(not as a string, but as the actualundefined
value). - When
console.log()
receivesundefined
, it printsundefined
to the console. - Output:
undefined
console.log(JSON.stringify([undefined]))
- Here, we're passing an array containing a single
undefined
element toJSON.stringify()
. - In JSON, there is no
undefined
data type. WhenJSON.stringify()
encountersundefined
within an array, it replaces it withnull
. - The resulting JSON string will be
"[null]"
. console.log()
prints this string to the console.- Output:
[null]
- Here, we're passing an array containing a single
So, the complete output will be:
undefined
[null]
Key points to understand:
JSON.stringify()
handlesundefined
differently depending on where it appears:- When
undefined
is the root value,JSON.stringify()
returnsundefined
(not a string). - When
undefined
is found within an array, it's converted tonull
in the resulting JSON string. - When
undefined
is found as a property value in an object, the property is omitted entirely from the JSON output.
- When
JSON does not have an
undefined
type, which is why it's handled specially byJSON.stringify()
.This behavior helps maintain valid JSON structure, as
undefined
is not a valid JSON value, butnull
is.It's important to be aware of these transformations when working with
JSON.stringify()
, especially when dealing with data that might containundefined
values.
This example demonstrates the nuanced behavior of JSON.stringify()
when dealing with undefined
values in different contexts.
44. Function call
44.https://bigfrontend.dev/quiz/Function-call
function a() {
console.log(1)
return {
a: function () {
console.log(2)
return a()
},
}
}
a().a()
Solution:
Let's break down this code step by step:
First, we define a function
a
:function a() { console.log(1) return { a: function () { console.log(2) return a() }, } }
This function does two things:
- It logs the number 1 to the console.
- It returns an object with a method also named
a
.
The returned object's
a
method:- Logs the number 2 to the console.
- Calls the original
a
function again.
Now, let's look at the function call:
a().a()
This is executed as follows:
a. First,
a()
is called:- It logs 1 to the console.
- It returns the object with the
a
method.
b. Then,
.a()
is called on the returned object:- It logs 2 to the console.
- It calls
a()
again. b. Then,a()
is again: - It logs 1 to the console.
- It return
{a: function () {...}}
. However, the function inside is not executed.
So, the execution flow is:
a()
is called- 1 is logged
- The object is returned
- The
a
method of the returned object is called - 2 is logged
a()
is called again (go back to step 1)- 1 is logged and returned an object. This creates an infinite loop, alternately logging 1 and 2:
1
2
1
Key points:
- This is an example of recursion, where a function indirectly calls itself.
- The name
a
is used for both the outer function and the method of the returned object, but they are distinct functions. - This code demonstrates how function returns and method calls can be chained.
65. Function name
65.https://bigfrontend.dev/quiz/Function-name
var foo = function bar() {
return 'BFE'
}
console.log(foo())
console.log(bar())
Solution: This code demonstrates named function expressions and scoping rules in JavaScript. Let's break it down:
Function Declaration:
var foo = function bar() { return 'BFE' }
This is a named function expression. It creates a function with the name 'bar' and assigns it to the variable 'foo'.
console.log(foo());
- This calls the function stored in the variable 'foo'.
- It will execute successfully and return 'BFE'.
- Output:
'BFE'
console.log(bar());
- This attempts to call a function named 'bar'.
- However, 'bar' is not defined in the current scope.
- This will result in a ReferenceError.
Key points:
In a named function expression, the function name (in this case, 'bar') is only available within the function's own scope. It's not created as a variable in the outer scope.
The variable to which the function is assigned (in this case, 'foo') is what's used to reference the function from the outside.
Attempting to use the function's name ('bar') outside of its own scope will result in a ReferenceError.
The expected behavior:
- The first
console.log(foo())
will output'BFE'
. - The second
console.log(bar())
will throw a ReferenceError: "bar is not defined".
So, if we were to run this code, we would see:
'BFE'
Uncaught ReferenceError: bar is not defined
This quiz tests understanding of:
- Named function expressions
- Variable scoping in JavaScript
- The difference between a function's name and the variable it's assigned to
It's a good example of how function names in named function expressions behave differently from function declarations, highlighting some nuanced aspects of JavaScript's scoping rules.
69. undefined
69.https://bigfrontend.dev/quiz/undefined
function foo(a, b, undefined, undefined) {
console.log('BFE.dev')
}
console.log(foo.length)
Solution:
Certainly! This code demonstrates an interesting aspect of JavaScript functions related to their parameters and the length
property. Let's break it down:
Function Declaration:
function foo(a, b, undefined, undefined) { console.log('BFE.dev') }
- This declares a function named
foo
with four parameters. - Interestingly, two of the parameters are named
undefined
. - In JavaScript, it's valid to have multiple parameters with the same name, though it's not a recommended practice.
- This declares a function named
console.log(foo.length)
- This logs the
length
property of the functionfoo
.
- This logs the
The key point here is understanding what function.length
represents:
- The
length
property of a function object indicates the number of parameters expected by the function. - It returns the number of parameters in the function declaration, excluding rest parameters.
- Importantly, it counts the number of parameters up to the first parameter with a default value.
In this case:
- The function
foo
has four parameters in its declaration. - None of these parameters have default values.
- The fact that two parameters have the same name (
undefined
) doesn't affect thelength
property.
Therefore, console.log(foo.length)
will output:
4
Key points to note:
- The
length
property of a function returns the number of parameters in its declaration. - Duplicate parameter names are allowed in non-strict mode (though they should be avoided).
- The actual names of the parameters (even if they're
undefined
) don't affect thelength
property. - This behavior can be useful for introspection or when writing higher-order functions that need to know about the arity (number of arguments) of other functions.
This quiz tests understanding of:
- Function parameters in JavaScript
- The
length
property of function objects - JavaScript's permissiveness with parameter naming
It's a subtle example that highlights some of the more unusual aspects of JavaScript's function syntax and properties.
12. Array
21. Array I
21.https://bigfrontend.dev/quiz/Array-I
const a = [0]
console.log(a.length)
a[3] = 3
console.log(a.length)
for (let item of a) {
console.log(item)
}
a.map((item) => {
console.log(item)
})
a.forEach((item) => {
console.log(item)
})
console.log(Object.keys(a))
delete a[3]
console.log(a.length)
a[2] = 2
a.length = 1
console.log(a[0], a[1], a[2])
Solution: Let's analyze each part of the code:
const a = [0]
- Initializes an array with one element, 0.
console.log(a.length)
Output:1
- The array has one element, so its length is 1.
a[3] = 3
- Assigns the value 3 to index 3, creating a sparse array.
console.log(a.length)
Output:4
- The length is now 4, as it's always one more than the highest index.
for (let item of a) { console.log(item) }
Output:0 undefined undefined 3
- This loop iterates over all items, including empty slots (as undefined).
a.map(item => {console.log(item)})
Output:0 3
map
skips empty slots.
a.forEach(item => {console.log(item)})
Output:0 3
forEach
skips empty slots.
console.log(Object.keys(a))
Output:['0', '3']
Object.keys
returns only the indices of existing elements.
delete a[3]
- Removes the element at index 3, leaving an empty slot.
console.log(a.length)
Output:4
- Deleting an element doesn't change the array's length.
a[2] = 2
- Assigns 2 to index 2, filling one of the empty slots.
a.length = 1
- Explicitly sets the array length to 1, truncating the array.
console.log(a[0],a[1],a[2])
Output:0 undefined undefined
- a[0] is still 0, but a[1] and a[2] are now undefined as they were truncated.
Key takeaways:
- Array length is always one more than the highest index.
- Assigning to an index higher than the current length creates a sparse array.
- Different array methods handle empty slots differently:
for...of
andmap
treat empty slots as undefined.forEach
skips empty slots.
Object.keys
only returns indices of existing elements.delete
leaves an empty slot but doesn't change the array length.- Setting
length
to a smaller value truncates the array.
This code demonstrates the flexibility and sometimes unexpected behavior of JavaScript arrays, particularly when dealing with sparse arrays and manipulating array length directly.
1
4
0
undefined
undefined
3
0
3
0
;(3)[('0', '3')]
4
0, undefined, undefined
37. push unshift
37.https://bigfrontend.dev/quiz/push-unshift
const arr = [1, 2]
arr.push(3, 4)
arr.unshift(5, 6)
console.log(arr)
Solution: Let's break down this code step by step:
const arr = [1,2]
- This creates a constant array
arr
with initial elements 1 and 2. - At this point,
arr
is [1, 2].
- This creates a constant array
arr.push(3,4)
- The
push()
method adds one or more elements to the end of an array. - It adds 3 and 4 to the end of the array.
- After this operation,
arr
is [1, 2, 3, 4].
- The
arr.unshift(5,6)
- The
unshift()
method adds one or more elements to the beginning of an array. - It adds 5 and 6 to the beginning of the array.
- After this operation,
arr
is [5, 6, 1, 2, 3, 4].
- The
console.log(arr)
- This prints the final state of the array to the console.
So, the output will be:
[5, 6, 1, 2, 3, 4]
Key points:
push()
adds elements to the end of an array.unshift()
adds elements to the beginning of an array.- Both methods can take multiple arguments to add multiple elements at once.
- These methods modify the original array; they don't create a new array.
- Even though
arr
is declared withconst
, we can still modify its contents. Theconst
declaration prevents reassignment of the variablearr
, but doesn't make the array itself immutable.
This quiz tests understanding of basic array manipulation methods in JavaScript and how they affect the array's contents.
55. sparse array
55.https://bigfrontend.dev/quiz/sparse-array
const arr = [1, , , 2]
// forEach
arr.forEach((i) => console.log(i))
// map
console.log(arr.map((i) => i * 2))
// for ... of
for (const i of arr) {
console.log(i)
}
// spread
console.log([...arr])
Solution: Certainly! This code demonstrates how different JavaScript array methods and operations handle sparse arrays (arrays with empty slots). Let's break it down:
const arr = [1,,,2]
This creates a sparse array with four elements:- Index 0: 1
- Index 1: empty
- Index 2: empty
- Index 3: 2
Now, let's see how different methods handle this array:
arr.forEach(i => console.log(i))
forEach
skips empty slots. It will output:1 2
console.log(arr.map(i => i * 2))
map
preserves the empty slots. It will output:[2,empty,empty,4]
for (const i of arr) { console.log(i) }
for...of
includes undefined for empty slots. It will output:1 undefined undefined 2
console.log([...arr])
The spread operator treats empty slots as undefined. It will output:[1, undefined, undefined, 2]
Key points:
Sparse arrays: JavaScript allows arrays to have "holes" or empty slots. These are different from slots containing
undefined
.forEach
: Skips empty slots entirely.map
: Preserves the structure of the original array, including empty slots.for...of
: Treats empty slots asundefined
.Spread operator: Converts empty slots to
undefined
.
This behavior can lead to unexpected results when working with sparse arrays. It's generally a good practice to avoid creating sparse arrays, as their behavior can be inconsistent across different array methods and operations.
This quiz tests understanding of:
- The concept of sparse arrays in JavaScript
- How different array methods and operations handle empty slots
- The difference between an empty slot and a slot containing
undefined
Understanding these nuances is important for writing robust JavaScript code, especially when dealing with arrays that might have gaps or when working with data from external sources that might produce sparse arrays.
72. Array length
72.https://bigfrontend.dev/quiz/array-length
class MyArray extends Array {
get length() {
return 3
}
}
const arr1 = new MyArray(10)
console.log(arr1.length)
const arr2 = new Array(10)
console.log(arr2.length)
Solution: Subclasses cannot override parentClass properties. This is by design.
10
10
74. Typed Array length
74.https://bigfrontend.dev/quiz/Typed-Array-length
class MyArray extends Uint8Array {
get length() {
return 3
}
}
const arr1 = new MyArray(10)
console.log(arr1.length)
const arr2 = new Uint8Array(10)
console.log(arr2.length)
Solution: Certainly! This code demonstrates class inheritance in JavaScript, specifically extending a typed array (Uint8Array), and the potential pitfalls of overriding core properties. Let's break it down:
Class Definition:
class MyArray extends Uint8Array { get length() { return 3 } }
MyArray
is a subclass ofUint8Array
, which is a typed array representing an array of 8-bit unsigned integers.- It overrides the
length
getter to always return 3, regardless of the actual array content.
Creating an instance of MyArray:
const arr1 = new MyArray(10) console.log(arr1.length)
- This creates a new
MyArray
instance with an initial size of 10. - However, due to the overridden
length
getter,arr1.length
will always return 3. - Output:
3
- This creates a new
Creating a regular Uint8Array:
const arr2 = new Uint8Array(10) console.log(arr2.length)
- This creates a standard Uint8Array with length 10.
- Output:
10
Key points:
- Typed arrays like Uint8Array are specialized array-like objects for handling binary data.
- Extending typed arrays is possible in JavaScript, just like extending regular arrays.
- Overriding core properties like
length
can lead to unexpected behavior, especially with typed arrays. - The
length
property is crucial for many array operations, so overriding it can break normal functionality. - The overridden
length
getter inMyArray
ignores the actual content or initialization of the array.
Important considerations:
- While this code demonstrates an interesting point about inheritance and property overriding, it's generally not a good practice to override
length
in this way for arrays, especially typed arrays. - Typed arrays are designed for performance and efficiency in handling binary data. Overriding
length
could interfere with this intended use. - Many built-in methods of Uint8Array rely on the
length
property behaving normally, so this override could cause issues with those methods. - This example shows how JavaScript's class system allows for powerful customizations, but also how it can be misused to create confusing or potentially broken behavior.
This quiz tests understanding of:
- Class inheritance in JavaScript, particularly with built-in types like typed arrays
- Overriding getters in subclasses
- The behavior of typed array constructors
- How overriding core properties can affect object behavior, especially in specialized types like typed arrays
It's a tricky example that highlights the potential pitfalls of subclassing built-in objects, especially more specialized ones like typed arrays, and overriding their core properties.
This is quite similar to this previous problem but the difference being we are extending from Uint8Array typed array that uses ArrayBuffer and in this case Subclass constructors may over-ride it to change the constructor assignment See this
84. Array.prototype.sort()
84.https://bigfrontend.dev/quiz/Array-prototype-sort
const a = [999, 1111, 111, 2, 0]
const b = a.sort()
console.log(a)
console.log(b)
Solution: Certainly! This code demonstrates the behavior of JavaScript's built-in sort()
method on arrays. Let's break it down:
Array Declaration:
const a = [999, 1111, 111, 2, 0]
This creates an array
a
with five numeric elements.Sorting:
const b = a.sort()
This sorts the array
a
and assigns the result tob
.Console Output:
console.log(a) console.log(b)
This logs both
a
andb
to the console.
Now, let's discuss the key points:
The
sort()
method sorts the elements of an array in place and returns the sorted array. This means it modifies the original array.When
sort()
is called without arguments, it sorts the elements as strings in alphabetical and ascending order.For numbers, this leads to an unexpected result because the numbers are converted to strings before sorting.
Both
a
andb
reference the same sorted array after the operation.
The output will be:
[0, 111, 1111, 2, 999]
[0, 111, 1111, 2, 999]
Explanation of the sorting:
- When converted to strings, the numbers are ordered as: "0", "111", "1111", "2", "999"
- This is why 1111 comes before 2 in the sorted array
Important notes:
- To sort numbers correctly, you should provide a comparison function to
sort()
:a.sort((x, y) => x - y)
const
doesn't make the array immutable; it just prevents reassignment of the variable.b
is not a new array; it's the same array asa
, just sorted.
This example highlights the importance of understanding how built-in methods work in JavaScript, especially when dealing with different data types. It also demonstrates that arrays in JavaScript are reference types, and operations like sort()
modify the original array.
90. array keys
90.https://bigfrontend.dev/quiz/array-keys
console.log(Reflect.ownKeys([]))
console.log(Reflect.ownKeys([,]))
console.log(Reflect.ownKeys([1, , 2]))
console.log(Reflect.ownKeys([...[1, , 2]]))
Solution: Certainly! This code demonstrates the use of Reflect.ownKeys()
on different array configurations in JavaScript. Let's break it down line by line:
console.log(Reflect.ownKeys([]))
- This creates an empty array and gets its own keys.
- Output:
['length']
- Even an empty array has a 'length' property.
console.log(Reflect.ownKeys([,]))
- This creates an array with one empty slot.
- Output:
['length']
- The empty slot doesn't create an own property.
console.log(Reflect.ownKeys([1,,2]))
- This creates an array with elements at indices 0 and 2, and an empty slot at index 1.
- Output:
['0', '2', 'length']
- The indices of non-empty slots become own properties.
console.log(Reflect.ownKeys([...[1,,2]]))
- This spreads the array
[1,,2]
into a new array. - The spread operator turns empty slots into
undefined
values. - Output:
['0', '1', '2', 'length']
- All indices, including the one that was an empty slot, become own properties.
- This spreads the array
Key points:
Reflect.ownKeys()
returns an array of all the own property names of an object, including non-enumerable properties.For arrays, this includes:
- Numeric indices of elements that are not empty slots
- The 'length' property
Empty slots in arrays (often called "holes") do not create own properties.
The spread operator (
...
) converts empty slots toundefined
, which does create own properties.Array indices are converted to strings in the returned array of keys.
This code highlights some interesting aspects of JavaScript arrays:
- The difference between empty slots and
undefined
values - How array indices become object properties
- The effect of the spread operator on array holes
It's a good demonstration of the internal workings of JavaScript arrays and how they differ from simple lists in other languages.
91. largest Array index
91.https://bigfrontend.dev/quiz/largest-Array-index
const arr = []
arr[2 ** 32 - 2] = 1
arr[2 ** 32 - 1] = 2
console.log(arr.at(-1))
Solution: This code demonstrates some interesting behavior related to array indexing and the limits of JavaScript arrays. Let's break it down:
const arr = []
- Creates an empty array.
arr[(2 ** 32) - 2] = 1
2 ** 32
is 4,294,967,296(2 ** 32) - 2
is 4,294,967,294- This sets the element at index 4,294,967,294 to 1
- This is the largest valid index for a JavaScript array
arr[(2 ** 32) - 1] = 2
(2 ** 32) - 1
is 4,294,967,295- This attempts to set an element at index 4,294,967,295
- However, this index is beyond the maximum allowed array index in JavaScript
console.log(arr.at(-1))
- The
at()
method accesses array elements using negative indices, counting from the end -1
refers to the last element of the array
- The
Now, let's consider what happens:
- The array is created with a length of 4,294,967,295 (the largest valid index + 1)
- The element at index 4,294,967,294 is set to 1
- The attempt to set an element at index 4,294,967,295 is ignored because it's beyond the array limit
arr.at(-1)
tries to access the last element of the array
The output will be:
1
Key points:
- JavaScript arrays have a maximum length of 2^32 - 1 (4,294,967,295)
- The highest valid index is one less than this: 2^32 - 2 (4,294,967,294)
- Attempts to set elements beyond this limit are silently ignored
- The
at()
method provides a convenient way to access elements from the end of the array - Despite the array appearing to have two elements set, only the one at the highest valid index is actually in the array
This code demonstrates the limits of JavaScript arrays and how they behave at these limits. It's an edge case that you're unlikely to encounter in typical programming scenarios, but it's useful for understanding the underlying implementation of arrays in JavaScript.
113. push
113.https://bigfrontend.dev/quiz/push
const a = [1, 2, 3]
const b = a.push(4)
const c = b.push(5)
console.log(c)
Solution: Let's break down this code step by step:
const a = [1, 2, 3]
This line creates an array a
with three elements: 1, 2, and 3.
const b = a.push(4)
The push()
method adds one or more elements to the end of an array and returns the new length of the array. It modifies the original array.
So, this line does two things:
- It adds the number 4 to the end of array
a
. Nowa
is [1,2,3,4]. - It assigns the new length of the array (which is 4) to the variable
b
.
const c = b.push(5)
This line is problematic. b
is not an array; it's a number (the length of array a
after pushing 4). Numbers don't have a push()
method.
console.log(c)
When this line runs, it will throw a TypeError because we tried to call push()
on a number in the previous line.
The correct output of this code would be a TypeError, something like:
TypeError: b.push is not a function
If the intention was to add 5 to the original array a
, the code should have been:
const a = [1, 2, 3]
a.push(4)
a.push(5)
console.log(a) // This would output [1,2,3,4,5]
This example illustrates a common misunderstanding about the push()
method. While it modifies the original array, it returns the new length, not the modified array itself.
13.time
54. setTimeout(0ms)
54.https://bigfrontend.dev/quiz/setTimeout-0ms
// This snippet's result may vary on browsers
setTimeout(() => {
console.log(2)
}, 2)
setTimeout(() => {
console.log(1)
}, 1)
setTimeout(() => {
console.log(0)
}, 0)
Solution:
This code snippet demonstrates the behavior of JavaScript's setTimeout
function with very small delay values. Let's break it down:
First
setTimeout
:setTimeout(() => { console.log(2) }, 2)
This schedules a callback to log
2
after a 2 millisecond delay.Second
setTimeout
:setTimeout(() => { console.log(1) }, 1)
This schedules a callback to log
1
after a 1 millisecond delay.Third
setTimeout
:setTimeout(() => { console.log(0) }, 0)
This schedules a callback to log
0
after a 0 millisecond delay.
Now, here's why the result may vary on different browsers:
Minimum delay: Most browsers have a minimum delay that they enforce for
setTimeout
, even if you specify a shorter time. This is typically around 4ms, but it can vary.Timer resolution: The actual resolution of timers can vary between browsers and operating systems.
Task queue and event loop: All these
setTimeout
callbacks are placed in the task queue, and their exact order of execution can be influenced by how the browser handles the event loop and task queue.Browser optimizations: Some browsers might optimize the handling of very short timeouts differently.
Given these factors, there are a few possible outcomes:
If the browser respects the exact times (which is rare), the output would be:
0 1 2
If the browser treats all these as "minimum delay" timeouts, they might execute in the order they were scheduled:
2 1 0
Some browsers might group these together and execute them in a somewhat unpredictable order.
The key takeaways from this quiz are:
setTimeout
with very small delays (0-4ms) often doesn't behave exactly as the numbers suggest.- There's no guarantee of precise timing for very short delays with
setTimeout
. - The order of execution for nearly simultaneous timeouts can be unpredictable and may vary between browsers.
- When working with timeouts, it's generally safer to use larger delays if you need more predictable behavior.
This quiz highlights the importance of understanding the limitations and potential inconsistencies of JavaScript's timing functions across different environments.
81. setTimeout II
81.https://bigfrontend.dev/quiz/setTimeout-2
let num
for (let i = 0; i < 5; i++) {
num = i
setTimeout(() => {
console.log(num)
}, 100)
}
Solution: This code demonstrates the interaction between loop iterations, variable scoping, and asynchronous functions in JavaScript. Let's break it down:
let num
declares a variable outside the loop.The
for
loop runs 5 times (0 to 4).In each iteration:
num = i
assigns the current value ofi
tonum
.setTimeout()
schedules a function to run after 100 milliseconds.
The loop completes almost instantly, much faster than the 100ms delay.
After the loop,
num
has the value 4 (the last value ofi
).After 100ms, all the scheduled
console.log(num)
calls execute.
The key point here is that all the setTimeout callbacks reference the same num
variable. By the time these callbacks execute, the loop has long finished, and num
has its final value of 4.
Therefore, the output will be:
4
4
4
4
4
This happens because:
- JavaScript's event loop: The setTimeout callbacks are queued to run after the main script execution.
- Closure: Each callback closes over the same
num
variable, not a copy of its value at each iteration. - Variable mutation:
num
is repeatedly reassigned in the loop, ending with the value 4.
If you wanted to log 0, 1, 2, 3, 4 instead, you could either:
- Use
let
in the loop:for (let i = 0; i < 5; i++)
and usei
directly in the setTimeout callback. - Use an IIFE (Immediately Invoked Function Expression) to create a new scope for each iteration.
This example highlights the importance of understanding variable scope, closures, and the asynchronous nature of JavaScript when working with loops and timers.
86. setTimeout III
86.https://bigfrontend.dev/quiz/setTimeout-III
let func = () => {
console.log(1)
}
setTimeout(() => {
func = () => {
console.log(2)
}
}, 0)
setTimeout(func, 100)
Solution: This code demonstrates the interaction between function reassignment, closures, and the JavaScript event loop. Let's break it down step by step:
Initial function definition:
let func = () => { console.log(1) }
This defines
func
as an arrow function that logs 1.First setTimeout:
setTimeout(() => { func = () => { console.log(2) } }, 0)
This schedules a function to run as soon as possible (but after the current script finishes). This function will reassign
func
to a new arrow function that logs 2.Second setTimeout:
setTimeout(func, 100)
This schedules the current value of
func
to be called after 100 milliseconds.
Now, let's consider the sequence of events:
- The initial
func
is defined. - Both setTimeouts are scheduled.
- The main script finishes executing.
- The first setTimeout callback runs (almost immediately), changing
func
to log 2. - After 100ms, the second setTimeout callback runs, executing the function that
func
referenced when the setTimeout was created.
The key point here is that when setTimeout(func, 100)
is called, it doesn't schedule "whatever func
will be in 100ms". Instead, it schedules "the function that func
is right now".
Therefore, even though func
is reassigned to a new function that logs 2, the originally scheduled function (which logs 1) is what actually runs after 100ms.
The output will be:
1
This example illustrates:
- How closures work in JavaScript (the setTimeout captures the current value of
func
, not a reference to the variable itself). - The asynchronous nature of setTimeout and the JavaScript event loop.
- The difference between reassigning a variable and mutating an object (if
func
were an object and we changed one of its properties, the scheduled setTimeout would see that change).
It's a tricky example that tests understanding of JavaScript's handling of function references and asynchronous operations.
128. setTimeout IV
128.https://bigfrontend.dev/quiz/settimeout-iv
function block(duration = 1000) {
const start = Date.now()
while (Date.now() - start < duration) {
window.timestamp = Date.now()
}
}
function a() {
console.log(1)
block()
setTimeout(() => console.log(2), 0)
setTimeout(() => console.log(3), 1)
}
function b() {
console.log(4)
}
console.log(5)
setTimeout(a, 0)
setTimeout(b, 500)
Solution: Let's break down this code and explain its behavior:
function block(duration = 1000) {
const start = Date.now()
while (Date.now() - start < duration) {
window.timestamp = Date.now()
}
}
function a() {
console.log(1)
block()
setTimeout(() => console.log(2), 0)
setTimeout(() => console.log(3), 1)
}
function b() {
console.log(4)
}
console.log(5)
setTimeout(a, 0)
setTimeout(b, 500)
Here's what happens:
console.log(5)
executes immediately, printing 5.setTimeout(a, 0)
schedules functiona
to run as soon as possible (but after the current script finishes).setTimeout(b, 500)
schedules functionb
to run after 500ms.The main script finishes, and the event loop starts processing the task queue.
Function
a
runs (almost immediately, as it was scheduled with a 0ms delay):- It prints 1.
- It calls
block()
, which runs a tight loop for 1000ms (1 second), blocking the main thread. - It schedules two more timeouts (for 2 and 3).
After
a
finishes (which is about 1000ms after it started due to the blocking call):- The timeout for
b
has already passed its 500ms delay, sob
runs next, printing 4. - Then the two timeouts from
a
run, printing 2 and 3 in quick succession.
- The timeout for
The output will be:
5
1
4
2
3
Key points to understand:
JavaScript is single-threaded. The
block
function demonstrates a "busy wait" that occupies the main thread, preventing any other JavaScript from running during that time.setTimeout
schedules a task to run after a delay, but it doesn't guarantee exact timing. If the main thread is busy, the scheduled task will have to wait.Tasks scheduled with
setTimeout(fn, 0)
don't run immediately; they're queued to run as soon as possible after the current script finishes.The
block
function ina
delays everything else from running for about 1 second.Even though
b
was scheduled to run after 500ms, it doesn't actually run until aftera
finishes blocking the main thread.The timeouts scheduled within
a
run afterb
, even though they were scheduled earlier, because they were scheduled after the blocking operation.
This code demonstrates how blocking operations can affect the timing of asynchronous tasks in JavaScript, and how the event loop and task queue work together to schedule and execute code.
Browsers may have different strategies for handling tasks scheduled with setTimeout and other asynchronous operations, leading to variations in the order of execution
In Chrome -> (5,1,2,4,3)
In Safari -> (5,1,4,2,3)
14.Object
57. non-writable
57.https://bigfrontend.dev/quiz/inherit-writable-flag
const a = {}
Object.defineProperty(a, 'foo1', {
value: 1,
})
const b = Object.create(a)
b.foo2 = 1
console.log(b.foo1)
console.log(b.foo2)
b.foo1 = 2
b.foo2 = 2
console.log(b.foo1)
console.log(b.foo2)
Solution: This code demonstrates object creation, property definition, and property inheritance in JavaScript. Let's break it down step by step:
const a = {}
- Creates an empty object
a
.
- Creates an empty object
Object.defineProperty(a, 'foo1', { value: 1 })
- Defines a property 'foo1' on object
a
with a value of 1. - By default, properties defined this way are not writable, not enumerable, and not configurable.
- Defines a property 'foo1' on object
const b = Object.create(a)
- Creates a new object
b
witha
as its prototype. b
inherits properties froma
.
- Creates a new object
b.foo2 = 1
- Adds a new property 'foo2' to
b
with a value of 1. - This property is created directly on
b
, not on its prototype.
- Adds a new property 'foo2' to
console.log(b.foo1)
- Outputs: 1
b
doesn't have its own 'foo1', so it looks up the prototype chain and finds it ona
.
console.log(b.foo2)
- Outputs: 1
- This is the 'foo2' property directly on
b
.
b.foo1 = 2
- Attempts to set 'foo1' on
b
to 2. - This fails silently because 'foo1' on
a
is not writable. - It doesn't create a new 'foo1' on
b
.
- Attempts to set 'foo1' on
b.foo2 = 2
- Sets 'foo2' on
b
to 2. - This succeeds because 'foo2' is a regular property on
b
.
- Sets 'foo2' on
console.log(b.foo1)
- Outputs: 1
- Still returns 1 from
a.foo1
because the attempt to change it failed.
console.log(b.foo2)
- Outputs: 2
- Returns the new value of 'foo2' on
b
.
Key points:
Object.defineProperty()
creates non-writable properties by default.Object.create()
sets up prototype inheritance.- Properties are looked up in the prototype chain if not found on the object itself.
- Attempting to modify a non-writable inherited property doesn't throw an error, but silently fails.
- Properties created with direct assignment (like
b.foo2 = 1
) are writable by default.
The output of this code will be:
1
1
1
2
This example illustrates important concepts in JavaScript's object-oriented programming model, including prototypal inheritance and property attributes.
58. inherit getter setter
58.https://bigfrontend.dev/quiz/inherit-getter-setter
let val = 0
class A {
set foo(_val) {
val = _val
}
get foo() {
return val
}
}
class B extends A {}
class C extends A {
get foo() {
return val
}
}
const b = new B()
console.log(b.foo)
b.foo = 1
console.log(b.foo)
const c = new C()
console.log(c.foo)
c.foo = 2
console.log(c.foo)
console.log(b.foo)
Solution: This code demonstrates the behavior of getters and setters in JavaScript classes, along with inheritance. Let's break it down step by step:
let val = 0
- Initializes a variable
val
to 0. This will be used by the classes.
- Initializes a variable
Class A:
class A { set foo(_val) { val = _val } get foo() { return val } }
- Defines a class with a getter and setter for
foo
. - The setter updates the global
val
. - The getter returns the global
val
.
- Defines a class with a getter and setter for
class B extends A { }
- B inherits from A without modifying anything.
Class C:
class C extends A { get foo() { return val } }
- C inherits from A but overrides the getter for
foo
. - It still returns the global
val
, effectively doing the same thing as A's getter. - When we override the
get
method, it appears that theset
method must also be overridden, otherwise undefined is returned See this
- C inherits from A but overrides the getter for
const b = new B()
- Creates an instance of B.
console.log(b.foo)
- Outputs: 0 (initial value of
val
)
- Outputs: 0 (initial value of
b.foo = 1
- Calls the inherited setter, setting
val
to 1.
- Calls the inherited setter, setting
console.log(b.foo)
- Outputs: 1 (new value of
val
)
- Outputs: 1 (new value of
const c = new C()
- Creates an instance of C.
console.log(c.foo)
- Outputs: 1 (current value of
val
)
- Outputs: 1 (current value of
c.foo = 2
- Because C only overrode the getter, not the setter, this will not update val as there is no setter defined
console.log(c.foo)
- Outputs: 1 (old value of
val
)
- Outputs: 1 (old value of
console.log(b.foo)
- Outputs: 1 (b.foo also reflects the old value of
val
)
- Outputs: 1 (b.foo also reflects the old value of
Key points:
- All instances share the same
val
variable. - B inherits both getter and setter from A.
- C inherits the setter from A but has its own getter (which does the same thing as A's getter).
- Changing
foo
on either b or c affects the sharedval
.
The output will be:
0
1
1
1
1
This quiz tests understanding of:
- Class inheritance in JavaScript
- Getter and setter methods
- How inherited properties work
- The difference between overriding a getter and not overriding it.
- Because C only overrode the getter, not the setter, this will not update val as there is no setter defined
- The concept of shared state across instances when using a global variable
59. override setter
59.https://bigfrontend.dev/quiz/override-setter
class A {
val = 1
get foo() {
return this.val
}
}
class B {
val = 2
set foo(val) {
this.val = val
}
}
const a = new A()
const b = new B()
console.log(a.foo)
console.log(b.foo)
b.foo = 3
console.log(b.val)
console.log(b.foo)
Solution: This code demonstrates the use of getter and setter methods in JavaScript classes, as well as some nuances of property access. Let's break it down step by step:
Class A:
class A { val = 1 get foo() { return this.val } }
- Class A has a property
val
initialized to 1. - It has a getter method for
foo
that returns the value ofval
.
- Class A has a property
Class B:
class B { val = 2 set foo(val) { this.val = val } }
- Class B has a property
val
initialized to 2. - It has a setter method for
foo
that sets the value ofval
.
- Class B has a property
const a = new A()
- Creates an instance of A.
const b = new B()
- Creates an instance of B.
console.log(a.foo)
- Outputs: 1
- This calls the getter method of
foo
in A, which returnsthis.val
(1).
console.log(b.foo)
- Outputs: undefined
- B only has a setter for
foo
, not a getter. When you try to accessfoo
, it returnsundefined
.
b.foo = 3
- This calls the setter method of
foo
in B, settingthis.val
to 3.
- This calls the setter method of
console.log(b.val)
- Outputs: 3
- This directly accesses the
val
property of b, which was set to 3 by the setter.
console.log(b.foo)
- Outputs: undefined
- Even though we set a value using the setter, trying to access
foo
still returnsundefined
because there's no getter defined.
Key points:
- Getters are used to read values, setters are used to write values.
- If a property only has a getter, it's read-only. If it only has a setter, you can set its value but reading it directly will return
undefined
. - Getters and setters can operate on other properties of the object (like
val
in this case). - The
val
property can be accessed directly, bypassing the getter/setter.
The output will be:
1
undefined
3
undefined
This quiz tests understanding of:
- How getters and setters work in JavaScript classes
- The behavior when a property has only a getter or only a setter
- The difference between accessing a property directly vs. through a getter/setter
63. in
63.https://bigfrontend.dev/quiz/in-coercion
const obj = {
foo: 'bar',
}
console.log('foo' in obj)
console.log(['foo'] in obj)
Solution: This code demonstrates the usage of the in
operator in JavaScript and how it behaves with different types of operands. Let's break it down:
const obj = { foo: 'bar' }
- This creates an object
obj
with a propertyfoo
that has the value'bar'
.
- This creates an object
console.log('foo' in obj)
- The
in
operator checks if the property'foo'
exists in the objectobj
. - This will output
true
because'foo'
is indeed a property ofobj
.
- The
console.log(['foo'] in obj)
- This line is tricky. The left operand of
in
is an array['foo']
. - When the
in
operator is used with a non-string left operand, JavaScript automatically converts it to a string. ['foo'].toString()
evaluates to'foo'
.- So this line is equivalent to
console.log('foo' in obj)
. - This will also output
true
.
- This line is tricky. The left operand of
The output of this code will be:
true
true
Key points:
- The
in
operator checks for the existence of a property in an object or its prototype chain. - The left operand of
in
should typically be a string or a symbol representing the property name. - If the left operand is not a string or symbol, JavaScript will convert it to a string.
- Arrays are converted to strings by joining their elements with commas. However, in this case, there's only one element, so
['foo']
becomes'foo'
when converted to a string. - This behavior can be counter-intuitive and is a good reason to always use string literals or variables containing strings with the
in
operator to avoid unexpected type coercion.
This quiz tests understanding of the in
operator and JavaScript's type coercion rules, particularly how non-string values are converted to strings in certain contexts.
108. property key
108.https://bigfrontend.dev/quiz/property-key
const obj = {}
const a = { name: 'a' }
const b = { name: 'b' }
obj[a] = { ...a }
obj[b] = { ...b }
console.log(obj[a].name)
console.log(obj[b].name)
Solution: Let's break down this code and explain what's happening:
const obj = {}
const a = { name: 'a' }
const b = { name: 'b' }
obj[a] = { ...a }
obj[b] = { ...b }
console.log(obj[a].name)
console.log(obj[b].name)
This code demonstrates some interesting behavior when using objects as keys in other objects. Let's go through it step-by-step:
We create an empty object
obj
.We create two objects
a
andb
, each with aname
property.We use
a
as a key inobj
and set its value to a new object created by spreadinga
.We do the same with
b
.We then try to log
obj[a].name
andobj[b].name
.
Now, here's the key point: When you use an object as a key in another object, JavaScript automatically converts that object to a string. The default string representation of an object is "[object Object]".
So, what's actually happening is:
obj[a]
is equivalent toobj["[object Object]"]
obj[b]
is also equivalent toobj["[object Object]"]
This means that obj[a]
and obj[b]
are referring to the same property of obj
. The second assignment (obj[b] = {...b}
) overwrites the first one.
Therefore, obj
ends up looking like this:
{
"[object Object]": { name: "b" }
}
When we log obj[a].name
and obj[b].name
, we're actually accessing the same object both times. This object is { name: "b" }
, which was the last one assigned.
So, the output will be:
b
b
This code demonstrates several important concepts in JavaScript:
- Object-to-string conversion: When used as a key, objects are converted to strings.
- The default string representation of objects:
"[object Object]"
. - Property assignment and overwriting in objects.
- The spread operator (
...
) for creating shallow copies of objects. - The potential pitfalls of using objects as keys in other objects.
It's a tricky example that highlights some of the nuances of working with objects in JavaScript, especially when it comes to using them as keys in other objects. In practice, if you need to use objects as keys, it's often better to use a Map
instead of a regular object, as Map
can use objects as keys without converting them to strings.
126. Object.keys()
126.https://bigfrontend.dev/quiz/object-keys
console.log([...Object.keys({ a: 1, b: 2 }), ...Object.keys({ b: 2, a: 1 })])
Solution:
Let's break down this code step by step:
console.log([...Object.keys({ a: 1, b: 2 }), ...Object.keys({ b: 2, a: 1 })])
Here's what's happening:
Object.keys({a: 1, b: 2})
:- This creates an array of the enumerable property names of the object
{a: 1, b: 2}
. - The result is
['a', 'b']
.
- This creates an array of the enumerable property names of the object
Object.keys({b: 2, a: 1})
:- This creates an array of the enumerable property names of the object
{b: 2, a: 1}
. - The result is
['b', 'a']
.
- This creates an array of the enumerable property names of the object
The spread operator
...
is used on both of these arrays inside a new array literal[ ]
.- This spreads the elements of both arrays into the new array.
The resulting array is then logged to the console.
The output of this code will be:
['a', 'b', 'b', 'a']
Key points to understand:
Object.keys()
returns an array of a given object's own enumerable property names, in the same order as a normal loop would.The order of keys in JavaScript objects is not guaranteed to be the same as the order in which they were defined, except for certain cases:
- Integer keys are sorted in ascending order.
- String keys are inserted in the order they were added if the object was created as an empty object literal.
- In modern JavaScript engines (ES2015+), the order of string keys is generally preserved as they were defined in an object literal.
The spread operator
...
is used to expand an array into individual elements.In this case, even though the objects have the same keys in different orders,
Object.keys()
returns them in the order they were defined in each object literal, resulting in['a', 'b']
for the first object and['b', 'a']
for the second.When these are spread into a new array, we get all four elements in the order they were returned by
Object.keys()
for each object.
This example demonstrates how Object.keys()
works with different object literals, and how the spread operator can be used to combine arrays. It also shows that the order of keys in an object can affect the order of the array returned by Object.keys()
.
15.String
85. String.raw()
85.https://bigfrontend.dev/quiz/String-raw
console.log(String.raw`BFE\n.${'dev'}`)
console.log(String.raw({ raw: 'BFE' }, 'd', 'e', 'v'))
// when you enter your input in text box below
// keep in mind it is treated as raw string and will be escaped
Solution: This code demonstrates the use of the String.raw
tag function in JavaScript. Let's break it down:
First
console.log
:console.log(String.raw`BFE\n.${'dev'}`)
- This uses
String.raw
as a tagged template literal. \n
is treated as a raw string, not as a newline character.${'dev'}
is a template literal placeholder, which is evaluated.
Output:
BFE\n.dev
- This uses
Second
console.log
:console.log(String.raw({ raw: 'BFE' }, 'd', 'e', 'v'))
- This uses
String.raw
as a function, not a tagged template. - The first argument is an object with a
raw
property containing the string 'BFE'. - Subsequent arguments ('d', 'e', 'v') are interpolated between each character of 'BFE'.
Output:
BdFevE
- This uses
Key points:
String.raw
as a tagged template:- It returns a string where backslashes are treated as literal characters.
- Template literal substitutions
(${...})
are still processed.
String.raw
as a function:- The first argument should be an object with a
raw
property containing a string. - Subsequent arguments are interpolated between each character of the
raw
string.
- The first argument should be an object with a
In both cases,
String.raw
prevents escape sequences (like\n
) from being interpreted.The comment about input being treated as a raw string and escaped is relevant if this quiz has an interactive component where users can input their own strings.
This quiz tests understanding of:
- Tagged template literals
- The
String.raw
function and its different usage patterns - How JavaScript handles raw strings and escape sequences
It's a good demonstration of some of the more advanced string manipulation features in modern JavaScript.
93. string
93.https://bigfrontend.dev/quiz/string
let a = 'bfe.dev'
a[0] = 'c'
console.log(a)
Solution: This code demonstrates an important characteristic of strings in JavaScript. Let's break it down:
let a = 'bfe.dev'
- This creates a string variable
a
with the value 'bfe.dev'.
- This creates a string variable
a[0] = 'c'
- This attempts to change the first character of the string from 'b' to 'c'.
console.log(a)
- This prints the value of
a
to the console.
- This prints the value of
The output will be:
bfe.dev
Key points:
Strings in JavaScript are immutable. This means that once a string is created, its individual characters cannot be changed.
When you try to modify a character in a string using bracket notation (like
a[0] = 'c'
), JavaScript will not throw an error in non-strict mode. Instead, it will silently fail to make the change.In strict mode, attempting to modify a string character would throw a TypeError.
The original string remains unchanged after the attempted modification.
If you need to modify a string, you have to create a new string. For example:
a = 'c' + a.slice(1)
This would create a new string with 'c' as the first character and the rest of the original string.
This behavior is different from some other programming languages where strings are mutable and can be changed character by character. In JavaScript, the immutability of strings is a fundamental characteristic that affects how string manipulation is performed.
Understanding this behavior is crucial for avoiding bugs when working with strings in JavaScript. It's also a common topic in JavaScript interviews and quizzes.
94. emoji
94.https://bigfrontend.dev/quiz/emoji
console.log('👍'.length)
Solution: Certainly! This code demonstrates an interesting aspect of how JavaScript handles Unicode characters, particularly emoji. Let's break it down:
console.log('👍'.length)
The output of this code will be:
2
Here's why:
The thumbs-up emoji '👍' is a single visual character, but in JavaScript's internal representation, it's actually composed of two code units.
JavaScript uses UTF-16 encoding for strings. In UTF-16, characters are represented by one or two 16-bit code units.
The thumbs-up emoji '👍' has the Unicode code point U+1F44D, which is outside the Basic Multilingual Plane (BMP).
Characters outside the BMP are represented in UTF-16 using two 16-bit code units, known as a surrogate pair.
The
length
property of a string in JavaScript returns the number of 16-bit code units in the string, not the number of visual characters or Unicode code points.
Key points:
- Many emojis and other characters from non-BMP Unicode planes will have a
length
of 2 or more in JavaScript. - This behavior can lead to unexpected results when working with strings containing emojis or other complex Unicode characters.
- If you need to work with the actual number of visual characters or Unicode code points, you might need to use more advanced string handling techniques or libraries.
For example, to get the correct length of a string with emojis, you could use:
console.log([...'👍'].length) // Output: 1
This spread operator technique splits the string into an array of its actual characters, giving the correct visual length.
Understanding this behavior is important for proper string manipulation, especially in applications that deal with international text or emojis.
16.Proxy
Creating a Simple Proxy Let's jump in and see how to create a Proxy. We'll start with a really basic example, in case you haven't seen Proxies before.
type MessageObject = {
message: string
}
let target: MessageObject = {
message: 'Hello, world!',
}
let handler: ProxyHandler<MessageObject> = {
get: (obj, prop) => {
return `Property ${String(prop)} is: ${obj[prop]}`
},
}
let proxy: MessageObject = new Proxy(target, handler)
console.log(proxy.message) // Output: Property message is: Hello, world!
- Auto-populating Properties
type LazyProfile = {
firstName: string
lastName: string
fullName?: string
}
let lazyProfileHandler = {
get: (target: LazyProfile, property: keyof LazyProfile) => {
if (property === 'fullName' && !target[property]) {
target[property] = `${target.firstName} ${target.lastName}`
}
return target[property]
},
}
let profile: LazyProfile = new Proxy({ firstName: 'John', lastName: 'Doe' }, lazyProfileHandler)
console.log(profile.fullName) // Output: John Doe
- Operation Counting
type Counter = {
[key: string]: any
_getCount: number
}
let countHandler = {
get: (target: Counter, property: keyof Counter) => {
if (property === '_getCount') {
return target[property]
}
target._getCount++
return target[property]
},
}
let counter: Counter = new Proxy({ a: 1, b: 2, _getCount: 0 }, countHandler)
counter.a
counter.b
console.log(counter._getCount) // Output: 2
- Immutable Objects
function createImmutable<T extends object>(obj: T): T {
return new Proxy(obj, {
set: () => {
throw new Error('This object is immutable')
},
})
}
const immutableObject = createImmutable({ name: 'Jane', age: 25 })
// immutableObject.age = 26; // Throws error
- Method Chaining and Fluent Interfaces
type FluentPerson = {
setName(name: string): FluentPerson
setAge(age: number): FluentPerson
save(): void
}
function FluentPerson(): FluentPerson {
let person: any = {}
return new Proxy(
{},
{
get: (target, property) => {
if (property === 'save') {
return () => {
console.log(person)
}
}
return (value: any) => {
person[property] = value
return target
}
},
}
) as FluentPerson
}
const person = FluentPerson()
person.setName('Alice').setAge(30).save() // Output: { setName: 'Alice', setAge: 30 }
- Smart Caching
function smartCache<T extends object>(obj: T, fetcher: (key: keyof T) => any): T {
const cache: Partial<T> = {}
return new Proxy(obj, {
get: (target, property: keyof T) => {
if (!cache[property]) {
cache[property] = fetcher(property)
}
return cache[property]
},
})
}
const userData = smartCache({ userId: 1 }, (prop) => {
console.log(`Fetching data for ${String(prop)}`)
return { name: 'Bob' } // Simulated fetch
})
console.log(userData.userId) // Output: Fetching data for userId, then returns { name: "Bob" }
- Dynamic Property Validation
let user = {
age: 25,
}
let validator = {
set: (obj, prop, value) => {
if (prop === 'age' && (typeof value !== 'number' || value < 18)) {
throw new Error('User must be at least 18 years old.')
}
obj[prop] = value
return true // Indicate success
},
}
let userProxy = new Proxy(user, validator)
userProxy.age = 30 // Works fine
console.log(userProxy.age) // Output: 30
// userProxy.age = 'thirty'; // Throws error
// userProxy.age = 17; // Throws error
- Watching for Changes
function onChange(obj, onChange) {
const handler = {
set: (target, property, value, receiver) => {
onChange(`Property ${String(property)} changed to ${value}`)
return Reflect.set(target, property, value, receiver)
},
}
return new Proxy(obj, handler)
}
const person = { name: 'John', age: 30 }
const watchedPerson = onChange(person, console.log)
watchedPerson.age = 31 // Console: Property age changed to 31
- Tracing: Logging function calls
function trace(originalFunction) {
return new Proxy(originalFunction, {
apply(target, thisArg, args) {
console.log('Calling function:', target.name, 'with arguments:', args);
// Important - don't forget the return statement or else the function's
// return value is lost!
return target.apply(thisArg, args);
}
});
}
- Data binding: Syncing multiple objects
<input type="text" id="username" />
const inputState = {
id: 'username',
value: ''
}
const input = document.querySelector('#username')
const handler = {
set: function(target, key, value) {
if (target.id && key === 'username') {
target[key] = value;
document.querySelector(`#${target.id}`).value = value;
return true
}
return false
}
}
const proxy = new Proxy(inputState, handler)
proxy.value = 'John Doe'
console.log(proxy.value, input.value)
// 'John Doe' will be printed for both
Reference: https://dev.to/mattlewandowski93/7-use-cases-for-javascript-proxies-3b29
80. Proxy I
80.https://bigfrontend.dev/quiz/proxy-i
const obj = new Map()
const map = new Map()
obj.foo = 1
map.set('foo', 2)
console.log(obj.foo)
console.log(map.get('foo'))
const proxyObj = new Proxy(obj, {})
const proxyMap = new Proxy(map, {})
console.log(proxyObj.foo)
console.log(proxyMap.get('foo'))
Solution: Certainly! This code demonstrates the usage of regular objects, Map objects, and Proxies in JavaScript. Let's break it down step by step:
Creating objects:
const obj = new Map() const map = new Map()
obj
is created as a Map but is then used as a regular object.map
is created as a Map and used as a Map.
Setting properties/values:
obj.foo = 1 map.set('foo', 2)
- For
obj
, we're setting a propertyfoo
directly, which works because JavaScript objects can have properties added dynamically. - For
map
, we're using theset
method to add a key-value pair.
- For
Logging values:
console.log(obj.foo) // Output: 1 console.log(map.get('foo')) // Output: 2
- We access
obj.foo
directly as a property. - We use
map.get('foo')
to retrieve the value from the Map.
- We access
Creating Proxies:
const proxyObj = new Proxy(obj, {}) const proxyMap = new Proxy(map, {})
- We create Proxies for both
obj
andmap
with empty handler objects. - These Proxies will behave exactly like the original objects because no custom behavior is defined.
- We create Proxies for both
Logging values through Proxies:
console.log(proxyObj.foo) // Output: 1 console.log(proxyMap.get('foo')) // Output: undefined which is the unexpected behavior.
We access the values through the Proxies, which pass through to the original objects.
The reason for
proxyMap.get('foo')
returnsundefined
:
- When we create a Proxy for a Map object, the Proxy doesn't automatically handle the special methods of Map like
get
,set
, etc. - When we call
proxyMap.get('foo')
, it's looking for a propertyget
on the Proxy object itself, not calling theget
method of the underlying Map. - Since there's no
get
property on the Proxy (or its prototype chain), it returnsundefined
.
To fix this and make the Proxy work correctly with Map methods, we need to use a handler with a
get
trap:const proxyMap = new Proxy(map, { get(target, prop) { if (typeof target[prop] === 'function') { return target[prop].bind(target) } return target[prop] }, })
With this handler,
proxyMap.get('foo')
would correctly return 2.
The output will be:
1
2
1
Error
Key points:
obj
, despite being created as a Map, is used as a regular object. This works because in JavaScript, you can add properties to any object.map
is used correctly as a Map object, usingset()
andget()
methods.- Proxies with empty handlers behave identically to their target objects.
- This code demonstrates the flexibility of JavaScript objects and the transparent nature of basic Proxies.
This example highlights the differences between regular objects and Map objects, as well as showing how Proxies can be used to wrap objects without changing their behavior (when using an empty handler). It's a good demonstration of different ways to store and access key-value pairs in JavaScript.
82. Proxy II
82.https://bigfrontend.dev/quiz/Proxy-II
class Dev {
#name
constructor(name) {
this.#name = name
}
get name() {
return this.#name
}
}
const dev = new Dev('BFE')
console.log(dev.name)
const proxyDev = new Proxy(dev, {})
console.log(proxyDev.name)
Solution: This code demonstrates the use of private class fields, getters, and Proxies in JavaScript. Let's break it down:
Class Definition:
class Dev { #name constructor(name) { this.#name = name } get name() { return this.#name } }
#name
is a private field, only accessible within the class.- The constructor initializes this private field.
- A getter
name
is defined to access the private field.
Creating an instance:
const dev = new Dev('BFE')
- This creates a new
Dev
instance with the name "BFE".
- This creates a new
Logging the name:
console.log(dev.name)
- This calls the getter
name
, which returns the private#name
. - Output:
BFE
- This calls the getter
Creating a Proxy:
const proxyDev = new Proxy(dev, {})
- This creates a Proxy for the
dev
object with an empty handler.
- This creates a Proxy for the
Logging the name through the Proxy:
console.log(proxyDev.name)
- This attempts to access the
name
property through the Proxy. - Important: This will throw an error.
- This attempts to access the
The output will be:
BFE
Uncaught TypeError: Cannot read private member #name from an object whose class did not declare it
Key points:
- Private fields (
#name
) are not accessible outside the class, even through getters. - The getter works fine when called directly on the instance (
dev.name
). - When using a Proxy, accessing private fields (even through getters) throws an error.
The error occurs because:
- JavaScript's private fields are truly private and are not part of the object's property set.
- Proxies work by intercepting property access, but they can't intercept access to private fields.
- When the Proxy tries to forward the
name
access to the target object, it can't do so for the private field.
To fix this and make the Proxy work, you would need to explicitly handle the getter in the Proxy handler:
const proxyDev = new Proxy(dev, {
get(target, prop, receiver) {
if (prop === 'name') {
return target.name
}
return Reflect.get(target, prop, receiver)
},
})
This quiz highlights the interaction between modern JavaScript features (private fields) and more established ones (Proxies), showing that they don't always work together seamlessly without additional handling.
17.Other
52. requestAnimationFrame
52.https://bigfrontend.dev/quiz/requestanimationframe
console.log(1)
setTimeout(() => {
console.log(2)
}, 100)
requestAnimationFrame(() => {
console.log(3)
})
requestAnimationFrame(() => {
console.log(4)
setTimeout(() => {
console.log(5)
}, 10)
})
const end = Date.now() + 200
while (Date.now() < end) {}
console.log(6)
Solution: This code demonstrates the interaction between different types of asynchronous operations in JavaScript, including setTimeout, requestAnimationFrame, and a blocking while loop. Let's break it down:
console.log(1)
- This runs immediately, outputting 1.
setTimeout(() => { console.log(2) }, 100)
- This schedules a callback to run after 100ms.
First
requestAnimationFrame(() => { console.log(3) })
- This schedules a callback to run before the next repaint.
Second
requestAnimationFrame(() => { ... })
- This schedules another callback to run before the next repaint.
- Inside this callback, there's another setTimeout for 10ms.
const end = Date.now() + 200; while (Date.now() < end) { }
- This creates a blocking loop that runs for 200ms.
console.log(6)
- This runs immediately after the blocking loop.
The order of execution will be:
console.log(1)
runs immediately.- The blocking while loop runs for 200ms.
console.log(6)
runs after the blocking loop.- The first
requestAnimationFrame
callback runs, logging 3. - The second
requestAnimationFrame
callback runs, logging 4. - The 100ms setTimeout callback runs, logging 2.
- The 10ms setTimeout (from inside the second rAF) runs, logging 5.
So, the final output order will likely be:
1
6
3
4
2
5
Key points:
- Synchronous code (including blocking loops) runs before any asynchronous callbacks.
requestAnimationFrame
callbacks typically run before the next repaint, which usually occurs every 16.7ms (60 fps).- setTimeout callbacks are pushed to the task queue and run after the current execution context is empty.
- The blocking while loop delays all other asynchronous operations.
- The exact timing can vary slightly depending on the browser and system performance.
This quiz tests understanding of JavaScript's event loop, the difference between microtasks and macrotasks, and how blocking operations affect the execution of asynchronous code. It's a complex example that demonstrates the intricacies of JavaScript's asynchronous behavior.
66. comma
66.https://bigfrontend.dev/quiz/comma
var obj = {
a: 'BFE',
b: 'dev',
func:
(function foo() {
return this.a
},
function bar() {
return this.b
}),
}
console.log(obj.func())
Solution: This code demonstrates some interesting aspects of JavaScript, including object literals, function expressions, and the behavior of this
. Let's break it down:
Object Definition:
var obj = { a: 'BFE', b: 'dev', func: (function foo() { return this.a }, function bar() { return this.b }), }
obj
is an object with propertiesa
,b
, andfunc
.- The
func
property is assigned the result of a comma-separated expression.
The
func
property:- It's a comma-separated expression containing two function expressions.
- In JavaScript, a comma-separated expression evaluates all its parts but returns only the last part.
- So,
func
will actually be assigned only the second function:function bar(){ return this.b; }
console.log(obj.func())
- This calls the function stored in
obj.func
. - When a function is called as a method of an object,
this
inside the function refers to that object. - In this case,
this
refers toobj
.
- This calls the function stored in
So, when obj.func()
is called:
- It's actually calling the
bar
function. this.b
refers toobj.b
, which is "dev".
Therefore, the output will be:
"dev"
Key points:
- In a comma-separated expression, only the last part is returned/assigned.
- When a function is called as a method (
obj.func()
),this
inside the function refers to the object it's called on. - The first function (
foo
) in the comma expression is effectively discarded. - This code might be confusing because it looks like
func
might be an array of functions, but it's not - it's just a single function due to the comma operator's behavior.
This quiz tests understanding of:
- The comma operator in JavaScript
- How
this
is determined in method calls - Function expressions in object literals
It's a tricky example that combines several JavaScript concepts in a way that might not be immediately obvious.
124. comma 2
124.https://bigfrontend.dev/quiz/comma-2
const a = (1, 2, 3)
console.log(a)
Solution: Let's break down this code:
const a = (1, 2, 3)
console.log(a)
This code demonstrates the use of the comma operator in JavaScript. Here's what's happening:
const a = (1,2,3)
- This line uses the comma operator, which is enclosed in parentheses.
- The comma operator evaluates each of its operands (from left to right) and returns the value of the last operand.
- In this case, it evaluates 1, then 2, and finally 3.
- The result of the entire expression is the value of the last operand, which is 3.
- So,
a
is assigned the value 3.
console.log(a)
- This simply prints the value of
a
to the console.
- This simply prints the value of
The output of this code will be:
3
Key points to understand:
The comma operator
,
in JavaScript evaluates multiple expressions, separated by commas, and returns the value of the last expression.When used in an assignment, only the final value is assigned to the variable.
The parentheses
()
are necessary here. Without them, this would be interpreted as three separate statements, anda
would be assigned the value 1.This usage of the comma operator is not common in practice and can lead to confusing code. It's generally better to write clearer, more explicit code.
The comma operator has very low precedence, lower than assignment, which is why the parentheses are needed in this case.
While this example demonstrates a feature of JavaScript, it's worth noting that using the comma operator in this way is generally not considered good practice in real-world code, as it can make the code harder to read and understand.
78. RegExp
78.https://bigfrontend.dev/quiz/RegExp
const arr = ['a', 'b', 'c', '1']
const regExp = /^[a-z]$/gi
const chars = arr.filter((elem) => regExp.test(elem))
console.log(chars)
Solution: Intuitively, it seems that output should be ["a","b","c"] since these items match the regular expression.
But when a regex has the global flag set, test() will advance the lastIndex of the regex. lastIndex is a property of RegExp that specifies the index at which to start the next match.
Basically,
As long as test() returns true, lastIndex will not reset—even when testing a different string! When test() returns false, the calling regex's lastIndex property will reset to 0. /^[a-z]$/gi 👉🏻 Any character from character set [a-z], g means global flag for multiple matches, i means character insensitive same as [a-zA-Z]
So the loop effectively becomes,
regExp.test('a') // true and it sets lastIndex = 1
regExp.test('b') // false as the lastIndex i.e. staring point is not 0, lastIndex resets
regExp.test('c') // true as lastIndex is 0 and regex satisfies
regExp.test('1') // false
Reference: https://bigfrontend.dev/quiz/RegExp/discuss
118. structuredClone()
118.https://bigfrontend.dev/quiz/structuredclone
const error = new Error('😢')
error.name = 'SyntaxError'
console.log(error instanceof Error)
console.log(error instanceof SyntaxError)
const clonededError = structuredClone(error)
console.log(clonededError instanceof Error)
console.log(clonededError instanceof SyntaxError)
Solution: I'd be happy to explain this code. Let's go through it step by step:
const error = new Error('😢')
error.name = 'SyntaxError'
console.log(error instanceof Error)
console.log(error instanceof SyntaxError)
const clonededError = structuredClone(error)
console.log(clonededError instanceof Error)
console.log(clonededError instanceof SyntaxError)
const error = new Error('😢')
:- Creates a new Error object with the message "😢".
error.name = 'SyntaxError'
:- Changes the
name
property of the error to 'SyntaxError'. - This doesn't actually change the type of the error, just its name.
- Changes the
console.log(error instanceof Error)
:- This will log
true
becauseerror
is indeed an instance of the Error class.
- This will log
console.log(error instanceof SyntaxError)
:- This will log
false
. Even though we changed the name to 'SyntaxError', the error is still an instance of Error, not SyntaxError.
- This will log
const clonededError = structuredClone(error)
:structuredClone()
has built-in support for cloning Error objects.- When cloning an Error, it preserves the Error type and creates a new Error instance.
- It also preserves the
name
property of the Error.
console.log(clonededError instanceof Error)
:- This will log
true
.
- This will log
console.log(clonededError instanceof SyntaxError)
:- In JavaScript, when an Error's
name
property is set to 'SyntaxError', it becomes an instance ofSyntaxError
as well. - This will also log
true
for the same reason as above.
- In JavaScript, when an Error's
To summarize the output:
true
false
true
true
Key points:
- Changing the
name
property of an Error doesn't change its type. instanceof
checks if an object is an instance of a particular constructor function.structuredClone()
creates a deep copy of an object
The structuredClone
function in JavaScript is used to create a deep clone of a given value. This method can handle complex objects, including those with nested structures, circular references, and non-serializable properties (like Map
, Set
, Date
, ArrayBuffer
, etc.), which can't be properly cloned using simpler methods like JSON.stringify
and JSON.parse
.
Key Characteristics of structuredClone
:
Deep Cloning:
structuredClone
creates a deep copy of the original object or value. This means all nested objects and properties are also cloned, and the resulting clone is completely independent of the original.- Changes made to the clone do not affect the original object and vice versa.
Support for Complex Data Types:
- It supports cloning of various built-in JavaScript objects like
Array
,Object
,Map
,Set
,Date
,RegExp
,Blob
,File
,ImageData
,ArrayBuffer
,TypedArray
, and others. - Unlike
JSON.stringify
/JSON.parse
,structuredClone
can clone objects containing non-enumerable properties, properties with getters and setters, and objects with prototype chains.
- It supports cloning of various built-in JavaScript objects like
Handling Circular References:
structuredClone
can clone objects with circular references without throwing an error, something that would cause issues withJSON.stringify
.
Performance:
structuredClone
is generally faster and more reliable for deep cloning compared to manual deep cloning or usingJSON
methods.
Syntax:
const clone = structuredClone(value)
value
: The object or value you want to clone.- Returns: A deep clone of the provided
value
.
Example Usage:
// Example with simple objects
const original = { a: 1, b: { c: 2 } }
const clone = structuredClone(original)
clone.b.c = 3
console.log(original.b.c) // Output: 2
console.log(clone.b.c) // Output: 3
// Example with complex objects
const originalComplex = new Map([['key', { a: 1, b: new Set([2, 3]) }]])
const cloneComplex = structuredClone(originalComplex)
console.log(cloneComplex.get('key').b.has(2)) // Output: true
console.log(cloneComplex.get('key').b === originalComplex.get('key').b) // Output: false
Limitations:
- Functions:
structuredClone
does not clone functions. If the object contains functions, they will not be copied. - Symbol properties: Properties keyed by
Symbol
are not cloned. - Error objects: While it can clone error objects, the non-enumerable properties (like
stack
) may not be preserved.
Conclusion: structuredClone
is a robust and versatile tool for deep cloning objects and values in JavaScript, making it preferable over other cloning techniques for most use cases, especially when dealing with complex or circular data structures.
88. try...catch
88.https://bigfrontend.dev/quiz/try-catch
var a = 'a'
try {
throw new Error('BFE.dev')
} catch {
var a = 'a1'
}
console.log(a)
var b = 'b'
try {
throw new Error('BFE.dev')
} catch (b) {
var b = 'b1'
}
console.log(b)
var c = 'c'
try {
throw new Error('BFE.dev')
} catch (error) {
var c = 'c1'
}
console.log(c)
Solution: This code demonstrates the behavior of variable declarations and scope in JavaScript, particularly within try...catch
blocks. Let's break it down case by case:
Case with variable
a
:var a = 'a' try { throw new Error('BFE.dev') } catch { var a = 'a1' } console.log(a)
var a
is hoisted to the top of its scope (function or global).- In the
catch
block,var a = 'a1'
is treated as a re-declaration and assignment. - The
catch
block without a parameter doesn't create a new scope forvar
declarations. - Output:
'a1'
Case with variable
b
:var b = 'b' try { throw new Error('BFE.dev') } catch (b) { var b = 'b1' } console.log(b)
var b
is hoisted.- The
catch (b)
creates a block-scopedb
that shadows the outerb
. var b = 'b1'
insidecatch
is hoisted to the function/global scope, not thecatch
block.- The block-scoped
b
is not affected by thevar
declaration. - Output:
'b'
(the outerb
is unchanged)
Case with variable
c
:var c = 'c' try { throw new Error('BFE.dev') } catch (error) { var c = 'c1' } console.log(c)
- Similar to case 1,
var c
is hoisted. - The
catch (error)
doesn't interfere withc
. var c = 'c1'
in thecatch
block reassigns the value of the outerc
.- Output:
'c1'
- Similar to case 1,
Key points:
var
declarations are hoisted to the top of their containing function or global scope.catch
blocks with a parameter create a new scope for that parameter, which can shadow outer variables.var
declarations insidecatch
blocks are not block-scoped; they affect the outer scope.- The behavior can be confusing and is one of the reasons why
let
andconst
are preferred in modern JavaScript for better scoping control.
This quiz highlights some of the quirks of var
declarations and scoping in JavaScript, especially in try...catch
contexts. It's a good reminder of why understanding these behaviors is important for avoiding unexpected results in JavaScript code.
120. try..catch..finally
120.https://bigfrontend.dev/quiz/try-catch-finally
function func() {
try {
console.log(1)
return
} catch (e) {
console.log(2)
} finally {
console.log(3)
}
console.log(4)
}
func()
Solution: Certainly! Let's break down this code and explain its execution:
function func() {
try {
console.log(1)
return
} catch (e) {
console.log(2)
} finally {
console.log(3)
}
console.log(4)
}
func()
Here's what happens when func()
is called:
The function enters the
try
block:console.log(1)
executes, printing1
to the console.- The
return
statement is encountered. This would normally exit the function immediately, but because there's afinally
block, its execution is delayed.
The
catch
block is skipped entirely because no error was thrown in thetry
block.The
finally
block executes:console.log(3)
runs, printing3
to the console.- After the
finally
block completes, the function returns (due to the earlierreturn
statement in thetry
block).
console.log(4)
is never reached because the function has already returned.
The output of this function will be:
1
3
Key points to note:
- The
try
block is where the main code execution happens. - The
catch
block only executes if an error is thrown in thetry
block. - The
finally
block always executes, regardless of whether an error occurred or if there was areturn
statement. - A
return
statement in thetry
block doesn't immediately exit the function if there's afinally
block. Thefinally
block will still execute before the function returns. - Code after the
try
-catch
-finally
structure (in this case,console.log(4)
) will not execute if the function has already returned.
This example demonstrates how try
, catch
, and finally
blocks interact with return
statements, and shows that finally
always executes, even when there's an early return
in the try
block.
125. try...catch...finally 2
125.https://bigfrontend.dev/quiz/try-catch-finally-2
const prettify = (str) => {
try {
if (typeof str === 'string') {
JSON.parse(str)
return 'prettified'
}
} catch (e) {
return 'errored'
} finally {
return str
}
}
console.log(prettify('BFE.dev'))
console.log(prettify('123'))
Solution: Certainly! Let's break down this code and explain its behavior:
const prettify = (str) => {
try {
if (typeof str === 'string') {
JSON.parse(str)
return 'prettified'
}
} catch (e) {
return 'errored'
} finally {
return str
}
}
console.log(prettify('BFE.dev'))
console.log(prettify('123'))
Here's what's happening:
The
prettify
function:- It takes a parameter
str
. - It has a
try
-catch
-finally
structure.
- It takes a parameter
Inside the
try
block:- It checks if
str
is a string. - If it is, it attempts to parse
str
as JSON usingJSON.parse(str)
. - If parsing succeeds, it would return "prettified" (but this return is overridden by the
finally
block).
- It checks if
The
catch
block:- If
JSON.parse(str)
throws an error, it would return "errored" (but again, this is overridden by thefinally
block).
- If
The
finally
block:- This block always executes, regardless of whether an error was thrown or not.
- It returns the original
str
parameter.
console.log(prettify('BFE.dev'))
:- 'BFE.dev' is not valid JSON, so
JSON.parse
will throw an error. - The
catch
block is entered, but its return value is ignored. - The
finally
block executes, returning 'BFE.dev'. - Output:
BFE.dev
- 'BFE.dev' is not valid JSON, so
console.log(prettify('123'))
:- '123' is valid JSON (it parses to the number 123).
- No error is thrown, so the
try
block completes. - The
finally
block still executes, returning '123'. - Output:
123
Key points:
- The
finally
block always executes last, and its return value overrides any return values from thetry
orcatch
blocks. - In this function, the logic in the
try
andcatch
blocks effectively does nothing because their return values are always overridden by thefinally
block. - The function will always return the original input string, regardless of whether it's valid JSON or not.
- The
JSON.parse()
call is essentially a no-op in this function, as its result is never used and any errors it throws are caught and then ignored.
This code demonstrates how finally
blocks interact with return
statements, but it's worth noting that the function doesn't actually "prettify" anything - it just returns the input string unchanged. The name prettify
is misleading given the function's actual behavior.