JavaScript Questions. Basics

TheTechnoCult
14 min readDec 21, 2023

--

1. Runtime and Event Loop

  • Runtimeenvironment or period of code execution.
  • Event Loop — a mechanism that allows JavaScript to handle asynchronous operations.
  • Asynchronicity — the ability to execute code out of order, perform independently, and not block the main flow.
  • Scope determines the accessibility (visibility) of variables.
    JavaScript variables have 3 types of scope: Block scope, Function scope, Global scope. Scope is about the space in code.
  • Lexical Environment — outer (outer scope) and inner (inner scope) accessible variables, and function declarations. Lexical Emvironment is about ability to remember other variables around it.
  • Stack is a region of memory that stores function calls, primitive values, and pointers to objects (but not the objects themselves).
  • Queue (or callback queue) is a list where asynchronous operations’ callbacks wait to be executed.
  • Heap is an area in memory where objects (including arrays and functions) are stored.

(!) Web workers and iframe have their own Event Loop: stack, heap, and message queue.

(!!) Microtasks (like promise callbacks) have priority over macrotasks (like setTimeout callbacks)

Examples of Macrotasks:

  • setTimeout callbacks.
  • setInterval callbacks.
  • Input/Output operations.
  • UI rendering.

Examples of Microtasks:

  • Promise callbacks: .then(), .catch(), and .finally().
  • Mutation Observer callbacks.
console.log('Start');

setTimeout(function () {
console.log('setTimeout (Macrotask)');
}, 0);

Promise.resolve().then(function () {
console.log('Promise Microtask');
});

console.log('End');

// Output:
/*
Start
End
Promise Microtask
setTimeout (Macrotask)
*/

2. Types and typeof operator

  • There are 8 types in JavaScript: Object, Null, Undefined, Boolean, Number, String, Symbol, BigInt.
  • typeof
let x
console.log(typeof x) // "undefined"

let y = null
console.log(typeof y) // "object" ((!) null is absence of object)

let z = false
console.log(typeof z) // "boolean"

let a = 5
console.log(typeof a) // "number"

let bigIntValue = 1234567890123456789012345678901234567890n
console.log(typeof bigIntValue) // "bigint"

let b = "Hello"
console.log(typeof b) // "string"

let sym = Symbol("foo")
console.log(typeof sym) // "symbol"

// object
let obj = {key: "value"}
console.log(typeof obj) // "object"

let arr = [1, 2, 3]
console.log(typeof arr) // "object" (arrays are a special type of object)

function fn() {}
console.log(typeof fn) // "function"

3. Error Types in JavaScript

  • Generic Error Constructor
throw new Error('Generic Error');
  • SyntaxError
let a = ; // SyntaxError
  • ReferenceError

Occurs when trying to reference a variable that is not declared or is not in the current scope.

console.log(undeclaredVar); // ReferenceError
  • TypeError
null.someMethod(); // TypeError
  • RangeError

Occurs when a value is not within the set or expected range. This is commonly seen when working with arrays, numbers, or date objects.

new Array(-1); // RangeError
  • Other: URIError (encoding or decoding URIs), EvalError (no longer used), AggregateError (aggregate multiple promise rejections), InternalError (non-standard, recursion tail calls).

4. "use strict" mode.

  • Variables must be declared.
  • Throws an error when attempting to write to read-only properties, such as the immutable properties of built-in objects.
  • Detects duplicate keys in object literals or duplicate parameters in function declarations.
  • The usage of deprecated syntax (like with, eval) and using 'arguments' as variable names is restricted.
  • Only object properties can be deleted.
var myVar = 42;
delete myVar; // This doesn't throw an error in non-strict mode, but it doesn't delete myVar either.
console.log(myVar); // 42

5. let and const can be used without optimization issues only within their block. Using them in nested blocks can lead to a loss of efficiency.

6. Difference between for...of and for...in.

for...of is used to iterate over the values of an iterable object (e.g., arrays, strings), while for...in is used to iterate over the enumerable properties of an object.

  • Example of for...of:
const array = [1, 2, 3];

for (const value of array) {
console.log(value); // Outputs 1, 2, 3
}
  • Example of for...in:
const object = { a: 1, b: 2, c: 3 };

for (const key in object) {
console.log(key); // Outputs 'a', 'b', 'c'
console.log(object[key]); // Outputs 1, 2, 3
}

7. Loose Equality ==. How it works?

  • 1. Check for Same Type:

Does the same as ==, compares them directly.

  • 2. Null and Undefined:

If one value is null and the other is undefined, they are considered equal.

  • 3. Number and String:

If one value is a number and the other is a string, the string is converted to a number, and then the comparison is made.

  • 4. Boolean Comparisons:

If one value is a boolean, it is converted to a number (true to 1, and false to 0), and then the comparison is performed.

  • 5. Object to Primitive Conversion:

If one value is an object (including arrays, functions, etc.) and the other is not, the object is converted to a primitive value (usually a string or a number, depending on context), and then the comparison is performed. The conversion process involves calling internal methods like valueOf() or toString() on the object.

8. Array Methods

8.1. push, pop, shift, unshift

  • push, pop (modification of the last element).
const arr = [1, 2]
arr.push(3) // returns the new length of the array
console.log(arr) // Output: [1, 2, 3]

arr.pop() // returns removed element
console.log(arr) // Output: [1, 2]

/*
(el) shift <-- |||| --> pop (el)
(lngth) unshift --> |||| <-- push (lngth)
*/
  • shift, unshift (modification of the first element).
const arr = [1, 2]
arr.unshift(0)
console.log(arr) // Output: [0, 1, 2]

arr.shift()
console.log(arr) // Output: [1, 2]

/*
(el) shift <-- |||| --> pop (el)
(lngth) unshift --> |||| <-- push (lngth)
*/

8.2. map, filter, and reduce are powerful array methods that are used for transforming, filtering, and combining array elements. These methods are a part of functional programming. Do not modify the array on which they are called (they do not mutate the original array), return a new array / data.

  • map
const arr = [1, 2, 3, 4, 5]

const arrMap = arr.map(cur => cur * cur)

console.log(arrMap) // Output: [1, 4, 9, 16, 25]

// returns a new array
[].map((cur, ind, arr) => {}, thisArg)

// thisArg - optional, a value to use as this when executing callbackFn(cur, ind, arr)
  • filter
const people = [
{ name: "Alice", age: 17 },
{ name: "Bob", age: 23 },
{ name: "Carol", age: 19 },
{ name: "Dave", age: 16 }
];

const res = people.filter(person => person.age >= 18)

console.log(res)
// Output: [{ name: "Bob", age: 23 }, { name: "Carol", age: 19 }]

// returns a new array
[].filter((cur, ind, arr) => {}, thisArg)

// thisArg - optional, a value to use as this when executing callbackFn(cur, ind, arr)
  • reduce
const arr = [1, 2, 3, 4, 2, 3, 2]

const duplicates = arr.reduce((acc, el) => {
// Initialize the count for this element if it doesn't exist
acc.counts[el] = (acc.counts[el] || 0) + 1

// If the count is exactly 2, it means it's a duplicate and we add it to the duplicates array
if (acc.counts[el] === 2) {
acc.duplicates.push(el)
}

return acc
}, { counts: {}, duplicates: [] }).duplicates

console.log(duplicates) // Output: [2, 3]
[].reduce((
acc = initVal || array[0],
cur = initVal ? array[0] : array[1],
ind,
arr) => {
...
}, initVal)

// acc - accumulator - (1) the prevVal/result of the callback fn or (2) initVal or (3) array[0]
// acc = initVal ? initVal : array[0]
// cur - currentValue - the value of the current element
// cur = initVal ? array[0] : array[1]

8.3. sort

Uses an in-place merge algorithm to sort the elements of an array, mutates the original array. The default behavior converts elements to strings and sorts them based on their Unicode code points.

const numbers = [11, 5, 2, 8, 1, 4]

// Using sort() without a compareFn
numbers.sort()
console.log(numbers) // Output: [1, 11, 2, 4, 5, 8]

// Using sort() with a compareFn for numerical sorting
numbers.sort((a, b) => a - b)
console.log(numbers) // Output: [1, 2, 4, 5, 8, 11]
function compareFn(a, b) {
if (a is less than b by some ordering criterion) {
return -1
} else if (a is greater than b by the ordering criterion) {
return 1
}
// a must be equal to b
return 0
}

8.4. splice, slice

  • splice

Removes, replaces or adds elements in the array. Returns array with deleted elements [el], an empty array [] if some elements were replaced, mutates the original array.


const arr = [1,2,3]
const arr2 = [1,2,3]
const arr3 = [1,2,3]

// add
arr.splice(1, 0, 11) // add 11, returns []

// delete
arr2.splice(2, 1) // del 3, returns [3]

// replace
arr3.splice(0, 1, 11) // add 11, returns [1]

console.log(arr) // Output: [1, 11, 2, 3]
console.log(arr2) // Output: [1, 2]
console.log(arr3) // Output: [11, 2, 3]
[]. splice(start, deleteCount, item1, item2, /* …, */ itemN)

// start - where (can be -1, it means count from the end of the array)
// deleteCount - delete what (can be 0)
// item1, item2, /* …, */ itemN (optional) - insert instead
  • slice

Returns a new array, immutable method. Extracts a portion of an array or a copy of the entire array.

const arr = ['a', 'b', 'c'];

// Extract a part of arr
const newArr = arr.slice(1, 3)
console.log(newArr) // Output: ['b', 'c']

// Create a copy of arr
const copyArr = arr.slice()
console.log(copyArr) // Output: ['a', 'b', 'c']

// Using Negative Indices
const newArr2 = arr.slice(-1)
console.log(newArr2) // Output: ['c']
array.slice(start, end)

8.5. concat

Returns a new array, immutable method.

let num1 = [1, 2, 3]
let num2 = [4, 5, 6]
let num3 = [7, 8, 9]

let combinedNumbers = num1.concat(num2, num3)

console.log(combinedNumbers) // Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

8.6. forEach

const arr = ['a', 'b', 'c']

arr.forEach((element) => console.log(element))

// Output: "a"
// Output: "b"
// Output: "c"

8.7. join

The opposite method String.split().

const elements = [1, 2, 3]

console.log(elements.join(''))
// Output: "123"

8.8. Search Methods: indexOf, includes, find, findIndex

  • indexOf

Returns the index of the element, -1 if not found.

const arr = [1, 2, 3]

console.log(arr.indexOf(1)) // Output: 0
console.log(arr.indexOf(11)) // Output: -1
  • includes

Returns true or false whether an array includes a certain value among its entries.

const arr = [1, 2, 3]

console.log(arr.includes(1)) // Output: true
console.log(arr.includes(11)) // Output: false
  • find

Returns the first element in the array that satisfies the condition, if not returns undefined.

const arr = [1, 2, 3, 4, 5]

console.log(arr.find((cur) => cur > 3)) // Output: 4
console.log(arr.find((cur) => cur > 11)) // Output: undefined
  • findIndex

Returns the first element in the array that satisfies the condition, if not returns -1.

const arr = [1, 2, 3, 4, 5]

console.log(arr.findIndex((cur) => cur > 1)) // Output: 1
console.log(arr.findIndex((cur) => cur > 13)) // Output: -1

8.9. Array.from() and Array.of()

  • Array.from()

Creates an array from the array-like or iterable objects. The second argument can get a map function.

function toArray() {
return Array.from(arguments)
}
let arr = toArray(1, 2, 3)
console.log(arr) // Output: Array [1, 2, 3]
console.log(Array.from('foo')) // Output: Array ["f", "o", "o"]
console.log(Array.from([1, 2, 3], (x) => x + x)) // Output: Array [2, 4, 6]

// mapFn(cur, ind, arr)
  • Array.of()

Creates a new array from a variable number of arguments, regardless of number or type of the arguments.

console.log(Array.of('foo', 2, 'bar', true)) 
// Output: Array ["foo", 2, "bar", true]

console.log(Array.of())
// Output: Array []

8.10. Array.isArray()

Arrays are a type of object. This can make it difficult to distinguish between an array and a generic object. This method checks if the object is array.

let arr = [1, 2, 3]
let num = 123
let obj = { a: 1, b: 2 }

console.log(Array.isArray(arr)) // Output: true
console.log(Array.isArray(num)) // Output: false
console.log(Array.isArray(obj)) // Output: false

8.11. some, every

  • some

Tests whether at least one element in the array passes the test.

const arr = [1, 2, 3, 4, 5]

// Checks whether an element is even
console.log(array.some((el) => el % 2 === 0)) // Expected output: true
  • every

Tests whether all the elements in the array passes the test.

const arr = [1, 2, 3, 4, 5]

// Checks whether an element is even
console.log(array.every((el) => el % 2 === 0)) // Expected output: false

9. Object Methods

9.1. Object.keys() and Object.values()

  • Object.keys()

This is the same as iterating with a for...in loop. Ignores Symbol values.

// Simple array
const arr = [1, , 3]; // Index 1 is an empty slot
console.log(Object.keys(arr)) // Output: ['0', '2']

// Array-like object
const person = {
name: "Alice",
age: 30,
profession: "Engineer"
}
console.log(Object.keys(person)) // Output: ['name', 'age', 'profession']
  • Object.values()
const myObject = { a: 1, b: 2, c: 3 }

console.log(Object.values(myObject)) // Output: [1, 2, 3]
  • Object.entries()

Returns an array of arrays with key-value pairs. Object.entries() is particularly useful in scenarios where both property names and values are needed, and it's commonly used with for...of loops or to convert objects into maps.

const person = {
name: 'Alice',
age: 30,
profession: 'Engineer'
}

console.log(Object.entries(person))
// Output: [['name', 'Alice'], ['age', 30], ['profession', 'Engineer']]
  • Object.fromEntries()

Transforms a list of key-value pairs into an object. The opposite of Object.entries().

const entries = [['name', 'Alice'], ['age', 30], ['profession', 'Engineer']]

console.log(Object.fromEntries(entries))
// Output: { name: 'Alice', age: 30, profession: 'Engineer' }

9.2. Object.create(), Object.assign(), Object.defineProperty(), Object.defineProperties()

  • Object.create()
const prototypeObj = {
sayHello() {
console.log(`Hello, my name is ${this.name}`)
}
}
const person = Object.create(prototypeObj)
person.name = 'Alice'
person.sayHello() // Output: "Hello, my name is Alice"


const person2 = Object.create(prototypeObj, {
name: {
value: 'Bob',
writable: true,
enumerable: true,
configurable: true
}
})
person2.sayHello() // Output: "Hello, my name is Bob"

Object.create(null) creates an object that has no prototype. Such objects can be useful as "pure dictionaries," but they lack built-in object properties like toString.

  • Object.assign()

Is commonly used for cloning and merging objects. Both String and Symbol properties are copied.

const obj1 = { a: 1, b: 2 }
const obj2 = { b: 3, c: 4 }

const combined = Object.assign(obj1, obj2)

console.log(combined) // Output: { a: 1, b: 3, c: 4 }
console.log(obj1) // Output: { a: 1, b: 3, c: 4 }
console.log(obj2) // Output: { b: 3, c: 4 }
  • Object.defineProperty()
const object = {}

Object.defineProperty(object, 'property1', {
value: 42,
writable: false,
enumerable: true,
configurable: true
})

console.log(object.property1) // Outputs: 42
object.property1 = 77 // Will not change the value because writable is false
  • Object.defineProperties()
const book = {}

Object.defineProperties(book, {
title: {
value: 'JavaScript: The Definitive Guide',
writable: true,
enumerable: true,
configurable: true
},
author: {
value: 'David Flanagan',
writable: false
},
// Define a getter and setter for a computed property
summary: {
get() {
return `${this.title} by ${this.author}`;
},
set(summary) {
[this.title, this.author] = summary.split(' by ');
}
}
})

console.log(book.summary) // Outputs: 'JavaScript: The Definitive Guide by David Flanagan'
book.title = 'New Title'
console.log(book.summary) // Outputs: 'New Title by David Flanagan'

9.3. Object.is()

Provides functionality similar to the strict equality operator (===), but with a few key differences that make it more reliable in certain situations.

Object.is(NaN, NaN) // true
NaN === NaN // false

Object.is(-0, +0) // false
-0 === +0 // true

9.4. Object.hasOwn() and hasOwnProperty()

  • Object.hasOwn() returns true if the specified property is a direct property of the object — not inherited through the prototype chain.
const exampleObj = {
property1: 42
}

console.log(Object.hasOwn(exampleObj, 'property1')) // true
console.log(Object.hasOwn(exampleObj, 'toString')) // false
console.log(Object.hasOwn(exampleObj, 'hasOwnProperty')) // false


const obj = Object.create(null) // obj has no prototype
obj.prop = 'exists'
// obj.hasOwnProperty('prop') would throw an error
console.log(Object.hasOwn(obj, 'prop')) // true
  • hasOwnProperty()
const obj = {
property: 'Value'
}

console.log(obj.hasOwnProperty('property')) // true
console.log(obj.hasOwnProperty('toString')) // false

9.5. Object Descriptor

value: The value of the property.

writable: If true, the property's value can be changed.

enumerable: If true, the property is included in enumerations, such as loops over property names.

configurable: If true, the property's descriptor can be changed and the property can be deleted.

get: A getter function for the property.

set: A setter function for the property.

  • Object.getOwnPropertyDescriptor()
const object = {
property1: 42
}

const descriptor = Object.getOwnPropertyDescriptor(object, 'property1')
console.log(descriptor)

// Output:
/*
{
value: 42,
writable: true,
enumerable: true,
configurable: true
}
*/
  • Object.getOwnPropertyDescriptors()
const object = {
property1: 42,
get property2() {
return this.property1
}
}

const descriptors = Object.getOwnPropertyDescriptors(object)
console.log(descriptors)

// Output:
/*
{
property1:
{value: 42, writable: true, enumerable: true, configurable: true},
property2:
{enumerable: true, configurable: true, get: [Function: get property2], set: undefined}
}
*/

9.6.

  • Object.getOwnPropertyNames()

Returns an array of all properties (including non-enumerable properties but not including Symbol properties) found directly upon a given object.

const myObject = {
a: 1,
b: 2,
c: 3
}
Object.defineProperty(myObject, 'invisible', {
enumerable: false,
value: 'hidden'
})

console.log(Object.getOwnPropertyNames(myObject))
// Output: ["a", "b", "c", "invisible"]
  • Object.getOwnPropertySymbols()

Returns an array of all symbol properties found directly upon a given object.

const mySymbol1 = Symbol('a')
const mySymbol2 = Symbol('b')

const myObj = {
[mySymbol1]: 'value1',
[mySymbol2]: 'value2'
}

const symbols = Object.getOwnPropertySymbols(myObj)

console.log(symbols) // [Symbol(a), Symbol(b)]
console.log(myObj[symbols[0]]) // 'value1'

9.7. Object.getPrototypeOf() and isPrototypeOf()

  • Object.getPrototypeOf()
const Obj = new Object()
const prototypeOfObj = Object.getPrototypeOf(Obj)

console.log(prototypeOfObj === Object.prototype) // true
  • isPrototypeOf()
function Obj() {}

const myInstance = new Obj()
console.log(Obj.prototype.isPrototypeOf(myInstance)) // true

9.8. Object.freeze(), Object.seal()

  • Object.freeze()

Is used to freeze an object. When an object is frozen, it can no longer be modified; its properties cannot be added, deleted, or changed.

const myObject = {
property1: 42
}

Object.freeze(myObject)

myObject.property1 = 33 // No effect because the object is frozen
console.log(myObject.property1); // 42

myObject.property2 = 'new' // No effect, can't add new properties
console.log(myObject.property2) // undefined

delete myObject.property // No effect, can't delete properties
console.log(myObject.property1) // 42
  • Object.seal()

When an object is sealed, it enters a state where it cannot have new properties added to it, and existing properties cannot be removed. However, unlike Object.freeze(), the values of the existing properties on a sealed object can still be changed if those properties are writable.

const myObject = {
property1: 42
}

Object.seal(myObject)

// Modifying an existing property
myObject.property1 = 33
console.log(myObject.property1) // 33

// Adding a new property has no effect
myObject.property2 = 'new'
console.log(myObject.property2) // undefined

// Deleting a property has no effect
delete myObject.property1
console.log(myObject.property1) // 33

9.9. valueOf(), toString()

  • valueOf()

Returns a primitive value representation of the object. It typically returns the object itself, but also can be redefined. The primary reason to override valueOf() is to provide a meaningful primitive representation of an object.

let obj = {}
console.log(obj.valueOf() === obj) // true
function MyNumberType(n) {
this.number = n
}

MyNumberType.prototype.valueOf = function() {
return this.number
};

let myObj = new MyNumberType(4)
console.log(myObj + 3) // 7, because myObj.valueOf() returns 4
  • toString()

Convert an object to a string representation. You can redefine toString() to provide a meaningful string representation of the object.

let obj = {}
console.log(obj.toString()) // "[object Object]"

let array = [1, 2, 'a', 'b']
console.log(array.toString()) // "1,2,a,b"

let num = 123
let bool = true
console.log(num.toString()) // "123"
console.log(bool.toString()) // "true"

let now = new Date()
console.log(now.toString()) // "Mon Apr 03 2023 12:30:00 GMT+0200 (Central European Summer Time)"


function Person(name, age) {
this.name = name
this.age = age
}

Person.prototype.toString = function() {
return `Person[name=${this.name}, age=${this.age}]`
};

let alice = new Person('Alice', 30)
console.log(alice.toString()) // "Person[name=Alice, age=30]"

10. Function Methods

  • bind(this)

Sets the this context of a function, regardless of how it is called.

function greet() {
console.log(`Hello, I am ${this.name}`)
}

const person = { name: 'Alice' }
const boundGreet = greet.bind(person)

boundGreet() // Output: "Hello, I am Alice"
  • call(this, args)

Invokes a function with a specified this value and individual arguments.

function greet(greeting, punctuation) {
console.log(`${greeting}, my name is ${this.name}${punctuation}`)
}

const person = { name: 'Alice' }

greet.call(person, 'Hello', '!') // Output: "Hello, my name is Alice!"
  • apply(this, [args])

Similar to call() but it takes arguments as an array (or an array-like object).

function greet(greeting, punctuation) {
console.log(`${greeting}, my name is ${this.name}${punctuation}`)
}

const person = { name: 'Alice' }

greet.apply(person, ['Hello', '!']) // Output: "Hello, my name is Alice!"

11. The difference between rest fn(...args) and spread [...arr] / {...obj} operators.

The rest and spread operators in JavaScript are both represented by the ellipsis (...) syntax, but they are used in different contexts and have different purposes.

  • Rest Operator fn(...args)
function myFunction(param1, param2, ...restOfParams) {
// restOfParams is an array containing the rest of the parameters
}
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0)
}

console.log(sum(1, 2, 3, 4, 5)) // Output: 15
  • Spread Operator [...arr] / {...obj}
const array1 = [1, 2, 3]
const array2 = [...array1, 4, 5] // Spread operator in arrays
console.log(array2) // Output: [1, 2, 3, 4, 5]

const obj1 = { x: 1, y: 2 }
const obj2 = { ...obj1, z: 3 } // Spread operator in objects
console.log(obj2) // Output: { x: 1, y: 2, z: 3 }

--

--

No responses yet