Welcome to my JavaScript notebook! Here, I’ve compiled a variety of tips, tricks, and simple snippets that I’ve gathered over time. These notes come from various sources, including my professional work, problem-solving on LeetCode, and personal projects. My aim is to document these insights for my own reference and to share them with anyone who might find them useful or interesting. Whether you’re a fellow developer or just curious about JavaScript, I hope you’ll find something valuable in these pages.
Array
Creating an array filled with empty arrays:
1let filledWithEmptyArrays = Array(3).fill().map(() => []);
Creating a 2D array (e.g., for a visitation matrix):
1let visited = Array.from({ length: l }, () => Array.from({ length: l }, () => 0));
Creating an array filled with zeros:
1Array(26).fill(0);
Creating an array filled with false:
1Array(n).fill(false);
dp
1let dp = new Array(N).fill(0);
2D array
1const N = 5;
2const M = 5;
3let dp = Array.from({ length: N + 1 }, () => new Array(M + 1).fill(0));
output:
1dp [
2 [ 0, 0, 0, 0, 0, 0 ],
3 [ 0, 0, 0, 0, 0, 0 ],
4 [ 0, 0, 0, 0, 0, 0 ],
5 [ 0, 0, 0, 0, 0, 0 ],
6 [ 0, 0, 0, 0, 0, 0 ],
7 [ 0, 0, 0, 0, 0, 0 ]
8]
visited
0 = Unvisited, 1 = Visiting, 2 = Visited; Or simply use boolean, depends on the use case.
1let visited = new Array(numCourses).fill(0);
adjacency
This approach is explicit about initialising the array with null values before mapping.
1let N = 10
2let adj = new Array(N).fill(null).map(() => []);
3let prerequisites = [[1,0],[0,1]];
4
5prerequisites.forEach(([a, b]) => adj[a].push(b));
Sorting Arrays
Sort an array of numbers in ascending order:
1[11, 2, 22, 1].sort((a, b) => a - b);
Priority Queue (pq)
Min-Heap based on sum of pairs:
1const pq = new PriorityQueue({
2 compare: (a, b) => a.sum - b.sum
3});
Max-Heap based on sum of pairs:
1const pq = new PriorityQueue({
2 compare: (a, b) => b.sum - a.sum
3});
Can enqueue with data set like so:
1pq.enqueue({ sum: 100, i: 0, j: 0 });
MinPriorityQueue
- This class allows you to manage elements in a min-heap fashion, where the smallest element is always at the top.
- It’s particularly useful for problems where you need to efficiently access and remove the smallest element repeatedly.
MaxPriorityQueue
- This class manages elements in a max-heap fashion, where the largest element is always at the top.
- Use it when you need to access and remove the largest element efficiently.
Example Usage:
Here’s a quick example of using a MinPriorityQueue in a LeetCode problem:
1let heap = new MinPriorityQueue();
2heap.enqueue(10);
3heap.enqueue(5);
4heap.enqueue(20);
5console.log(heap.dequeue().element); // Outputs 5
Note: These classes are specific to the LeetCode environment and are not available in standard JavaScript. You will need to implement your own or use a third-party library if you are working outside LeetCode.
1npm install --save @datastructures-js/priority-queue
1const {
2 PriorityQueue,
3 MinPriorityQueue,
4 MaxPriorityQueue
5} = require('@datastructures-js/priority-queue');
PriorityQueue in this repo is implemented as 3 types:
- PriorityQueue that accepts a custom comparator between elements.
- MinPriorityQueue which considers an element with smaller priority number as higher in priority.
- MaxPriorityQueue which cosiders an element with bigger priority number as higher in priority.
Build From Scratch
Min-Heap Implementation
A min-heap is a binary tree where the parent nodes are smaller than their children. Here’s a basic implementation:
1class MinHeap {
2 constructor() {
3 this.heap = [];
4 }
5
6 getParentIndex(index) {
7 return Math.floor((index - 1) / 2);
8 }
9
10 getLeftChildIndex(index) {
11 return 2 * index + 1;
12 }
13
14 getRightChildIndex(index) {
15 return 2 * index + 2;
16 }
17
18 swap(index1, index2) {
19 [this.heap[index1], this.heap[index2]] = [this.heap[index2], this.heap[index1]];
20 }
21
22 enqueue(value) {
23 this.heap.push(value);
24 this.heapifyUp();
25 }
26
27 heapifyUp() {
28 let index = this.heap.length - 1;
29 while (index > 0) {
30 const parentIndex = this.getParentIndex(index);
31 if (this.heap[parentIndex] > this.heap[index]) {
32 this.swap(parentIndex, index);
33 index = parentIndex;
34 } else {
35 break;
36 }
37 }
38 }
39
40 dequeue() {
41 if (this.heap.length === 0) return null;
42 if (this.heap.length === 1) return this.heap.pop();
43 const minValue = this.heap[0];
44 this.heap[0] = this.heap.pop();
45 this.heapifyDown(0);
46 return minValue;
47 }
48
49 heapifyDown(index) {
50 let smallest = index;
51 const leftChild = this.getLeftChildIndex(index);
52 const rightChild = this.getRightChildIndex(index);
53
54 if (leftChild < this.heap.length && this.heap[leftChild] < this.heap[smallest]) {
55 smallest = leftChild;
56 }
57 if (rightChild < this.heap.length && this.heap[rightChild] < this.heap[smallest]) {
58 smallest = rightChild;
59 }
60 if (smallest !== index) {
61 this.swap(index, smallest);
62 this.heapifyDown(smallest);
63 }
64 }
65
66 size() {
67 return this.heap.length;
68 }
69}
Max-Heap Implementation
A max-heap is similar to a min-heap, but the parent nodes are larger than their children.
1class MaxHeap {
2 constructor() {
3 this.heap = [];
4 }
5
6 getParentIndex(index) {
7 return Math.floor((index - 1) / 2);
8 }
9
10 getLeftChildIndex(index) {
11 return 2 * index + 1;
12 }
13
14 getRightChildIndex(index) {
15 return 2 * index + 2;
16 }
17
18 swap(index1, index2) {
19 [this.heap[index1], this.heap[index2]] = [this.heap[index2], this.heap[index1]];
20 }
21
22 enqueue(value) {
23 this.heap.push(value);
24 this.heapifyUp();
25 }
26
27 heapifyUp() {
28 let index = this.heap.length - 1;
29 while (index > 0) {
30 const parentIndex = this.getParentIndex(index);
31 if (this.heap[parentIndex] < this.heap[index]) { // Max-Heap condition
32 this.swap(parentIndex, index);
33 index = parentIndex;
34 } else {
35 break;
36 }
37 }
38 }
39
40 dequeue() {
41 if (this.heap.length === 0) return null;
42 if (this.heap.length === 1) return this.heap.pop();
43 const maxValue = this.heap[0];
44 this.heap[0] = this.heap.pop();
45 this.heapifyDown(0);
46 return maxValue;
47 }
48
49 heapifyDown(index) {
50 let largest = index;
51 const leftChild = this.getLeftChildIndex(index);
52 const rightChild = this.getRightChildIndex(index);
53
54 if (leftChild < this.heap.length && this.heap[leftChild] > this.heap[largest]) { // Max-Heap condition
55 largest = leftChild;
56 }
57 if (rightChild < this.heap.length && this.heap[rightChild] > this.heap[largest]) { // Max-Heap condition
58 largest = rightChild;
59 }
60 if (largest !== index) {
61 this.swap(index, largest);
62 this.heapifyDown(largest);
63 }
64 }
65
66 size() {
67 return this.heap.length;
68 }
69}
Key Points to Remember:
- Min-Heap: Always keeps the smallest element at the top.
- Max-Heap: Always keeps the largest element at the top.
- Time Complexity: Insertion and deletion operations both take $O(\log n)$ time, where $n$ is the number of elements in the heap.
- Use Cases: Min-heaps are useful for problems requiring efficient access to the minimum element (e.g., Dijkstra’s algorithm), while max-heaps are used when you need quick access to the maximum element (e.g., priority scheduling).
LeetCode Imported Tips
When working on LeetCode problems, you might come across built-in utility classes like MinPriorityQueue and MaxPriorityQueue. These classes are provided by the LeetCode environment to simplify the process of implementing priority queues, which are essentially min-heaps and max-heaps.
1JavaScript node.js 20.10.0
2Your code is run with --harmony flag, enabling new ES6 features.
3
4lodash.js library is included by default.
5
6For Priority Queue / Queue data structures, you may use 5.4.0 version of datastructures-js/priority-queue and 4.2.3 version of datastructures-js/queue.
- LeetCode - What are the environments for the programming languages?
- GitHub - @datastructures-js/priority-queue
Working with JSON
Convert an object or array to a JSON string:
1JSON.stringify(obj);
map - set()
Initialise a map with key-value pairs:
1const myMap = new Map([
2 ['key1', 'value1'],
3 ['key2', 'value2'],
4 ['key3', 'value3']
5]);
Convert map values to an array:
1const valuesArray = Array.from(myMap.values());
set - add()
…
Math
Find the maximum value in a list of numbers:
1Math.max(...list);
Round a number to at most 2 decimal places:
1Math.round(num * 100) / 100;
To ensure numbers like 1.005 round correctly:
1Math.round((num + Number.EPSILON) * 100) / 100;
CSS: Adding Styles to :before and :after
Dynamically add CSS styles to :before and :after selectors:
1var styleElem = document.head.appendChild(document.createElement("style"));
2styleElem.innerHTML = ".add-fading-" + nth + ":before { z-index: 1 !important; }";
NPM Packages
md-file-tree by @michalbe
Generate a markdown tree of all files in a directory, recursively:
1## Display in console
2md-file-tree
3
4## Output to a file such as README.md
5md-file-tree > README.md
JavaScript Obfuscator
Obfuscate JavaScript code to make it harder to understand:
1var fs = require("fs");
2var jsObfuscator = require("js-obfuscator");
3
4fs.readFile("./000_unencrypted-js/main.js", "UTF-8", function (error, code) {
5 if (error) throw error;
6 var obfuscatedResult = jsObfuscator.obfuscate(code);
7 fs.writeFile("./js/main.js", obfuscatedResult.getObfuscatedCode(), function (fsError) {
8 if (fsError) console.log(fsError);
9 console.log("Obfuscated.");
10 });
11});
TypeScript: Child Manager Configuration
Example configuration for managing multiple processes:
1{
2 "processes": [
3 {
4 "name": "Frontend",
5 "command": {
6 "executor": "yarn",
7 "args": [
8 "start"
9 ],
10 "path": "../fe-project",
11 "isWindows": false
12 },
13 "maxLogs": 200
14 },
15 {
16 "name": "WebSocket",
17 "command": {
18 "executor": "yarn",
19 "args": [
20 "start"
21 ],
22 "path": "../server-project",
23 "isWindows": false
24 },
25 "maxLogs": 300
26 },
27 {
28 "name": "Docker",
29 "command": {
30 "executor": "docker-compose",
31 "args": [
32 "up"
33 ],
34 "path": "../be-project",
35 "isWindows": false
36 },
37 "maxLogs": 300
38 }
39 ],
40 "captureExit": true,
41 "longLive": false,
42 "debug": false
43}
HTML: onkeydown
To put on <form> to prevent user clicks on enter.
1onkeydown="return event.key != 'Enter';"
Spread operator
1const obj = { name: "Jing Hui", age: 24 }
2
3console.log(obj)
4
5const test = {...obj, age: 25, nationality: "malaysian"}
Destructuring assignments
You can use them in different contexts, such as swapping values, extracting properties from objects or arrays, and working with functions:
Swapping Two Variables
This is the most common use case: swapping two variables without using a temporary variable.
1let a = 5;
2let b = 10;
3
4// Swap a and b
5[a, b] = [b, a];
6
7console.log(a); // Output: 10
8console.log(b); // Output: 5
got this from Rotate Array (todo add link)
Extracting Values from Arrays
You can use destructuring to assign array elements to variables.
1const numbers = [1, 2, 3, 4];
2
3// Destructure first two elements
4const [first, second] = numbers;
5
6console.log(first); // Output: 1
7console.log(second); // Output: 2
8
9// Skip elements
10const [, , third] = numbers;
11
12console.log(third); // Output: 3
Default Values in Array Destructuring
If the array is shorter than expected, you can provide default values.
1const colors = ['red'];
2
3// Destructure with a default value
4const [primaryColor, secondaryColor = 'blue'] = colors;
5
6console.log(primaryColor); // Output: 'red'
7console.log(secondaryColor); // Output: 'blue' (default value)
Object Destructuring
You can destructure objects to extract specific properties into variables.
1const person = {
2 name: 'Alice',
3 age: 30,
4 job: 'Engineer'
5};
6
7// Destructure object properties
8const { name, age } = person;
9
10console.log(name); // Output: 'Alice'
11console.log(age); // Output: 30
Object Destructuring with Aliases
You can also rename the extracted properties by assigning them to new variable names.
1const employee = {
2 firstName: 'John',
3 lastName: 'Doe'
4};
5
6// Destructure with alias
7const { firstName: fName, lastName: lName } = employee;
8
9console.log(fName); // Output: 'John'
10console.log(lName); // Output: 'Doe'
Nested Object Destructuring
Destructuring can also be used for nested objects.
1const student = {
2 info: {
3 name: 'Mark',
4 grade: 'A'
5 },
6 courses: ['Math', 'Physics']
7};
8
9// Destructure nested properties
10const { info: { name }, courses: [firstCourse] } = student;
11
12console.log(name); // Output: 'Mark'
13console.log(firstCourse); // Output: 'Math'
Function Parameter Destructuring
You can destructure objects or arrays passed as function parameters.
1// Destructure object in function parameters
2function printStudent({ name, age }) {
3 console.log(`${name} is ${age} years old.`);
4}
5
6const student = { name: 'Emily', age: 25 };
7printStudent(student); // Output: 'Emily is 25 years old.'
8
9// Destructure array in function parameters
10function sum([a, b]) {
11 return a + b;
12}
13
14console.log(sum([3, 7])); // Output: 10
Rest Operator in Destructuring (Spread Operator)
You can use the rest (...) operator to capture the remaining elements after destructuring.
1const fruits = ['apple', 'banana', 'cherry', 'date'];
2
3// Destructure first element and capture the rest
4const [firstFruit, ...otherFruits] = fruits;
5
6console.log(firstFruit); // Output: 'apple'
7console.log(otherFruits); // Output: ['banana', 'cherry', 'date']
Ignoring Values in Destructuring
You can ignore certain elements when destructuring arrays.
1const scores = [100, 90, 85, 80];
2
3// Ignore the second score
4const [firstScore, , thirdScore] = scores;
5
6console.log(firstScore); // Output: 100
7console.log(thirdScore); // Output: 85
Combining Arrays and Destructuring
Destructuring works seamlessly when combining multiple arrays.
1const arr1 = [1, 2, 3];
2const arr2 = [4, 5, 6];
3
4// Combine and destructure
5const combined = [...arr1, ...arr2];
6const [first, second, ...rest] = combined;
7
8console.log(first); // Output: 1
9console.log(second); // Output: 2
10console.log(rest); // Output: [3, 4, 5, 6]
TODO: Rest Operator (...)
The rest operator is used to gather multiple elements into a single array or object. You usually see it in function parameters or destructuring.
Example 1: Rest in Function Parameters
In functions, the rest operator gathers all the extra arguments passed to the function into an array.
1function showNumbers(...numbers) {
2 console.log(numbers); // Gathers arguments into an array
3}
4
5showNumbers(1, 2, 3, 4, 5); // Output: [1, 2, 3, 4, 5]
Breakdown:
- The
...numbersgathers all the arguments (1, 2, 3, 4, 5) into a single array namednumbers.
Example 2: Rest in Array Destructuring
In destructuring, the rest operator gathers remaining elements of an array into a new array.
1const [first, second, ...rest] = [10, 20, 30, 40, 50];
2console.log(first); // Output: 10
3console.log(second); // Output: 20
4console.log(rest); // Output: [30, 40, 50]
Breakdown:
firstgets the value10,secondgets20, and the rest of the values (30, 40, 50) are collected into therestarray.
Example 3: Rest in Object Destructuring
You can also use the rest operator to gather remaining properties of an object.
1const person = { name: 'Alice', age: 25, country: 'USA' };
2const { name, ...details } = person;
3console.log(name); // Output: Alice
4console.log(details); // Output: { age: 25, country: 'USA' }
Breakdown:
namegetsAlice, and the remaining properties (age,country) are collected into thedetailsobject.
Spread Operator (...)
The spread operator is used to spread out the elements of an array, object, or iterable into individual elements.
Example 1: Spread in Arrays
Spread operator expands the elements of an array. This is often used to combine arrays.
1const arr1 = [1, 2];
2const arr2 = [3, 4];
3const combined = [...arr1, ...arr2];
4console.log(combined); // Output: [1, 2, 3, 4]
Breakdown:
...arr1spreads out[1, 2], and...arr2spreads out[3, 4], combining them into one array.
Example 2: Spread in Function Calls
You can pass the elements of an array as individual arguments to a function using the spread operator.
1const numbers = [5, 6, 7];
2console.log(Math.max(...numbers)); // Output: 7
Breakdown:
...numbersspreads the array[5, 6, 7]intoMath.maxas individual arguments (5, 6, 7).
Example 3: Spread in Objects
The spread operator can copy properties from one object to another.
1const obj1 = { a: 1, b: 2 };
2const obj2 = { c: 3 };
3const merged = { ...obj1, ...obj2 };
4console.log(merged); // Output: { a: 1, b: 2, c: 3 }
Breakdown:
...obj1spreads the properties ofobj1into a new object, followed by...obj2spreading the properties ofobj2.
Summary of Differences
| Feature | Rest Operator (...) | Spread Operator (...) |
|---|---|---|
| Purpose | Gathers elements into an array/object | Spreads elements into individual items |
| Usage | Function parameters, destructuring | Function calls, array/object merging |
| Example | function(...args) {} | Math.max(...[1, 2, 3]) |
Final Thoughts
Rest collects multiple elements into an array or object.
- Example: Collect remaining arguments in a function.
Spread takes an array or object and breaks it into individual elements.
- Example: Pass an array as arguments to a function or merge arrays/objects.
๐ Common Errors
Array Referencing Issue
The method that causes all elements to reference the same array:
1let adj = new Array(N).fill([]);
- When using fill with objects (including arrays), it copies the reference to the object.
- For primitive types (like numbers or strings), it copies the value.
- Avoiding Common Pitfalls: Don’t use fill with mutable objects if you need distinct instances.
โ The Correct Way!
This approach is more concise and directly initialises the array elements:
1let adj = new Array(N).fill(null).map(() => []);
2// OR
3let adj = Array.from({ length: N }, () => []);