Y
Published on

BigFrontEnd JavaScript Quizzes

Authors
  • avatar
    Name
    Yinhuan Yuan
    Twitter

Introduction

This blog post summarizes JavaScript Quizzes on BigFrontEnd.Dev.

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

  1. 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 a undefined
  2. Function declarations and implementation are also hoisted.
  3. const and let: While they are hoisted, they are not initialized, and trying to access them before their declaration will result in a ReferenceError.
  4. 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.
  5. window.foo always refers to the global foo (in a browser environment).
  6. Variable declaration var is not limited in block.
  7. 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:

  1. 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 use c before its declaration without an error.
  2. Variables declared with let or const are not hoisted in the same way. Trying to use them before declaration results in a ReferenceError.
  3. const variables must be assigned a value when declared and cannot be reassigned.
  4. 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:

  1. First, we define func1 as an arrow function that logs the number 1.

  2. We call func1(), which will output 1 to the console.

  3. We then call func2(). Even though func2 is defined after this call in the code, it works because of function hoisting. Function declarations are hoisted to the top of their scope, so func2 is available throughout the entire script.

  4. The func2 function is defined, which logs the number 2.

  5. We try to call func3(). This will throw a TypeError because at this point, func3 is undefined. Even though var func3 is hoisted, only the declaration is hoisted, not the assignment.

  6. Finally, we assign a function expression to func3. This function is named func4, but this name is only available inside the function itself (for recursion, for example). Outside the function, it's only known as func3.

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 (like func3) have their declarations hoisted, but not their assignments. So func3 exists but is undefined 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:

  1. We start by declaring a global variable a and assigning it the value 1.

  2. We define a function func().

  3. 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)
  4. We call func()

  5. We log the value of the global a

  6. We check if 'b' is not a property of the window object

  7. If 'b' is not in window, we declare var b and assign it 1

  8. We log the value of b

Now, let's explain the output and behavior:

  1. 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 local a that shadows the global a.
    • The assignment a = 2 and console.log(a) work with this local a.
    • So, the first console.log(a) inside func() outputs 2.
  2. After func() is called, we console.log(a) again. This refers to the global a, which was never changed. So it outputs 1.

  3. The if statement checks if 'b' is not a property of window. In a browser environment, var declarations at the global scope create properties on the window object. However, due to hoisting, var b is already declared before this check, so 'b' in window would be true, and the if block would not execute.

  4. The final console.log(b) would output undefined. This is because var b was hoisted to the top of its scope (global in this case), but since the if 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 the window 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 local d to a string.
  • So inside the IIFE, typeof d is 'string'. Outside the IIFE, the global d remains unchanged, so typeof 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:

  1. Function declarations are hoisted and initially take precedence over variable declarations.
  2. Reassignments happen after hoisting, potentially changing the type of a variable.
  3. Function expressions (like in the last example) don't create a variable in the outer scope.
  4. In function scopes, local variables take precedence over global ones.
  5. 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:

  1. let foo = 10

    • This declares a variable foo in the global scope with a value of 10.
  2. function func1() { ... }

    • This defines a function func1.
  3. 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 is undefined.
  4. func1()

    • This calls func1, which will log undefined.
  5. function func2() { ... }

    • This defines a function func2.
  6. 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 a ReferenceError.
  7. func2()

    • This calls func2, which will throw a ReferenceError.

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 (and const) 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 a ReferenceError, while accessing a var variable before its declaration results in undefined.
  • 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:

  1. var foo = 1;

    • This declares a global variable foo and assigns it the value 1.
  2. (function () { ... })()

    • This is an Immediately Invoked Function Expression (IIFE). It creates a new scope.
  3. 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 is undefined 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

So, the complete output will be:

undefined
1
2
3
1

Key points:

  1. Variable declarations with var are hoisted to the top of their scope.
  2. Function scopes create their own context for variables.
  3. When a variable is declared inside a function with var, it's local to that function.
  4. 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).
  5. window.foo always refers to the global foo (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:

  1. function foo() { ... }

    • This defines a function named foo.
  2. 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 is undefined.
  3. 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 while i is less than 3, and increments i after each iteration.
    • Inside the loop, it logs the current value of i.
  4. foo()

    • This calls the foo function.

When we run this code, the output will be:

undefined
0
1
2

Explanation of the output:

  • The first undefined comes from logging i 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 is undefined.
  • 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:

  1. 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.
  2. 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.
  3. foo()

    • This attempts to call the function foo.
  4. bar()

    • This attempts to call the function bar.

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 because bar is not defined.

Scenario 2: Strict mode or in some modern JavaScript environments:

  • foo() will execute and log 'BFE'.
  • bar() will throw a ReferenceError because bar is not defined.

The key points are:

  1. 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.

  2. 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.

  3. However, for compatibility reasons, many browsers still hoist function declarations in blocks to the containing function or global scope in non-strict mode.

  4. In strict mode, block-scoped function declarations behave as they're supposed to in ES6+: they're only accessible within their block.

  5. 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:

  1. 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'.
  2. 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.
  3. foo()

    • This attempts to call a function named foo.

Now, let's consider the behavior:

  1. The if statement condition is true, so 'dev' will be logged to the console.

  2. The function expression in the if condition does not create a variable named foo in the outer scope. It only exists within the scope of the if statement.

  3. When foo() is called outside the if block, it will result in a ReferenceError because foo is not defined in this scope.

The output and behavior will be:

dev
Uncaught ReferenceError: foo is not defined

Key points:

  1. Function expressions in conditions are truthy.
  2. Function expressions do not create variables in the surrounding scope, unlike function declarations.
  3. The function foo is not hoisted or made available outside the if 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:

  1. First function declaration:

    function foo() {
      console.log(1)
    }
    
  2. Variable declaration and assignment:

    var foo = 2
    
  3. Second function declaration:

    function foo() {
      console.log(3)
    }
    
  4. Function call:

    foo()
    

Now, let's consider how JavaScript interprets this code:

  1. Function declarations are hoisted to the top of their scope. Both function declarations for foo are hoisted.

  2. Variable declarations (but not assignments) are also hoisted. The var foo declaration is hoisted, but not its assignment.

  3. The second function declaration overwrites the first one.

  4. 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()
    
  5. When the assignment foo = 2 is executed, it overwrites the function foo with the number 2.

  6. 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:

  1. Function declarations are hoisted and can overwrite each other.
  2. Variable declarations are hoisted, but not their assignments.
  3. Assignments can overwrite functions.
  4. 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:

  1. let a = 1;

    • Declares a variable a in the global scope and initializes it to 1.
  2. (function() { ... }())

    • This is an Immediately Invoked Function Expression (IIFE). It creates a new scope.
  3. Inside the IIFE:

    let foo = () => a
    
    • Declares a function foo that returns the value of a.
    • The a here refers to the a in its lexical scope, which is the IIFE, not the global a.
  4. let a = 2;

    • Declares a new variable a inside the IIFE, shadowing the global a.
    • This a is initialized to 2.
  5. console.log(foo())

    • Calls foo() and logs its result.

Now, here's the tricky part:

  • When foo is defined, it captures the a from its lexical environment (the IIFE).
  • However, at the point where foo is defined, the local a 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 local a, which is now initialized to 2.

Therefore, the output will be:

2

Key points:

  1. Lexical scoping: The function foo captures the a from its immediate scope (the IIFE), not the global scope.
  2. Temporal Dead Zone: Variables declared with let exist from the start of their block, but can't be accessed until after their declaration.
  3. Variable shadowing: The a inside the IIFE shadows the global a.
  4. Closure: foo forms a closure over its lexical environment, which includes the local a.

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()
  1. function a() {: This declares a function named a with no parameters.

  2. '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.

  3. dev = 'BFE': This line attempts to assign the string 'BFE' to a variable named dev. However, there's no var, let, or const declaration for dev.

  4. console.log(dev): This would attempt to print the value of dev to the console.

  5. }: This closes the function definition.

  6. a(): This calls the function a.

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:

  1. var is hoisted out of the block and the let is hoisted within the block.
  2. const a = b = c = 1 is c=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.

  1. 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 variable i is the same for every iteration of the loop, and its value is shared across all the setTimeout callbacks.
  • The setTimeout function is asynchronous and runs after the loop has completed. By the time the setTimeout callbacks are executed, the loop has already incremented i to 5.
  • As a result, each setTimeout logs the value of i after the loop has finished, so all of them log 5.

Output:

5
5
5
5
5
  1. 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-scoped i variable. Therefore, each setTimeout callback captures its own distinct i value.
  • When the setTimeout callbacks are executed, each callback logs the value of i that was current at the time of that particular iteration.
  • As a result, the values logged are 0, 1, 2, 3, and 4, corresponding to each iteration of the loop.

Output:

0
1
2
3
4

3.Summary:

  • With var: The loop variable i is shared across all iterations, so all setTimeout callbacks log the final value of i, which is 5.
  • With let: Each iteration creates a new i variable that is unique to that iteration, so each setTimeout callback logs the correct value of i 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:

  1. 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.

  2. Function Call:

    func()
    

    This executes the function.

  3. Console Log:

    console.log(typeof a, typeof b, typeof c)
    

    This attempts to log the types of a, b, and c.

Now, let's analyze what's happening:

  1. Inside the function:

    • const a = b = c = 1 is actually equivalent to:
      c = 1
      b = c
      const a = b
      
    • c and b 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.
  2. After the function call:

    • a is not accessible outside the function because it's block-scoped (due to const).
    • b and c are global variables (in non-strict mode) and retain their values.
  3. The console.log statement:

    • typeof a is "undefined" because a is not defined in the global scope.
    • typeof b is "number" because b is a global variable with the value 1.
    • typeof c is "number" because c is also a global variable with the value 1.

Therefore, the output will be:

undefined number number

Key points:

  1. Only a is truly declared as a local variable (with const).
  2. b and c become global variables due to the lack of declaration keywords.
  3. This behavior can be a source of bugs and is one reason why "use strict" is recommended.
  4. In strict mode, this would actually throw an error because b and c 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

  1. <, >, <=, >= convert the null and undefined to Number. == does not conver them and only return true for comparing null and undefined.

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:

  1. console.log(JSON.stringify([1,2,null,3]))

    • JSON.stringify converts null to null in the JSON string.
    • Output: '[1,2,null,3]'
  2. console.log(JSON.stringify([1,2,undefined,3]))

    • JSON.stringify omits undefined values in arrays.
    • Output: '[1,2,null,3]'
  3. console.log(null === undefined)

    • Strict equality check. null and undefined are different types.
    • Output: false
  4. console.log(null == undefined)

    • Loose equality. null and undefined are considered equal.
    • Output: true
  5. console.log(null == 0)

    • Loose equality. null is not considered equal to 0.
    • Output: false
  6. console.log(null < 0)

    • Comparison converts null to 0. 0 is not less than 0.
    • Output: false
  7. console.log(null > 0)

    • Comparison converts null to 0. 0 is not greater than 0.
    • Output: false
  8. console.log(null <= 0)

    • Comparison converts null to 0. 0 is less than or equal to 0.
    • Output: true
  9. console.log(null >= 0)

    • Comparison converts null to 0. 0 is greater than or equal to 0.
    • Output: true
  10. console.log(undefined == 0)

    • Loose equality. undefined is not considered equal to 0.
    • Output: false
  11. console.log(undefined < 0)

    • Comparison with undefined returns false.
    • Output: false
  12. console.log(undefined > 0)

    • Comparison with undefined returns false.
    • Output: false
  13. console.log(undefined <= 0)

    • Comparison with undefined returns false.
    • Output: false
  14. 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:

  1. console.log(0 == false)

    • false is coerced to 0
    • 0 == 0 is true
    • Output: true
  2. console.log('' == false)

    • false is coerced to 0
    • '' is coerced to 0
    • 0 == 0 is true
    • Output: true
  3. 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
  4. console.log(undefined == false)

    • undefined is not coerced to a number in this case
    • undefined == false is always false
    • Output: false
  5. console.log(null == false)

    • null is not coerced to a number in this case
    • null == false is always false
    • Output: false
  6. console.log('1' == true)

    • true is coerced to 1
    • '1' is coerced to 1
    • 1 == 1 is true
    • Output: true
  7. console.log(1n == true)

    • true is coerced to 1
    • 1n (BigInt) is compared to 1
    • 1n == 1 is true
    • Output: true
  8. 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] == 1: true. The array [1] is converted to a primitive, which becomes '1', then to a number 1.

  2. [1] == '1': true. [1] is converted to '1', so it's '1' == '1'.

  3. ['1'] == '1': true. Similar to #2, ['1'] becomes '1'.

  4. ['1'] == 1: true. ['1'] becomes '1', then '1' is converted to number 1.

  5. [1] == ['1']: false. When comparing two objects (arrays are objects), it checks if they're the same object in memory, which they're not.

  6. new Boolean(true) == 1: true. The Boolean object is converted to its primitive value true, which is then converted to number 1.

  7. new Boolean(true) == new Boolean(true): false. These are two different objects in memory.

  8. Boolean(true) == '1': true. Boolean(true) is just true, which is converted to number 1, then '1' is also converted to 1.

  9. Boolean(false) == [0]: true. [0] is converted to '0', then to number 0, which equals false.

  10. new Boolean(true) == '1': true. The Boolean object is converted to its primitive value true, which is then converted to number 1.

  11. new Boolean(false) == [0]: false. The Boolean object is converted to its primitive value true, which is then converted to number 1.

  12. null == undefined: true. This is a special case in JavaScript where null and undefined 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 and undefined 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:

  1. 2.0 == "2" == new Boolean(true) == "1"

    This is evaluated from left to right:

  2. First comparison: 2.0 == "2"

    • The string "2" is coerced to a number
    • 2.0 == 2 is true
  3. 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
  4. 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:

  1. The loose equality operator (==) performs type coercion.
  2. Numbers and strings are compared numerically after coercion.
  3. Boolean objects are converted to primitive booleans when compared with ==.
  4. true is coerced to 1 when compared with a string or number.
  5. 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:

  1. [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's toString() 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
  2. [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") is 0
    • 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:

  1. Type coercion: How JavaScript converts between types when using the == operator.
  2. Array to primitive conversion: How arrays are converted to primitives (using toString()).
  3. 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:

  1. console.log(Boolean('false'))

    • This converts the string 'false' to a boolean.
    • Any non-empty string is truthy in JavaScript.
    • Output: true
  2. console.log(Boolean(false))

    • This is already a boolean false value.
    • Output: false
  3. 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'
  4. 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
  5. console.log('3' - ' 02 ')

    • Both strings are converted to numbers.
    • '3' becomes 3, ' 02 ' becomes 2 (leading/trailing spaces are ignored).
    • Output: 1
  6. console.log('3' * ' 02 ')

    • Similar to subtraction, both are converted to numbers for multiplication.
    • Output: 6
  7. console.log(Number('1'))

    • Converts the string '1' to a number.
    • Output: 1
  8. console.log(Number('number'))

    • Tries to convert 'number' to a number, which isn't possible.
    • Output: NaN (Not a Number)
  9. console.log(Number(null))

    • null is converted to 0 when using the Number() function.
    • Output: 0
  10. console.log(Number(false))

    • false is converted to 0 when using the Number() function.
    • Output: 0
  11. console.log(Number(undefined))

    • undefined is converted to NaN 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:

  1. console.log([] + [])

    • Both arrays are converted to empty strings
    • "" + "" results in an empty string
    • Output: ""
  2. console.log([] + 1)

    • [] is converted to an empty string
    • "" + 1 coerces 1 to a string
    • Output: "1"
  3. console.log([[]] + 1)

    • [[]] is converted to ""
    • "" + 1 coerces 1 to a string
    • Output: "1"
  4. console.log([[1]] + 1)

    • [[1]] is converted to "1"
    • "1" + 1 coerces 1 to a string
    • Output: "11"
  5. console.log([[[[2]]]] + 1)

    • [[[[2]]]] is converted to "2"
    • "2" + 1 coerces 1 to a string
    • Output: "21"
  6. console.log([] - 1)

    • [] is converted to 0 for subtraction
    • 0 - 1 is performed
    • Output: -1
  7. console.log([[]] - 1)

    • [[]] is converted to 0 for subtraction
    • 0 - 1 is performed
    • Output: -1
  8. console.log([[1]] - 1)

    • [[1]] is converted to 1 for subtraction
    • 1 - 1 is performed
    • Output: 0
  9. console.log([[[[2]]]] - 1)

    • [[[[2]]]] is converted to 2 for subtraction
    • 2 - 1 is performed
    • Output: 1
  10. console.log([] + {})

    • [] becomes ""
    • becomes "[object Object]"
    • Output: "[object Object]"
  11. console.log({} + {})

    • Both become "[object Object]"
    • Output: "[object Object][object Object]"
  12. 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:

  1. console.log( [] + {} )

    • Output: "[object Object]"
    • [] is coerced to an empty string ""
    • {} is coerced to "[object Object]"
    • The result is string concatenation
  2. console.log( + {} )

    • Output: NaN
    • The unary + attempts to convert {} to a number, which results in NaN
  3. console.log( + [] )

    • Output: 0
    • The unary + converts an empty array to 0
  4. console.log( {} + [])

    • Output: "[object Object]"
    • In most browsers, this is interpreted the same as ({} + [])
    • {} is coerced to "[object Object]" and [] to an empty string
  5. console.log( ({}) + [])

    • Output: "[object Object]"
    • Same as #4, but the parentheses ensure {} is treated as an object literal
  6. console.log( ({}) + [])

    • Output: "[object Object]"
    • Identical to #5
  7. console.log( ({}) + [])

    • Output: "[object Object]"
    • Identical to #5 and #6
  8. 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
  9. 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]"
  10. 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]" + ""

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:

  1. const foo = [0]

    • This creates a constant foo that is an array containing a single element, the number 0.
  2. 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 to true for the purpose of the if condition.
  3. 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 its toString() method. b. [0].toString() results in the string "0". c. The string "0" is then compared to true. 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 to false.

The output of this code will be:

false

Key points:

  1. Arrays are truthy in boolean contexts (like if statements), regardless of their contents.
  2. The loose equality operator == performs type coercion, which can lead to unexpected results.
  3. When comparing different types with ==, JavaScript follows a complex set of rules to coerce the values.
  4. In this case, even though foo is truthy in the if statement, it's not loosely equal to true.

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 or false

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:

  1. console.log(0 == '0') Output: true

    • Loose equality performs type coercion. The string '0' is coerced to a number.
  2. console.log(0 === '0') Output: false

    • Strict equality checks both value and type. 0 is a number, '0' is a string.
  3. console.log(Object.is(0, '0')) Output: false

    • Object.is() behaves similarly to === in this case.
  4. console.log(0 == 0) Output: true

    • Same value and type.
  5. console.log(0 === 0) Output: true

    • Same value and type.
  6. console.log(Object.is(0, 0)) Output: true

    • Same value and type.
  7. console.log(0 == -0) Output: true

    • In JavaScript, 0 and -0 are considered equal under ==.
  8. console.log(0 === -0) Output: true

    • Even strict equality considers 0 and -0 to be the same.
  9. console.log(Object.is(0, -0)) Output: false

    • This is where Object.is() differs. It distinguishes between 0 and -0.
  10. console.log(NaN == NaN) Output: false

    • NaN is not equal to anything, including itself, under ==.
  11. console.log(NaN === NaN) Output: false

    • NaN is not equal to anything, including itself, even under ===.
  12. console.log(Object.is(NaN, NaN)) Output: true

    • Object.is() considers NaN equal to itself.
  13. console.log(0 == false) Output: true

    • Loose equality coerces false to 0.
  14. console.log(0 === false) Output: false

    • Strict equality checks type. 0 is a number, false is a boolean.
  15. console.log(Object.is(0, false)) Output: false

    • Object.is() behaves like === in this case.

Key takeaways:

  1. Loose equality (==) performs type coercion, which can lead to unexpected results.
  2. Strict equality (===) compares both value and type, but still considers 0 and -0 to be equal.
  3. 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 ===
  4. NaN is not equal to itself under == or ===, but is under Object.is().
  5. When comparing with booleans, == performs coercion while === and Object.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:

  1. 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.

  2. console.log([] == false) Output: true Explanation: Both [] and false are coerced to the number 0 when using ==.

  3. console.log(!![]) Output: true Explanation: The ! operator first converts [] to a boolean (which is true for any non-empty object), then negates it. The second ! negates it again, resulting in true.

  4. 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.

  5. console.log(!![1]) Output: true Explanation: Similar to !![], this converts [1] to a boolean (which is true), negates it, then negates it again.

  6. console.log(Boolean([])) Output: true Explanation: When converted to a boolean, any non-empty object (including empty arrays) becomes true.

  7. console.log(Boolean(new Boolean([]))) Output: true Explanation: new Boolean([]) creates a Boolean object. Any object, when passed to Boolean(), returns true.

  8. console.log(Boolean(new Boolean(false))) Output: true Explanation: Although we're passing false to the Boolean constructor, new Boolean(false) creates a Boolean object. As before, any object passed to Boolean() returns true.

Key takeaways:

  1. Empty arrays ([]) are truthy when converted to boolean, but can be equal to 0 or false when using loose equality (==) due to type coercion.

  2. The !! operator is a common way to convert a value to its boolean equivalent.

  3. When comparing arrays to primitives using ==, the array is first converted to a primitive value (often a string), which can lead to unexpected results.

  4. Boolean() function converts its argument to a boolean primitive:

    • Objects (including arrays and Boolean objects) always convert to true.
    • Empty strings, 0, null, undefined, and NaN convert to false.
    • All other values convert to true.
  5. 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:

  1. console.log(/^4\d\d$/.test('404'))

    • Output: true
    • The regular expression ^4\d\d$ matches:
      • ^ start of the string
      • 4 the digit 4
      • \d\d two digits
      • $ end of the string
    • '404' is a string that perfectly matches this pattern.
  2. 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.
  3. 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.
  4. 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.

Key points:

  • The test() method returns true if the regex matches any part of the string, and false 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 over valueOf and toString.

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 use toString, 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:

  1. The order of precedence for object-to-primitive conversion is: Symbol.toPrimitive > valueOf > toString
  2. Different operations may prefer numeric or string conversion.
  3. parseInt always converts its argument to a string first.
  4. The unary plus operator (+) forces numeric conversion.
  5. 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.

  1. 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
  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
  1. 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
  1. 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
  1. 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
  1. 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
  1. 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!')
})
  1. 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)
  1. 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'
  1. 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
  1. 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)
  })
  1. 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 }
  1. 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)
  1. 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()()
  1. We start by declaring a global variable dev and assigning it the value 'bfe'.

  2. 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.
  3. We reassign the global dev variable to 'bigfrontend'.

  4. 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.

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:

  1. Variable scoping: The dev inside a() is a different variable from the global dev.
  2. Closures: The inner function maintains access to the dev variable from its containing scope.
  3. 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:

  1. function Foo() { }

    • This defines a constructor function Foo.
  2. Foo.prototype.bar = 1

    • This adds a property bar with value 1 to Foo's prototype.
  3. const a = new Foo()

    • This creates a new instance of Foo called a.
  4. console.log(a.bar)

    • This logs 1, as a inherits bar from Foo's prototype.
  5. Foo.prototype.bar = 2

    • This changes the value of bar in Foo's prototype to 2.
  6. const b = new Foo()

    • This creates another instance of Foo called b.
  7. console.log(a.bar)

    • This logs 2, as a still references the same prototype object, which now has bar set to 2.
  8. console.log(b.bar)

    • This also logs 2, as b references the same prototype object as a.
  9. Foo.prototype = {bar: 3}

    • This completely replaces Foo's prototype with a new object that has bar set to 3.
  10. const c = new Foo()

    • This creates a third instance of Foo called c, which will use the new prototype.
  11. console.log(a.bar)

    • This still logs 2, because a still references the old prototype object.
  12. console.log(b.bar)

    • This also logs 2, because b also still references the old prototype object.
  13. console.log(c.bar)

    • This logs 3, because c was created after the prototype was replaced and thus uses the new prototype object.

The output will be:

1
2
2
2
2
3

Key points:

  1. Objects created with new inherit properties from their constructor's prototype.
  2. Changing a property on the prototype affects all instances that refer to that prototype.
  3. Replacing the entire prototype only affects instances created afterwards; existing instances still reference the old prototype.
  4. 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:

  1. function F() { this.foo = 'bar' }

    • This defines a constructor function F.
    • When called with new, it creates an object and sets its foo property to 'bar'.
  2. const f = new F()

    • This creates a new instance of F and assigns it to the variable f.
    • 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 by F.prototype. c. Calls F with this set to the new object. d. Returns the new object (unless the constructor returns a different object).
  3. console.log(f.prototype)

    • This attempts to log the prototype property of f.
    • The output will be: undefined

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 from F. It doesn't have its own prototype property.
  • f does have an internal [[Prototype]] (accessible via __proto__ in most environments), which references F.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:

  1. The difference between a constructor function's prototype and an instance's [[Prototype]].
  2. How the new keyword works with constructor functions.
  3. 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:

  1. obj1 is created with a method foo using the shorthand method syntax.
  2. The prototype of obj1 is set to an object with its own foo method.
  3. When obj1.foo() is called, it successfully uses super.foo() to call the foo method from its prototype.
  4. 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:

  1. obj2 is created with a foo property assigned a function expression.
  2. The prototype of obj2 is set to an object with its own foo method.
  3. When obj2.foo() is called, it throws a SyntaxError because super 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 of super.
  • In Case 2, the method is defined as a property with a function expression foo: function() {...}, which does not allow the use of super.

The output will be: For Case 1: bar For Case 2: SyntaxError: 'super' keyword unexpected here

This quiz highlights that:

  1. The super keyword can be used in object literals, but only in methods defined using the shorthand syntax.
  2. Function expressions assigned to object properties cannot use super, even if they're used as methods.
  3. 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:

  1. We define class A with a property a set to 'a'.

  2. We add a property c to A's prototype, setting it to 'c'.

  3. We define class B that extends A, with its own property b set to 'b'.

  4. We create an instance of A called a.

  5. We create an instance of B called b.

  6. We then try to access various properties on both instances.

Now, let's look at each console.log statement:

  1. console.log(a.a):

    • a is an instance of A, which has the property a.
    • This logs 'a'.
  2. console.log(a.b):

    • a is an instance of A, which doesn't have a property b.
    • This logs undefined.
  3. console.log(a.c):

    • a doesn't have its own property c, but it can access c from A.prototype.
    • This logs 'c'.
  4. console.log(b.a):

    • b is an instance of B, which extends A. It inherits the property a from A.
    • This logs 'a'.
  5. console.log(b.b):

    • b is an instance of B, which has its own property b.
    • This logs 'b'.
  6. console.log(b.c):

    • b doesn't have its own property c, but it can access c from A.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:

  1. Class definition and inheritance (class and extends keywords).
  2. Instance properties (defined in the class body with =).
  3. Prototype properties (added to A.prototype).
  4. Property lookup through the prototype chain.
  5. 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:

  1. console.log(Function.prototype.__proto__ === Object.prototype)

    • True. The prototype of Function.prototype is Object.prototype.
  2. console.log(Function.__proto__ === Object.__proto__)

    • True. Both Function and Object are functions, so they share the same prototype (Function.prototype).
  3. console.log(Function.__proto__.__proto__ === Object.prototype)

    • True. Function.proto is Function.prototype, and its prototype is Object.prototype.
  4. console.log(Object.constructor.prototype === Object.prototype)

    • False. Object.constructor is Function, so this compares Function.prototype to Object.prototype.
  5. console.log(Function.constructor === Function)

    • True. Function is its own constructor.
  6. console.log(Object.constructor === Object)

    • False. Object's constructor is Function, not Object itself.
  7. console.log(Array.__proto__ === Function.__proto__)

    • True. Both Array and Function are functions, so they share the same prototype (Function.prototype).
  8. console.log(Array.constructor === Function)

    • True. The constructor of Array is Function.
  9. console.log(Object.__proto__ === Function)

    • False. Object.proto is Function.prototype, not Function itself.
  10. console.log(Function.__proto__ === Function.prototype)

    • True. Function's prototype is Function.prototype.
  11. console.log(Object instanceof Object)

    • True. The prototype chain of Object eventually leads to Object.prototype.
  12. console.log(Function instanceof Function)

    • True. Function is an instance of itself.
  13. 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 with new.
  • 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:

  1. console.log(obj.toString === Object.toString)

    • Result: false
    • obj.toString refers to Object.prototype.toString
    • Object.toString refers to Function.prototype.toString (because Object is a function)
  2. console.log(fun.toString === Function.toString)

    • Result: true
    • fun.toString refers to Function.prototype.toString
    • Function.toString also refers to Function.prototype.toString,
    • Since all functions in JavaScript inherit from Function.prototype, fun.toString is equivalent to the same method defined on Function.prototype. Thus, it logs true
  3. console.log(obj.toString === Object.prototype.toString)

    • Result: true
    • obj.toString is inherited from Object.prototype.toString
  4. console.log(fun.toString === Function.prototype.toString)

    • Result: true
    • fun.toString is inherited from Function.prototype.toString
  5. console.log(Object.toString === Object.prototype.toString)

    • Result: false
    • Object.toString refers to Function.prototype.toString (because Object is a function)
    • Object.prototype.toString is a different method
  6. console.log(Function.toString === Function.prototype.toString)

    • Result: true
    • Function.toString looks for the toString method on the Function object itself. If its not found there, it'll look up the prototype chain and find it on Function.prototype. Therefore, both expressions essentially point to the same method. Logs true

Key points to understand:

  1. Objects inherit methods from their prototype chain. Regular objects inherit from Object.prototype, while functions inherit from Function.prototype.

  2. Object, Function, and other built-in constructors are themselves functions, so they inherit from Function.prototype.

  3. The toString method on most objects comes from Object.prototype.toString.

  4. The toString method on functions comes from Function.prototype.toString.

  5. When you access a method directly on a constructor (like Object.toString or Function.toString), you're getting the method from Function.prototype, not from the constructor's own prototype.

  6. 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:

  1. We start with an array items containing two objects, each with an id property.

  2. 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 the id 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 that id.
  3. console.log(items.toString()):

    • When you call toString() on an array, it calls toString() 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]
  4. 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:

  1. Object.groupBy() is a powerful method for grouping array elements. It creates a null-prototype object and lacks the the Object.prototype.toString() method.

  2. The toString() method doesn't provide a very informative representation of objects or complex data structures. It's generally more useful to use JSON.stringify() or custom logging for these kinds of structures.

  3. When called on an array, toString() gives a string representation of the array's elements.

  4. When called on an object, toString() simply returns "[object Object]" unless it's overridden.

6.this

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:

  1. We have an object obj with a property dev and several methods.

  2. obj.a() and obj.b(): These are regular function methods. When called, this refers to the object, so they return 'bfe'.

  3. obj.c(): This is an arrow function. Arrow functions don't have their own this context, so this.dev here refers to the global (or module) scope, which doesn't have a dev property. It will return undefined.

  4. obj.d(): This is a regular function that returns the result of an immediately invoked arrow function. The arrow function captures the this from the outer function, so it returns 'bfe'.

  5. obj.e(): This calls this.b(), which is equivalent to calling obj.b(), so it returns 'bfe'.

  6. obj.f(): This returns the function b itself (without calling it). obj.f()() then calls this returned function, and since it's called without a context, this will be undefined or global in non-strict mode. It will likely return undefined.

  7. obj.g(): This calls this.c(), which is equivalent to obj.c(). As explained earlier, this will return undefined.

  8. obj.h(): This returns the arrow function c itself. obj.h()() then calls this returned arrow function. Arrow functions keep their original this binding, which in this case is the global context, so it will return undefined.

  9. obj.i(): This returns a new arrow function. When this returned function is called with obj.i()(), it uses the this from where it was defined (which is the obj), 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:

  1. We define a Site class with a property name set to 'BFE'.

  2. The getHandle() method returns an object with its own name property and three methods to get the name.

  3. We create an instance of Site called site.

  4. We call each of the getName methods through site.getHandle().

Now, let's look at each method call:

  1. 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 by getHandle()).
    • This object has name: 'bigfrontend', so it returns 'bigfrontend'.
  2. site.getHandle().getName2()

    • getName2 is defined as an arrow function.
    • Arrow functions do not have their own this context. They inherit this from the enclosing scope.
    • In this case, the enclosing scope is the Site instance, where name is 'BFE'.
    • So this returns 'BFE'.
  3. 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 by getHandle()).
    • 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:

  1. Method definitions and their effect on this binding.
  2. The behavior of arrow functions with regard to this.
  3. 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.

19. this

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:

  1. console.log(obj.a) Output: 1 Explanation: This simply logs the value of the a property of obj.

  2. obj.b() Output: 1 Explanation: When called as a method, this inside b refers to obj.

  3. ;(obj.b)() Output: 1 Explanation: The parentheses don't change the context, so this still refers to obj.

  4. const b = obj.b; b() Output: undefined Explanation: When b is called as a standalone function, this becomes the global object (or undefined in strict mode), which doesn't have an a property.

  5. obj.b.apply({a: 2}) Output: 2 Explanation: apply explicitly sets this to the provided object {a: 2}.

  6. obj.c() Output: 1 Explanation: Method shorthand syntax behaves the same as b().

  7. obj.d() Output: undefined Explanation: Arrow functions capture this from their enclosing lexical scope at the time of their creation. Here, this refers to the global object (or undefined in modules).

  8. ;(obj.d)() Output: undefined Explanation: Same as above, the arrow function's this is not affected by how it's called.

  9. obj.d.apply({a:2}) Output: undefined Explanation: apply doesn't affect arrow functions' this binding.

  10. obj.e() Output: undefined Explanation: e is an immediately invoked function expression (IIFE) that returns an arrow function. The IIFE's this is the global object, so the returned arrow function's this is also the global object.

  11. ;(obj.e)() Output: undefined Explanation: Same as above, parentheses don't change the behavior.

  12. obj.e.call({a:2}) Output: undefined Explanation: call doesn't affect the arrow function's this binding.

  13. obj.f()() Output: 1 Explanation: f is a regular function, so its this is obj. It returns an arrow function that captures this this, so it logs obj.a which is 1.

  14. ;(obj.f())() Output: 1 Explanation: Same as above, parentheses don't change the behavior.

  15. obj.f().call({a:2}) Output: 1 Explanation: The arrow function returned by f has already captured this as obj, so call doesn't affect its this 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, and bind methods can change this 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.

33. this II

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:

  1. console.log(obj.b()) Output: 1 When called as a method on obj, this inside b() refers to obj, so this.a is 1.

  2. console.log((true ? obj.b : a)()) Output: undefined The ternary operator returns obj.b, but it's then called without any context. This is equivalent to let temp = obj.b; temp(). In this case, this becomes undefined in strict mode or the global object in non-strict mode. Either way, this.a is undefined.

  3. console.log((true, obj.b)()) Output: undefined The comma operator evaluates both operands and returns the last one. Similar to #2, this detaches b from obj before calling it, so this is lost.

  4. console.log((3, obj['b'])()) Output: undefined This is the same as #3, just using bracket notation to access b. The function is still called without context.

  5. console.log((obj.b)()) Output: 1 Parentheses don't affect the this binding. obj.b is called as a method on obj, so this is obj.

  6. console.log((obj.c = obj.b)()) Output: undefined This assigns obj.b to a new property c on obj, but then immediately calls the result of this assignment. The assignment expression returns the assigned value (the function), but detached from obj. So it's called without context, similar to #2 and #3.

Key points to understand:

  1. When a function is called as a method (like obj.b()), this inside the function refers to the object it was called on.
  2. 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).
  3. The way a function is called matters more than how it's defined when determining this.
  4. Parentheses around a method call don't affect this binding.
  5. 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

41. this III

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:

  1. const obj = { ... }

    • This creates an object with properties a, b, c, d, and e.
  2. a: 1

    • Simple property assignment.
  3. b: this.a + 1

    • In this context, this refers to the global object (or undefined in strict mode), not to obj.
    • this.a is undefined, so this.a + 1 evaluates to NaN.
  4. 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.
  5. d() { return this.a + 1 }

    • This is a method. When called as obj.d(), this will refer to obj.
    • It will return obj.a + 1, which is 2.
  6. e() { return (() => this.a + 1)() }

    • This method returns the result of an immediately invoked arrow function.
    • The arrow function captures this from the e method, where this refers to obj.
    • It will return obj.a + 1, which is 2.

Now, let's look at the console.log statements:

  1. console.log(obj.b)

    • Outputs: NaN
    • This is the value of b computed when the object was created.
  2. console.log(obj.c())

    • Outputs: NaN
    • The arrow function c uses this from the global scope, where a is undefined.
  3. console.log(obj.d())

    • Outputs: 2
    • When called as a method, this refers to obj, so it returns 1 + 1.
  4. console.log(obj.e())

    • Outputs: 2
    • The arrow function inside e captures this from the method scope, which is obj.

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

49. this IV

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:

  1. var bar = 1

    • This declares a global variable bar with value 1.
  2. function foo() { return this.bar++ }

    • This defines a function foo that increments and returns this.bar.
  3. const a = { ... }

    • This creates an object a with properties bar, foo1, and foo2.

Now, let's look at each console.log statement:

  1. console.log(a.foo1.call())

    • call() without arguments sets this 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
  2. console.log(a.foo1())

    • This calls foo1 as a method of a, so this refers to a.
    • It increments and returns a.bar (10), then sets it to 11.
    • Output: 10
  3. console.log(a.foo2.call())

    • call() is used on foo2, but foo2 internally calls foo() 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
  4. console.log(a.foo2())

    • This calls foo2 as a method of a, but foo2 internally calls foo() 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

So, the final output will be:

1
10
2
3

Key points:

  1. The value of this is determined by how a function is called, not where it's defined.
  2. Using call() without arguments or with null/undefined sets this to the global object in non-strict mode.
  3. When a function is called as a method (object.method()), this refers to the object.
  4. When a function is called without any context (like inside foo2), this defaults to the global object in non-strict mode.
  5. The global bar and a.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.

97. this V

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:

  1. 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
  2. The log method uses this.list.forEach() to iterate over the list array.

  3. Inside forEach, there's an anonymous function that takes each item as an argument.

  4. This function attempts to log this.prefix + item for each item in the list.

  5. 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.

107. this VI

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:

  1. We define an object a with two properties:

    • dev: a string with the value 'BFE.dev'
    • update: an arrow function that takes a name parameter and attempts to update this.dev
  2. We call a.update('bigfrontend.dev'), attempting to update the dev property.

  3. 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:

  1. Arrow functions do not have their own this context. Instead, they inherit this from the enclosing lexical scope at the time they are defined.

  2. In this case, the arrow function is defined in the global scope (or whatever scope a is defined in), not as a method of a.

  3. Therefore, when this.dev = name is executed inside the arrow function, it's not updating the dev property of a. 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 is undefined.
  4. The original dev property of a 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:

  1. The behavior of this in different function types.
  2. How arrow functions inherit this from their enclosing lexical scope.
  3. The difference between arrow functions and regular functions when used as object methods.
  4. 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.

117. this VII

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()
  1. Class A:

    • It has a static property dev set to 'BFE'.
    • It also has an instance property dev set to 'bigfrontend'.
  2. Class B:

    • It extends class A, inheriting its properties and methods.
    • It has an instance method log() that logs this.dev.
    • It also has a static method log() that logs this.dev.
  3. 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.
  4. 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.

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:

  1. 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 methods foo and bar.

  2. obj.foo();

    • This is a straightforward method call on the object.
    • this inside foo refers to obj.
    • Output: 'BFE'
  3. (obj.foo)();

    • The parentheses around obj.foo don't change anything here.
    • It's still a method call on the object.
    • this inside foo still refers to obj.
    • Output: 'BFE'
  4. (obj.foo || obj.bar)();

    • This is where things get tricky.
    • obj.foo || obj.bar is evaluated first. Since obj.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 be undefined.
    • Either way, this.msg will be undefined.
    • Output: undefined

So, the final output will be:

BFE
BFE
undefined

Key points:

  1. Method calls (like obj.foo()) set this to the object the method is called on.
  2. Parentheses around a method reference ((obj.foo)()) don't affect the this binding.
  3. When a method is passed around or used in an expression (like obj.foo || obj.bar), it loses its this binding.
  4. Functions called without any context (like in the third case) have this set to the global object in non-strict mode, or undefined 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:

  1. 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 variable a 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 (or undefined in strict mode).
  • First console.log:
    • Local a is undefined due to hoisting
    • this.a is 1 (global a)
    • undefined + 1 results in NaN
  • a = '2' assigns the local a
  • Second console.log:
    • Local a is now '2'
    • this.a is still 1
    • '2' + 1 results in '21' (string concatenation)
  1. 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 global name property.
  • implicitly coerced name to string "1" The output will be:
NaN
"21"
"undefined1"
"21"

Key points:

  1. Variable declarations with var are hoisted, but initializations are not.
  2. this in a regular function call refers to the global object (in non-strict mode).
  3. Global variables become properties of the global object.
  4. 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.

100. var vs let

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)
  1. var a = 'BFE' This declares a variable a using var and assigns it the value 'BFE'.

  2. let b = 'bigfrontend' This declares a variable b using let and assigns it the value 'bigfrontend'.

  3. console.log(this.a) This attempts to log the value of a as a property of this.

  4. console.log(this.b) This attempts to log the value of b as a property of this.

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:

  1. Variables declared with var in the global scope become properties of the global object (which is window in browsers or global in Node.js).

  2. Variables declared with let (or const) do not become properties of the global object, even when declared in the global scope.

  3. The behavior of this depends on where this code is run:

    • In a browser's global scope, this refers to the window object.
    • In Node.js at the top level of a module, this is equivalent to module.exports.
    • In strict mode or in a module, this may be undefined.

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)
  1. We define a function A(). In JavaScript, functions can be used as constructors when called with the new keyword.

  2. Inside the function, we set two properties on this:

    • this.dev1 = 'BFE'
    • this.dev2 = 'dev'
  3. However, the function then explicitly returns an object:

    return {
      dev1: 'bigfrontend',
    }
    
  4. We create a new instance of A using the new keyword: const a = new A()

  5. Now, here's the key point: When a constructor function returns an object, that object becomes the result of the new expression, instead of the this object that was being built up.

  6. So, a is not the object that had dev1: 'BFE' and dev2: 'dev'. Instead, a is the object that was explicitly returned: { dev1: 'bigfrontend' }.

  7. console.log(a.dev1) will output 'bigfrontend', because that's the dev1 property of the returned object.

  8. console.log(a.dev2) will output undefined, because the returned object doesn't have a dev2 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:

  1. A property with the Symbol as its key and 'BFE' as its value.
  2. 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:

  1. Promise executors run synchronously. .then callbacks are added to the microtask queue and setTimeout callbacks are added to the macrotask queue. The microtask is executed firstly before the macrotask.
  2. .then only takes the callback as parameter. Other type parameter will be ignored. If the .then callback does not return, a undefined will be passed forward.
  3. .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. 1 is logged (synchronous)
  2. 2 is logged (synchronous, inside Promise executor)
  3. 3 is logged (synchronous, inside Promise executor)
  4. 4 is logged (synchronous)
  5. 7 is logged (synchronous)
  6. The main script finishes, so the event loop checks the microtask queue
  7. 5 is logged (from the first .then() callback)
  8. 6 is logged (from the second .then() callback)
  9. The event loop checks the macrotask queue
  10. 9 is logged (from the setTimeout with 0ms delay)
  11. 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:

  1. Synchronous code runs immediately.
  2. Promise executors run synchronously.
  3. .then() callbacks are added to the microtask queue.
  4. setTimeout callbacks are added to the macrotask queue.
  5. Microtasks are processed before macrotasks.
  6. 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:

  1. We create a new Promise. The executor function (the function passed to the Promise constructor) runs immediately.

  2. 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.
  3. The key point here is that once a promise is settled (either resolved or rejected), its state cannot change. The first resolve or reject call determines the promise's final state and value.

  4. 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).
  5. Since our promise was resolved (with the value 1), the first function passed to .then() will be called.

  6. 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:

  1. A promise can only be settled once. Once it's resolved or rejected, subsequent calls to resolve() or reject() are ignored.

  2. The first resolve() or reject() call determines the promise's fate.

  3. In this case, resolve(1) is the first call, so it sets the promise's state to fulfilled with a value of 1.

  4. The .then() method's first argument (the fulfillment handler) is called because the promise was fulfilled, not rejected.

  5. 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:

  1. Promise.resolve(1):

    • This creates a resolved promise with the value 1.
  2. First .then(() => 2):

    • The first then callback doesn't use the value 1 from the resolved promise. Instead, it returns 2.
    • The promise chain now has the value 2.
  3. Second .then(3):

    • This then is passed 3, 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.
  4. Third .then((value) => value * 3):

    • This then takes the value 2 from the previous step and multiplies it by 3.
    • The promise chain now has the value 6.
  5. 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 previous then. The value 6 is passed through unchanged.
    • The promise chain still has the value 6.
  6. 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 the then(3) and then(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:

  1. First .then((val) => {...}):

    • The resolved value from Promise.resolve(1) is 1.
    • It logs 1.
    • Returns val + 1, which is 2.
  2. Second .then((val) => {...}):

    • Receives the value 2 from the previous step.
    • Logs 2.
    • Doesn't return anything, so the next val is undefined.
  3. 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).
  4. Fourth .then((val) => {...}):

    • Receives undefined from the previous .then.
    • Logs undefined.
    • Returns a rejected promise with the value 4 using Promise.reject(4).
  5. .catch((val) => {...}):

    • Catches the rejection from the previous step.
    • Logs 4.
  6. .finally((val) => {...}):

    • .finally doesn't receive any argument, so val is undefined.
    • Logs undefined.
    • Returns 10, but this value is ignored for the purposes of the finally block itself.
    • The next .then receives the resolved value 10.
  7. Last .then((val) => {...}):

    • Unable to receive the value 10 from the resolved promise after the finally.
    • Logs undefined.

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:

  1. 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.
  2. 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.
  3. 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.

Key points to remember about Promise.all():

  1. It takes an iterable (usually an array) of promises as input.
  2. It returns a single Promise that resolves to an array of the results of the input promises.
  3. The returned promise resolves when all input promises have resolved, or rejects if any of the input promises reject.
  4. If the input array is empty, it resolves immediately with an empty array.
  5. Non-promise values in the input array are treated as already resolved promises.
  6. 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:

  1. Promise.resolve(1)

    • Creates a Promise that immediately resolves with the value 1.
  2. .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.
  3. .catch((error) => { ... })

    • Catches the 'error' from the rejected Promise in the finally.
    • Logs 'error' to the console.
    • Throws a new error 'error2'.
  4. .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.
  5. .then(console.log)

    • This then would log the result of the previous operation, but there isn't one because the previous finally doesn't pass anything along.
  6. .catch(console.log)

    • Catches the 'error2' thrown in the earlier catch block.
    • Logs 'error2' to the console.

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 why data is undefined 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 subsequent catch 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:

  1. console.log(1)

    • This is synchronous and executes immediately.
  2. First setTimeout with 10ms delay

    • This is queued to run after 10ms.
  3. Second setTimeout with 0ms delay

    • This is queued to run in the next event loop iteration.
  4. 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.
  5. console.log(13)

    • This runs synchronously.
  6. 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 logs undefined.
    • finally logs 12.
  7. 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:

  1. Synchronous code runs first.
  2. Promise callbacks (then, catch, finally) are microtasks and run after all synchronous code but before the next event loop iteration.
  3. setTimeout callbacks are macrotasks and run in subsequent event loop iterations.
  4. Even a 0ms setTimeout runs after microtasks.
  5. In a promise chain, if a promise is rejected, it skips then callbacks until it finds a catch.
  6. After a catch, the promise chain continues normally.
  7. 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:

  1. We create a new Promise called promise:
const promise = new Promise((resolve, reject) => {
  // ... inner code ...
})
  1. 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
  1. We resolve the outer promise with promise2:
resolve(promise2)
  1. Finally, we chain a .then() method to the outer promise:
promise.then(console.log)

Now, let's see what happens when this code runs:

  1. promise2 is created. Since it starts with Promise.reject('error'), it will immediately trigger the rejection handler in its .then() method.

  2. This rejection handler logs 2 to the console.

  3. After logging 2, the rejection is considered handled, and promise2 resolves successfully (with no value, which means it resolves to undefined).

  4. The outer promise is resolved with promise2. Since promise2 is already settled (resolved to undefined), this effectively means promise resolves to undefined.

  5. The .then(console.log) on the outer promise will therefore log undefined.

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:

  1. We define createPromise(), which returns a Promise that immediately resolves with the value 1.

  2. We define func1(), which creates a promise and attaches a then handler to log the result, then immediately logs 2.

  3. We define func2(), an async function that awaits the promise from createPromise(), then logs 3.

  4. We log 4.

  5. We call func1().

  6. We call func2().

Now, let's look at the execution order:

  1. console.log(4) executes first, logging 4.

  2. 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.
  3. 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.
  4. The main script finishes executing.

  5. In the next event loop iteration:

    • The then handler from func1() runs, logging 1.
    • The remainder of func2() runs, logging 3.

Therefore, the output will be:

4
2
1
3

This code demonstrates several important concepts in JavaScript:

  1. Asynchronous execution and the event loop.
  2. The difference between then() callbacks and async/await syntax.
  3. How promises are scheduled and resolved.
  4. 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:

  1. We create four promises:

    • promise1, promise2, and promise3 are immediately resolved with values 1, 2, and 3 respectively.
    • promise4 is immediately rejected with value 4.
  2. We define an async function promiseAll:

    • It uses Promise.all() to wait for two groups of promises.
    • group1 waits for promise1 and promise2 to resolve.
    • group2 waits for promise3 and promise4 to resolve.
    • The function attempts to return an array containing both groups.
  3. We call promiseAll() and chain .then(console.log) and .catch(console.log) to handle the result or error.

Now, let's consider the execution:

  1. group1 = await Promise.all([promise1, promise2]):

    • This will succeed because both promises resolve.
    • group1 will be [1, 2].
  2. group2 = await Promise.all([promise3, promise4]):

    • This will fail because promise4 is rejected.
    • When any promise in Promise.all() is rejected, the entire Promise.all() is immediately rejected.
    • This rejection will be caught by the catch block of the async function.
  3. Because of the rejection in step 2, the function will not reach the return statement.

  4. The rejection from Promise.all([promise3, promise4]) will be propagated to the catch method chained to promiseAll().

Therefore, the output will be:

4

This is because:

  • The rejection value of promise4 (which is 4) is caught by the catch method.
  • console.log in the catch method prints this rejection value.

Key points demonstrated by this code:

  1. Promise.all() resolves only if all promises in the array resolve, and rejects if any promise rejects.
  2. In an async function, a rejected promise will cause the function to immediately return a rejected promise, skipping any code after the rejection.
  3. Error handling in promise chains: rejections are caught by catch methods.
  4. 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:

  1. console.log(4)

    • This is synchronous and executes immediately.
  2. setTimeout(function(){ console.log(5) }, 0)

    • This schedules a callback to run in the next event loop iteration (macrotask).
  3. async1() is called

    • console.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.
  4. New Promise creation

    • console.log(6) runs synchronously inside the Promise executor.
    • resolve() is called, scheduling the then callback as a microtask.
  5. console.log(8)

    • This runs synchronously.
  6. After all synchronous code, microtasks are processed:

    • The suspended part of async1() resumes, logging 2.
    • The Promise's then callback runs, logging 7.
  7. 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:

  1. Synchronous code runs first, in the order it appears.
  2. async functions run synchronously until the first await, then the rest is scheduled as a microtask.
  3. Promise callbacks (then, catch, finally) are microtasks and run after all synchronous code but before the next event loop iteration.
  4. setTimeout callbacks are macrotasks and run in subsequent event loop iterations, after all microtasks are completed.
  5. 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:

  1. const promise = Promise.resolve() creates a resolved promise.

  2. function f() returns this promise.

  3. 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.
  4. async function b() { return await f() }:

    • This async function awaits the result of f() before returning.
    • The await keyword unpacks the promise returned by f(), then repacks it into a new promise (because it's an async function).
  5. function c() { return f() }:

    • This is a regular function that simply returns the result of f(), which is the original promise.

Now, let's look at the comparisons:

  1. console.log(a() === b())

    • This logs false.
    • Both a() and b() return promises, but they are different promise objects.
    • Even though they resolve to the same value, they are not the same promise instance.
  2. console.log(b() === c())

    • This logs false.
    • For the same reason as above, b() returns a new promise, while c() returns the original promise.
  3. console.log(a() === c())

    • This logs false.
    • a() returns a new promise (because it's an async function), while c() returns the original promise.

Key points to understand:

  1. Async functions always return promises. If the return value is not a promise, it gets wrapped in one.
  2. The await keyword unwraps a promise, and then the async function rewraps the result in a new promise.
  3. Regular functions (like c) that return promises don't create new promise objects; they just pass along the existing promise.
  4. Promise equality (===) compares object identity, not the resolved values of the promises.
  5. 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 value 1. Since await is used, JavaScript waits for the promise to resolve or reject. In this case, it rejects, and control immediately jumps to the catch block.
  • catch (e): The rejection (1) is caught here, and e is logged, so 1 will be printed to the console.
  • Return value: After the catch block executes, the function completes without returning anything, which means it returns undefined.

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 using await. The rejection is not immediately caught by the catch block within b. Instead, the rejected promise is returned, and any code that calls b() is responsible for handling the rejection.
  • catch (e): The catch block will not be executed because the rejection happens after the return statement, meaning the error is not caught within the function b.
  • 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 calls a(). Since a() handles its rejection internally and returns undefined, this await will resolve with undefined without throwing an error.
  • await b(): This calls b(), which returns a rejected promise. Since start() doesn't have a try...catch around this call, the rejected promise will propagate, and the start() function will return a rejected promise with the value 2.

start() Execution

start()
  • start() begins by calling a(). This results in 1 being logged to the console, as a() catches the rejection.
  • Next, b() is called. Since b() returns a rejected promise and doesn't catch the error, start()'s promise will be rejected with 2. If there is no external error handling (e.g., another try...catch or catch block chained to the promise returned by start()), this rejection will result in an unhandled promise rejection.

Summary:

  • Output:
    • 1 will be printed from the catch block in a().
    • Nothing will be printed for b() because the rejection is not caught within b().
  • Behavior:
    • a() handles the rejection internally, so start() continues execution.
    • b() doesn't handle the rejection, so start() results in an unhandled promise rejection with the value 2, 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:

  1. console.log(1)

    • This runs immediately, outputting 1.
  2. window.onmessage = () => { console.log(2) }

    • This sets up an event listener for the 'message' event.
  3. Promise.resolve().then(() => { console.log(3) })

    • This creates a microtask that will log 3.
  4. setTimeout(() => { console.log(4) }, 0)

    • This schedules a macrotask to log 4 after a minimum delay.
  5. console.log(5)

    • This runs immediately, outputting 5.
  6. 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.
  7. console.log(6)

    • This runs immediately, outputting 6.

Now, let's consider the execution order:

  1. All synchronous code runs first, so we see outputs: 1, 5, 6.

  2. After synchronous code, microtasks are processed. The Promise's then callback is a microtask, so 3 is logged next.

  3. 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:

  1. Synchronous code runs first.
  2. Microtasks (like Promise callbacks) run after all synchronous code but before the next event loop iteration.
  3. Macrotasks (like setTimeout callbacks and most DOM events) run in subsequent event loop iterations.
  4. postMessage triggers an event asynchronously, even if called with a delay of 0.
  5. 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:

  1. console.log(1)

    • This runs immediately, outputting 1.
  2. document.body.addEventListener('click', () => { console.log(2) })

    • This sets up an event listener for the 'click' event on the body.
  3. Promise.resolve().then(() => { console.log(3) })

    • This creates a microtask that will log 3.
  4. setTimeout(() => { console.log(4) }, 0)

    • This schedules a macrotask to log 4 after a minimum delay.
  5. console.log(5)

    • This runs immediately, outputting 5.
  6. document.body.click()

    • This triggers the click event on the body synchronously.
  7. console.log(6)

    • This runs immediately, outputting 6.

Now, let's consider the execution order:

  1. All synchronous code runs first, so we see outputs: 1, 5, 6.

  2. The document.body.click() is a synchronous method call

  3. After synchronous code, microtasks are processed. The Promise's then callback is a microtask, so 3 is logged next.

  4. 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:

  1. Synchronous code runs first.
  2. Microtasks (like Promise callbacks) run after all synchronous code but before the next event loop iteration.
  3. 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:

  1. console.log(1)

    • This runs immediately, outputting 1.
  2. const mc = new MessageChannel()

    • This creates a new MessageChannel with two ports: port1 and port2.
  3. mc.port1.onmessage = () => { console.log(2) }

    • This sets up a message event handler on port1.
  4. Promise.resolve().then(() => { console.log(3) })

    • This creates a microtask that will log 3.
  5. setTimeout(() => { console.log(4) }, 0)

    • This schedules a macrotask to log 4 after a minimum delay.
  6. console.log(5)

    • This runs immediately, outputting 5.
  7. 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.
  8. console.log(6)

    • This runs immediately, outputting 6.

Now, let's consider the execution order:

  1. All synchronous code runs first, so we see outputs: 1, 5, 6.

  2. After synchronous code, microtasks are processed. The Promise's then callback is a microtask, so 3 is logged next.

  3. 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:

  1. Synchronous code runs first.
  2. Microtasks (like Promise callbacks) run after all synchronous code but before the next event loop iteration.
  3. MessageChannel operations are asynchronous and are queued as tasks.
  4. Tasks (like setTimeout callbacks and MessageChannel events) run in subsequent event loop iterations.
  5. 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:

  1. Generator Function Definition:

    function* gen() {
      yield 2 * (yield 100)
    }
    

    This defines a generator function that uses the yield keyword twice.

  2. Creating a Generator Instance:

    const generator = gen()
    

    This creates an instance of the generator.

  3. 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
  4. 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
  5. 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:

  1. The first next() call starts the generator and runs until the first yield.
  2. Subsequent next() calls resume the generator from where it last paused.
  3. The value passed to next() becomes the result of the yield expression where the generator last paused.
  4. When there's nothing left to yield, next() returns an object with value: undefined and done: true.

This quiz tests understanding of:

  • Generator functions and their behavior
  • How yield works in generators
  • The relationship between next() calls and yield 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:

  1. function* gen() defines a generator function. Generator functions use yield to pause execution and return a value.

  2. 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 a finally block, execution continues there.
    • yield 4 is never reached due to the return statement before it.
  3. The finally block:

    • This block always executes, even if there's a return in the try block.
    • yield 5 returns 5 and pauses the generator.
    • return 6 ends the generator.
    • yield 7 is never reached due to the return statement before it.
  4. console.log([...gen()]):

    • The spread operator ... is used with the generator.
    • This creates an array with all yielded values from the generator.

Now, let's look at the output:

;[1, 2, 5]

Here's why:

  • The generator yields 1, then 2.
  • The return 3 in the try block doesn't add to the array (spread ignores return values).
  • The finally block executes, yielding 5.
  • The return 6 in the finally 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 a return.
  • 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:

  1. const g = gen() creates a generator object.

  2. console.log(g.next().value):

    • This yields 1.
    • Output: 1
  3. console.log(g.next().value):

    • This enters the try block and yields 2.
    • Output: 2
  4. 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
  5. 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
  6. console.log(g.next().value):

    • The generator is now completed, so the next call to g.next() returns undefined.
    • Output: undefined

So, the final output will be:

1
2
4
6
undefined

Key points to note:

  1. The yield 3 and yield 5 are never reached due to the return(6) call.
  2. The finally block always executes, even when return() is called on the generator.
  3. After return() is called, subsequent next() calls always return {value: undefined, done: true}.
  4. The return(6) call causes the generator to terminate, but not before executing the finally block.
  5. The value passed to return() (6 in this case) becomes the value property of the returned object, overriding any yield in the finally 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:

  1. 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.
  2. 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 to item.
    • console.log(item) prints the first item, which is 1.
    • break immediately exits the loop after the first iteration.
  3. 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) prints 2.
    • break exits the loop after the first iteration.

The output of this code will be:

1
2

Key points to understand:

  1. entries() returns an iterator, not an array. Iterators maintain their state across usage.
  2. The destructuring [, item] in the for...of loop ignores the index and only uses the value.
  3. break in each loop ensures only one item is processed per loop.
  4. The second loop continues from where the first loop left off in the iterator.
  5. 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:

  1. const obj = g()

    • This creates a generator object but doesn't start executing the generator function yet.
  2. obj.next()

    • This starts the execution of the generator function.
    • console.log(1) executes, printing 1.
    • Enters the try block.
    • console.log(2) executes, printing 2.
    • 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.
  3. 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, executing console.log(4) and printing 4.
    • The generator then terminates.

The output of this code will be:

1
2
4

Key points to note:

  1. The console.log(3) and the throw new Error('error') never execute because obj.return() is called before reaching that point.

  2. The finally block always executes, even when the generator is terminated early with return().

  3. If obj.next() had been called again instead of obj.return(), it would have continued execution, printed 3, then thrown an error.

  4. The return() method on a generator object is used to terminate the generator prematurely. It will still trigger finally blocks if they exist.

  5. After obj.return(), the generator is in a completed state. Any further calls to next() 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:

  1. We define two generator functions, genA() and genB().

  2. genA() uses yield [1, 2, 3]:

    • This yields the entire array [1, 2, 3] as a single value.
  3. genB() uses yield* [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.
  4. We call genA().next().value:

    • This creates an instance of the genA generator, immediately calls next() on it, and accesses the value property of the resulting object.
    • Since genA yields the entire array at once, this will return [1, 2, 3].
  5. We call genB().next().value:

    • This creates an instance of the genB generator, immediately calls next() on it, and accesses the value property of the resulting object.
    • Since genB uses yield*, it yields the first value from the array, which is 1.

Therefore, the output will be:

[1, 2, 3]
1

Key points to understand:

  1. yield:

    • Yields a single value.
    • When used with an array, it yields the entire array as one value.
  2. 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.
  3. Generator's next() method:

    • Returns an object with value (the yielded value) and done (boolean indicating if the generator is finished) properties.
  4. Accessing .value on the result of next():

    • Gives us the yielded value directly.

This code demonstrates:

  • The difference between yield and yield* 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:

  1. let a = 1 This initializes a variable a with the value 1.

  2. const b = ++a This uses the pre-increment operator (++a). It increments a by 1 before assigning the value to b. So:

    • a becomes 2
    • b is assigned the new value of a, which is 2
  3. const c = a++ This uses the post-increment operator (a++). It assigns the current value of a to c, then increments a by 1. So:

    • c is assigned the current value of a, which is 2
    • a is then incremented to 3
  4. console.log(a) // 3 At this point, a has been incremented twice, so its value is 3.

  5. console.log(b) // 2 b was assigned the value of a after the first increment, so its value is 2.

  6. console.log(c) // 2 c was assigned the value of a 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:

  1. console.log(0 == 1 == 2)

    • First, 0 == 1 is evaluated, which is false
    • Then, false == 2 is evaluated. false is coerced to 0, so it's 0 == 2
    • This is false
    • Output: false
  2. console.log(2 == 1 == 0)

    • First, 2 == 1 is evaluated, which is false
    • Then, false == 0 is evaluated. false is coerced to 0, so it's 0 == 0
    • This is true
    • Output: true
  3. console.log(0 < 1 < 2)

    • First, 0 < 1 is evaluated, which is true
    • Then, true < 2 is evaluated. true is coerced to 1, so it's 1 < 2
    • This is true
    • Output: true
  4. console.log(1 < 2 < 3)

    • First, 1 < 2 is evaluated, which is true
    • Then, true < 3 is evaluated. true is coerced to 1, so it's 1 < 3
    • This is true
    • Output: true
  5. console.log(2 > 1 > 0)

    • First, 2 > 1 is evaluated, which is true
    • Then, true > 0 is evaluated. true is coerced to 1, so it's 1 > 0
    • This is true
    • Output: true
  6. console.log(3 > 2 > 1)

    • First, 3 > 2 is evaluated, which is true
    • Then, true > 1 is evaluated. true is coerced to 1, so it's 1 > 1
    • This is false
    • Output: false

Key points:

  • JavaScript evaluates these expressions from left to right.
  • Each comparison returns either true or false, which is then used in the next comparison.
  • When true or false is used in a comparison with a number, true is coerced to 1 and false 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:

  1. console.log(1 + 2)

    • Simple addition: 1 + 2 = 3
    • Output: 3
  2. console.log(1 + + 2)

    • The second + is a unary plus, which doesn't change 2
    • 1 + 2 = 3
    • Output: 3
  3. console.log(1 + + + 2)

    • Multiple unary plus operators, still doesn't change 2
    • 1 + 2 = 3
    • Output: 3
  4. console.log(1 + '2')

    • Number + String, coerces to string concatenation
    • Output: '12'
  5. console.log(1 + + '2')

    • The unary plus converts '2' to number 2
    • 1 + 2 = 3
    • Output: 3
  6. console.log('1' + 2)

    • String + Number, coerces to string concatenation
    • Output: '12'
  7. console.log('1' + + 2)

    • The unary plus doesn't affect 2 here
    • String + Number, still string concatenation
    • Output: '12'
  8. console.log(1 + true)

    • true is coerced to 1
    • 1 + 1 = 2
    • Output: 2
  9. console.log(1 + + true)

    • The unary plus doesn't change true's coercion to 1
    • 1 + 1 = 2
    • Output: 2
  10. console.log('1' + true)

    • String concatenation with true
    • Output: '1true'
  11. console.log('1' + + true)

    • The unary plus converts true to 1
    • String concatenation: '1' + 1
    • Output: '11'
  12. console.log(1 + null)

    • null is coerced to 0
    • 1 + 0 = 1
    • Output: 1
  13. console.log(1 + + null)

    • The unary plus converts null to 0
    • 1 + 0 = 1
    • Output: 1
  14. console.log('1' + null)

    • String concatenation with null
    • Output: '1null'
  15. console.log('1' + + null)

    • The unary plus converts null to 0
    • String concatenation: '1' + 0
    • Output: '10'
  16. console.log(1 + undefined)

    • undefined in addition results in NaN
    • Output: NaN
  17. console.log(1 + + undefined)

    • The unary plus on undefined results in NaN
    • 1 + NaN = NaN
    • Output: NaN
  18. console.log('1' + undefined)

    • String concatenation with undefined
    • Output: '1undefined'
  19. console.log('1' + + undefined)

    • The unary plus on undefined results in NaN
    • String concatenation: '1' + NaN
    • Output: '1NaN'
  20. 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:

  1. console.log(typeof null)

    • Output: 'object'
    • This is a known quirk in JavaScript. null is not actually an object, but typeof null returns 'object' due to a historical bug.
  2. console.log(null instanceof Object)

    • Output: false
    • Despite the typeof quirk, null is not an instance of Object.
  3. console.log(typeof 1)

    • Output: 'number'
    • 1 is a primitive number.
  4. console.log(1 instanceof Number)

    • Output: false
    • Primitive numbers are not instances of the Number object.
  5. console.log(1 instanceof Object)

    • Output: false
    • Primitive numbers are not instances of Object.
  6. console.log(Number(1) instanceof Object)

    • Output: false
    • Number(1) returns a primitive number, not a Number object.
  7. console.log(new Number(1) instanceof Object)

    • Output: true
    • new Number(1) creates a Number object, which is an instance of Object.
  8. console.log(typeof true)

    • Output: 'boolean'
    • true is a primitive boolean.
  9. console.log(true instanceof Boolean)

    • Output: false
    • Primitive booleans are not instances of the Boolean object.
  10. console.log(true instanceof Object)

    • Output: false
    • Primitive booleans are not instances of Object.
  11. console.log(Boolean(true) instanceof Object)

    • Output: false
    • Boolean(true) returns a primitive boolean, not a Boolean object.
  12. console.log(new Boolean(true) instanceof Object)

    • Output: true
    • new Boolean(true) creates a Boolean object, which is an instance of Object.
  13. console.log([] instanceof Array)

    • Output: true
    • An array is an instance of Array.
  14. console.log([] instanceof Object)

    • Output: true
    • Arrays are also instances of Object (inheritance).
  15. console.log((() => {}) instanceof Object)

    • Output: true
    • Functions are instances of Object in JavaScript.

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 (like new Number(1)) are instances of both their type and Object.
  • Arrays and functions are objects in JavaScript.
  • The behavior of null with typeof 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:

  1. console.log(Function instanceof Object)

    • Output: true
    • In JavaScript, functions are objects.
    • Function is an instance of Object because all functions inherit from Object.prototype.
  2. console.log(Object instanceof Function)

    • Output: true
    • Object is a constructor function.
    • All constructor functions in JavaScript are instances of Function.
  3. 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.
  4. console.log(Object instanceof Object)

    • Output: true
    • Object is a function object (constructor function).
    • All function objects are instances of Object.

Key points to understand:

  1. In JavaScript, functions are first-class objects. They are instances of Object.

  2. Function is the constructor for all functions, including itself and Object.

  3. Object is a constructor function, which makes it an instance of Function.

  4. The instanceof operator checks if the prototype property of the constructor appears anywhere in the prototype chain of the object.

  5. This creates a circular relationship:

    • Function is an instance of Object
    • Object is an instance of Function
    • 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:

  1. console.log(1 / 0) Output: Infinity

    • Division by zero results in Infinity in JavaScript.
  2. console.log(-1 / 0) Output: -Infinity

    • Negative number divided by zero gives -Infinity.
  3. console.log(0 / 0) Output: NaN

    • Zero divided by zero is undefined, so JavaScript returns NaN (Not a Number).
  4. console.log(0 === -0) Output: true

    • JavaScript treats 0 and -0 as equal under normal comparison.
  5. console.log(Object.is(0, -0)) Output: false

    • Object.is() can distinguish between 0 and -0.
  6. console.log(Object.is(0, Math.round(-0.5))) Output: false

    • Math.round(-0.5) returns -0, which Object.is() distinguishes from 0.
  7. console.log(Object.is(0, Math.round(0.5))) Output: false

    • Math.round(0.5) returns 1, not 0.
  8. console.log(0 * Infinity) Output: NaN

    • Multiplying 0 by Infinity is indeterminate, so it results in NaN.
  9. console.log(Infinity / Infinity) Output: NaN

    • Dividing Infinity by Infinity is indeterminate, resulting in NaN.
  10. console.log(Object.is(0, Math.sign(0))) Output: true

    • Math.sign(0) returns +0, which is indistinguishable from 0 even with Object.is().
  11. console.log(Object.is(0, Math.sign(-0))) Output: false

    • Math.sign(-0) returns -0, which Object.is() can distinguish from +0.
  12. console.log(1 / -0) Output: -Infinity

    • Division by negative zero results in negative Infinity.
  13. console.log(1 / 0) Output: Infinity

    • Division by positive zero results in positive Infinity.
  14. console.log(1n / 0n) Output: Uncaught RangeError: Division by zero

    • BigInt division by zero throws an error, unlike regular number division.

Key takeaways:

  1. Division by zero with regular numbers results in Infinity or -Infinity, not an error.
  2. JavaScript distinguishes between +0 and -0 in some operations, but treats them as equal in comparisons.
  3. Object.is() can distinguish between +0 and -0, unlike ===.
  4. Operations involving Infinity can result in NaN when the result is indeterminate.
  5. Math.sign() preserves the sign of zero, which can be detected by Object.is().
  6. 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:

  1. 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 of a) and then increments a to 2.
    • Then, we add the new value of a (which is 2)
    • So, 1 + 2 = 3
  2. 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 of b
    • So it's equivalent to b + (+b)
    • Both b and +b evaluate to 1
    • Therefore, 1 + 1 = 2
  3. 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 of c) and then decrements c to 0.
    • Then, we subtract the new value of c (which is 0)
    • So, 1 - 0 = 1
  4. let d = 1 console.log(d - - - d)

    • Output: 0
    • This is parsed as d - (-(-d))
    • The rightmost -d negates d, resulting in -1
    • The next - negates that, resulting in 1
    • So it's equivalent to d - (1)
    • Therefore, 1 - (1) = 0

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. 1 / 0: In JavaScript, this results in Infinity.

  2. 0 / 0: This is an undefined mathematical operation, resulting in NaN (Not a Number).

  3. -1 / 0: This results in -Infinity.

  4. 1 / 0 * 0: Infinity * 0 is undefined mathematically, so it results in NaN.

  5. 1 / 0 * 1: Infinity * 1 is still Infinity.

  6. 1 / 0 * -1: This is equivalent to -Infinity.

  7. 1 / 0 * 1 + 1 / 0 * 1: This is Infinity + Infinity, which is still Infinity.

  8. 1 / 0 * 1 - 1 / 0 * 1: This is Infinity - Infinity, which is undefined mathematically, resulting in NaN.

  9. 1 / 0 * 1 * (1 / 0 * 1): This is Infinity * Infinity, which is Infinity.

  10. 1 / 0 * 1 / (1 / 0 * 1): This is Infinity / Infinity, which is undefined mathematically, resulting in NaN.

  11. 0 / Infinity: In mathematics, this is interpreted as a limit approaching 0, so JavaScript returns 0.

  12. 0 * Infinity: This is undefined mathematically, resulting in NaN.

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 in NaN.
  • Operations with Infinity follow mathematical rules where possible, but undefined operations (like Infinity - Infinity) result in NaN.
  • 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:

  1. +[]:

    • An empty array coerced to a number is 0.
  2. !+[]:

    • !0 is true.
  3. ~~!+[]:

    • ~~true is 1. The double tilde (~~) is a bitwise NOT operation performed twice, effectively coercing to an integer.
  4. (~~!+[])+(~~!+[]):

    • This is 1 + 1 = 2.
  5. (~~!+[])+(~~!+[])+[]:

    • 2 + [] results in string concatenation: "2" (the array is coerced to an empty string).
  6. (~~!+[])+(~~!+[])+[]+(~~!+[]):

    • "2" + 1 = "21" (string concatenation)
  7. +((~~!+[])+(~~!+[])+[]+(~~!+[])):

    • The outer + coerces the string "21" back to a number.

So, the final result is the number 21.

Therefore, console.log(num) will output:

21

Key points:

  1. + can be used for both numeric addition and string concatenation, depending on the types of its operands.
  2. ! negates a value and returns a boolean.
  3. ~~ is a shorthand for converting a value to an integer (similar to Math.floor() for positive numbers).
  4. Empty arrays coerce to 0 when used as numbers, and to "" when used as strings.
  5. 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:

  1. console.log(1 + 1)

    • Simple addition: 1 + 1 = 2
    • Output: 2
  2. console.log(1 + + 1)

    • The second + is a unary plus, which doesn't change 1
    • Equivalent to 1 + 1 = 2
    • Output: 2
  3. console.log(1 + + 1 + 1)

    • Equivalent to 1 + 1 + 1 = 3
    • Output: 3
  4. console.log(1 + + 1 + + 1)

    • Equivalent to 1 + 1 + 1 = 3
    • Output: 3
  5. console.log(1 + + + 1)

    • Multiple unary plus operators, still doesn't change 1
    • Equivalent to 1 + 1 = 2
    • Output: 2
  6. console.log(1 + + '1' + + '1')

    • Unary plus converts '1' to number 1
    • Equivalent to 1 + 1 + 1 = 3
    • Output: 3
  7. console.log('1' + + '1' + + '1')

    • First + is string concatenation
    • Unary plus converts '1' to number 1
    • Equivalent to '1' + 1 + 1 = '111'
    • Output: '111'
  8. console.log('a' + + 'b')

    • Unary plus tries to convert 'b' to a number, resulting in NaN
    • Equivalent to 'a' + NaN = 'aNaN'
    • Output: 'aNaN'
  9. console.log('a' + + 'b' + 'c')

    • Similar to previous, but with 'c' concatenated
    • Equivalent to 'a' + NaN + 'c' = 'aNaNc'
    • Output: 'aNaNc'
  10. 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:

  1. The unary plus (+) attempts to convert its operand to a number.
  2. When used with numbers, unary plus doesn't change the value.
  3. When used with strings that represent numbers, it converts them to actual numbers.
  4. When used with strings that don't represent numbers, it results in NaN.
  5. 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:

  1. console.log(NaN == NaN)

    • Output: false
    • NaN is not equal to itself when using loose equality (==).
  2. console.log(NaN === NaN)

    • Output: false
    • NaN is not equal to itself even when using strict equality (===).
  3. console.log(Object.is(NaN, NaN))

    • Output: true
    • Object.is() considers NaN equal to itself.
  4. console.log([NaN].indexOf(NaN))

    • Output: -1
    • indexOf() uses strict equality (===), so it can't find NaN in the array.
  5. console.log([NaN].includes(NaN))

    • Output: true
    • includes() uses SameValueZero algorithm, which considers NaN equal to itself.
  6. console.log(Math.max(NaN, 1))

    • Output: NaN
    • If any argument to Math.max() is NaN, the result is NaN.
  7. console.log(Math.min(NaN, 1))

    • Output: NaN
    • If any argument to Math.min() is NaN, the result is NaN.
  8. console.log(Math.min(NaN, Infinity))

    • Output: NaN
    • Same as above, NaN as an argument results in NaN.

Key points:

  1. NaN is the only value in JavaScript that is not equal to itself using == or ===.
  2. Object.is() and Array.prototype.includes() use a different comparison algorithm that considers NaN equal to itself.
  3. indexOf() uses strict equality and thus can't find NaN in an array.
  4. Any mathematical operation involving NaN (including comparisons in Math.max() and Math.min()) results in NaN.

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:

  1. We define a as a string 'BFE.dev'.
  2. We define b as a number 1.

Now, let's look at each console.log statement:

  1. console.log(Number.isNaN(a))

    • Number.isNaN() checks if the passed value is exactly the NaN value.
    • The string 'BFE.dev' is not NaN, so this returns false.
  2. console.log(Number.isNaN(b))

    • 1 is a number, but it's not NaN, so this returns false.
  3. 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.
  4. console.log(isNaN(b))

    • 1 is already a number and it's not NaN, so this returns false.

Therefore, the output will be:

false
false
true
false

Key points to understand:

  1. 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 the NaN value.
  2. 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 is NaN or if it becomes NaN after conversion to a number.

This code demonstrates:

  • The difference between Number.isNaN() and the global isNaN() 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:

  1. 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
  2. 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
  3. 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

Key points:

  1. In JavaScript, numbers starting with 0 are interpreted as octal, but only if all digits are valid octal digits (0-7).
  2. If a number starts with 0 but contains 8 or 9, it's treated as a decimal number.
  3. Octal literals (starting with 0) are considered legacy and are not allowed in strict mode or in ECMAScript 6 modules.
  4. 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:

  1. We declare a constant a and assign it the string value 'BFE.dev'.

  2. The if statement condition is !typeof a === 'string'.

  3. The typeof operator has higher precedence than the logical NOT (!) operator, so this condition is evaluated as follows:

    • First, typeof a is evaluated. Since a 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 to false.
    • Finally, we have: false === 'string'
  4. false === 'string' is always false, because we're comparing a boolean to a string using strict equality.

  5. Since the condition evaluates to false, the else 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:

  1. Operator precedence: typeof has higher precedence than !.
  2. Type coercion: how the ! operator coerces its operand to a boolean.
  3. Strict equality (===): no type coercion is performed.
  4. 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:

  1. console.log(!!''):

    • The empty string '' is a falsy value in JavaScript.
    • The first ! negates this falsy value, resulting in true.
    • The second ! negates true, resulting in false.
    • So, !!'' evaluates to false.
  2. console.log(!!{}):

    • An empty object {} is a truthy value in JavaScript.
    • The first ! negates this truthy value, resulting in false.
    • The second ! negates false, resulting in true.
    • So, !!{} evaluates to true.
  3. console.log(!![]):

    • An empty array [] is a truthy value in JavaScript.
    • The first ! negates this truthy value, resulting in false.
    • The second ! negates false, resulting in true.
    • So, !![] evaluates to true.

Therefore, the output will be:

false
true
true

Key points to understand:

  1. Truthy and Falsy values in JavaScript:

    • Falsy values include: false, 0, '' (empty string), null, undefined, and NaN.
    • All other values are considered truthy, including empty objects {} and empty arrays [].
  2. 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.
  3. 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())
  1. First, let's look at the expression inside the parentheses: 'b' + 'a' + + 'b' + 'a'

  2. Breaking it down from left to right:

    • 'b' + 'a' concatenates to 'ba'
    • Now we have: 'ba' + + 'b' + 'a'
  3. 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 becomes NaN (Not a Number)
  4. So now we have: 'ba' + NaN + 'a'

  5. When we add NaN to a string, it gets converted to the string 'NaN' 'ba' + 'NaN' + 'a'

  6. This all concatenates to: 'baNaNa'

  7. 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:

  1. First, we define a function log that takes four parameters: a, b, c, and d.

  2. Inside the function, we have two console.log statements and some manipulations of the arguments object.

  3. We call the function log(1,2,3), passing only three arguments.

Now, let's go through what happens when we run this code:

  1. console.log(a,b,c,d)

    • This will output: 1 2 3 undefined
    • a, b, and c are assigned the values 1, 2, and 3 respectively.
    • d is undefined because no fourth argument was provided.
  2. arguments[0] = 'bfe'

    • This changes the first argument to 'bfe'.
    • In non-strict mode, this also changes the value of a to 'bfe'.
  3. 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.
  4. console.log(a,b,c,d)

    • This will output: 'bfe' 2 3 undefined
    • a is now 'bfe' due to the change in arguments[0].
    • b and c remain unchanged.
    • d is still undefined, despite arguments[3] being set.

Key points:

  • In non-strict mode, changing arguments[i] also changes the corresponding named parameter, and vice versa (for i < 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:

  1. console.log(['0'].map(parseInt))

    • Output: [0]
    • Here, parseInt is called with just one argument '0', so it works as expected.
  2. 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)
  3. console.log(['0','1','1'].map(parseInt))

    • Output: [0, NaN, 1]
    • This is doing:
      • parseInt('0', 0, ['0','1','1']) -> 0
      • parseInt('1', 1, ['0','1','1']) -> NaN (1 is not a valid radix)
      • parseInt('1', 2, ['0','1','1']) -> 1 (valid binary digit)
  4. console.log(['0','1','1','1'].map(parseInt))

    • Output: [0, NaN, 1, 1]
    • This is doing:
      • parseInt('0', 0, ['0','1','1','1']) -> 0
      • parseInt('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)

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 for map, 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 with map, you should wrap it in a function that only passes the string argument:
    ;['0', '1', '1', '1'].map((num) => parseInt(num))
    
    This would correctly return [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:

  1. console.log(parseInt(0.00001))

    • Output: 0
    • parseInt() converts its argument to a string first, so this becomes parseInt("0.00001").
    • parseInt() stops parsing at the first non-digit character (the decimal point).
    • Therefore, it returns 0.
  2. console.log(parseInt(0.000001))

    • Output: 0
    • Same reasoning as above.
  3. 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'.
  4. console.log(parseInt("0x12"))

    • Output: 18
    • The "0x" prefix is recognized as a hexadecimal number.
    • 12 in hexadecimal is 18 in decimal.
  5. console.log(parseInt("1e2"))

    • Output: 1
    • parseInt() stops parsing at the first non-digit character ('e').
    • So it only parses the '1'.

Key points:

  1. parseInt() first converts its argument to a string if it's not already a string.
  2. It parses from left to right and stops at the first character it can't parse as part of the number.
  3. It recognizes and correctly parses hexadecimal numbers with the "0x" prefix.
  4. It does not recognize scientific notation (e.g., "1e2") as a single number.
  5. 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:

  1. 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'.
  2. 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.
  3. console.log(parseInt(' 0100'))

    • Result: 100
    • Explanation: Leading whitespace and zeros are ignored. The function reads this as the decimal number 100.
  4. 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 by parseInt().

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:

  1. 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) and b is 2 (the second element).
  • In the second iteration, a is undefined (because the previous iteration didn't return anything) and b is 3.
  • The function doesn't return anything, so the final result of reduce() is undefined.
  1. 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) and b is 1 (the first element).
  • In the second iteration, a is undefined (because the previous iteration didn't return anything) and b is 2.
  • In the third iteration, a is again undefined and b is 3.
  • The function doesn't return anything, so the final result of reduce() is undefined.

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:
    ;[1, 2, 3].reduce((a, b) => {
      console.log(a, b)
      return a + b
    }, 0)
    
    This would log each step and correctly sum the array.

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:

  1. 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 is d.
  2. 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, and c are all functions, so typeof returns "function" for each.
    • d is not defined in the global scope, so typeof d returns "undefined".
  3. Function c Execution:

    c()
    

    This calls the function stored in c. Inside this function:

    • First console.log(typeof d) outputs: "function" This is because d refers to the function itself within its own scope.
    • d = 'e' attempts to reassign d, but this doesn't affect the function name.
    • Second console.log(typeof d) still outputs: "function" The assignment d = 'e' doesn't change the type of d within the function scope.

Key points to understand:

  1. Function declarations (a) and function expressions (b and c) are all of type "function".

  2. 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 why typeof d outside the function returns "undefined".

  3. Inside the named function expression, the function name (d) is read-only. Attempts to reassign it (like d = 'e') are ignored in strict mode or fail silently in non-strict mode. This is why the type of d remains "function" even after the attempted reassignment.

  4. The variable c holds a reference to the function. The internal name d 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.

  1. console.log(Math.min()) Output: Infinity

    Explanation: When Math.min() is called with no arguments, it returns Infinity. This might seem counterintuitive, but it's designed this way so that any number you compare with this result will be considered smaller.

  2. 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.

  3. console.log(Math.min(1)) Output: 1

    Explanation: When Math.min() is called with a single argument, it simply returns that argument.

  4. 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.

  5. 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 in NaN (Not a Number).

Key points to remember:

  1. Math.min() and Math.max() with no arguments return Infinity and -Infinity respectively.

  2. These functions work as expected when given multiple numeric arguments.

  3. 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
    
  4. 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
    
  5. If any argument is NaN or can't be converted to a number, the result is NaN:

    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:

  1. 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.
  2. 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.
  3. 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():

  1. Strings are always valid JSON and are enclosed in double quotes.
  2. Booleans (true and false) are valid JSON literals.
  3. Numbers are generally valid JSON, but with some exceptions:
    • NaN and Infinity are converted to null.
  4. null is a valid JSON literal.
  5. undefined is handled differently depending on context:
    • In arrays, it's converted to null.
    • As an object property value, the entire property is omitted.
  6. Object properties with undefined values are omitted from the JSON output.
  7. 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:

  1. console.log(JSON.stringify(undefined))

    • JSON.stringify() is a method that converts a JavaScript value to a JSON string.
    • When undefined is passed directly to JSON.stringify(), it returns undefined (not as a string, but as the actual undefined value).
    • When console.log() receives undefined, it prints undefined to the console.
    • Output: undefined
  2. console.log(JSON.stringify([undefined]))

    • Here, we're passing an array containing a single undefined element to JSON.stringify().
    • In JSON, there is no undefined data type. When JSON.stringify() encounters undefined within an array, it replaces it with null.
    • The resulting JSON string will be "[null]".
    • console.log() prints this string to the console.
    • Output: [null]

So, the complete output will be:

undefined
[null]

Key points to understand:

  1. JSON.stringify() handles undefined differently depending on where it appears:

    • When undefined is the root value, JSON.stringify() returns undefined (not a string).
    • When undefined is found within an array, it's converted to null 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.
  2. JSON does not have an undefined type, which is why it's handled specially by JSON.stringify().

  3. This behavior helps maintain valid JSON structure, as undefined is not a valid JSON value, but null is.

  4. It's important to be aware of these transformations when working with JSON.stringify(), especially when dealing with data that might contain undefined 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:

  1. 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.
  2. The returned object's a method:

    • Logs the number 2 to the console.
    • Calls the original a function again.
  3. 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:

  1. a() is called
  2. 1 is logged
  3. The object is returned
  4. The a method of the returned object is called
  5. 2 is logged
  6. a() is called again (go back to step 1)
  7. 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:

  1. 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'.

  2. console.log(foo());

    • This calls the function stored in the variable 'foo'.
    • It will execute successfully and return 'BFE'.
    • Output: 'BFE'
  3. 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:

  1. 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.

  2. The variable to which the function is assigned (in this case, 'foo') is what's used to reference the function from the outside.

  3. 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:

  1. 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.
  2. console.log(foo.length)

    • This logs the length property of the function foo.

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 the length property.

Therefore, console.log(foo.length) will output:

4

Key points to note:

  1. The length property of a function returns the number of parameters in its declaration.
  2. Duplicate parameter names are allowed in non-strict mode (though they should be avoided).
  3. The actual names of the parameters (even if they're undefined) don't affect the length property.
  4. 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:

  1. const a = [0]

    • Initializes an array with one element, 0.
  2. console.log(a.length) Output: 1

    • The array has one element, so its length is 1.
  3. a[3] = 3

    • Assigns the value 3 to index 3, creating a sparse array.
  4. console.log(a.length) Output: 4

    • The length is now 4, as it's always one more than the highest index.
  5. for (let item of a) { console.log(item) } Output:

    0
    undefined
    undefined
    3
    
    • This loop iterates over all items, including empty slots (as undefined).
  6. a.map(item => {console.log(item)}) Output:

    0
    3
    
    • map skips empty slots.
  7. a.forEach(item => {console.log(item)}) Output:

    0
    3
    
    • forEach skips empty slots.
  8. console.log(Object.keys(a)) Output: ['0', '3']

    • Object.keys returns only the indices of existing elements.
  9. delete a[3]

    • Removes the element at index 3, leaving an empty slot.
  10. console.log(a.length) Output: 4

    • Deleting an element doesn't change the array's length.
  11. a[2] = 2

    • Assigns 2 to index 2, filling one of the empty slots.
  12. a.length = 1

    • Explicitly sets the array length to 1, truncating the array.
  13. 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:

  1. Array length is always one more than the highest index.
  2. Assigning to an index higher than the current length creates a sparse array.
  3. Different array methods handle empty slots differently:
    • for...of and map treat empty slots as undefined.
    • forEach skips empty slots.
  4. Object.keys only returns indices of existing elements.
  5. delete leaves an empty slot but doesn't change the array length.
  6. 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:

  1. const arr = [1,2]

    • This creates a constant array arr with initial elements 1 and 2.
    • At this point, arr is [1, 2].
  2. 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].
  3. 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].
  4. 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 with const, we can still modify its contents. The const declaration prevents reassignment of the variable arr, 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:

  1. 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:

  1. arr.forEach(i => console.log(i)) forEach skips empty slots. It will output:

    1
    2
    
  2. console.log(arr.map(i => i * 2)) map preserves the empty slots. It will output:

    [2,empty,empty,4]
    
  3. for (const i of arr) { console.log(i) } for...of includes undefined for empty slots. It will output:

    1
    undefined
    undefined
    2
    
  4. console.log([...arr]) The spread operator treats empty slots as undefined. It will output:

    [1, undefined, undefined, 2]
    

Key points:

  1. Sparse arrays: JavaScript allows arrays to have "holes" or empty slots. These are different from slots containing undefined.

  2. forEach: Skips empty slots entirely.

  3. map: Preserves the structure of the original array, including empty slots.

  4. for...of: Treats empty slots as undefined.

  5. 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:

  1. Class Definition:

    class MyArray extends Uint8Array {
      get length() {
        return 3
      }
    }
    
    • MyArray is a subclass of Uint8Array, 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.
  2. 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
  3. 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:

  1. Typed arrays like Uint8Array are specialized array-like objects for handling binary data.
  2. Extending typed arrays is possible in JavaScript, just like extending regular arrays.
  3. Overriding core properties like length can lead to unexpected behavior, especially with typed arrays.
  4. The length property is crucial for many array operations, so overriding it can break normal functionality.
  5. The overridden length getter in MyArray 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:

  1. Array Declaration:

    const a = [999, 1111, 111, 2, 0]
    

    This creates an array a with five numeric elements.

  2. Sorting:

    const b = a.sort()
    

    This sorts the array a and assigns the result to b.

  3. Console Output:

    console.log(a)
    console.log(b)
    

    This logs both a and b to the console.

Now, let's discuss the key points:

  1. The sort() method sorts the elements of an array in place and returns the sorted array. This means it modifies the original array.

  2. When sort() is called without arguments, it sorts the elements as strings in alphabetical and ascending order.

  3. For numbers, this leads to an unexpected result because the numbers are converted to strings before sorting.

  4. Both a and b 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:

  1. To sort numbers correctly, you should provide a comparison function to sort():
    a.sort((x, y) => x - y)
    
  2. const doesn't make the array immutable; it just prevents reassignment of the variable.
  3. b is not a new array; it's the same array as a, 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:

  1. console.log(Reflect.ownKeys([]))

    • This creates an empty array and gets its own keys.
    • Output: ['length']
    • Even an empty array has a 'length' property.
  2. console.log(Reflect.ownKeys([,]))

    • This creates an array with one empty slot.
    • Output: ['length']
    • The empty slot doesn't create an own property.
  3. 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.
  4. 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.

Key points:

  1. Reflect.ownKeys() returns an array of all the own property names of an object, including non-enumerable properties.

  2. For arrays, this includes:

    • Numeric indices of elements that are not empty slots
    • The 'length' property
  3. Empty slots in arrays (often called "holes") do not create own properties.

  4. The spread operator (...) converts empty slots to undefined, which does create own properties.

  5. 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:

  1. const arr = []

    • Creates an empty array.
  2. 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
  3. 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
  4. 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

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:

  1. JavaScript arrays have a maximum length of 2^32 - 1 (4,294,967,295)
  2. The highest valid index is one less than this: 2^32 - 2 (4,294,967,294)
  3. Attempts to set elements beyond this limit are silently ignored
  4. The at() method provides a convenient way to access elements from the end of the array
  5. 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:

  1. It adds the number 4 to the end of array a. Now a is [1,2,3,4].
  2. 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:

  1. First setTimeout:

    setTimeout(() => {
      console.log(2)
    }, 2)
    

    This schedules a callback to log 2 after a 2 millisecond delay.

  2. Second setTimeout:

    setTimeout(() => {
      console.log(1)
    }, 1)
    

    This schedules a callback to log 1 after a 1 millisecond delay.

  3. 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:

  1. 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.

  2. Timer resolution: The actual resolution of timers can vary between browsers and operating systems.

  3. 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.

  4. Browser optimizations: Some browsers might optimize the handling of very short timeouts differently.

Given these factors, there are a few possible outcomes:

  1. If the browser respects the exact times (which is rare), the output would be:

    0
    1
    2
    
  2. If the browser treats all these as "minimum delay" timeouts, they might execute in the order they were scheduled:

    2
    1
    0
    
  3. Some browsers might group these together and execute them in a somewhat unpredictable order.

The key takeaways from this quiz are:

  1. setTimeout with very small delays (0-4ms) often doesn't behave exactly as the numbers suggest.
  2. There's no guarantee of precise timing for very short delays with setTimeout.
  3. The order of execution for nearly simultaneous timeouts can be unpredictable and may vary between browsers.
  4. 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:

  1. let num declares a variable outside the loop.

  2. The for loop runs 5 times (0 to 4).

  3. In each iteration:

    • num = i assigns the current value of i to num.
    • setTimeout() schedules a function to run after 100 milliseconds.
  4. The loop completes almost instantly, much faster than the 100ms delay.

  5. After the loop, num has the value 4 (the last value of i).

  6. 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:

  1. JavaScript's event loop: The setTimeout callbacks are queued to run after the main script execution.
  2. Closure: Each callback closes over the same num variable, not a copy of its value at each iteration.
  3. 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:

  1. Use let in the loop: for (let i = 0; i < 5; i++) and use i directly in the setTimeout callback.
  2. 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:

  1. Initial function definition:

    let func = () => {
      console.log(1)
    }
    

    This defines func as an arrow function that logs 1.

  2. 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.

  3. 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:

  1. The initial func is defined.
  2. Both setTimeouts are scheduled.
  3. The main script finishes executing.
  4. The first setTimeout callback runs (almost immediately), changing func to log 2.
  5. 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:

  1. How closures work in JavaScript (the setTimeout captures the current value of func, not a reference to the variable itself).
  2. The asynchronous nature of setTimeout and the JavaScript event loop.
  3. 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:

  1. console.log(5) executes immediately, printing 5.

  2. setTimeout(a, 0) schedules function a to run as soon as possible (but after the current script finishes).

  3. setTimeout(b, 500) schedules function b to run after 500ms.

  4. The main script finishes, and the event loop starts processing the task queue.

  5. 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).
  6. 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, so b runs next, printing 4.
    • Then the two timeouts from a run, printing 2 and 3 in quick succession.

The output will be:

5
1
4
2
3

Key points to understand:

  1. 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.

  2. 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.

  3. Tasks scheduled with setTimeout(fn, 0) don't run immediately; they're queued to run as soon as possible after the current script finishes.

  4. The block function in a delays everything else from running for about 1 second.

  5. Even though b was scheduled to run after 500ms, it doesn't actually run until after a finishes blocking the main thread.

  6. The timeouts scheduled within a run after b, 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:

  1. const a = {}

    • Creates an empty object a.
  2. 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.
  3. const b = Object.create(a)

    • Creates a new object b with a as its prototype.
    • b inherits properties from a.
  4. 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.
  5. console.log(b.foo1)

    • Outputs: 1
    • b doesn't have its own 'foo1', so it looks up the prototype chain and finds it on a.
  6. console.log(b.foo2)

    • Outputs: 1
    • This is the 'foo2' property directly on b.
  7. 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.
  8. b.foo2 = 2

    • Sets 'foo2' on b to 2.
    • This succeeds because 'foo2' is a regular property on b.
  9. console.log(b.foo1)

    • Outputs: 1
    • Still returns 1 from a.foo1 because the attempt to change it failed.
  10. console.log(b.foo2)

    • Outputs: 2
    • Returns the new value of 'foo2' on b.

Key points:

  1. Object.defineProperty() creates non-writable properties by default.
  2. Object.create() sets up prototype inheritance.
  3. Properties are looked up in the prototype chain if not found on the object itself.
  4. Attempting to modify a non-writable inherited property doesn't throw an error, but silently fails.
  5. 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:

  1. let val = 0

    • Initializes a variable val to 0. This will be used by the classes.
  2. 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.
  3. class B extends A { }

    • B inherits from A without modifying anything.
  4. 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 the set method must also be overridden, otherwise undefined is returned See this
  5. const b = new B()

    • Creates an instance of B.
  6. console.log(b.foo)

    • Outputs: 0 (initial value of val)
  7. b.foo = 1

    • Calls the inherited setter, setting val to 1.
  8. console.log(b.foo)

    • Outputs: 1 (new value of val)
  9. const c = new C()

    • Creates an instance of C.
  10. console.log(c.foo)

    • Outputs: 1 (current value of val)
  11. c.foo = 2

    • Because C only overrode the getter, not the setter, this will not update val as there is no setter defined
  12. console.log(c.foo)

    • Outputs: 1 (old value of val)
  13. console.log(b.foo)

    • Outputs: 1 (b.foo also reflects the old value of val)

Key points:

  1. All instances share the same val variable.
  2. B inherits both getter and setter from A.
  3. C inherits the setter from A but has its own getter (which does the same thing as A's getter).
  4. Changing foo on either b or c affects the shared val.

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:

  1. 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 of val.
  2. 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 of val.
  3. const a = new A()

    • Creates an instance of A.
  4. const b = new B()

    • Creates an instance of B.
  5. console.log(a.foo)

    • Outputs: 1
    • This calls the getter method of foo in A, which returns this.val (1).
  6. console.log(b.foo)

    • Outputs: undefined
    • B only has a setter for foo, not a getter. When you try to access foo, it returns undefined.
  7. b.foo = 3

    • This calls the setter method of foo in B, setting this.val to 3.
  8. console.log(b.val)

    • Outputs: 3
    • This directly accesses the val property of b, which was set to 3 by the setter.
  9. console.log(b.foo)

    • Outputs: undefined
    • Even though we set a value using the setter, trying to access foo still returns undefined because there's no getter defined.

Key points:

  1. Getters are used to read values, setters are used to write values.
  2. 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.
  3. Getters and setters can operate on other properties of the object (like val in this case).
  4. 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:

  1. const obj = { foo: 'bar' }

    • This creates an object obj with a property foo that has the value 'bar'.
  2. console.log('foo' in obj)

    • The in operator checks if the property 'foo' exists in the object obj.
    • This will output true because 'foo' is indeed a property of obj.
  3. 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.

The output of this code will be:

true
true

Key points:

  1. The in operator checks for the existence of a property in an object or its prototype chain.
  2. The left operand of in should typically be a string or a symbol representing the property name.
  3. If the left operand is not a string or symbol, JavaScript will convert it to a string.
  4. 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.
  5. 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:

  1. We create an empty object obj.

  2. We create two objects a and b, each with a name property.

  3. We use a as a key in obj and set its value to a new object created by spreading a.

  4. We do the same with b.

  5. We then try to log obj[a].name and obj[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:

  1. obj[a] is equivalent to obj["[object Object]"]
  2. obj[b] is also equivalent to obj["[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:

  1. Object-to-string conversion: When used as a key, objects are converted to strings.
  2. The default string representation of objects: "[object Object]".
  3. Property assignment and overwriting in objects.
  4. The spread operator (...) for creating shallow copies of objects.
  5. 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:

  1. 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'].
  2. 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'].
  3. 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.
  4. The resulting array is then logged to the console.

The output of this code will be:

['a', 'b', 'b', 'a']

Key points to understand:

  1. Object.keys() returns an array of a given object's own enumerable property names, in the same order as a normal loop would.

  2. 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.
  3. The spread operator ... is used to expand an array into individual elements.

  4. 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.

  5. 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:

  1. 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

  2. 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

Key points:

  1. String.raw as a tagged template:

    • It returns a string where backslashes are treated as literal characters.
    • Template literal substitutions (${...}) are still processed.
  2. 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.
  3. In both cases, String.raw prevents escape sequences (like \n) from being interpreted.

  4. 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:

  1. let a = 'bfe.dev'

    • This creates a string variable a with the value 'bfe.dev'.
  2. a[0] = 'c'

    • This attempts to change the first character of the string from 'b' to 'c'.
  3. console.log(a)

    • This prints the value of a to the console.

The output will be:

bfe.dev

Key points:

  1. Strings in JavaScript are immutable. This means that once a string is created, its individual characters cannot be changed.

  2. 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.

  3. In strict mode, attempting to modify a string character would throw a TypeError.

  4. The original string remains unchanged after the attempted modification.

  5. 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:

  1. The thumbs-up emoji '👍' is a single visual character, but in JavaScript's internal representation, it's actually composed of two code units.

  2. JavaScript uses UTF-16 encoding for strings. In UTF-16, characters are represented by one or two 16-bit code units.

  3. The thumbs-up emoji '👍' has the Unicode code point U+1F44D, which is outside the Basic Multilingual Plane (BMP).

  4. Characters outside the BMP are represented in UTF-16 using two 16-bit code units, known as a surrogate pair.

  5. 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!
  1. 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
  1. 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
  1. 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
  1. 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 }
  1. 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" }
  1. 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
  1. 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
  1. 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);
    }
  });
}
  1. 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:

  1. 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.
  2. Setting properties/values:

    obj.foo = 1
    map.set('foo', 2)
    
    • For obj, we're setting a property foo directly, which works because JavaScript objects can have properties added dynamically.
    • For map, we're using the set method to add a key-value pair.
  3. 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.
  4. Creating Proxies:

    const proxyObj = new Proxy(obj, {})
    const proxyMap = new Proxy(map, {})
    
    • We create Proxies for both obj and map with empty handler objects.
    • These Proxies will behave exactly like the original objects because no custom behavior is defined.
  5. 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') returns undefined:

    1. When we create a Proxy for a Map object, the Proxy doesn't automatically handle the special methods of Map like get, set, etc.
    2. When we call proxyMap.get('foo'), it's looking for a property get on the Proxy object itself, not calling the get method of the underlying Map.
    3. Since there's no get property on the Proxy (or its prototype chain), it returns undefined.

    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:

  1. 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.
  2. map is used correctly as a Map object, using set() and get() methods.
  3. Proxies with empty handlers behave identically to their target objects.
  4. 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:

  1. 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.
  2. Creating an instance:

    const dev = new Dev('BFE')
    
    • This creates a new Dev instance with the name "BFE".
  3. Logging the name:

    console.log(dev.name)
    
    • This calls the getter name, which returns the private #name.
    • Output: BFE
  4. Creating a Proxy:

    const proxyDev = new Proxy(dev, {})
    
    • This creates a Proxy for the dev object with an empty handler.
  5. 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.

The output will be:

BFE
Uncaught TypeError: Cannot read private member #name from an object whose class did not declare it

Key points:

  1. Private fields (#name) are not accessible outside the class, even through getters.
  2. The getter works fine when called directly on the instance (dev.name).
  3. 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:

  1. console.log(1)

    • This runs immediately, outputting 1.
  2. setTimeout(() => { console.log(2) }, 100)

    • This schedules a callback to run after 100ms.
  3. First requestAnimationFrame(() => { console.log(3) })

    • This schedules a callback to run before the next repaint.
  4. Second requestAnimationFrame(() => { ... })

    • This schedules another callback to run before the next repaint.
    • Inside this callback, there's another setTimeout for 10ms.
  5. const end = Date.now() + 200; while (Date.now() < end) { }

    • This creates a blocking loop that runs for 200ms.
  6. console.log(6)

    • This runs immediately after the blocking loop.

The order of execution will be:

  1. console.log(1) runs immediately.
  2. The blocking while loop runs for 200ms.
  3. console.log(6) runs after the blocking loop.
  4. The first requestAnimationFrame callback runs, logging 3.
  5. The second requestAnimationFrame callback runs, logging 4.
  6. The 100ms setTimeout callback runs, logging 2.
  7. 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:

  1. Synchronous code (including blocking loops) runs before any asynchronous callbacks.
  2. requestAnimationFrame callbacks typically run before the next repaint, which usually occurs every 16.7ms (60 fps).
  3. setTimeout callbacks are pushed to the task queue and run after the current execution context is empty.
  4. The blocking while loop delays all other asynchronous operations.
  5. 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:

  1. Object Definition:

    var obj = {
      a: 'BFE',
      b: 'dev',
      func:
        (function foo() {
          return this.a
        },
        function bar() {
          return this.b
        }),
    }
    
    • obj is an object with properties a, b, and func.
    • The func property is assigned the result of a comma-separated expression.
  2. 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; }
  3. 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 to obj.

So, when obj.func() is called:

  • It's actually calling the bar function.
  • this.b refers to obj.b, which is "dev".

Therefore, the output will be:

"dev"

Key points:

  1. In a comma-separated expression, only the last part is returned/assigned.
  2. When a function is called as a method (obj.func()), this inside the function refers to the object it's called on.
  3. The first function (foo) in the comma expression is effectively discarded.
  4. 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:

  1. 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.
  2. console.log(a)

    • This simply prints the value of a to the console.

The output of this code will be:

3

Key points to understand:

  1. The comma operator , in JavaScript evaluates multiple expressions, separated by commas, and returns the value of the last expression.

  2. When used in an assignment, only the final value is assigned to the variable.

  3. The parentheses () are necessary here. Without them, this would be interpreted as three separate statements, and a would be assigned the value 1.

  4. 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.

  5. 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)
  1. const error = new Error('😢'):

    • Creates a new Error object with the message "😢".
  2. 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.
  3. console.log(error instanceof Error):

    • This will log true because error is indeed an instance of the Error class.
  4. 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.
  5. 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.
  6. console.log(clonededError instanceof Error):

    • This will log true.
  7. console.log(clonededError instanceof SyntaxError):

    • In JavaScript, when an Error's name property is set to 'SyntaxError', it becomes an instance of SyntaxError as well.
    • This will also log true for the same reason as above.

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:

  1. 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.
  2. 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.
  3. Handling Circular References:

    • structuredClone can clone objects with circular references without throwing an error, something that would cause issues with JSON.stringify.
  4. Performance:

    • structuredClone is generally faster and more reliable for deep cloning compared to manual deep cloning or using JSON 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:

  1. 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 for var declarations.
    • Output: 'a1'
  2. 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-scoped b that shadows the outer b.
    • var b = 'b1' inside catch is hoisted to the function/global scope, not the catch block.
    • The block-scoped b is not affected by the var declaration.
    • Output: 'b' (the outer b is unchanged)
  3. 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 with c.
    • var c = 'c1' in the catch block reassigns the value of the outer c.
    • Output: 'c1'

Key points:

  1. var declarations are hoisted to the top of their containing function or global scope.
  2. catch blocks with a parameter create a new scope for that parameter, which can shadow outer variables.
  3. var declarations inside catch blocks are not block-scoped; they affect the outer scope.
  4. The behavior can be confusing and is one of the reasons why let and const 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:

  1. The function enters the try block:

    • console.log(1) executes, printing 1 to the console.
    • The return statement is encountered. This would normally exit the function immediately, but because there's a finally block, its execution is delayed.
  2. The catch block is skipped entirely because no error was thrown in the try block.

  3. The finally block executes:

    • console.log(3) runs, printing 3 to the console.
    • After the finally block completes, the function returns (due to the earlier return statement in the try block).
  4. 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:

  1. The try block is where the main code execution happens.
  2. The catch block only executes if an error is thrown in the try block.
  3. The finally block always executes, regardless of whether an error occurred or if there was a return statement.
  4. A return statement in the try block doesn't immediately exit the function if there's a finally block. The finally block will still execute before the function returns.
  5. 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:

  1. The prettify function:

    • It takes a parameter str.
    • It has a try-catch-finally structure.
  2. Inside the try block:

    • It checks if str is a string.
    • If it is, it attempts to parse str as JSON using JSON.parse(str).
    • If parsing succeeds, it would return "prettified" (but this return is overridden by the finally block).
  3. The catch block:

    • If JSON.parse(str) throws an error, it would return "errored" (but again, this is overridden by the finally block).
  4. The finally block:

    • This block always executes, regardless of whether an error was thrown or not.
    • It returns the original str parameter.
  5. 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
  6. 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:

  1. The finally block always executes last, and its return value overrides any return values from the try or catch blocks.
  2. In this function, the logic in the try and catch blocks effectively does nothing because their return values are always overridden by the finally block.
  3. The function will always return the original input string, regardless of whether it's valid JSON or not.
  4. 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.