├── .DS_Store
├── README.md
└── chapters
├── 00_variables.js
├── 01_data_types.js
├── 02_type_conversion.js
├── 03_type_coercion.js
├── 04.1_ternary.js
├── 04.2_nullish_coalescing.js
├── 04_operators.js
├── 05_control_flow.js
├── 06_loops.js
├── 07_arrays.js
├── 08_strings.js
├── 09_functions.js
├── 10_scope.js
├── 11_closure.js
├── 12.1_error_object.js
├── 12.1_optional_chaining.js
├── 12_objects.js
├── 13_inheritance_objects.js
├── 14_classes.js
├── 15_inheritance_classes.js
├── 16_destructuring.js
├── 17_spread_rest.js
├── 18_this.js
├── 19_call_apply_bind.js
├── 20_error_handling.js
├── 21_debugging.js
├── 22_callbacks.js
├── 23.1_async_await.js
├── 23_promises.js
├── 24_asynchronous.js
├── 25_dom_manipulation.js
├── 26_events.js
├── 27_storage.js
├── 28_indexed_db.js
├── 29_symbol.js
├── 30_fetch.js
├── 31_modules.js
├── 32_template_literals.js
├── 33_date_time.js
├── 34_math.js
├── 35_bitwise.js
├── 36_regex.js
├── 37_set_timeout.js
├── 38_setInterval.js
├── 39_json_stringify.js
├── 40_json_parse.js
├── 41_map.js
├── 42_weak_map.js
├── 43_set.js
├── 44_weak_map.js
├── 45_generators.js
├── 46_iterators.js
├── 47_big_int.js
├── 48.0_web_apis.js
├── 48.1_web_apis_2.js
├── 48.2_web_apis_3.js
├── 49_canvas.js
├── 50_drag_drop.js
├── 51_file_and_blob.js
├── 52_websockets.js
├── 53_web_workers.js
├── 54_service_workers.js
├── 55_custom_events.js
├── 56_webrtc.js
├── 57_dynamic_imports.js
├── 58_decorators.js
├── 59_proxy.js
├── 60_reflect.js
├── 61_performance.js
├── 62_navigator.js
├── 63_user_timing_api.js
├── 64_navigation_timing.js
└── 65_lazy_loading.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ishtms/learn-javascript-easy-way/f0a78b648b2ed934975a4b24ceab9a4aaa4450cd/.DS_Store
--------------------------------------------------------------------------------
/chapters/00_variables.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The `variables` function demonstrates various aspects of variable declaration
3 | * and scoping in JavaScript, which include:
4 | * - Global Scope
5 | * - Function Scope (`var`)
6 | * - Block Scope (`let`, `const`)
7 | * - Hoisting
8 | * - Temporal Dead Zone (TDZ)
9 | * - Variable Shadowing
10 | * - Variable Redeclaration
11 | */
12 |
13 | /**
14 | * ========================
15 | * Global Scope
16 | * ========================
17 | * Variables declared outside of any function or block are in the "global scope."
18 | * They can be accessed and modified from any part of the code.
19 | */
20 | let globalVar = "I am a global variable";
21 | console.log(`Global Scope: globalVar = ${globalVar}`);
22 |
23 | function variables() {
24 | /**
25 | * ========================
26 | * 'var' Declaration
27 | * ========================
28 | * Variables declared with 'var' are function-scoped. Their visibility is
29 | * limited to the function where they are declared.
30 | *
31 | * Hoisting:
32 | * ---------
33 | * Variables and function declarations are moved, or "hoisted," to the top
34 | * of their containing scope during the compilation phase.
35 | */
36 | console.log(`Hoisting with 'var': a = ${a}`); // Output: undefined due to hoisting
37 | var a = 10;
38 | console.log(`'var' Declaration: a = ${a}`);
39 |
40 | /**
41 | * ========================
42 | * 'let' Declaration
43 | * ========================
44 | * Variables declared using 'let' are block-scoped. They are only accessible
45 | * within the block in which they are contained.
46 | *
47 | * Temporal Dead Zone (TDZ):
48 | * -------------------------
49 | * 'let' and 'const' declarations are not initialized, so accessing them
50 | * before their declaration results in a ReferenceError.
51 | */
52 | // console.log(`TDZ with 'let': c = ${c}`); // Uncommenting this will result in a ReferenceError
53 | let c = 30;
54 | console.log(`'let' Declaration: c = ${c}`);
55 |
56 | /**
57 | * ========================
58 | * 'const' Declaration
59 | * ========================
60 | * Variables declared using 'const' are block-scoped and cannot be reassigned
61 | * after they are initialized.
62 | *
63 | * Note:
64 | * -----
65 | * 'const' makes the variable itself immutable, but if it points to an object,
66 | * the object's properties can still be modified.
67 | */
68 | const e = 50;
69 | console.log(`'const' Declaration: e = ${e}`);
70 | const obj = { key: "value" };
71 | // obj = {}; // Uncommenting this line will result in an error
72 | obj.key = "new_value"; // Allowed
73 | console.log(`'const' object modification: obj.key = ${obj.key}`);
74 |
75 | /**
76 | * ========================
77 | * Variable Shadowing
78 | * ========================
79 | * Variables can "shadow" an outer variable by having the same name.
80 | * The inner variable will "overshadow" the outer one within its scope.
81 | */
82 | {
83 | let c = 60;
84 | const e = 70;
85 | console.log(`Shadowing: Inner scope: c = ${c}, e = ${e}`);
86 | }
87 | console.log(`Shadowing: Outer scope: c = ${c}, e = ${e}`);
88 |
89 | /**
90 | * ========================
91 | * Variable Redeclaration
92 | * ========================
93 | * Variables declared with 'var' can be redeclared in the same scope.
94 | * However, 'let' and 'const' cannot be redeclared in the same scope.
95 | */
96 | var a = 80; // Allowed
97 | // let c = 90; // Uncommenting this line will result in an error
98 | // const e = 100; // Uncommenting this line will result in an error
99 | }
100 |
101 | variables();
102 |
103 | // Demonstrating globalVar is accessible outside the function as well.
104 | console.log(`Global Scope: Accessing globalVar outside function: ${globalVar}`);
105 |
--------------------------------------------------------------------------------
/chapters/01_data_types.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The `dataTypes` function illustrates the variety of data types in JavaScript:
3 | * - Undefined
4 | * - Null
5 | * - Boolean
6 | * - Number
7 | * - BigInt
8 | * - String
9 | * - Symbol
10 | * - Object
11 | * - Function
12 | * - Array
13 | */
14 |
15 | function dataTypes() {
16 | /**
17 | * ========================
18 | * Undefined
19 | * ========================
20 | * An "undefined" type represents a variable that has been declared but not yet
21 | * initialized with a value.
22 | */
23 | let undefinedVar;
24 | console.log(`Undefined: ${undefinedVar}, typeof: ${typeof undefinedVar}`);
25 |
26 | /**
27 | * ========================
28 | * Null
29 | * ========================
30 | * The "null" type signifies the intentional absence of a value.
31 | * Note: typeof null will return 'object', which is a longstanding bug in JavaScript.
32 | */
33 | let nullVar = null;
34 | console.log(`Null: ${nullVar}, typeof: ${typeof nullVar}`);
35 |
36 | /**
37 | * ========================
38 | * Boolean
39 | * ========================
40 | * The "boolean" type has two possible values: true or false.
41 | */
42 | let bool = true;
43 | console.log(`Boolean: ${bool}, typeof: ${typeof bool}`);
44 |
45 | /**
46 | * ========================
47 | * Number
48 | * ========================
49 | * The "number" type can represent both integers and floating-point numbers.
50 | */
51 | let num = 42;
52 | let float = 42.42;
53 | console.log(`Number: ${num}, typeof: ${typeof num}`);
54 | console.log(`Floating-Point Number: ${float}, typeof: ${typeof float}`);
55 |
56 | /**
57 | * ========================
58 | * BigInt
59 | * ========================
60 | * The "BigInt" type can represent integers of arbitrary length.
61 | */
62 | let bigInt = 42n;
63 | console.log(`BigInt: ${bigInt}, typeof: ${typeof bigInt}`);
64 |
65 | /**
66 | * ========================
67 | * String
68 | * ========================
69 | * The "string" type represents textual data.
70 | */
71 | let str = "Hello";
72 | console.log(`String: ${str}, typeof: ${typeof str}`);
73 |
74 | /**
75 | * ========================
76 | * Symbol
77 | * ========================
78 | * The "Symbol" type represents a unique value that's not equal to any other value.
79 | */
80 | let sym = Symbol("description");
81 | console.log(`Symbol: ${String(sym)}, typeof: ${typeof sym}`);
82 |
83 | /**
84 | * ========================
85 | * Object
86 | * ========================
87 | * The "object" type represents a collection of key-value pairs.
88 | */
89 | let obj = { key: "value" };
90 | console.log(`Object: ${JSON.stringify(obj)}, typeof: ${typeof obj}`);
91 |
92 | /**
93 | * ========================
94 | * Function
95 | * ========================
96 | * Functions in JavaScript are objects with the capability of being callable.
97 | */
98 | function func() {}
99 | console.log(`Function: ${func}, typeof: ${typeof func}`);
100 |
101 | /**
102 | * ========================
103 | * Array
104 | * ========================
105 | * Arrays are specialized objects used to store multiple values in a single variable.
106 | */
107 | let arr = [1, 2, 3];
108 | console.log(`Array: ${arr}, typeof: ${typeof arr}`);
109 |
110 | /**
111 | * ========================
112 | * Nuances
113 | * ========================
114 | * Null is a falsy value but it's not the boolean 'false'.
115 | */
116 | console.log(`Nuances: Null is falsy but not false: ${Boolean(nullVar) === false && nullVar !== false}`);
117 | }
118 |
119 | dataTypes();
120 |
--------------------------------------------------------------------------------
/chapters/02_type_conversion.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The `typeConversion` function demonstrates various techniques of type conversion in JavaScript.
3 | * This includes both explicit and implicit type conversions, each of which is outlined below.
4 | */
5 |
6 | function typeConversion() {
7 | /**
8 | * =================================
9 | * String to Number
10 | * =================================
11 | * You can convert a string to a number using the unary '+' operator or the Number() function.
12 | * Both methods return NaN (Not-a-Number) if the string cannot be converted to a number.
13 | */
14 | let strToNum = +"42";
15 | let strToNumUsingFunc = Number("42");
16 | console.log(`String to Number using unary '+': ${strToNum}, typeof: ${typeof strToNum}`);
17 | console.log(`String to Number using Number(): ${strToNumUsingFunc}, typeof: ${typeof strToNumUsingFunc}`);
18 |
19 | /**
20 | * =================================
21 | * Number to String
22 | * =================================
23 | * Numbers can be converted to strings using the String() function or the .toString() method.
24 | */
25 | let numToStr = String(42);
26 | let numToStrUsingMethod = (42).toString();
27 | console.log(`Number to String using String(): ${numToStr}, typeof: ${typeof numToStr}`);
28 | console.log(`Number to String using .toString(): ${numToStrUsingMethod}, typeof: ${typeof numToStrUsingMethod}`);
29 |
30 | /**
31 | * =================================
32 | * Boolean to Number
33 | * =================================
34 | * Boolean values can be converted to numbers using the Number() function.
35 | * Here, 'true' is converted to 1 and 'false' would be converted to 0.
36 | */
37 | let boolToNum = Number(true);
38 | console.log(`Boolean to Number: ${boolToNum}, typeof: ${typeof boolToNum}`);
39 |
40 | /**
41 | * =================================
42 | * Number to Boolean
43 | * =================================
44 | * Numbers can be converted to boolean values using the Boolean() function.
45 | * Here, any non-zero number will be converted to 'true' and zero to 'false'.
46 | */
47 | let numToBool = Boolean(1);
48 | console.log(`Number to Boolean: ${numToBool}, typeof: ${typeof numToBool}`);
49 |
50 | /**
51 | * =================================
52 | * Implicit Conversion
53 | * =================================
54 | * JavaScript performs implicit type conversion in certain expressions.
55 | * For example, using '+' with a string and a number results in string concatenation.
56 | */
57 | let implicit = "5" + 1;
58 | let implicitTwo = "5" * 1;
59 | console.log(`Implicit Conversion (String + Number): ${implicit}, typeof: ${typeof implicit}`);
60 | console.log(`Implicit Conversion (String * Number): ${implicitTwo}, typeof: ${typeof implicitTwo}`);
61 |
62 | /**
63 | * =================================
64 | * ParseInt and ParseFloat
65 | * =================================
66 | * The parseInt() and parseFloat() functions can convert a string into an integer or a floating-point number.
67 | * The second argument of parseInt is the radix, which specifies the base of the numeral system.
68 | */
69 | let parsedInt = parseInt("42px", 10);
70 | let parsedFloat = parseFloat("42.42px");
71 | console.log(`parseInt: ${parsedInt}, typeof: ${typeof parsedInt}`);
72 | console.log(`parseFloat: ${parsedFloat}, typeof: ${typeof parsedFloat}`);
73 |
74 | /**
75 | * =================================
76 | * JSON.parse and JSON.stringify
77 | * =================================
78 | * These functions are used for converting objects and arrays to JSON-formatted strings, and vice versa.
79 | */
80 | let obj = { key: "value" };
81 | let objToStr = JSON.stringify(obj);
82 | let strToObj = JSON.parse(objToStr);
83 | console.log(`JSON.stringify: ${objToStr}, typeof: ${typeof objToStr}`);
84 | console.log(`JSON.parse: ${JSON.stringify(strToObj)}, typeof: ${typeof strToObj}`);
85 |
86 | /**
87 | * =================================
88 | * Nuances and Special Cases
89 | * =================================
90 | * 1) Using '+' with a string and a number results in string concatenation.
91 | * 2) Despite representing an invalid or unrepresentable value, typeof NaN returns 'number'.
92 | * 3) Using parseInt() without specifying a radix can yield unpredictable results, especially with older browsers.
93 | */
94 | console.log(`Nuances: '5' + 1 results in a string: ${"5" + 1}`);
95 | console.log(`Nuances: typeof NaN is ${typeof NaN}`);
96 | console.log(`Nuances: parseInt('010') without radix is ${parseInt("010")}`);
97 | }
98 |
99 | typeConversion();
100 |
--------------------------------------------------------------------------------
/chapters/03_type_coercion.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The `typeCoercion` function explores the various scenarios where JavaScript performs
3 | * type coercion automatically. Type coercion can lead to unintended consequences,
4 | * so understanding how and when it happens is crucial.
5 | */
6 | function typeCoercion() {
7 | /**
8 | * ===========================================
9 | * Equality Operator (==)
10 | * ===========================================
11 | * The equality operator (==) performs type coercion when the operands are of different types.
12 | * This can lead to counter-intuitive results.
13 | */
14 | console.log(`'5' == 5: ${"5" == 5}`); // true, because the string '5' is coerced to the number 5
15 |
16 | /**
17 | * ===========================================
18 | * Inequality Operator (!=)
19 | * ===========================================
20 | * Much like the equality operator, the inequality operator (!=) also coerces types if they're different.
21 | */
22 | console.log(`'5' != 5: ${"5" != 5}`); // false, because '5' is coerced to 5, making them equal
23 |
24 | /**
25 | * ===========================================
26 | * Strict Equality (===) and Inequality (!==)
27 | * ===========================================
28 | * Unlike their non-strict counterparts, these operators do not perform type coercion,
29 | * and they compare both the value and the type.
30 | */
31 | console.log(`'5' === 5: ${"5" === 5}`); // false, because no type coercion is performed
32 | console.log(`'5' !== 5: ${"5" !== 5}`); // true, because they are of different types
33 |
34 | /**
35 | * ===========================================
36 | * Boolean Contexts
37 | * ===========================================
38 | * When a non-boolean value is used in a context that expects a boolean, JavaScript will coerce the value.
39 | */
40 | if ("non-empty string") {
41 | // Non-empty strings are coerced to true
42 | console.log("This will run because a non-empty string is truthy.");
43 | }
44 |
45 | /**
46 | * ===========================================
47 | * Number Contexts
48 | * ===========================================
49 | * In numerical operations (excluding the + operator), non-numbers are coerced to numbers.
50 | */
51 | console.log(`'5' * '2': ${"5" * "2"}`); // 10, because both strings are coerced to numbers
52 |
53 | /**
54 | * ===========================================
55 | * String Concatenation
56 | * ===========================================
57 | * The + operator can be a bit tricky. It will concatenate strings, but if one operand is a number,
58 | * it will coerce the number to a string before concatenation.
59 | */
60 | console.log(`5 + '5': ${5 + "5"}`); // '55', because the number 5 is coerced to a string
61 |
62 | /**
63 | * ===========================================
64 | * Array Coercion
65 | * ===========================================
66 | * Arrays will be coerced into strings in specific situations, such as when used with the + operator.
67 | */
68 | console.log(`[1, 2] + [3, 4]: ${[1, 2] + [3, 4]}`); // '1, 23, 4', because both arrays are coerced to strings
69 |
70 | /**
71 | * ===========================================
72 | * Nuances and Caveats
73 | * ===========================================
74 | * 1) undefined and null are loosely equal to each other but not to any other value.
75 | * 2) When comparing with null using non-strict comparison, only undefined will be equal to it.
76 | */
77 | console.log(`undefined == null: ${undefined == null}`); // true, a special case in JavaScript
78 | console.log(`0 == null: ${0 == null}`); // false, because null is only loosely equal to undefined
79 | }
80 |
81 | typeCoercion();
82 |
--------------------------------------------------------------------------------
/chapters/04.1_ternary.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Conditional (Ternary) Operator
4 | * ========================================================
5 | * The conditional (ternary) operator is a shorthand way to perform conditional (if-else-like) operations.
6 | * The syntax is: condition ? expression1 : expression2
7 | * If the condition is true, expression1 is executed, otherwise, expression2 is executed.
8 | */
9 |
10 | /**
11 | * ========================================================
12 | * Basic Usage
13 | * ========================================================
14 | * In a simple example, you can use it to assign a value to a variable based on a condition.
15 | */
16 | const isAdult = true;
17 | const type = isAdult ? "Adult" : "Minor";
18 | console.log(`Person is an: ${type}`); // Output: 'Person is an: Adult'
19 |
20 | /**
21 | * ========================================================
22 | * Nested Ternary Operators
23 | * ========================================================
24 | * Ternary operators can be nested for multiple conditions, but this may reduce readability.
25 | */
26 | const age = 25;
27 | const ageGroup = age < 18 ? "Minor" : age < 60 ? "Adult" : "Senior";
28 | console.log(`Age Group: ${ageGroup}`); // Output: 'Age Group: Adult'
29 |
30 | /**
31 | * ========================================================
32 | * Using with Functions
33 | * ========================================================
34 | * You can also execute functions using the ternary operator.
35 | */
36 | function greetMorning() {
37 | return "Good Morning";
38 | }
39 |
40 | function greetEvening() {
41 | return "Good Evening";
42 | }
43 |
44 | const isMorning = true;
45 | console.log(isMorning ? greetMorning() : greetEvening()); // Output: 'Good Morning'
46 |
47 | /**
48 | * ========================================================
49 | * As an Expression
50 | * ========================================================
51 | * The ternary operator is an expression, meaning it returns a value.
52 | * You can use it inline with other operations.
53 | */
54 | const score = 95;
55 | console.log(`You ${score > 50 ? "passed" : "failed"} the exam.`); // Output: 'You passed the exam.'
56 |
57 | /**
58 | * ========================================================
59 | * Nuances and Advanced Techniques
60 | * ========================================================
61 | */
62 |
63 | /**
64 | * 1. Type Coercion
65 | * ----------------
66 | * Be cautious about type coercion when using the ternary operator, as it follows the same rules as other JavaScript operators.
67 | */
68 | const value = "5";
69 | const number = value == 5 ? "Loose equality" : "Strict inequality";
70 | console.log(`Type Coercion: ${number}`); // Output: 'Loose equality'
71 |
72 | /**
73 | * 2. Avoid Side Effects
74 | * ----------------------
75 | * Avoid using ternary operators for operations that produce side effects, like assignments or function calls with side effects.
76 | */
77 |
78 | /**
79 | * 3. Readability
80 | * --------------
81 | * While chaining or nesting ternary operators can be powerful, it can also make code harder to read and maintain.
82 | */
83 |
--------------------------------------------------------------------------------
/chapters/04.2_nullish_coalescing.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Nullish Coalescing Operator (??)
4 | * ========================================================
5 | * The nullish coalescing operator (??) is a logical operator that returns the right-hand operand
6 | * when the left-hand operand is null or undefined; otherwise, it returns the left-hand operand.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * Basic Usage
12 | * ========================================================
13 | * It is often used to assign default values to variables.
14 | */
15 | const name = null;
16 | const defaultName = "John Doe";
17 | const username = name ?? defaultName;
18 | console.log(`Username: ${username}`); // Output: 'Username: John Doe'
19 |
20 | /**
21 | * ========================================================
22 | * Difference from Logical OR (||)
23 | * ========================================================
24 | * The nullish coalescing operator only checks for null or undefined,
25 | * while the logical OR operator (||) checks for any falsy value.
26 | */
27 | const zeroValue = 0;
28 | const orResult = zeroValue || "default";
29 | const nullishResult = zeroValue ?? "default";
30 | console.log(`OR Result: ${orResult}`); // Output: 'OR Result: default'
31 | console.log(`Nullish Result: ${nullishResult}`); // Output: 'Nullish Result: 0'
32 |
33 | /**
34 | * ========================================================
35 | * Combining with Ternary Operator
36 | * ========================================================
37 | * The nullish coalescing operator can be combined with the ternary operator for more complex conditions.
38 | */
39 | const age = null;
40 | const ageValue = age ?? (age === 0 ? "Zero" : "Undefined");
41 | console.log(`Age Value: ${ageValue}`); // Output: 'Age Value: Undefined'
42 |
43 | /**
44 | * ========================================================
45 | * Use in Object Properties
46 | * ========================================================
47 | * You can also use it to provide default values for object properties.
48 | */
49 | const user = {
50 | firstName: "Jane",
51 | };
52 | const lastName = user?.lastName ?? "Doe";
53 | console.log(`User's Last Name: ${lastName}`); // Output: 'User's Last Name: Doe'
54 |
55 | /**
56 | * ========================================================
57 | * Nuances and Advanced Techniques
58 | * ========================================================
59 | */
60 |
61 | /**
62 | * 1. Short-Circuiting
63 | * --------------------
64 | * Similar to other logical operators, the nullish coalescing operator short-circuits,
65 | * meaning it won't evaluate the right-hand expression if it doesn't need to.
66 | */
67 |
68 | /**
69 | * 2. Operator Precedence
70 | * ----------------------
71 | * Be cautious with operator precedence when combining nullish coalescing with other operators.
72 | * Using parentheses can help make the behavior explicit.
73 | */
74 | const result = 5 + (null ?? 2); // This will be 5 + 2 = 7
75 |
76 | /**
77 | * 3. Type Considerations
78 | * -----------------------
79 | * Nullish coalescing is useful for avoiding unexpected type conversions,
80 | * particularly when dealing with possible null or undefined values.
81 | */
82 |
83 | /**
84 | * 4. Use Cases
85 | * ------------
86 | * It's commonly used in function arguments, object destructuring, and variable assignments
87 | * to provide default values only when a variable is null or undefined, but not for other falsy values.
88 | */
89 |
--------------------------------------------------------------------------------
/chapters/05_control_flow.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The `controlFlow` function demonstrates various control flow mechanisms in JavaScript.
3 | * This includes conditional statements, loops, and other ways to control the flow of code execution.
4 | */
5 | function controlFlow() {
6 | /**
7 | * =================================================
8 | * If-Else Statements
9 | * =================================================
10 | * The if-else statement is used to execute a block of code if a specified condition is true.
11 | * If the condition is false, another block of code can be executed through the else statement.
12 | */
13 | const x = 10;
14 | if (x > 5) {
15 | console.log("x is greater than 5");
16 | } else {
17 | console.log("x is not greater than 5");
18 | }
19 |
20 | /**
21 | * =================================================
22 | * If-Else-If Ladder
23 | * =================================================
24 | * An if-else-if ladder is useful for decision-making between multiple alternatives.
25 | */
26 | const y = 20;
27 | if (y > 50) {
28 | console.log("y is greater than 50");
29 | } else if (y > 10) {
30 | console.log("y is greater than 10 but less than or equal to 50");
31 | } else {
32 | console.log("y is less than or equal to 10");
33 | }
34 |
35 | /**
36 | * =================================================
37 | * Ternary Conditional Operator (?:)
38 | * =================================================
39 | * A shorthand representation for simple if-else statements. It takes three operands.
40 | */
41 | const z = 30;
42 | const result = z > 25 ? "z is greater than 25" : "z is not greater than 25";
43 | console.log(`Ternary Operator: ${result}`);
44 |
45 | /**
46 | * =================================================
47 | * Switch-Case Statement
48 | * =================================================
49 | * The switch-case statement is useful for decision-making among multiple case clauses.
50 | * 'break' is used to exit a case once a match is found.
51 | */
52 | const fruit = "apple";
53 | switch (fruit) {
54 | case "banana":
55 | console.log("It's a banana!");
56 | break;
57 | case "apple":
58 | console.log("It's an apple!");
59 | break;
60 | default:
61 | console.log("Unknown fruit.");
62 | }
63 |
64 | /**
65 | * =================================================
66 | * Falsy Values and Truthy Values
67 | * =================================================
68 | * Falsy values: false, 0, "", null, undefined, NaN
69 | * Truthy values: All other values not considered falsy.
70 | */
71 | if ("") {
72 | console.log("This won't be logged, because an empty string is falsy.");
73 | } else {
74 | console.log("An empty string is a falsy value.");
75 | }
76 |
77 | /**
78 | * =================================================
79 | * Short-circuit Evaluation
80 | * =================================================
81 | * The logical OR (||) and AND (&&) operators can be used for conditions,
82 | * but they can also be used to evaluate and return expressions.
83 | */
84 | const output = null || "Default Value";
85 | console.log(`Short-circuit: ${output}`); // Logs "Default Value"
86 |
87 | /**
88 | * =================================================
89 | * Nullish Coalescing Operator (??)
90 | * =================================================
91 | * The ?? operator returns the right-hand operand when the left-hand operand is null or undefined.
92 | * This is a more precise version of the behavior provided by ||.
93 | */
94 | const value = null ?? "Fallback";
95 | console.log(`Nullish Coalescing: ${value}`); // Logs "Fallback"
96 | }
97 |
98 | controlFlow();
99 |
--------------------------------------------------------------------------------
/chapters/06_loops.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The `loops` function demonstrates various types of loops available in JavaScript.
3 | * Each loop has its own use-case and characteristics.
4 | */
5 | function loops() {
6 | /**
7 | * ========================================================
8 | * For Loop
9 | * ========================================================
10 | * The for loop is the most basic loop in JavaScript.
11 | * It consists of three optional expressions: initialization, condition, and final-expression.
12 | */
13 | console.log("For Loop:");
14 | for (let i = 0; i < 5; i++) {
15 | console.log(i); // Output: 0, 1, 2, 3, 4
16 | }
17 |
18 | /**
19 | * ========================================================
20 | * While Loop
21 | * ========================================================
22 | * The while loop continues executing as long as its condition is true.
23 | */
24 | console.log("While Loop:");
25 | let j = 0;
26 | while (j < 5) {
27 | console.log(j); // Output: 0, 1, 2, 3, 4
28 | j++;
29 | }
30 |
31 | /**
32 | * ========================================================
33 | * Do-While Loop
34 | * ========================================================
35 | * Similar to a while loop, but it guarantees at least one execution of the code block,
36 | * even if the condition is false.
37 | */
38 | console.log("Do-While Loop:");
39 | let k = 0;
40 | do {
41 | console.log(k); // Output: 0, 1, 2, 3, 4
42 | k++;
43 | } while (k < 5);
44 |
45 | /**
46 | * ========================================================
47 | * Array forEach Loop
48 | * ========================================================
49 | * The forEach() method executes a provided function once for each array element.
50 | */
51 | console.log("Array forEach Loop:");
52 | const arr = [1, 2, 3, 4, 5];
53 | arr.forEach((item, index) => {
54 | console.log(`Item at ${index}: ${item}`);
55 | });
56 |
57 | /**
58 | * ========================================================
59 | * Loop Control Statements
60 | * ========================================================
61 | * The 'break' statement terminates the loop, and 'continue' skips to the next iteration.
62 | */
63 | console.log("Loop Control Statements:");
64 | for (let i = 0; i < 5; i++) {
65 | if (i === 2) {
66 | continue; // Skips the current iteration
67 | }
68 | if (i === 4) {
69 | break; // Terminates the loop
70 | }
71 | console.log(i); // Output: 0, 1, 3
72 | }
73 |
74 | /**
75 | * ========================================================
76 | * Nested Loops
77 | * ========================================================
78 | * You can nest loops within loops, but be cautious, as it can lead to performance issues.
79 | */
80 | console.log("Nested Loops:");
81 | for (let i = 0; i < 3; i++) {
82 | for (let j = 0; j < 3; j++) {
83 | console.log(`i: ${i}, j: ${j}`); // Outputs 9 combinations of i and j
84 | }
85 | }
86 |
87 | /**
88 | * ========================================================
89 | * The 'for...in' Loop
90 | * ========================================================
91 | * Enumerates the properties of an object.
92 | * NOTE: It's not recommended for array iterations as it also enumerates prototype properties.
93 | */
94 | console.log("For...in Loop:");
95 | const obj = { a: 1, b: 2, c: 3 };
96 | for (const prop in obj) {
97 | console.log(`obj.${prop} = ${obj[prop]}`);
98 | }
99 |
100 | /**
101 | * ========================================================
102 | * The 'for...of' Loop
103 | * ========================================================
104 | * The for...of loop iterates over values in iterable objects like arrays, strings, maps, etc.
105 | */
106 | console.log("For...of Loop:");
107 | const str = "Hello";
108 | for (const char of str) {
109 | console.log(char); // Output: H, e, l, l, o
110 | }
111 | }
112 |
113 | loops();
114 |
--------------------------------------------------------------------------------
/chapters/08_strings.js:
--------------------------------------------------------------------------------
1 | function strings() {
2 | /**
3 | * ========================================================
4 | * String creation
5 | * ========================================================
6 | * In JavaScript, strings can be created in multiple ways:
7 | * 1. Using single quotes: '...'
8 | * 2. Using double quotes: "..."
9 | * 3. Using backticks: `...` (ES6 template literals)
10 | */
11 | const str1 = "Hello";
12 | const str2 = "World";
13 | const str3 = `Hello, ${str2}`;
14 | console.log(`String using Single Quotes: ${str1}`);
15 | console.log(`String using Double Quotes: ${str2}`);
16 | console.log(`String using Template Literals: ${str3}`);
17 |
18 | /**
19 | * ========================================================
20 | * String length
21 | * ========================================================
22 | * The 'length' property can be used to obtain the number of code units in a string.
23 | */
24 | console.log(`String Length: ${str1.length}`);
25 |
26 | /**
27 | * ========================================================
28 | * String Concatenation
29 | * ========================================================
30 | * Strings can be joined together (concatenated) using either the '+' operator or the 'concat()' method.
31 | */
32 | const concatenated = str1 + " " + str2;
33 | const concatenatedWithMethod = str1.concat(" ", str2);
34 | console.log(`Concatenated with +: ${concatenated}`);
35 | console.log(`Concatenated with concat(): ${concatenatedWithMethod}`);
36 |
37 | /**
38 | * ========================================================
39 | * Common String Methods
40 | * ========================================================
41 | * JavaScript provides various methods to manipulate and query strings.
42 | * Examples include 'indexOf', 'slice', 'toUpperCase', and 'toLowerCase'.
43 | */
44 | const index = str1.indexOf("e"); // Finds the first occurrence of 'e'
45 | const sliced = str1.slice(1, 4); // Extracts characters between index 1 and 3
46 | const upper = str1.toUpperCase(); // Converts the string to uppercase
47 | const lower = str1.toLowerCase(); // Converts the string to lowercase
48 | console.log(`Index of 'e': ${index}`);
49 | console.log(`Slice from index 1 to 3: ${sliced}`);
50 | console.log(`Uppercase: ${upper}`);
51 | console.log(`Lowercase: ${lower}`);
52 |
53 | /**
54 | * ========================================================
55 | * Template Literals
56 | * ========================================================
57 | * Template literals provide more advanced features like string interpolation, expressions, and multiline support.
58 | */
59 | const multiLine = `This is a
60 | multiline
61 | string.`;
62 | console.log(`Multiline String: \n${multiLine}`);
63 |
64 | /**
65 | * ========================================================
66 | * Sub-strings and Character Access
67 | * ========================================================
68 | * Various methods like 'substring', 'substr', and 'charAt' are available to extract substrings and individual characters.
69 | */
70 | const subString = str1.substring(1, 4); // Extracts characters between index 1 and 3
71 | const subStr = str1.substr(1, 3); // Starts from index 1 and extracts 3 characters
72 | const char = str1.charAt(1); // Gets the character at index 1
73 | console.log(`Substring (1, 4): ${subString}`);
74 | console.log(`Substr (1, 3): ${subStr}`);
75 | console.log(`Char at index 1: ${char}`);
76 |
77 | /**
78 | * ========================================================
79 | * String Split and Join
80 | * ========================================================
81 | * ----------------------
82 | * The 'split' method can be used to turn a string into an array based on a delimiter.
83 | * The 'join' method reverses this operation, joining an array into a string.
84 | */
85 | const splitString = str1.split(""); // Splits the string into its individual characters
86 | const joinedString = splitString.join("-"); // Joins the array elements with a hyphen
87 | console.log(`Split String: ${splitString}`);
88 | console.log(`Joined String: ${joinedString}`);
89 |
90 | /**
91 | * ========================================================
92 | * String immutability
93 | * ========================================================
94 | * Strings in JavaScript are immutable, meaning they cannot be changed once created.
95 | * Any method that appears to alter a string will actually return a new string.
96 | */
97 | const newStr = str1.replace("H", "Y"); // Returns a new string with 'H' replaced by 'Y'
98 | console.log(`Original String: ${str1}`);
99 | console.log(`New String: ${newStr}`);
100 | }
101 |
102 | strings();
103 |
--------------------------------------------------------------------------------
/chapters/09_functions.js:
--------------------------------------------------------------------------------
1 | function functions() {
2 | /**
3 | * ========================================================
4 | * Function Declaration
5 | * ========================================================
6 | * In a function declaration, the function is defined using the 'function' keyword and can be called by its name.
7 | * Function declarations are hoisted, making them available throughout their scope.
8 | */
9 | function sayHello(name) {
10 | return `Hello, ${name}`;
11 | }
12 | console.log(sayHello("Ishtmeet")); // Outputs: "Hello, Ishtmeet"
13 |
14 | /**
15 | * ========================================================
16 | * Function Expression
17 | * ========================================================
18 | * A function expression assigns a function to a variable.
19 | * Unlike function declarations, function expressions are not hoisted.
20 | */
21 | const sayGoodbye = function (name) {
22 | return `Goodbye, ${name}`;
23 | };
24 | console.log(sayGoodbye("Bob")); // Outputs: "Goodbye, Bob"
25 |
26 | /**
27 | * ========================================================
28 | * Arrow Function
29 | * ========================================================
30 | * Arrow functions, introduced in ES6, provide a more concise syntax for function expressions.
31 | * Note that 'this' behaves differently in arrow functions.
32 | */
33 | const add = (a, b) => a + b;
34 | console.log(add(3, 4)); // Outputs: "7"
35 |
36 | /**
37 | * ========================================================
38 | * Callback Functions
39 | * ========================================================
40 | * Functions can be passed as arguments to other functions.
41 | * This enables a higher-order programming paradigm.
42 | */
43 | function process(callback, value) {
44 | return callback(value);
45 | }
46 | console.log(process(add.bind(null, 5), 3)); // Outputs: "8"
47 |
48 | /**
49 | * ========================================================
50 | * IIFE (Immediately Invoked Function Expression)
51 | * ========================================================
52 | * An IIFE is a function that runs as soon as it is defined.
53 | * It's a way to run functions immediately, to use variable scope in a specific way.
54 | */
55 | (function () {
56 | console.log("IIFE executed"); // Outputs: "IIFE executed"
57 | })();
58 |
59 | /**
60 | * ========================================================
61 | * Default Parameters
62 | * ========================================================
63 | * Starting with ES6, default parameter values can be defined in the function signature.
64 | */
65 | function greet(name = "Guest") {
66 | console.log(`Hi ${name}`); // Outputs: "Hi Guest" if no argument is provided
67 | }
68 | greet();
69 | greet("Emily"); // Outputs: "Hi Emily"
70 |
71 | /**
72 | * ========================================================
73 | * Rest Parameters
74 | * ========================================================
75 | * Rest parameters (introduced in ES6) allow functions to accept multiple arguments and collect them into an array.
76 | */
77 | function sum(...numbers) {
78 | return numbers.reduce((a, b) => a + b, 0);
79 | }
80 | console.log(sum(1, 2, 3, 4)); // Outputs: "10"
81 |
82 | /**
83 | * ========================================================
84 | * Function Hoisting
85 | * ========================================================
86 | * Function declarations are hoisted, allowing them to be used before they are defined in the code.
87 | * Function expressions are not hoisted.
88 | */
89 | console.log(earlyInvoke()); // Outputs: "Hoisted"
90 |
91 | function earlyInvoke() {
92 | return "Hoisted";
93 | }
94 | }
95 |
96 | functions();
97 |
--------------------------------------------------------------------------------
/chapters/10_scope.js:
--------------------------------------------------------------------------------
1 | function scopes() {
2 | /**
3 | * ========================================================
4 | * Global Scope
5 | * ========================================================
6 | * Variables declared outside any function, loop, or block are in the global scope.
7 | * They can be accessed and modified from any part of the code.
8 | */
9 | let globalVar = "I'm global!";
10 | console.log(`Global scope: ${globalVar}`); // Output: "Global scope: I'm global!"
11 |
12 | /**
13 | * ========================================================
14 | * Function Scope
15 | * ========================================================
16 | * Variables declared within a function are in the function's local scope.
17 | * They are not accessible outside of that function.
18 | */
19 | function functionScopeDemo() {
20 | let functionVar = "I'm local to this function!";
21 | console.log(`Function scope: ${functionVar}`); // Output: "Function scope: I'm local to this function!"
22 | }
23 | functionScopeDemo();
24 |
25 | /**
26 | * ========================================================
27 | * Block Scope
28 | * ========================================================
29 | * Variables declared with 'let' and 'const' within a block (e.g., loops, conditionals) are block-scoped.
30 | */
31 | {
32 | let blockVar = "I'm local to this block!";
33 | console.log(`Block scope: ${blockVar}`); // Output: "Block scope: I'm local to this block!"
34 | }
35 |
36 | /**
37 | * ========================================================
38 | * Hoisting
39 | * ========================================================
40 | * Variables declared with 'var' are hoisted to the top of their scope and automatically initialized with 'undefined'.
41 | * Variables declared with 'let' and 'const' are also hoisted but are not initialized, leading to a 'ReferenceError' if accessed before declaration.
42 | */
43 | console.log(`Hoisting: varVar is ${varVar}`); // Outputs: "Hoisting: varVar is undefined"
44 | var varVar = "I'm hoisted";
45 | // console.log(`Hoisting: letVar is ${letVar}`); // Uncommenting will throw a ReferenceError
46 | let letVar = "I'm also hoisted but not initialized";
47 |
48 | /**
49 | * ========================================================
50 | * Shadowing
51 | * ========================================================
52 | * When a variable in a local scope has the same name as a variable in an outer scope, this is known as shadowing.
53 | * The inner, or local, variable takes precedence over the outer variable.
54 | */
55 | let shadowVar = "I'm in the outer scope";
56 | {
57 | let shadowVar = "I'm in the inner scope";
58 | console.log(`Shadowing: ${shadowVar}`); // Output: "Shadowing: I'm in the inner scope"
59 | }
60 | console.log(`Shadowing: ${shadowVar}`); // Output: "Shadowing: I'm in the outer scope"
61 |
62 | /**
63 | * ========================================================
64 | * Closure
65 | * ========================================================
66 | * A closure is a function object that has access to its own scope, the scope of the outer function, and the global scope.
67 | */
68 | function outerFunction() {
69 | let outerVar = "I'm an outer variable!";
70 | function innerFunction() {
71 | console.log(`Closure: ${outerVar}`); // Can access outerVar
72 | }
73 | return innerFunction;
74 | }
75 | const myClosure = outerFunction();
76 | myClosure(); // Output: "I'm an outer variable!"
77 |
78 | /**
79 | * ========================================================
80 | * Lexical Scoping
81 | * ========================================================
82 | * Lexical scoping means that the scope of a variable is determined by its position in the code, not by function calls or other runtime operations.
83 | * Nested functions have access to variables declared in their outer scope.
84 | */
85 | function lexicalOuter() {
86 | let lexicalVar = "I'm lexically outside!";
87 | function lexicalInner() {
88 | console.log(`Lexical Scoping: ${lexicalVar}`); // Output: "Lexical Scoping: I'm lexically outside!"
89 | }
90 | lexicalInner();
91 | }
92 | lexicalOuter();
93 | }
94 |
95 | scopes();
96 |
--------------------------------------------------------------------------------
/chapters/11_closure.js:
--------------------------------------------------------------------------------
1 | function closures() {
2 | /**
3 | * ========================================================
4 | * Basic Closure
5 | * ========================================================
6 | * A closure is an inner function that has access to the outer function's variables.
7 | * It encapsulates the lexical environment, allowing the inner function to access variables from the outer function.
8 | */
9 | function outerFunction(outerVariable) {
10 | function innerFunction(innerVariable) {
11 | // innerFunction is a closure because it captures outerVariable.
12 | console.log(`Outer variable: ${outerVariable}`);
13 | console.log(`Inner variable: ${innerVariable}`);
14 | }
15 | return innerFunction;
16 | }
17 | const newFunction = outerFunction("outer");
18 | newFunction("inner"); // Outputs: "Outer variable: outer" and "Inner variable: inner"
19 |
20 | /**
21 | * ========================================================
22 | * Data Encapsulation and Private Variables
23 | * ========================================================
24 | * Closures can encapsulate variables, effectively making them private.
25 | * This allows for data privacy and modularity.
26 | */
27 | function counter() {
28 | let count = 0;
29 | return {
30 | increment: function () {
31 | count++;
32 | return count;
33 | },
34 | decrement: function () {
35 | count--;
36 | return count;
37 | },
38 | };
39 | }
40 | const myCounter = counter();
41 | console.log(`Counter: ${myCounter.increment()}`); // Outputs: "Counter: 1"
42 | console.log(`Counter: ${myCounter.increment()}`); // Outputs: "Counter: 2"
43 | // Variable 'count' is encapsulated, making it inaccessible from outside.
44 |
45 | /**
46 | * ========================================================
47 | * Dynamic Function Generation
48 | * ========================================================
49 | * Closures allow for dynamic function generation.
50 | * These dynamically generated functions can keep track of variables from their outer scope.
51 | */
52 | function multiplier(factor) {
53 | return function (x) {
54 | return x * factor;
55 | };
56 | }
57 | const timesTwo = multiplier(2);
58 | console.log(`Times Two: ${timesTwo(4)}`); // Outputs: "Times Two: 8"
59 |
60 | /**
61 | * ========================================================
62 | * Event Handling and Callbacks
63 | * ========================================================
64 | * Closures enable you to handle events and callbacks effectively by preserving state across multiple invocations.
65 | * This is critical in asynchronous programming.
66 | */
67 | let clickCount = 0;
68 | document.addEventListener("click", function () {
69 | clickCount++;
70 | console.log(`Click count: ${clickCount}`);
71 | });
72 | // The event listener closure captures the clickCount variable, allowing it to persist across multiple clicks.
73 |
74 | /**
75 | * ========================================================
76 | * Closure Gotchas: Looping
77 | * ========================================================
78 | * Be cautious of closures capturing loop variables.
79 | * This is often called the "loop variable closure problem" and leads to unexpected behavior.
80 | */
81 | for (var i = 1; i <= 3; i++) {
82 | setTimeout(function () {
83 | console.log(`Loop Problem: ${i}`); // Outputs: "Loop Problem: 4" three times
84 | }, 1000);
85 | }
86 |
87 | // Solution: Use 'let' instead of 'var' or pass 'i' as an argument to a self-invoking function.
88 | for (let i = 1; i <= 3; i++) {
89 | setTimeout(function () {
90 | console.log(`Loop Solved: ${i}`); // Outputs: "Loop Solved: 1", "Loop Solved: 2", "Loop Solved: 3"
91 | }, 1000);
92 | }
93 | }
94 |
95 | closures();
96 |
--------------------------------------------------------------------------------
/chapters/12.1_optional_chaining.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Optional Chaining (?.)
4 | * ========================================================
5 | * The optional chaining operator (?.) permits reading the value of a property located deep within a chain
6 | * of connected objects without having to expressly validate that each reference in the chain is valid.
7 | * It short-circuits if it encounters a null or undefined, returning undefined as the evaluation result.
8 | */
9 |
10 | /**
11 | * ========================================================
12 | * Basic Usage
13 | * ========================================================
14 | * Optional chaining is used to safely access deeply nested properties of an object.
15 | */
16 | const user = {
17 | profile: {
18 | name: "John",
19 | age: 30,
20 | },
21 | };
22 |
23 | const userName = user?.profile?.name;
24 | console.log(`User Name: ${userName}`); // Output: 'User Name: John'
25 |
26 | /**
27 | * ========================================================
28 | * Accessing Array Items
29 | * ========================================================
30 | * You can use optional chaining when attempting to access an index of an array that might be undefined.
31 | */
32 | const arr = [1, 2, 3];
33 | const value = arr?.[4];
34 | console.log(`Array Value: ${value}`); // Output: 'Array Value: undefined'
35 |
36 | /**
37 | * ========================================================
38 | * Function or Method Calls
39 | * ========================================================
40 | * You can also use optional chaining when calling a function or method that might not exist.
41 | */
42 | const greet = user?.profile?.greet?.();
43 | console.log(`Greeting: ${greet}`); // Output: 'Greeting: undefined'
44 |
45 | /**
46 | * ========================================================
47 | * Combined with Nullish Coalescing
48 | * ========================================================
49 | * Optional chaining can be combined with the nullish coalescing operator for setting defaults.
50 | */
51 | const city = user?.profile?.address?.city ?? "Unknown";
52 | console.log(`City: ${city}`); // Output: 'City: Unknown'
53 |
54 | /**
55 | * ========================================================
56 | * Nuances and Advanced Techniques
57 | * ========================================================
58 | */
59 | /**
60 | * 1. Short-Circuiting
61 | * --------------------
62 | * If optional chaining encounters null or undefined, it short-circuits the rest of the evaluation.
63 | */
64 | const obj = { prop: null };
65 | const shortCircuit = obj?.prop?.nonExistentMethod?.();
66 | console.log(`Short-Circuit Result: ${shortCircuit}`); // Output: 'Short-Circuit Result: undefined'
67 |
68 | /**
69 | * 2. Operator Precedence
70 | * ----------------------
71 | * To avoid any ambiguity when combining optional chaining with other operators, use parentheses.
72 | */
73 | const mixed = { value: 5 };
74 | const ambiguous = mixed?.value + 1; // This will result in 6, as you would expect
75 | const clarified = mixed?.value + 1; // Use parentheses to make the operation order explicit
76 | console.log(`Ambiguous: ${ambiguous}`); // Output: 'Ambiguous: 6'
77 | console.log(`Clarified: ${clarified}`); // Output: 'Clarified: 6'
78 |
79 | /**
80 | * 3. Functionality Limitation
81 | * ---------------------------
82 | * The operator only checks for null or undefined, but not for other falsy values like 0 or ''.
83 | */
84 | const zeroCheck = { zero: 0 };
85 | const zeroValue = zeroCheck?.zero ?? "No value";
86 | console.log(`Zero Value: ${zeroValue}`); // Output: 'Zero Value: 0'
87 |
88 | // const user: User = { id: 1, name: { first: 'John' } };
89 | // const lastName = user?.name?.last; // No TypeScript error here
90 |
--------------------------------------------------------------------------------
/chapters/13_inheritance_objects.js:
--------------------------------------------------------------------------------
1 | function inheritanceWithObjects() {
2 | /**
3 | * ========================================================
4 | * Prototypal Inheritance
5 | * ========================================================
6 | * JavaScript uses prototypes for inheritance.
7 | * Every object can have a reference to its prototype, an object from which it inherits properties.
8 | */
9 | const animal = {
10 | makeSound: function () {
11 | return "Some generic animal sound";
12 | },
13 | };
14 |
15 | const dog = Object.create(animal);
16 | dog.bark = function () {
17 | return "Woof!";
18 | };
19 |
20 | console.log(dog.makeSound()); // Logs "Some generic animal sound", inherited from animal
21 |
22 | /**
23 | * ========================================================
24 | * Constructor Function Inheritance
25 | * ========================================================
26 | * Inheritance can also be implemented using constructor functions.
27 | * The 'Vehicle' constructor serves as a base class for other vehicle types.
28 | */
29 | function Vehicle(type) {
30 | this.type = type;
31 | }
32 |
33 | Vehicle.prototype.move = function () {
34 | return "Moving!";
35 | };
36 |
37 | function Car(brand) {
38 | Vehicle.call(this, "Car"); // Call the parent constructor
39 | this.brand = brand;
40 | }
41 |
42 | // Set up the prototype chain
43 | Car.prototype = Object.create(Vehicle.prototype);
44 | Car.prototype.constructor = Car;
45 | Car.prototype.honk = function () {
46 | return "Beep!";
47 | };
48 |
49 | const myCar = new Car("Tesla");
50 | console.log(myCar.move()); // Logs "Moving!", inherited from Vehicle
51 |
52 | /**
53 | * ========================================================
54 | * Overriding Methods
55 | * ========================================================
56 | * Inherited methods can be overridden in the derived object.
57 | * Here, the makeSound method of 'dog' is overridden.
58 | */
59 | dog.makeSound = function () {
60 | return "Woof! Woof!";
61 | };
62 |
63 | console.log(dog.makeSound()); // Logs "Woof! Woof!", overridden method
64 |
65 | /**
66 | * ========================================================
67 | * Using `instanceof`
68 | * ========================================================
69 | * The `instanceof` operator checks if an object is an instance of a particular class or constructor.
70 | */
71 | console.log(myCar instanceof Car); // Logs true
72 | console.log(myCar instanceof Vehicle); // Logs true
73 |
74 | /**
75 | * ========================================================
76 | * Static Methods and Inheritance
77 | * ========================================================
78 | * Static methods are not associated with individual instances but with the constructor itself.
79 | * They are inherited by child constructors.
80 | */
81 | Vehicle.staticMethod = function () {
82 | return "I am a static method in Vehicle";
83 | };
84 |
85 | console.log(Car.staticMethod()); // Logs 'I am a static method in Vehicle', inherited from Vehicle
86 |
87 | /**
88 | * ========================================================
89 | * Object's Own Methods
90 | * ========================================================
91 | * Using Object.prototype.hasOwnProperty() to check if a property is owned by the object or inherited from the prototype.
92 | */
93 | for (let prop in dog) {
94 | if (dog.hasOwnProperty(prop)) {
95 | console.log(`${prop}: ${dog[prop]}`);
96 | }
97 | }
98 | }
99 |
100 | inheritanceWithObjects();
101 |
--------------------------------------------------------------------------------
/chapters/14_classes.js:
--------------------------------------------------------------------------------
1 | function classes() {
2 | /**
3 | * ========================================================
4 | * Class Declaration
5 | * ========================================================
6 | * A class serves as a blueprint for creating objects.
7 | * It encapsulates data and behavior for an object.
8 | */
9 | class Animal {
10 | constructor(name) {
11 | this.name = name;
12 | }
13 |
14 | makeSound() {
15 | return `${this.name} makes a sound.`;
16 | }
17 | }
18 |
19 | // Create an instance of the Animal class
20 | const animal = new Animal("Dog");
21 | console.log(animal.makeSound()); // Outputs: "Dog makes a sound."
22 |
23 | /**
24 | * ========================================================
25 | * Class Inheritance
26 | * ========================================================
27 | * Classes can inherit properties and methods from other classes.
28 | * The `extends` keyword is used for this purpose.
29 | */
30 | class Dog extends Animal {
31 | makeSound() {
32 | return `${this.name} barks.`;
33 | }
34 | }
35 |
36 | // Create an instance of the Dog class
37 | const dog = new Dog("Buddy");
38 | console.log(dog.makeSound()); // Outputs: "Buddy barks."
39 |
40 | /**
41 | * ========================================================
42 | * Access Modifiers
43 | * ========================================================
44 | * JavaScript classes don't have traditional access modifiers like private or public,
45 | * but you can use prefixes like # to denote private fields.
46 | */
47 | class Car {
48 | #brand;
49 |
50 | constructor(brand) {
51 | this.#brand = brand;
52 | }
53 |
54 | getBrand() {
55 | return this.#brand;
56 | }
57 | }
58 |
59 | const car = new Car("Tesla");
60 | console.log(car.getBrand()); // Outputs: "Tesla"
61 |
62 | /**
63 | * ========================================================
64 | * Static Methods
65 | * ========================================================
66 | * Static methods are methods that belong to the class, not an instance of the class.
67 | */
68 | class MathUtility {
69 | static add(a, b) {
70 | return a + b;
71 | }
72 | }
73 |
74 | console.log(MathUtility.add(5, 3)); // Outputs: 8
75 |
76 | /**
77 | * ========================================================
78 | * Getters and Setters
79 | * ========================================================
80 | * Getters and setters are special methods that allow controlled access to class properties.
81 | */
82 | class Circle {
83 | constructor(radius) {
84 | this._radius = radius;
85 | }
86 |
87 | get radius() {
88 | return this._radius;
89 | }
90 |
91 | set radius(value) {
92 | if (value < 0) {
93 | console.log("Invalid radius");
94 | } else {
95 | this._radius = value;
96 | }
97 | }
98 | }
99 |
100 | const circle = new Circle(5);
101 | console.log(circle.radius); // Outputs: 5
102 |
103 | circle.radius = 10;
104 | console.log(circle.radius); // Outputs: 10
105 |
106 | /**
107 | * ========================================================
108 | * Class Expression
109 | * ========================================================
110 | * Classes can be defined using class expressions, much like function expressions.
111 | */
112 | const Rectangle = class {
113 | constructor(width, height) {
114 | this.width = width;
115 | this.height = height;
116 | }
117 |
118 | area() {
119 | return this.width * this.height;
120 | }
121 | };
122 |
123 | const rectangle = new Rectangle(5, 10);
124 | console.log(rectangle.area()); // Outputs: 50
125 | }
126 |
127 | classes();
128 |
--------------------------------------------------------------------------------
/chapters/15_inheritance_classes.js:
--------------------------------------------------------------------------------
1 | function inheritance() {
2 | /**
3 | * ========================================================
4 | * Base Class
5 | * ========================================================
6 | * This is the base class that other classes will inherit from.
7 | * The `Animal` class encapsulates general features that are common to all animals.
8 | */
9 | class Animal {
10 | constructor(name) {
11 | this.name = name;
12 | }
13 |
14 | makeSound() {
15 | return `${this.name} makes a sound.`;
16 | }
17 | }
18 |
19 | /**
20 | * ========================================================
21 | * Derived Class
22 | * ========================================================
23 | * The `Dog` class inherits properties and methods from the `Animal` class.
24 | * The `extends` keyword is used to specify the class to inherit from.
25 | */
26 | class Dog extends Animal {
27 | makeSound() {
28 | return `${this.name} barks.`;
29 | }
30 | }
31 |
32 | // Create an instance of the Dog class, which is a derived class of Animal
33 | const myDog = new Dog("Fido");
34 | console.log(myDog.makeSound()); // Outputs: "Fido barks."
35 |
36 | /**
37 | * ========================================================
38 | * Method Overriding
39 | * ========================================================
40 | * Derived classes can override methods inherited from the base class.
41 | * The Dog class above overrides the `makeSound` method.
42 | */
43 |
44 | /**
45 | * ========================================================
46 | * Super Keyword
47 | * ========================================================
48 | * The `super` keyword is used to call corresponding methods of the base class.
49 | * This is useful for extending the base implementations.
50 | */
51 | class Cat extends Animal {
52 | makeSound() {
53 | const baseSound = super.makeSound();
54 | return `${baseSound} Actually, it meows.`;
55 | }
56 | }
57 |
58 | // Create an instance of the Cat class
59 | const myCat = new Cat("Whiskers");
60 | console.log(myCat.makeSound()); // Outputs: "Whiskers makes a sound. Actually, it meows."
61 |
62 | /**
63 | * ========================================================
64 | * Multiple Inheritance and Mixins
65 | * ========================================================
66 | * JavaScript does not support multiple inheritance directly.
67 | * However, you can achieve similar functionality using Mixins.
68 | */
69 | let FlyMixin = {
70 | fly() {
71 | return `${this.name} is flying.`;
72 | },
73 | };
74 |
75 | // Adding mixin methods to the Dog class
76 | Object.assign(Dog.prototype, FlyMixin);
77 |
78 | // Now, Dog class has a fly() method
79 | console.log(myDog.fly()); // Outputs: "Fido is flying."
80 |
81 | /**
82 | * ========================================================
83 | * instanceof Operator
84 | * ========================================================
85 | * The `instanceof` operator is used to check the type of an object.
86 | */
87 | console.log(myDog instanceof Animal); // Outputs: true
88 | console.log(myDog instanceof Dog); // Outputs: true
89 | console.log(myDog instanceof Cat); // Outputs: false
90 | }
91 |
92 | inheritance();
93 |
--------------------------------------------------------------------------------
/chapters/16_destructuring.js:
--------------------------------------------------------------------------------
1 | function destructuring() {
2 | /**
3 | * ========================================================
4 | * Array Destructuring
5 | * ========================================================
6 | * Array destructuring allows you to unpack elements from arrays into variables.
7 | */
8 | const [first, second] = [1, 2];
9 | console.log(first); // Outputs 1
10 | console.log(second); // Outputs 2
11 |
12 | // Skipping elements
13 | const [, , third] = [1, 2, 3];
14 | console.log(third); // Outputs 3
15 |
16 | /**
17 | * ========================================================
18 | * Default Values
19 | * ========================================================
20 | * You can provide default values for both array and object destructuring.
21 | */
22 | const { name = "Ishtmeet" } = {};
23 | console.log(name); // Outputs 'Ishtmeet'
24 |
25 | /**
26 | * ========================================================
27 | * Object Destructuring
28 | * ========================================================
29 | * Object destructuring allows you to unpack properties from objects into variables.
30 | */
31 | const { a, b } = { a: 10, b: 20 };
32 | console.log(a); // Outputs 10
33 | console.log(b); // Outputs 20
34 |
35 | // Renaming variables
36 | const { a: alpha, b: beta } = { a: 1, b: 2 };
37 | console.log(alpha); // Outputs 1
38 | console.log(beta); // Outputs 2
39 |
40 | /**
41 | * ========================================================
42 | * Nested Destructuring
43 | * ========================================================
44 | * Destructuring can also be nested for more complex data structures like nested arrays and objects.
45 | */
46 | const {
47 | p: { q },
48 | } = { p: { q: "Nested" } };
49 | console.log(q); // Outputs 'Nested'
50 |
51 | /**
52 | * ========================================================
53 | * Function Parameters Destructuring
54 | * ========================================================
55 | * Function parameters can also be destructured for easier access to properties.
56 | */
57 | function greet({ name, age }) {
58 | console.log(`Hello, ${name}. You are ${age} years old.`);
59 | }
60 | greet({ name: "Ishtmeet", age: 25 }); // Outputs "Hello, Ishtmeet. You are 25 years old."
61 |
62 | /**
63 | * ========================================================
64 | * Using Rest Syntax
65 | * ========================================================
66 | * Rest elements can also be destructured, separating the first item from the rest of the array.
67 | */
68 | const [head, ...tail] = [1, 2, 3, 4];
69 | console.log(head); // Outputs 1
70 | console.log(tail); // Outputs [2, 3, 4]
71 |
72 | /**
73 | * ========================================================
74 | * Mixed Destructuring
75 | * ========================================================
76 | * Arrays and objects can be destructured in a single statement for complex structures.
77 | */
78 | const {
79 | x,
80 | y,
81 | z: [, zz],
82 | } = { x: 1, y: 2, z: [3, 4] };
83 | console.log(x, y, zz); // Outputs 1, 2, 4
84 |
85 | /**
86 | * ========================================================
87 | * Destructuring Gotchas
88 | * ========================================================
89 | * JavaScript interprets the opening curly brace as a block statement.
90 | * To destructure into an already declared variable, it should be wrapped in parentheses.
91 | */
92 | let foo;
93 | ({ foo } = { foo: "bar" });
94 | console.log(foo); // Outputs 'bar'
95 | }
96 |
97 | destructuring();
98 |
--------------------------------------------------------------------------------
/chapters/17_spread_rest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Spread Operator: Expands an iterable (array, string, etc.) into its elements.
3 | * Rest Operator: Collects remaining elements or properties into an array or object.
4 | */
5 | function spreadAndRest() {
6 | /**
7 | * ========================================================
8 | * Spread Operator in Arrays
9 | * ========================================================
10 | * The spread operator can be used to spread elements of an iterable, such as an array, into individual elements.
11 | */
12 | const arr1 = [1, 2, 3];
13 | const arr2 = [...arr1, 4, 5];
14 | console.log(arr2); // Outputs [1, 2, 3, 4, 5]
15 |
16 | /**
17 | * ========================================================
18 | * Spread Operator in Objects
19 | * ========================================================
20 | * The spread operator can also be used to spread an object's own enumerable properties into a new object.
21 | */
22 | const obj1 = { a: 1, b: 2 };
23 | const obj2 = { ...obj1, c: 3 };
24 | console.log(obj2); // Outputs { a: 1, b: 2, c: 3 }
25 |
26 | /**
27 | * ========================================================
28 | * Combining Objects with Spread
29 | * ========================================================
30 | * If the same property exists in both objects, the one from the latter object will overwrite the former.
31 | */
32 | const obj3 = { a: 1, b: 2 };
33 | const obj4 = { b: 3, c: 4 };
34 | const obj5 = { ...obj3, ...obj4 };
35 | console.log(obj5); // Outputs { a: 1, b: 3, c: 4 }
36 |
37 | /**
38 | * ========================================================
39 | * Spread Operator in Function Calls
40 | * ========================================================
41 | * The spread operator can be used to pass elements of an array as arguments to a function.
42 | */
43 | const numbers = [1, 2, 3];
44 | console.log(Math.max(...numbers)); // Outputs 3
45 |
46 | /**
47 | * ========================================================
48 | * Rest Operator in Function Parameters
49 | * ========================================================
50 | * The rest operator is used to collect all remaining elements into an array.
51 | * This can be particularly useful in functions to capture an indefinite number of arguments.
52 | */
53 | function sum(...args) {
54 | return args.reduce((a, b) => a + b);
55 | }
56 | console.log(sum(1, 2, 3, 4)); // Outputs 10
57 |
58 | /**
59 | * ========================================================
60 | * Rest Operator in Array Destructuring
61 | * ========================================================
62 | * The rest operator can also be used in array destructuring to collect remaining elements.
63 | */
64 | const [first, ...rest] = [1, 2, 3, 4];
65 | console.log(first); // Outputs 1
66 | console.log(rest); // Outputs [2, 3, 4]
67 |
68 | /**
69 | * ========================================================
70 | * Rest Operator in Object Destructuring
71 | * ========================================================
72 | * In object destructuring, the rest operator collects remaining (non-destructured) properties into a new object.
73 | */
74 | const { x, ...others } = { x: 1, y: 2, z: 3 };
75 | console.log(x); // Outputs 1
76 | console.log(others); // Outputs { y: 2, z: 3 }
77 |
78 | /**
79 | * ========================================================
80 | * Rest vs Spread
81 | * ========================================================
82 | * The key difference is in the context where they are used.
83 | * The spread operator is used in variable assignment contexts to 'spread' elements or properties.
84 | * The rest operator is used in variable declaration contexts to collect remaining elements or properties.
85 | */
86 | }
87 |
88 | spreadAndRest();
89 |
--------------------------------------------------------------------------------
/chapters/18_this.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The `this` keyword refers to the object it belongs to, and its value can differ based on how it's used.
3 | */
4 | function thisKeyword() {
5 | /**
6 | * ========================================================
7 | * Global Context
8 | * ========================================================
9 | * In a global execution context, which is code that is outside of any function, `this` refers to the global object.
10 | */
11 | console.log(this === window); // Outputs true in a browser environment
12 |
13 | /**
14 | * ========================================================
15 | * Function Context
16 | * ========================================================
17 | * Inside a regular function, the value of `this` varies.
18 | * In "strict mode", it is undefined. Otherwise, it's the global object.
19 | */
20 | function regularFunction() {
21 | "use strict";
22 | console.log(this); // Outputs undefined in strict mode
23 | }
24 | regularFunction();
25 |
26 | /**
27 | * ========================================================
28 | * Object Context
29 | * ========================================================
30 | * In a method, which is a function stored as an object property, `this` refers to the object.
31 | */
32 | const obj = {
33 | name: "Ishtmeet",
34 | greet: function () {
35 | console.log(`Hello, my name is ${this.name}`); // Outputs "Hello, my name is Ishtmeet"
36 | },
37 | };
38 | obj.greet();
39 |
40 | /**
41 | * ========================================================
42 | * Constructor Context
43 | * ========================================================
44 | * In a constructor function, `this` refers to the object that the constructor creates.
45 | */
46 | function Person(name) {
47 | this.name = name;
48 | }
49 | const bob = new Person("Bob");
50 | console.log(bob.name); // Outputs "Bob"
51 |
52 | /**
53 | * ========================================================
54 | * Event Handler Context
55 | * ========================================================
56 | * In an event handler, `this` refers to the DOM element that received the event.
57 | */
58 | // HTML:
59 | document.getElementById("myButton").addEventListener("click", function () {
60 | console.log(this.id); // Outputs "myButton"
61 | });
62 |
63 | /**
64 | * ========================================================
65 | * Explicit Binding
66 | * ========================================================
67 | * Using methods like `call()`, `apply()`, and `bind()`, you can explicitly set what `this` refers to.
68 | */
69 | function showName(label) {
70 | console.log(`${label}: ${this.name}`);
71 | }
72 | const ishtmeet = { name: "Ishtmeet" };
73 | showName.call(ishtmeet, "User"); // Outputs "User: Ishtmeet"
74 |
75 | /**
76 | * ========================================================
77 | * Arrow Functions
78 | * ========================================================
79 | * Arrow functions don't have their own `this`. Instead, they inherit `this` from their surrounding function context.
80 | */
81 | const arrowFunc = () => {
82 | console.log(this); // Outputs the `this` value of its surrounding function or context
83 | };
84 | arrowFunc();
85 | }
86 |
87 | thisKeyword();
88 |
--------------------------------------------------------------------------------
/chapters/19_call_apply_bind.js:
--------------------------------------------------------------------------------
1 | /**
2 | * These methods allow for the manipulation of the 'this' keyword, as well as for borrowing and reusing methods across objects.
3 | */
4 | function callApplyBind() {
5 | /**
6 | * ========================================================
7 | * The Call Method
8 | * ========================================================
9 | * The call() method invokes a function with a given 'this' value and arguments provided individually.
10 | * This is useful when you want to use a function's logic on an object which didn't originally have that function.
11 | */
12 | function showFullName(firstname, lastname) {
13 | console.log(`${this.title} ${firstname} ${lastname}`);
14 | }
15 | const person1 = { title: "Mr." };
16 | showFullName.call(person1, "Ishtmeet", "Doe"); // Outputs: "Mr. Ishtmeet Doe"
17 |
18 | /**
19 | * ========================================================
20 | * The Apply Method
21 | * ========================================================
22 | * The apply() method is almost identical to call(), but it takes arguments as an array, or array-like object.
23 | * This makes it a good choice when the number of function parameters is uncertain.
24 | */
25 | const person2 = { title: "Dr." };
26 | const names = ["Jane", "Doe"];
27 | showFullName.apply(person2, names); // Outputs: "Dr. Jane Doe"
28 |
29 | /**
30 | * ========================================================
31 | * The Bind Method
32 | * ========================================================
33 | * The bind() method returns a new function that, when called, has its 'this' keyword set to the provided value.
34 | * It allows you to permanently set the 'this' value for a function, regardless of how or where it is called.
35 | */
36 | const person3 = { title: "Prof." };
37 | const showFullNameProf = showFullName.bind(person3);
38 | showFullNameProf("Emily", "Blunt"); // Outputs: "Prof. Emily Blunt"
39 |
40 | /**
41 | * ========================================================
42 | * Use with Event Handlers
43 | * ========================================================
44 | * The bind() method is often used within event handlers to ensure the correct object is bound to 'this'.
45 | * This avoids unexpected behavior, especially when working with DOM elements.
46 | */
47 | const button = document.createElement("button");
48 | button.textContent = "Click me";
49 | const obj = {
50 | text: "Hello, world!",
51 | handleClick: function () {
52 | alert(this.text);
53 | },
54 | };
55 | button.addEventListener("click", obj.handleClick.bind(obj));
56 | document.body.appendChild(button); // Clicking this button will alert "Hello, world!"
57 |
58 | /**
59 | * ========================================================
60 | * Nuance: Bind Returns a New Function
61 | * ========================================================
62 | * The bind() method returns a new function every time it's called.
63 | * Therefore, the same binding of a function to an object will create different function instances.
64 | */
65 | const bound1 = showFullName.bind(person3);
66 | const bound2 = showFullName.bind(person3);
67 | console.log(bound1 === bound2); // Outputs: false
68 |
69 | /**
70 | * ========================================================
71 | * Bonus: `call` and `apply` Can Accept Additional Parameters
72 | * ========================================================
73 | * Both `call` and `apply` can accept more than one argument. The first argument is the object to which `this` should refer,
74 | * and the remaining arguments are those that should be passed to the function being called/applyed.
75 | */
76 | function multiply(a, b) {
77 | return a * b * this.factor;
78 | }
79 |
80 | const calculator = { factor: 10 };
81 | console.log(multiply.call(calculator, 5, 2)); // Outputs: 100 (5 * 2 * 10)
82 | console.log(multiply.apply(calculator, [5, 2])); // Outputs: 100 (5 * 2 * 10)
83 | }
84 |
85 | callApplyBind();
86 |
--------------------------------------------------------------------------------
/chapters/20_error_handling.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Error handling allows you to gracefully handle exceptions and continue the program's execution.
3 | */
4 | function errorHandling() {
5 | /**
6 | * ========================================================
7 | * Basic Try-Catch
8 | * ========================================================
9 | * The try-catch block allows you to execute code within the "try" block.
10 | * If an exception occurs, the code inside the "catch" block gets executed.
11 | */
12 | try {
13 | // Code that could possibly throw an error goes here
14 | const x = JSON.parse('{"name": "Ishtmeet"}');
15 | } catch (e) {
16 | // Code to execute if an exception occurs in the try block
17 | console.error("An error occurred:", e);
18 | }
19 |
20 | /**
21 | * ========================================================
22 | * The Finally Block
23 | * ========================================================
24 | * The "finally" block will execute after both the try and catch blocks, regardless of whether an exception was thrown.
25 | * It's often used for cleanup tasks like releasing resources.
26 | */
27 | try {
28 | // Code that could possibly throw an error
29 | } catch (e) {
30 | // Code to execute if an exception occurs
31 | } finally {
32 | // This code will execute regardless of whether an exception occurred or not
33 | console.log("Finally block executed");
34 | }
35 |
36 | /**
37 | * ========================================================
38 | * Throwing Exceptions
39 | * ========================================================
40 | * You can programmatically throw exceptions using the 'throw' keyword.
41 | * Thrown exceptions can be of any type, but using the Error object is often more descriptive.
42 | */
43 | try {
44 | throw new Error("This is a custom error");
45 | } catch (e) {
46 | console.error("Caught a custom error:", e);
47 | }
48 |
49 | /**
50 | * ========================================================
51 | * Nuance: Catching Multiple Error Types
52 | * ========================================================
53 | * In complex applications, multiple types of errors can occur.
54 | * You can use 'instanceof' or other properties to distinguish between them.
55 | */
56 | try {
57 | // Code that could throw multiple types of exceptions
58 | throw new SyntaxError("A syntax error for demonstration purposes");
59 | } catch (e) {
60 | if (e instanceof TypeError) {
61 | console.error("Type Error:", e);
62 | } else if (e instanceof SyntaxError) {
63 | console.error("Syntax Error:", e);
64 | } else {
65 | console.error("Unknown error:", e);
66 | }
67 | }
68 |
69 | /**
70 | * ========================================================
71 | * Nuance: Catch Without Variable
72 | * ========================================================
73 | * JavaScript allows catch blocks without specifying the error variable.
74 | * However, this isn't recommended as you lose the error details, making debugging difficult.
75 | */
76 | try {
77 | throw new Error("Some error");
78 | } catch {
79 | console.error("An error occurred, but we're not displaying it");
80 | }
81 |
82 | /**
83 | * ========================================================
84 | * Nuance: Optional Catch Binding
85 | * ========================================================
86 | * ECMAScript 2019 onwards permits the use of catch blocks without an error variable.
87 | * While syntactically valid, this is generally not advised unless you have a very specific reason.
88 | */
89 | try {
90 | // some code that might throw an error
91 | } catch {
92 | // handle the error without capturing its details
93 | }
94 |
95 | /**
96 | * ========================================================
97 | * Bonus: Re-Throwing Errors
98 | * ========================================================
99 | * Sometimes, you might catch an error to log or modify it, but then want the error to propagate up.
100 | * You can "re-throw" the error after catching it.
101 | */
102 | try {
103 | throw new Error("An error to be re-thrown");
104 | } catch (e) {
105 | console.error("Logging the error before re-throwing:", e);
106 | throw e; // Re-throw the error
107 | }
108 | }
109 |
110 | errorHandling();
111 |
--------------------------------------------------------------------------------
/chapters/23.1_async_await.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Async/Await
4 | * ========================================================
5 | * 'async/await' is a modern way to write asynchronous code in JavaScript.
6 | * It provides a more readable and maintainable syntax over callbacks and Promises.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Basic Syntax
12 | * ========================================================
13 | * Declaring a function as 'async' makes it return a Promise implicitly.
14 | * You can use 'await' within an 'async' function to pause the execution until a Promise is resolved or rejected.
15 | */
16 | async function fetchData() {
17 | const response = await fetch("https://api.example.com/data");
18 | const data = await response.json();
19 | return data;
20 | }
21 | // Invoke the function to see it in action
22 | fetchData().then((data) => console.log(data));
23 |
24 | /**
25 | * ========================================================
26 | * 2. Handling Errors
27 | * ========================================================
28 | * It's crucial to handle errors when dealing with asynchronous operations.
29 | * 'try/catch' blocks are commonly used within 'async' functions for this purpose.
30 | */
31 | async function fetchDataWithErrorHandling() {
32 | try {
33 | const response = await fetch("https://api.example.com/data");
34 | if (!response.ok) {
35 | throw new Error("Network response was not ok");
36 | }
37 | const data = await response.json();
38 | return data;
39 | } catch (error) {
40 | console.error("Fetch Error:", error);
41 | }
42 | }
43 | // Invoke the function to see error handling in action
44 | fetchDataWithErrorHandling().catch((error) => console.error(error));
45 |
46 | /**
47 | * ========================================================
48 | * Nuances and Advanced Techniques
49 | * ========================================================
50 | */
51 |
52 | /**
53 | * Error Propagation
54 | * -----------------
55 | * Throwing an exception within an 'async' function causes it to return a Promise that is rejected.
56 | */
57 | async function failAsync() {
58 | throw new Error("Failed");
59 | // This is equivalent to: return Promise.reject(new Error('Failed'));
60 | }
61 |
62 | /**
63 | * Concurrency with Promise.all()
64 | * ------------------------------
65 | * 'async/await' can work in tandem with Promise.all() to execute multiple asynchronous operations concurrently.
66 | */
67 | async function fetchAllData() {
68 | const [data1, data2] = await Promise.all([
69 | fetch("https://api.example.com/data1").then((res) => res.json()),
70 | fetch("https://api.example.com/data2").then((res) => res.json()),
71 | ]);
72 | return { data1, data2 };
73 | }
74 |
75 | /**
76 | * Using 'for-await-of' with Async Iterables
77 | * -----------------------------------------
78 | * The 'for-await-of' loop enables you to traverse through async iterables as though they were synchronous.
79 | */
80 | async function handleAsyncIterable(asyncIterable) {
81 | for await (const item of asyncIterable) {
82 | console.log(item);
83 | }
84 | }
85 |
86 | /**
87 | * Non-Promise Asynchronous Operations
88 | * ----------------------------------
89 | * While 'await' is designed to work with Promises, it can also be used with non-Promise values.
90 | * When you 'await' a non-Promise value, it's returned instantly.
91 | */
92 | async function notReallyAsync() {
93 | const value = await 42;
94 | return value; // 42, instantly
95 | }
96 |
97 | /**
98 | * Running Async Functions Immediately
99 | * -----------------------------------
100 | * You can immediately invoke an async function using an IIFE (Immediately Invoked Function Expression).
101 | */
102 | (async function () {
103 | const data = await fetchData();
104 | console.log(data);
105 | })();
106 |
--------------------------------------------------------------------------------
/chapters/27_storage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Web Storage: localStorage and sessionStorage
4 | * ========================================================
5 | * Web storage allows you to store key-value pairs in a web browser with no expiration time (localStorage)
6 | * or for the duration of the session (sessionStorage).
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * Basic Usage of localStorage
12 | * ========================================================
13 | * localStorage allows you to store data with no expiration time. This data will persist even after closing the browser.
14 | */
15 | localStorage.setItem("username", "IshtmeetDoe");
16 | const username = localStorage.getItem("username");
17 | console.log(`Username: ${username}`); // Output: Username: IshtmeetDoe
18 | localStorage.removeItem("username");
19 |
20 | /**
21 | * ========================================================
22 | * Basic Usage of sessionStorage
23 | * ========================================================
24 | * sessionStorage allows you to store data for the duration of a page session.
25 | */
26 | sessionStorage.setItem("sessionId", "123456");
27 | const sessionId = sessionStorage.getItem("sessionId");
28 | console.log(`Session ID: ${sessionId}`); // Output: Session ID: 123456
29 | sessionStorage.removeItem("sessionId");
30 |
31 | /**
32 | * ========================================================
33 | * Iterating Over Items
34 | * ========================================================
35 | * Both localStorage and sessionStorage provide a way to iterate over stored items.
36 | */
37 | localStorage.setItem("username", "IshtmeetDoe");
38 | localStorage.setItem("email", "ishtmeet.doe@example.com");
39 | for (let i = 0; i < localStorage.length; i++) {
40 | const key = localStorage.key(i);
41 | const value = localStorage.getItem(key);
42 | console.log(`${key}: ${value}`);
43 | }
44 |
45 | /**
46 | * ========================================================
47 | * Storage Event
48 | * ========================================================
49 | * The storage event is triggered when a storage area changes, due to setItem, removeItem, or clear method calls.
50 | */
51 | window.addEventListener("storage", function (event) {
52 | console.log(`Key changed: ${event.key}, New value: ${event.newValue}`);
53 | });
54 |
55 | /**
56 | * ========================================================
57 | * Nuances and Best Practices
58 | * ========================================================
59 | */
60 |
61 | /**
62 | * 1. Data Type Limitation
63 | * -----------------------
64 | * Both localStorage and sessionStorage can only directly store strings. To store objects or arrays, serialize them to JSON.
65 | */
66 | const user = { name: "IshtmeetDoe", age: 30 };
67 | localStorage.setItem("user", JSON.stringify(user));
68 | const retrievedUser = JSON.parse(localStorage.getItem("user"));
69 | console.log(retrievedUser); // Output: { name: 'IshtmeetDoe', age: 30 }
70 |
71 | /**
72 | * 2. Storage Limit
73 | * ----------------
74 | * Both localStorage and sessionStorage have storage limits, typically around 5MB per domain in most modern browsers.
75 | */
76 |
77 | /**
78 | * 3. Session Storage and Tabs
79 | * ---------------------------
80 | * sessionStorage is isolated per tab or window. Opening a new tab or window won't share the stored data.
81 | */
82 |
83 | /**
84 | * 4. Synchronous Nature
85 | * ---------------------
86 | * The APIs for web storage are synchronous, meaning they could impact performance if used extensively.
87 | */
88 |
89 | /**
90 | * 5. Browser Support
91 | * ------------------
92 | * Web storage is widely supported across browsers, but it's a good idea to check for compatibility.
93 | */
94 | if (typeof Storage !== "undefined") {
95 | // Web storage supported
96 | } else {
97 | // Web storage not supported
98 | }
99 |
100 | /**
101 | * 6. Storage Event Specifics
102 | * --------------------------
103 | * The 'storage' event is fired only in the context of other windows/tabs, not in the window/tab that made the change.
104 | */
105 |
106 | /**
107 | * 7. Security Implications
108 | * ------------------------
109 | * Avoid storing sensitive information like passwords or tokens in web storage, as they can be accessed through JavaScript.
110 | */
111 |
112 | /**
113 | * 8. Cross-Origin Restrictions
114 | * ----------------------------
115 | * Web storage adheres to the same-origin policy, meaning data stored will only be available on the same origin.
116 | */
117 |
--------------------------------------------------------------------------------
/chapters/28_indexed_db.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * IndexedDB
4 | * ========================================================
5 | * IndexedDB is a low-level API for storing large amounts of structured data like files and JSON objects.
6 | * It allows for the creation of database schemas, transactions, and significantly higher storage limits than localStorage and sessionStorage.
7 | *
8 | * Understanding and mastering IndexedDB will enable you to build web applications capable of storing a large amount of data
9 | * on the client-side, which is particularly useful for offline-first applications.
10 | */
11 |
12 | /**
13 | * ========================================================
14 | * 1. Opening a Database
15 | * ========================================================
16 | * The 'indexedDB.open()' method is used to create or open an IndexedDB database.
17 | * The method takes two parameters: the name of the database and an optional version number.
18 | */
19 | const request = indexedDB.open("myDatabase", 1);
20 |
21 | // Event fired when the database is opened successfully
22 | request.onsuccess = function (event) {
23 | const db = event.target.result;
24 | };
25 |
26 | // Event fired when the database couldn't be opened
27 | request.onerror = function (event) {
28 | console.log("Could not open database:", event);
29 | };
30 |
31 | /**
32 | * ========================================================
33 | * 2. Object Stores
34 | * ========================================================
35 | * Object stores are essentially tables where records are stored.
36 | * An object store is created in the 'onupgradeneeded' event, which is triggered when the database is first created or
37 | * the version number changes.
38 | */
39 | request.onupgradeneeded = function (event) {
40 | const db = event.target.result;
41 | // The 'keyPath' specifies the property of the object to be used as the key
42 | const objectStore = db.createObjectStore("myObjectStore", { keyPath: "id" });
43 | };
44 |
45 | /**
46 | * ========================================================
47 | * 3. CRUD Operations
48 | * ========================================================
49 | * The fundamental Create, Read, Update, and Delete (CRUD) operations can be performed in IndexedDB.
50 | */
51 | // Create or Update (Upsert) a record
52 | const addTransaction = db.transaction(["myObjectStore"], "readwrite");
53 | const addStore = addTransaction.objectStore("myObjectStore");
54 | addStore.add({ id: 1, name: "example" });
55 |
56 | // Read a record by its key
57 | const readTransaction = db.transaction(["myObjectStore"]);
58 | const readStore = readTransaction.objectStore("myObjectStore");
59 | const getRequest = readStore.get(1);
60 | getRequest.onsuccess = function (event) {
61 | console.log("Data:", event.target.result);
62 | };
63 |
64 | /**
65 | * ========================================================
66 | * 4. Indexing
67 | * ========================================================
68 | * Indices allow you to build more complex queries, providing quick lookups and efficient ordering.
69 | */
70 | const index = objectStore.createIndex("name", "name", { unique: false });
71 |
72 | /**
73 | * ========================================================
74 | * Nuances and Advanced Techniques
75 | * ========================================================
76 | */
77 |
78 | /**
79 | * 1. Transaction Lifecycles
80 | * -------------------------
81 | * Transactions automatically commit if they are inactive for a brief period or when all requests have been executed.
82 | */
83 |
84 | /**
85 | * 2. Cursor
86 | * ---------
87 | * Cursors allow you to iterate over multiple records, offering fine-grained control over the iteration process.
88 | */
89 | const cursorRequest = objectStore.openCursor();
90 | cursorRequest.onsuccess = function (event) {
91 | const cursor = event.target.result;
92 | if (cursor) {
93 | console.log("Entry:", cursor.value);
94 | cursor.continue();
95 | }
96 | };
97 |
98 | /**
99 | * 3. Error Handling
100 | * -----------------
101 | * Make use of the 'onerror', 'onabort', and 'oncomplete' events to handle various transaction outcomes.
102 | */
103 | addTransaction.onerror = function () {
104 | console.log("Transaction failed");
105 | };
106 |
107 | /**
108 | * 4. Versioning
109 | * -------------
110 | * IndexedDB uses a versioning model. The 'onupgradeneeded' event is the only place where the database schema can be updated.
111 | */
112 |
113 | /**
114 | * 5. Promised-based Libraries
115 | * ---------------------------
116 | * The native IndexedDB API is event-based, but libraries like 'idb' can be used to work with IndexedDB using Promises,
117 | * making it easier to manage asynchronous operations.
118 | */
119 |
--------------------------------------------------------------------------------
/chapters/29_symbol.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Symbol
4 | * ========================================================
5 | * Symbols are a unique, immutable primitive data type in JavaScript.
6 | * They are often used as unique keys for object properties, helping prevent property name collisions
7 | * and enabling more robust data structures like maps and sets.
8 | */
9 |
10 | /**
11 | * ========================================================
12 | * 1. Creating a Symbol
13 | * ========================================================
14 | * Create a Symbol using the `Symbol()` function. You can optionally provide a string description,
15 | * which can make debugging easier but does not affect the uniqueness.
16 | */
17 | const symbol1 = Symbol();
18 | const symbol2 = Symbol("description"); // With a description for debugging
19 |
20 | /**
21 | * ========================================================
22 | * 2. Symbols Are Unique
23 | * ========================================================
24 | * Every symbol is unique, even if you give them the same description.
25 | */
26 | const symbol3 = Symbol("description");
27 | console.log(symbol2 === symbol3); // Output: false
28 |
29 | /**
30 | * ========================================================
31 | * 3. Symbols as Object Properties
32 | * ========================================================
33 | * Symbols can be used as object property keys, and these properties won't clash with string keys
34 | * or be iterated over in a normal loop.
35 | */
36 | const obj = {};
37 | obj[symbol1] = "value1";
38 | obj[symbol2] = "value2";
39 | console.log(obj[symbol1]); // Output: 'value1'
40 |
41 | /**
42 | * ========================================================
43 | * 4. Iteration and Enumeration
44 | * ========================================================
45 | * Symbols are not enumerable. They won't appear in for...in loops or be returned by Object.keys().
46 | */
47 | for (let key in obj) {
48 | console.log(key); // No output
49 | }
50 | console.log(Object.keys(obj)); // Output: []
51 |
52 | /**
53 | * ========================================================
54 | * 5. Retrieving Symbol Properties
55 | * ========================================================
56 | * To get an array of an object's own symbol properties, use Object.getOwnPropertySymbols().
57 | */
58 | const symbols = Object.getOwnPropertySymbols(obj);
59 | console.log(symbols); // Output: [Symbol(), Symbol(description)]
60 |
61 | /**
62 | * ========================================================
63 | * Nuances and Best Practices
64 | * ========================================================
65 | */
66 |
67 | /**
68 | * 1. Symbol.for() and Symbol.keyFor()
69 | * -----------------------------------
70 | * Symbols can be registered to a global registry and later retrieved. This enables symbol reuse across modules.
71 | */
72 | const globalSymbol = Symbol.for("globalSymbol");
73 | console.log(Symbol.keyFor(globalSymbol)); // Output: 'globalSymbol'
74 |
75 | /**
76 | * 2. Well-known Symbols
77 | * ----------------------
78 | * JavaScript has built-in, well-known symbols that can be used to alter object behavior.
79 | */
80 | // For instance, Symbol.iterator allows you to customize object iteration.
81 | Array.prototype[Symbol.iterator];
82 | // Symbol.hasInstance allows overriding the instanceof operator
83 | Symbol.hasInstance;
84 | // Symbol.isConcatSpreadable customizes array concatenation behavior
85 | Symbol.isConcatSpreadable;
86 |
87 | /**
88 | * 3. Symbol and Computed Property Names
89 | * -------------------------------------
90 | * Symbols can be used as computed property names in object literals.
91 | */
92 | const dynamicObj = {
93 | [Symbol("dynamicProperty")]: "I am dynamic",
94 | };
95 | // Note: Accessing dynamicObj with Symbol('dynamicProperty') would be undefined because each symbol is unique.
96 | console.log(dynamicObj[Symbol("dynamicProperty")]); // Output: undefined
97 |
98 | /**
99 | * 4. Coercion
100 | * -----------
101 | * Symbols cannot be implicitly coerced into strings or numbers, doing so will result in a TypeError.
102 | */
103 | // console.log(`Symbol: ${symbol1}`); // Throws TypeError
104 | console.log(`Symbol: ${String(symbol1)}`); // Explicit coercion works
105 |
106 | /**
107 | * 5. Symbols and Private Properties
108 | * ---------------------------------
109 | * Symbols are often used to simulate private properties in JavaScript objects. However, they are not truly private.
110 | */
111 |
112 | /**
113 | * 6. Symbol Properties are Skipped by JSON.stringify()
114 | * -----------------------------------------------------
115 | * When using JSON.stringify(), symbol-keyed properties are omitted, making Symbols non-serializable into JSON format.
116 | */
117 | console.log(JSON.stringify(obj)); // Output: {}
118 |
--------------------------------------------------------------------------------
/chapters/30_fetch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Fetch API
4 | * ========================================================
5 | * The Fetch API provides a modern, promise-based mechanism for making HTTP requests
6 | * in the browser and is also available in Node.js via third-party packages.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Basic GET Request
12 | * ========================================================
13 | * A simple GET request can be made by passing a URL to `fetch()`. The method returns a Promise
14 | * that resolves to the Response object representing the completed request.
15 | */
16 | fetch("https://jsonplaceholder.typicode.com/todos/1")
17 | .then((response) => response.json())
18 | .then((data) => console.log(data))
19 | .catch((error) => console.error("Fetch error:", error));
20 |
21 | /**
22 | * ========================================================
23 | * 2. Basic POST Request
24 | * ========================================================
25 | * For POST requests, specify the HTTP method, headers, and body.
26 | */
27 | const postData = {
28 | title: "foo",
29 | body: "bar",
30 | userId: 1,
31 | };
32 |
33 | fetch("https://jsonplaceholder.typicode.com/posts", {
34 | method: "POST",
35 | headers: {
36 | "Content-Type": "application/json",
37 | },
38 | body: JSON.stringify(postData),
39 | })
40 | .then((response) => response.json())
41 | .then((data) => console.log(data))
42 | .catch((error) => console.error("Fetch error:", error));
43 |
44 | /**
45 | * ========================================================
46 | * Nuances and Advanced Use-Cases
47 | * ========================================================
48 | */
49 |
50 | /**
51 | * 1. Custom Headers
52 | * -----------------
53 | * To send custom headers, create a Headers object and append the headers you need.
54 | */
55 | const customHeaders = new Headers();
56 | customHeaders.append("Authorization", "Bearer YOUR_ACCESS_TOKEN");
57 |
58 | fetch("https://api.example.com/data", { headers: customHeaders })
59 | .then((response) => response.json())
60 | .then((data) => console.log(data));
61 |
62 | /**
63 | * 2. Handling Timeouts
64 | * --------------------
65 | * Fetch does not have a built-in timeout feature. You can implement it using Promise.race().
66 | */
67 | const timeoutFetch = (url, options, timeout = 3000) => {
68 | return Promise.race([
69 | fetch(url, options),
70 | new Promise((_, reject) => setTimeout(() => reject(new Error("Fetch timeout")), timeout)),
71 | ]);
72 | };
73 |
74 | /**
75 | * 3. Request and Response Metadata
76 | * --------------------------------
77 | * Request and Response objects contain metadata like status, headers, etc.
78 | */
79 | fetch("https://jsonplaceholder.typicode.com/todos/1").then((response) => {
80 | console.log(response.status); // HTTP status code
81 | console.log(response.headers.get("Content-Type")); // Content-Type header
82 | });
83 |
84 | /**
85 | * 4. Manual Redirects
86 | * -------------------
87 | * Fetch allows you to manually handle redirects by setting the redirect option.
88 | */
89 | fetch("https://example.com", { redirect: "manual" }).then((response) => {
90 | // Handle manual redirects here
91 | });
92 |
93 | /**
94 | * 5. Abort Request
95 | * ----------------
96 | * A fetch request can be aborted using the AbortController API.
97 | */
98 | const controller = new AbortController();
99 | const { signal } = controller;
100 |
101 | fetch("https://example.com", { signal }).catch((error) => {
102 | if (error.name === "AbortError") {
103 | console.log("Fetch aborted");
104 | }
105 | });
106 | // To abort the request
107 | controller.abort();
108 |
109 | /**
110 | * 6. Streaming
111 | * ------------
112 | * Fetch API supports streaming of data using Response.body, which is a ReadableStream.
113 | */
114 | fetch("https://example.com/large-file").then((response) => {
115 | const reader = response.body.getReader();
116 | // Handle streaming here
117 | });
118 |
119 | /**
120 | * 7. FormData
121 | * -----------
122 | * FormData API can be used with fetch to easily upload files or send form data.
123 | */
124 | const formData = new FormData();
125 | formData.append("key", "value");
126 | fetch("https://example.com/upload", {
127 | method: "POST",
128 | body: formData,
129 | });
130 |
131 | /**
132 | * 8. CORS and Fetch
133 | * -----------------
134 | * Fetch is CORS-aware. To make requests to another origin, the server must include the proper CORS headers.
135 | */
136 |
137 | /**
138 | * 9. Fetch vs. Axios
139 | * ------------------
140 | * Axios is a popular HTTP client with features like built-in timeout and automatic JSON parsing, which Fetch lacks by default.
141 | */
142 |
--------------------------------------------------------------------------------
/chapters/32_template_literals.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Template Literals
4 | * ========================================================
5 | */
6 |
7 | /**
8 | * ========================================================
9 | * Basic Usage
10 | * ========================================================
11 | * Template literals simplify string creation and manipulation by enabling embedded expressions,
12 | * variables, and even function calls directly within strings.
13 | */
14 |
15 | // Creating a basic string with template literals
16 | const str = `Hello, world!`;
17 |
18 | // String with variables
19 | const name = "Ishtmeet";
20 | const greeting = `Hello, ${name}!`; // Output: "Hello, Ishtmeet!"
21 |
22 | // String with expressions
23 | const x = 10,
24 | y = 20;
25 | const sum = `The sum is ${x + y}.`; // Output: "The sum is 30."
26 |
27 | /**
28 | * ========================================================
29 | * Multi-Line Strings
30 | * ========================================================
31 | * Template literals provide a clean way to handle multi-line strings, eliminating the need for escape characters.
32 | */
33 |
34 | const multiLineStr = `
35 | This is a
36 | multi-line
37 | string.
38 | `; // Output includes line breaks.
39 |
40 | /**
41 | * ========================================================
42 | * Tagged Template Literals
43 | * ========================================================
44 | * Tag functions allow you to parse and manipulate template literals for custom behavior.
45 | */
46 |
47 | function myTag(strings, ...values) {
48 | console.log(strings); // Outputs: Array of string literals like ["Hello ", ", the sum is ", "."]
49 | console.log(values); // Outputs: Array of evaluated expressions like ["Ishtmeet", 30]
50 | return "Modified String";
51 | }
52 |
53 | const tagged = myTag`Hello ${name}, the sum is ${x + y}.`; // Calls myTag function, Output: "Modified String"
54 |
55 | /**
56 | * ========================================================
57 | * Nuances and Advanced Use-Cases
58 | * ========================================================
59 | */
60 |
61 | /**
62 | * 1. Nesting Template Literals
63 | */
64 | // Template literals can be nested within another template literal.
65 | const nested = `outer ${`inner ${name}`}`; // Output: "outer inner Ishtmeet"
66 |
67 | /**
68 | * 2. Raw Strings
69 | *
70 | * The String.raw tag function allows you to treat backslashes as literal characters.
71 | */
72 | const rawStr = String.raw`This is a raw string \n ${name}.`; // Output: "This is a raw string \n Ishtmeet"
73 |
74 | /**
75 | * 3. Custom Interpolators
76 | *
77 | * You can create custom interpolating functions for special formatting needs.
78 | */
79 | function currency(strings, ...values) {
80 | let output = strings[0];
81 | values.forEach((val, index) => {
82 | output += `$${val.toFixed(2)}${strings[index + 1]}`;
83 | });
84 | return output;
85 | }
86 |
87 | const price = 25;
88 | const fee = 2;
89 | const totalCost = currency`Total cost is ${price + fee}.`; // Output: "Total cost is $27.00."
90 |
91 | /**
92 | * 4. HTML Templating
93 | * Template literals are a handy tool for generating HTML structures.
94 | */
95 | const html = `
96 |
97 |
Hello, ${name}!
98 |
99 | `; // Useful for DOM manipulation
100 |
101 | /**
102 | * 5. Performance Considerations
103 | *
104 | * Although convenient, template literals might not always be the most performant choice for string operations, especially in loops.
105 | * The performance difference is negligible for most use-cases, but it's worth keeping in mind.
106 | */
107 |
108 | /**
109 | * 6. Security Concerns
110 | *
111 | * When incorporating user input, make sure to sanitize the data to prevent Cross-Site Scripting (XSS) vulnerabilities.
112 | */
113 | const user_input = ""; // Unsafe input
114 | const sanitized_input = /* some sanitization method */ "";
115 | const secureString = `User said: ${sanitized_input}`; // Always sanitize
116 |
117 | /**
118 | * 7. Escaping Template Syntax
119 | */
120 | // To include literal ${} characters, you can escape them using backslashes.
121 | const escapeExample = `Display the template syntax like this: $\{}`; // Output: Display the template syntax like this: ${}
122 |
--------------------------------------------------------------------------------
/chapters/33_date_time.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Date and Time
4 | * ========================================================
5 | */
6 |
7 | /**
8 | * ========================================================
9 | * Creating Date Objects
10 | * ========================================================
11 | * JavaScript provides the `Date` object for working with date and time.
12 | * You can create a `Date` object in several ways.
13 | */
14 |
15 | // Current date and time
16 | const now = new Date();
17 |
18 | // Specific date (Year, Month-1, Day, Hours, Minutes, Seconds, Milliseconds)
19 | const specificDate = new Date(2023, 8, 19, 12, 30, 0, 0); // Sep 19, 2023, 12:30:00
20 |
21 | // From a timestamp (milliseconds since January 1, 1970, 00:00:00 UTC)
22 | const fromTimestamp = new Date(1632066000000);
23 |
24 | // From a date string
25 | const fromString = new Date("September 19, 2023 12:30:00");
26 |
27 | /**
28 | * ========================================================
29 | * Getting Components
30 | * ========================================================
31 | * JavaScript offers various methods to retrieve individual date and time components.
32 | */
33 |
34 | const year = now.getFullYear(); // Outputs: 2023 (or current year)
35 | const month = now.getMonth(); // Outputs: 8 (0-indexed, January is 0)
36 | const day = now.getDate(); // Outputs: 19 (or current day)
37 |
38 | /**
39 | * ========================================================
40 | * Setting Components
41 | * ========================================================
42 | * You can also manipulate individual date and time components using setters.
43 | */
44 |
45 | now.setFullYear(2024); // Changes the year to 2024
46 | now.setHours(13); // Changes the hour to 13 (1 PM)
47 |
48 | /**
49 | * ========================================================
50 | * Formatting Dates
51 | * ========================================================
52 | * JavaScript provides methods to format Date objects into more readable strings.
53 | */
54 |
55 | const formatted = now.toDateString(); // Outputs: "Tue Sep 19 2024" (or similar format)
56 |
57 | /**
58 | * ========================================================
59 | * Nuances and Advanced Use-Cases
60 | * ========================================================
61 | */
62 |
63 | /**
64 | * 1. Timezone
65 | *
66 | * By default, Date objects are created in the client's local timezone.
67 | * But you can also use UTC (Coordinated Universal Time).
68 | */
69 | const utcHours = now.getUTCHours();
70 |
71 | /**
72 | * 2. Daylight Saving
73 | *
74 | * JavaScript automatically adjusts for daylight saving time changes.
75 | */
76 |
77 | /**
78 | * 3. Invalid Date
79 | *
80 | * A Date object initialized with incorrect parameters will produce an "Invalid Date" object.
81 | *
82 | * NOTE: Some browsers may return the below as a valid date, setting date to the 00:00:00 Jan 1, 2014
83 | */
84 | const invalidDate = new Date("ishtmeet-doe 2014").toString();
85 |
86 | /**
87 | * 4. Leap Year Considerations
88 | *
89 | * The Date object automatically accounts for leap years.
90 | */
91 | const feb29_2024 = new Date(2024, 1, 29); // Valid because 2024 is a leap year
92 |
93 | /**
94 | * 5. Parsing Strings
95 | *
96 | * Different browsers may parse date strings differently, so it's safer to avoid relying on this.
97 | * Instead, use the standardized formats when possible.
98 | */
99 |
100 | /**
101 | * 6. ISO 8601 Format
102 | *
103 | * For cross-browser compatibility, use ISO 8601 formatted strings.
104 | */
105 | const isoString = now.toISOString(); // Outputs: "2023-09-19T09:30:00.000Z" (or similar)
106 |
107 | /**
108 | * 7. Performance Considerations
109 | *
110 | * Creating Date objects in loops can be a performance hit, so try to minimize this where possible.
111 | */
112 |
113 | /**
114 | * 8. Libraries for Date Manipulation
115 | *
116 | * For more complex date-time operations, libraries like `moment.js` or `date-fns` offer additional functionalities.
117 | */
118 |
119 | // Example:
120 | const moment = require("moment"); // Import Moment.js
121 | moment("2023-09-19").format("dddd"); // Outputs: "Tuesday"
122 |
--------------------------------------------------------------------------------
/chapters/34_math.js:
--------------------------------------------------------------------------------
1 | /*
2 | * ========================================================
3 | * Math Object
4 | * ========================================================
5 | * The Math object provides static properties and methods for mathematical constants and functions.
6 | * It doesn't need to be instantiated.
7 | */
8 |
9 | /*
10 | * ========================================================
11 | * Common Constants
12 | * ========================================================
13 | * The Math object includes several mathematical constants for convenience.
14 | */
15 | const pi = Math.PI; // π (Pi): 3.141592653589793
16 | const e = Math.E; // Euler's Number: 2.718281828459045
17 |
18 | /*
19 | * ========================================================
20 | * Basic Operations
21 | * ========================================================
22 | * The Math object provides methods for basic mathematical operations.
23 | */
24 | const sqrt = Math.sqrt(16); // Square root: Output will be 4
25 | const abs = Math.abs(-7); // Absolute value: Output will be 7
26 |
27 | /*
28 | * ========================================================
29 | * Rounding Operations
30 | * ========================================================
31 | * JavaScript includes several methods to perform various kinds of rounding.
32 | */
33 | const ceil = Math.ceil(4.3); // Rounds up: Output will be 5
34 | const floor = Math.floor(4.7); // Rounds down: Output will be 4
35 | const round = Math.round(4.5); // Standard rounding: Output will be 5
36 |
37 | /*
38 | * ========================================================
39 | * Trigonometry Functions
40 | * ========================================================
41 | * Math includes trigonometric functions like sin, cos, tan, etc.
42 | */
43 | const sin = Math.sin(Math.PI / 2); // Output will be 1
44 | const cos = Math.cos(Math.PI); // Output will be -1
45 |
46 | /*
47 | * ========================================================
48 | * Generating Random Numbers
49 | * ========================================================
50 | * The Math object provides a method for generating pseudo-random numbers.
51 | */
52 | const random = Math.random(); // Generates a random number between 0 (inclusive) and 1 (exclusive)
53 |
54 | /*
55 | * ========================================================
56 | * Nuances and Advanced Use-Cases
57 | * ========================================================
58 | */
59 |
60 | /*
61 | * 1. Generating Random Integers
62 | * To generate a random integer within a specific range [min, max]:
63 | */
64 | const randomInt = Math.floor(Math.random() * (10 - 1 + 1)) + 1; // Random integer between 1 and 10
65 |
66 | /*
67 | * 2. Precision Issues
68 | * Floating-point arithmetic in JavaScript may not be 100% precise.
69 | */
70 | const imprecise = Math.sqrt(2);
71 | const precise = parseFloat(imprecise.toFixed(4)); // Rounded to up to 4 decimal places
72 |
73 | /*
74 | * 3. Clamping Values
75 | * To clamp a number within a specific range:
76 | */
77 | const value = 25; // Replace with the actual value
78 | const clamped = Math.min(Math.max(value, 10), 20); // Clamps value between 10 and 20
79 |
80 | /*
81 | * 4. Calculating Hypotenuse
82 | * Calculates the hypotenuse of a right-angled triangle.
83 | */
84 | const hypotenuse = Math.hypot(3, 4); // Output will be 5
85 |
86 | /*
87 | * 5. Exponentiation
88 | * You can use either Math.pow() or the ** operator for exponentiation.
89 | */
90 | const pow = Math.pow(2, 3); // Output will be 8
91 | const exp = 2 ** 3; // Output will be 8
92 |
93 | /*
94 | * 6. Special Cases: NaN and Infinity
95 | * Some operations may result in NaN (Not-a-Number) or Infinity.
96 | */
97 | const nanResult = Math.sqrt(-1); // Output will be NaN
98 |
99 | /*
100 | * 7. Sign Function
101 | * Determines the sign of a number. It returns 1, -1, 0, -0, or NaN.
102 | */
103 | const sign = Math.sign(-7); // Output will be -1
104 |
105 | /*
106 | * 8. Logarithmic Functions
107 | */
108 | const ln = Math.log(Math.E); // Natural Logarithm: Output will be 1
109 | const log10 = Math.log10(100); // Base 10 Logarithm: Output will be 2
110 |
111 | /*
112 | * 9. Bitwise Operations
113 | * For bitwise operations, consider using bitwise operators (|, &, ^, ~, <<, >>, >>>) instead of Math methods.
114 | */
115 |
--------------------------------------------------------------------------------
/chapters/35_bitwise.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Basic Bitwise Operators
3 | * -----------------------
4 | * Bitwise operators act on the individual bits of numbers.
5 | */
6 |
7 | /**
8 | * AND Operator (&)
9 | * Combines bits such that if both corresponding bits are 1, the result is 1.
10 | */
11 | const andResult = 5 & 3; // 1
12 |
13 | /**
14 | * OR Operator (|)
15 | * Combines bits such that if either of the corresponding bits is 1, the result is 1.
16 | */
17 | const orResult = 5 | 3; // 7
18 |
19 | /**
20 | * XOR Operator (^)
21 | * Combines bits such that if only one of the corresponding bits is 1, the result is 1.
22 | */
23 | const xorResult = 5 ^ 3; // 6
24 |
25 | /**
26 | * NOT Operator (~)
27 | * Inverts all the bits in a number.
28 | */
29 | const notResult = ~5; // -6
30 |
31 | /**
32 | * Left Shift (<<)
33 | * Shifts all bits to the left by a specified number of positions.
34 | */
35 | const leftShift = 5 << 1; // 10
36 |
37 | /**
38 | * Right Shift (>>)
39 | * Shifts all bits to the right by a specified number of positions.
40 | */
41 | const rightShift = 5 >> 1; // 2
42 |
43 | /**
44 | * Zero-Fill Right Shift (>>>)
45 | * Similar to >>, but fills with zeroes from the left.
46 | */
47 | const zeroFillRightShift = -5 >>> 1; // 2147483645
48 |
49 | /**
50 | * --------------------------------------
51 | * Nuances and Advanced Use-Cases
52 | * --------------------------------------
53 | */
54 |
55 | /**
56 | * 1. Bit Manipulation Use-Cases
57 | *
58 | * Bitwise operations are commonly used in tasks like encryption, image processing, and more.
59 | */
60 |
61 | /**
62 | * 2. Bit Masks
63 | *
64 | * You can use bit masks to extract or modify subsets of bit data within a larger set.
65 | */
66 | const mask = 0b1010; // Bit mask
67 | const data = 0b1111; // Data to be manipulated
68 | const maskedData = data & mask; // 0b1010
69 |
70 | /**
71 | * 3. Sign Extension
72 | *
73 | * Right-shifting can preserve the sign bit for signed numbers, depending on the operator used.
74 | */
75 | const negativeNumber = -10;
76 | const signPreserved = negativeNumber >> 1; // -5
77 |
78 | /**
79 | * 4. Caution with Large Numbers
80 | *
81 | * Bitwise operations in JavaScript convert numbers to 32-bit signed integers, which may lead to unexpected results with large numbers.
82 | */
83 |
84 | /**
85 | * 5. Bitwise vs Logical Operators
86 | *
87 | * Bitwise AND (&) and OR (|) should not be confused with logical AND (&&) and OR (||).
88 | */
89 |
90 | /**
91 | * 6. Type Conversion
92 | *
93 | * Bitwise operations automatically convert numbers to integers, discarding any fractional part.
94 | */
95 | const floatNumber = 5.7;
96 | const integerConversion = floatNumber & 1; // 1
97 |
98 | /**
99 | * 7. Two's Complement
100 | *
101 | * The NOT (~) operator flips all bits and adds 1, effectively calculating the two's complement.
102 | */
103 |
104 | /**
105 | * 8. Quick Calculations
106 | *
107 | * You can use left shifts (<<) for quick multiplication by 2 and right shifts (>>) for quick division by 2.
108 | */
109 | const quickMultiply = 5 << 1; // 10
110 | const quickDivide = 10 >> 1; // 5
111 |
112 | /**
113 | * 9. Cross-Browser Compatibility
114 | *
115 | * Bitwise operations are supported across all major browsers and JavaScript environments.
116 | */
117 |
118 | /**
119 | * 10. Performance
120 | *
121 | * Bitwise operations are extremely fast and can be used for performance optimization in some cases.
122 | */
123 |
--------------------------------------------------------------------------------
/chapters/36_regex.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creating Regular Expressions
3 | * ----------------------------
4 | * There are two primary ways to create regular expressions: Literal Syntax and Constructor Syntax.
5 | */
6 |
7 | /**
8 | * Literal Syntax
9 | * This is the most straightforward method and is defined between slashes (/ /).
10 | */
11 | const regexLiteral = /world/;
12 |
13 | /**
14 | * Constructor Syntax
15 | * Allows for more dynamic and complex expressions. Defined using the RegExp constructor.
16 | */
17 | const regexConstructor = new RegExp("world");
18 |
19 | /**
20 | * Flags
21 | * -----
22 | * Flags can be added to a regular expression to affect the search behavior.
23 | */
24 |
25 | /**
26 | * Global search ('g')
27 | * Searches for all instances of the pattern in the string, not just the first one.
28 | */
29 | const globalRegex = /world/g;
30 |
31 | /**
32 | * Case-insensitive search ('i')
33 | * Ignores case while matching.
34 | */
35 | const caseInsensitiveRegex = /world/i;
36 |
37 | /**
38 | * Methods for Regular Expressions
39 | * -------------------------------
40 | * JavaScript offers several methods to work with regular expressions.
41 | */
42 |
43 | /**
44 | * test()
45 | * Returns true if a match is found; otherwise, it returns false.
46 | */
47 | const hasWorld = /world/.test("Hello, world!"); // true
48 |
49 | /**
50 | * exec()
51 | * Executes a search for a match and returns an array of details; null if no match is found.
52 | */
53 | const execArray = /world/.exec("Hello, world!"); // ['world', index: 7, input: 'Hello, world!', groups: undefined]
54 |
55 | /**
56 | * String Methods with Regular Expressions
57 | * --------------------------------------
58 | * Regular expressions can also be used with certain String methods.
59 | */
60 |
61 | /**
62 | * match()
63 | * Similar to exec(), but attached to the String prototype.
64 | */
65 | const matchArray = "Hello, world!".match(/world/); // ['world', index: 7, input: 'Hello, world!', groups: undefined]
66 |
67 | /**
68 | * replace()
69 | * Replaces the matched substring with a new substring.
70 | */
71 | const replaced = "Hello, world!".replace(/world/, "everyone"); // "Hello, everyone!"
72 |
73 | /**
74 | * -------------------------------------
75 | * Nuances and Advanced Use-Cases
76 | * -------------------------------------
77 | */
78 |
79 | /**
80 | * 1. Capturing Groups
81 | * Using parentheses () creates capturing groups, useful for extracting part of the matched string.
82 | */
83 | const groups = "1 + 2 = 3".match(/(\d) \+ (\d) = (\d)/);
84 |
85 | /**
86 | * 2. Non-Capturing Groups
87 | * Add ?: at the start within the parentheses to make it a non-capturing group.
88 | */
89 | const nonCapturing = "1 + 2 = 3".match(/(?:\d) \+ (?:\d) = (\d)/);
90 |
91 | /**
92 | * 3. Lookahead and Lookbehind
93 | * Use lookahead (?=) and lookbehind (?<=) for conditional matching without consuming characters.
94 | */
95 | const lookahead = "1 dollars".match(/\d(?= dollars)/); // ["1"]
96 |
97 | /**
98 | * 4. Quantifiers
99 | * Quantifiers specify how many instances of a character, group, or character set must be present for a match.
100 | */
101 | const quantifiers = "1000".match(/\d{2,4}/); // ["1000"]
102 |
103 | /**
104 | * 5. Word Boundaries
105 | * The \b assertion finds a match at the boundary of a word within a string.
106 | */
107 | const wordBoundary = "hello world".match(/\bworld\b/); // ["world"]
108 |
109 | /**
110 | * 6. Global Flag Pitfall
111 | * When a regex with the 'g' flag is reused, searches start from lastIndex and thus can give unpredictable results.
112 | */
113 | const globalPitfall = /a/g;
114 | globalPitfall.lastIndex = 1;
115 | const isPitfall = globalPitfall.test("abc"); // false
116 |
117 | /**
118 | * 7. Escaping Characters
119 | * Backslash (\) is used to escape special characters in a regex pattern.
120 | */
121 | const specialChars = "How much $ for a #?".match(/\$\sfor\sa\s#/); // ["$ for a #"]
122 |
123 | /**
124 | * 8. Unicode Property Escapes
125 | * Use \p{Property=Value} to match characters with certain Unicode properties. Note that this requires the 'u' flag.
126 | */
127 | const unicodeDigits = "123 ١٢٣".match(/\p{Numeric_Type=Digit}/gu); // ["1", "2", "3", "١", "٢", "٣"]
128 |
129 | /**
130 | * 9. Modifiers
131 | * Multiple flags can be used together to compose complex search behaviors. For instance, /world/gi will perform a global, case-insensitive search.
132 | */
133 | const multiFlag = /world/gi;
134 |
135 | /**
136 | * 10. String Split
137 | * The split() method can also utilize regular expressions to break a string into an array.
138 | */
139 | const splitArray = "apple, banana, cherry".split(/\,\s/); // ["apple", "banana", "cherry"]
140 |
--------------------------------------------------------------------------------
/chapters/37_set_timeout.js:
--------------------------------------------------------------------------------
1 | function _setTimeout() {
2 | /**
3 | * ========================================================
4 | * Basic Syntax and Usage of setTimeout
5 | * ========================================================
6 | * The setTimeout method allows you to schedule code execution after
7 | * a specified delay. The delay is not guaranteed but approximate, due to
8 | * the single-threaded nature of JavaScript.
9 | *
10 | * Syntax: setTimeout(callback, delayInMilliseconds, ...additionalArguments)
11 | * - callback: The function that will be executed after the delay.
12 | * - delayInMilliseconds: The delay in milliseconds.
13 | * - ...additionalArguments: Optional arguments that are passed to the callback.
14 | */
15 | setTimeout(() => {
16 | console.log("Basic Usage: This message will appear after approximately 1 second.");
17 | }, 1000);
18 |
19 | /**
20 | * ========================================================
21 | * Canceling a Scheduled Timeout
22 | * ========================================================
23 | * The setTimeout function returns a TimeoutID, which can be used to cancel
24 | * the timeout using clearTimeout method.
25 | */
26 | const timeoutID = setTimeout(() => {
27 | console.log("This message will never appear.");
28 | }, 2000);
29 | clearTimeout(timeoutID);
30 |
31 | /**
32 | * ========================================================
33 | * Passing Parameters to the Callback
34 | * ========================================================
35 | * setTimeout allows passing additional arguments to the callback function.
36 | * These arguments follow the delay parameter in the function signature.
37 | */
38 | setTimeout(
39 | (param1, param2) => {
40 | console.log(`Passed Parameters: ${param1}, ${param2}`);
41 | },
42 | 1500,
43 | "Parameter1",
44 | "Parameter2"
45 | );
46 |
47 | /**
48 | * ========================================================
49 | * Handling 'this' Context within setTimeout
50 | * ========================================================
51 | * If you're using an arrow function as the callback, the value of 'this'
52 | * will be inherited from the enclosing scope.
53 | *
54 | * For regular functions, the 'this' value will be either the window object
55 | * (in a browser) or undefined (in strict mode).
56 | */
57 | const exampleObject = {
58 | name: "Ishtmeet",
59 | greet: function () {
60 | setTimeout(() => {
61 | console.log(`Hello, ${this.name}`);
62 | }, 500);
63 | },
64 | };
65 | exampleObject.greet(); // Output: "Hello, Ishtmeet"
66 |
67 | /**
68 | * ========================================================
69 | * Nuances and Edge Cases
70 | * ========================================================
71 | * 1. Zero Delay: Even if you set zero milliseconds as the delay, the actual
72 | * execution may take longer because the callback enters the event queue.
73 | * 2. Maximum Delay: Delay can't exceed 2147483647 milliseconds (~24.8 days).
74 | * Any value longer will be truncated.
75 | */
76 | setTimeout(() => {
77 | console.log("Zero delay doesn't mean immediate execution.");
78 | }, 0);
79 |
80 | setTimeout(() => {
81 | console.log("This message will display after 1 millisecond, not 25 days.");
82 | }, 2147483648);
83 | }
84 |
85 | _setTimeout();
86 |
--------------------------------------------------------------------------------
/chapters/38_setInterval.js:
--------------------------------------------------------------------------------
1 | // Function to explore the intricacies and features of setInterval
2 | function _setInterval() {
3 | /**
4 | * ========================================================
5 | * Basic Syntax and Usage of setInterval
6 | * ========================================================
7 | * The setInterval method schedules repeated execution of code
8 | * at specific intervals. Just like setTimeout, the actual intervals are approximate.
9 | *
10 | * Syntax: setInterval(callback, intervalInMilliseconds, ...additionalArguments)
11 | * - callback: The function that will be executed at each interval.
12 | * - intervalInMilliseconds: The interval time in milliseconds.
13 | * - ...additionalArguments: Optional arguments that are passed to the callback.
14 | */
15 | const basicIntervalID = setInterval(() => {
16 | console.log("Basic Usage: This message will repeat every 2 seconds.");
17 | }, 2000);
18 |
19 | // To stop the above basic example after 6 seconds
20 | setTimeout(() => {
21 | clearInterval(basicIntervalID);
22 | }, 6000);
23 |
24 | /**
25 | * ========================================================
26 | * Canceling a Scheduled Interval
27 | * ========================================================
28 | * The setInterval function returns an IntervalID, which can be used to cancel
29 | * the interval using the clearInterval method.
30 | */
31 | const cancelableIntervalID = setInterval(() => {
32 | console.log("This message will display only once.");
33 | }, 3000);
34 | clearInterval(cancelableIntervalID);
35 |
36 | /**
37 | * ========================================================
38 | * Passing Parameters to the Callback
39 | * ========================================================
40 | * setInterval allows passing additional arguments to the callback function.
41 | * These arguments follow the interval parameter in the function signature.
42 | */
43 | setInterval(
44 | (param1, param2) => {
45 | console.log(`Passed Parameters: ${param1}, ${param2}`);
46 | },
47 | 4000,
48 | "Parameter1",
49 | "Parameter2"
50 | );
51 |
52 | /**
53 | * ========================================================
54 | * Handling 'this' Context within setInterval
55 | * ========================================================
56 | * The rules for the 'this' context within the callback function are similar
57 | * to those in setTimeout. Arrow functions inherit 'this' from the surrounding
58 | * code, while regular functions don't.
59 | */
60 | const exampleObject = {
61 | name: "Bob",
62 | greet: function () {
63 | setInterval(() => {
64 | console.log(`Hello, ${this.name}`);
65 | }, 5000);
66 | },
67 | };
68 | exampleObject.greet(); // Output: "Hello, Bob"
69 |
70 | /**
71 | * ========================================================
72 | * Nuances and Edge Cases
73 | * ========================================================
74 | * 1. Zero Interval: Even with zero milliseconds as the interval, the actual
75 | * execution may not be immediate due to JavaScript's single-threaded nature.
76 | * 2. Maximum Interval: The maximum length is 2147483647 milliseconds (~24.8 days),
77 | * similar to setTimeout.
78 | */
79 | setInterval(() => {
80 | console.log("Zero interval doesn't mean immediate repetition.");
81 | }, 0);
82 | }
83 |
84 | // Run the exploreSetInterval function to observe the behavior of setInterval
85 | _setInterval();
86 |
--------------------------------------------------------------------------------
/chapters/39_json_stringify.js:
--------------------------------------------------------------------------------
1 | function jsonStringify() {
2 | /**
3 | * ========================================================
4 | * Basic Syntax and Usage of JSON.stringify()
5 | * ========================================================
6 | * The JSON.stringify() method converts a JavaScript value (object, array, string,
7 | * number, boolean, or null) to a JSON-formatted string.
8 | *
9 | * Syntax: JSON.stringify(value, replacer?, space?)
10 | * - value: The JavaScript value to convert to a JSON string.
11 | * - replacer: Either a function or an array used to filter or modify the results.
12 | * - space: Specifies the indentation for readability.
13 | */
14 | const person = {
15 | name: "Ishtmeet",
16 | age: 25,
17 | };
18 | const jsonString = JSON.stringify(person);
19 | console.log(`Basic Usage: ${jsonString}`); // Output: '{"name":"Ishtmeet","age":25}'
20 |
21 | /**
22 | * ========================================================
23 | * Using Replacer Function
24 | * ========================================================
25 | * The replacer function can be used to filter out values or to transform the values
26 | * before they get stringified. The replacer function takes two arguments, the 'key' and 'value'.
27 | */
28 | const replacerFunction = (key, value) => {
29 | if (typeof value === "number") {
30 | return value * 2;
31 | }
32 | return value;
33 | };
34 | console.log(`With Replacer Function: ${JSON.stringify(person, replacerFunction)}`); // Output: '{"name":"Ishtmeet","age":50}'
35 |
36 | /**
37 | * ========================================================
38 | * Using Space for Indentation
39 | * ========================================================
40 | * The 'space' parameter specifies the number of spaces to use for indentation.
41 | * This makes the output JSON string more readable.
42 | */
43 | const prettyJsonString = JSON.stringify(person, null, 4);
44 | console.log(`Pretty Printed JSON: \n${prettyJsonString}`);
45 |
46 | /**
47 | * ========================================================
48 | * Nuances and Edge Cases
49 | * ========================================================
50 | */
51 |
52 | /**
53 | * Handling Undefined and Functions
54 | * --------------------------------
55 | * JSON.stringify() will omit properties with undefined values, functions, or Symbol types.
56 | */
57 | const objWithUndefined = { name: "Ishtmeet", greet: undefined, sayHi: function () {} };
58 | console.log(`Omitting Undefined and Functions: ${JSON.stringify(objWithUndefined)}`); // Output: '{"name":"Ishtmeet"}'
59 |
60 | /**
61 | * Handling Circular References
62 | * ----------------------------
63 | * JSON.stringify() throws an error when there are circular references in the object.
64 | */
65 | const circularObj = { name: "Ishtmeet" };
66 | circularObj.self = circularObj;
67 | // Uncomment the following line will result in an error
68 | // console.log(JSON.stringify(circularObj));
69 |
70 | /**
71 | * toJSON Method
72 | * ------------
73 | * If an object has a toJSON method, JSON.stringify() calls it and stringifies
74 | * the value returned by toJSON().
75 | */
76 | const objWithToJSON = {
77 | name: "Ishtmeet",
78 | age: 25,
79 | toJSON() {
80 | return {
81 | name: this.name,
82 | };
83 | },
84 | };
85 | console.log(`Using toJSON method: ${JSON.stringify(objWithToJSON)}`); // Output: '{"name":"Ishtmeet"}'
86 |
87 | /**
88 | * Handling Dates
89 | * --------------
90 | * JSON.stringify() will convert Date objects to their ISO string representation.
91 | */
92 | const objWithDate = { name: "Ishtmeet", birthDate: new Date() };
93 | console.log(`Handling Dates: ${JSON.stringify(objWithDate)}`);
94 | }
95 |
96 | jsonStringify();
97 |
--------------------------------------------------------------------------------
/chapters/40_json_parse.js:
--------------------------------------------------------------------------------
1 | function jsonParse() {
2 | /**
3 | * ========================================================
4 | * Basic Syntax and Usage of JSON.parse()
5 | * ========================================================
6 | * The JSON.parse() method converts a JSON-formatted string into a JavaScript object or value.
7 | *
8 | * Syntax: JSON.parse(text, reviver?)
9 | * - text: The JSON string to parse.
10 | * - reviver: A function to transform the resulting object.
11 | */
12 | const jsonString = '{"name": "Ishtmeet", "age": 25}';
13 | const parsedObject = JSON.parse(jsonString);
14 | console.log(`Basic Usage:`, parsedObject); // Output: { name: 'Ishtmeet', age: 25 }
15 |
16 | /**
17 | * ========================================================
18 | * Using Reviver Function
19 | * ========================================================
20 | * The reviver function is used for post-processing the result.
21 | * It receives two arguments: 'key' and 'value'.
22 | */
23 | const reviverFunction = (key, value) => {
24 | if (key === "age") {
25 | return value + 1;
26 | }
27 | return value;
28 | };
29 | const parsedWithReviver = JSON.parse(jsonString, reviverFunction);
30 | console.log(`With Reviver Function:`, parsedWithReviver); // Output: { name: 'Ishtmeet', age: 26 }
31 |
32 | /**
33 | * ========================================================
34 | * Nuances and Edge Cases
35 | * ========================================================
36 | */
37 |
38 | /**
39 | * Malformed JSON Strings
40 | * ----------------------
41 | * If JSON.parse() encounters a malformed JSON string, it throws a SyntaxError.
42 | */
43 | // Uncomment the next line to see the error
44 | // const malformedJSON = JSON.parse("{'name': 'Ishtmeet'}"); // Single quotes are not valid
45 |
46 | /**
47 | * Parsing Dates
48 | * -------------
49 | * JSON.parse() doesn't automatically convert date strings into Date objects.
50 | * You need to manually convert them.
51 | */
52 | const parsedDateObject = JSON.parse('{"date": "2022-01-01T12:00:00Z"}');
53 | parsedDateObject.date = new Date(parsedDateObject.date);
54 | console.log(`Parsing Dates:`, parsedDateObject.date instanceof Date); // Output: true
55 | }
56 |
57 | jsonParse();
58 |
--------------------------------------------------------------------------------
/chapters/41_map.js:
--------------------------------------------------------------------------------
1 | function map() {
2 | /**
3 | * ========================================================
4 | * Initializing a Map
5 | * ========================================================
6 | * The Map object is initialized using the new Map() constructor.
7 | * You can optionally pass an array of key-value pairs to initialize the map.
8 | */
9 | const myMap = new Map([
10 | ["key1", "value1"],
11 | ["key2", "value2"],
12 | [1, "one"],
13 | ]);
14 |
15 | /**
16 | * ========================================================
17 | * Adding Elements
18 | * ========================================================
19 | * The set() method is used to add key-value pairs to the map.
20 | * It replaces the value if the key already exists.
21 | */
22 | myMap.set("key3", "value3");
23 | myMap.set({}, "emptyObject"); // Note: Object keys are supported
24 |
25 | /**
26 | * ========================================================
27 | * Accessing Values
28 | * ========================================================
29 | * The get() method is used to retrieve the value corresponding to a given key.
30 | */
31 | console.log(`Accessing Values:`, myMap.get("key1")); // Output: 'value1'
32 | console.log(`Accessing Values:`, myMap.get(1)); // Output: 'one'
33 |
34 | /**
35 | * ========================================================
36 | * Removing Elements
37 | * ========================================================
38 | * The delete() method removes a key-value pair from the map.
39 | * It returns true if the item exists and has been removed, otherwise false.
40 | */
41 | myMap.delete("key1");
42 |
43 | /**
44 | * ========================================================
45 | * Checking for Existence
46 | * ========================================================
47 | * The has() method checks if a key exists in the map.
48 | */
49 | console.log(`Checking Existence:`, myMap.has("key2")); // Output: true
50 |
51 | /**
52 | * ========================================================
53 | * Iterating Over a Map
54 | * ========================================================
55 | * The Map object can be iterated using forEach(), for...of, and its built-in iterators.
56 | */
57 | myMap.forEach((value, key) => {
58 | console.log(`Iterating using forEach: Key: ${key}, Value: ${value}`);
59 | });
60 |
61 | /**
62 | * ========================================================
63 | * Nuances and Advanced Techniques
64 | * ========================================================
65 | */
66 |
67 | /**
68 | * Order of Elements
69 | * -----------------
70 | * Maps maintain the insertion order, unlike regular JavaScript objects.
71 | */
72 |
73 | /**
74 | * Key Equality
75 | * ------------
76 | * Maps use the "SameValueZero" equality algorithm. For example, NaN is considered equal to NaN.
77 | */
78 | myMap.set(NaN, "notANumber");
79 | console.log(`Key Equality:`, myMap.get(NaN)); // Output: 'notANumber'
80 |
81 | /**
82 | * Chaining
83 | * --------
84 | * Methods like set() return the Map object itself, allowing for chained calls.
85 | */
86 | myMap.set("key4", "value4").set("key5", "value5");
87 |
88 | /**
89 | * Built-in Iterators
90 | * ------------------
91 | * Maps have built-in iterators and can be looped using for...of.
92 | */
93 | for (const [key, value] of myMap) {
94 | console.log(`Iterating using for...of: Key: ${key}, Value: ${value}`);
95 | }
96 |
97 | /**
98 | * Converting to Array
99 | * -------------------
100 | * Maps can be converted to arrays for easier manipulation and traversal.
101 | */
102 | const mapArray = Array.from(myMap);
103 | console.log(`Converting to Array:`, mapArray); // Output: Array of key-value pairs
104 | }
105 |
106 | map();
107 |
--------------------------------------------------------------------------------
/chapters/42_weak_map.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * WeakMap
4 | * ========================================================
5 | * A WeakMap is a collection of key-value pairs where the keys must be objects
6 | * and the values can be arbitrary values. WeakMaps hold "weak" references to the keys,
7 | * which means that they do not prevent garbage collection in case there are no other references to the object.
8 | */
9 |
10 | /**
11 | * ========================================================
12 | * Initializing a WeakMap
13 | * ========================================================
14 | * WeakMaps are initialized using the `new WeakMap()` constructor.
15 | * You can also initialize it with an iterable of key-value pairs, where keys must be objects.
16 | */
17 | const myWeakMap = new WeakMap([
18 | [{}, "firstValue"],
19 | [{}, "secondValue"],
20 | ]);
21 |
22 | /**
23 | * ========================================================
24 | * Adding Elements
25 | * ========================================================
26 | * The set() method adds a new key-value pair into the WeakMap.
27 | * Remember, the key must be an object.
28 | */
29 | const obj1 = {};
30 | const obj2 = {};
31 |
32 | myWeakMap.set(obj1, "obj1Value");
33 | myWeakMap.set(obj2, "obj2Value");
34 |
35 | /**
36 | * ========================================================
37 | * Accessing Values
38 | * ========================================================
39 | * The get() method retrieves the value associated with a given object key.
40 | */
41 | console.log(myWeakMap.get(obj1)); // Output: 'obj1Value'
42 |
43 | /**
44 | * ========================================================
45 | * Removing Elements
46 | * ========================================================
47 | * The delete() method removes the key-value pair associated with a given object key.
48 | */
49 | myWeakMap.delete(obj1); // Will remove obj1 and its associated value
50 |
51 | /**
52 | * ========================================================
53 | * Checking for Existence
54 | * ========================================================
55 | * The has() method checks if a WeakMap contains a specific key.
56 | */
57 | console.log(myWeakMap.has(obj2)); // Output: true
58 |
59 | /**
60 | * ========================================================
61 | * WeakMap Nuances and Limitations
62 | * ========================================================
63 | */
64 |
65 | /**
66 | * No Iteration Support
67 | * --------------------
68 | * WeakMaps can't be iterated using methods like `forEach` or for...of loops.
69 | * This is because they are designed to be 'garbage-collection friendly' and do not have enumerable keys.
70 | */
71 |
72 | /**
73 | * Garbage Collection
74 | * ------------------
75 | * WeakMaps don't prevent the keys from being garbage collected.
76 | * If there are no other references to the object, the garbage collector will remove it and its associated value from the WeakMap.
77 | */
78 |
79 | /**
80 | * No Primitive Data Types as Keys
81 | * -------------------------------
82 | * Primitive data types (numbers, strings, etc.) cannot be used as keys.
83 | */
84 |
85 | /**
86 | * No `size` Property
87 | * ------------------
88 | * Unlike Maps, WeakMaps do not have a `size` property.
89 | */
90 |
91 | /**
92 | * No Clear Method
93 | * ---------------
94 | * WeakMaps do not have a `clear()` method to remove all key-value pairs.
95 | */
96 |
97 | /**
98 | * Chainable Methods
99 | * -----------------
100 | * Methods like set() return the WeakMap object itself, allowing for chained calls.
101 | */
102 | myWeakMap.set(obj2, "newValue").set(obj1, "obj1NewValue");
103 |
--------------------------------------------------------------------------------
/chapters/43_set.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Set
4 | * ========================================================
5 | * A Set is a data structure that allows you to store unique values of any type,
6 | * whether they are primitive types or objects. Sets are particularly useful when you
7 | * want to avoid duplicate values.
8 | */
9 |
10 | /**
11 | * ========================================================
12 | * 1. Initializing a Set
13 | * ========================================================
14 | * You can initialize a set with the `new Set()` constructor.
15 | * You can also pass an iterable object (like an array) to the constructor to initialize the set with values.
16 | */
17 | const mySet = new Set([1, 2, 3, 4, 5]);
18 |
19 | /**
20 | * ========================================================
21 | * 2. Adding Elements
22 | * ========================================================
23 | * The add() method is used to insert a new element into the set.
24 | */
25 | mySet.add(6);
26 | mySet.add("six");
27 |
28 | /**
29 | * ========================================================
30 | * 3. Removing Elements
31 | * ========================================================
32 | * The delete() method removes a specific element from the set.
33 | * It returns true if the element is found and removed, otherwise it returns false.
34 | */
35 | const wasDeleted = mySet.delete(1); // Returns true because '1' was in the set and has been removed
36 |
37 | /**
38 | * ========================================================
39 | * 4. Checking for Existence
40 | * ========================================================
41 | * The has() method returns a boolean indicating whether an element is present in the set.
42 | */
43 | console.log(mySet.has(2)); // Output will be true
44 |
45 | /**
46 | * ========================================================
47 | * 5. Clearing All Elements
48 | * ========================================================
49 | * The clear() method is used to remove all elements from the set.
50 | */
51 | // Uncomment the following line to clear the set
52 | // mySet.clear();
53 |
54 | /**
55 | * ========================================================
56 | * 6. Iterating Over a Set
57 | * ========================================================
58 | * The forEach method and for...of loop can be used to iterate over the Set.
59 | */
60 | mySet.forEach((value) => {
61 | console.log(value); // Logs each value in the set
62 | });
63 |
64 | // Using for...of
65 | for (const value of mySet) {
66 | console.log(value);
67 | }
68 |
69 | /**
70 | * ========================================================
71 | * Nuances and Advanced Techniques
72 | * ========================================================
73 | */
74 |
75 | /**
76 | * Uniqueness is Strict
77 | * --------------------
78 | * Sets enforce strict uniqueness; even two objects with the same shape are considered different.
79 | */
80 | mySet.add({});
81 | mySet.add({}); // Both objects will be added since they are different references
82 |
83 | /**
84 | * Primitive Uniqueness
85 | * ---------------------
86 | * In a set, NaN is considered equal to NaN, which is different from the Array behavior.
87 | */
88 | mySet.add(NaN);
89 | mySet.add(NaN); // Won't add another NaN as it is already present
90 |
91 | /**
92 | * Order of Elements
93 | * -----------------
94 | * Sets maintain the insertion order, which means items will be iterated in the order in which they were added.
95 | */
96 |
97 | /**
98 | * Initialization Shortcuts
99 | * ------------------------
100 | * Sets can be initialized from arrays or even other sets, effectively removing any duplicates from arrays.
101 | */
102 | const arraySet = new Set([1, 2, 2, 3, 4]); // Will remove the duplicate '2'
103 | const anotherSet = new Set(mySet); // Creates a new set from an existing one
104 |
105 | /**
106 | * Set Size
107 | * --------
108 | * You can check the size (number of unique elements) of the set using the size property.
109 | */
110 | console.log(mySet.size); // Outputs the number of unique elements
111 |
112 | /**
113 | * Chaining Methods
114 | * ----------------
115 | * Since add() returns the Set object, you can chain multiple add() calls together.
116 | */
117 | mySet.add(7).add(8);
118 |
--------------------------------------------------------------------------------
/chapters/44_weak_map.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * WeakSet in JavaScript
4 | * ========================================================
5 | * A WeakSet is a special kind of set that holds only objects (not primitive types)
6 | * and does not prevent them from being garbage-collected. This makes it particularly
7 | * useful for scenarios that require temporary storage of object references.
8 | */
9 |
10 | /**
11 | * ========================================================
12 | * 1. Initializing a WeakSet
13 | * ========================================================
14 | * You can initialize a WeakSet using the `new WeakSet()` constructor.
15 | * The constructor optionally accepts an iterable of objects.
16 | */
17 | const weakSet = new WeakSet([{ a: 1 }, { b: 2 }]);
18 |
19 | /**
20 | * ========================================================
21 | * 2. Adding Elements
22 | * ========================================================
23 | * To add an object to a WeakSet, use the add() method.
24 | * Note that only objects can be added, not primitive values.
25 | */
26 | const myObj = { c: 3 };
27 | weakSet.add(myObj);
28 |
29 | /**
30 | * ========================================================
31 | * 3. Removing Elements
32 | * ========================================================
33 | * The delete() method removes a specific object from the WeakSet.
34 | * It returns true if the object is found and successfully removed.
35 | */
36 | const wasDeleted = weakSet.delete(myObj); // Returns true as 'myObj' was in the set and has been removed
37 |
38 | /**
39 | * ========================================================
40 | * 4. Checking for Existence
41 | * ========================================================
42 | * The has() method returns a boolean indicating whether an object exists in the WeakSet.
43 | */
44 | console.log(weakSet.has(myObj)); // Output will be false because it's been removed
45 |
46 | /**
47 | * ========================================================
48 | * Nuances and Advanced Techniques
49 | * ========================================================
50 | */
51 |
52 | /**
53 | * Garbage Collection
54 | * ------------------
55 | * Since WeakSets hold weak references to objects, the JavaScript engine can safely garbage-collect them.
56 | * This is useful when you want a collection of objects that does not prevent those objects from being garbage-collected.
57 | */
58 |
59 | /**
60 | * Non-Enumerable
61 | * --------------
62 | * Unlike Sets, WeakSets are non-enumerable, which means you cannot iterate over them.
63 | * This lack of enumerability is by design to allow the JavaScript engine to perform garbage collection.
64 | */
65 |
66 | /**
67 | * Limited API
68 | * -----------
69 | * WeakSets do not have methods like size, keys, values, or forEach. They also lack a clear() method.
70 | * This is intentional, again for allowing better garbage collection.
71 | */
72 |
73 | /**
74 | * No Primitive Values
75 | * -------------------
76 | * WeakSets can only store objects. Adding primitive values like numbers or strings will throw a TypeError.
77 | */
78 |
79 | /**
80 | * Use-Cases
81 | * ---------
82 | * WeakSets are commonly used for scenarios where you want a collection that won't prevent its items from being garbage-collected.
83 | * This makes it useful for things like storing DOM elements that may be removed from the DOM at any time.
84 | */
85 |
86 | /**
87 | * No Duplicates
88 | * -------------
89 | * Just like Sets, WeakSets also enforce uniqueness among their elements.
90 | * Adding the same object more than once will have no effect.
91 | */
92 |
--------------------------------------------------------------------------------
/chapters/45_generators.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * JavaScript Generators
4 | * ========================================================
5 | * Generators are special functions that allow you to pause and resume their execution,
6 | * preserving their internal state between pauses. This is particularly useful for things
7 | * like iteration, asynchronous operations, and more.
8 | *
9 | * Generators offer a unique way to handle iterative or asynchronous logic
10 | * in a cleaner, more manageable manner.
11 | */
12 |
13 | /**
14 | * ========================================================
15 | * Defining a Generator Function
16 | * ========================================================
17 | * A generator function is defined using the 'function*' syntax.
18 | * When this function is called, it returns a generator object.
19 | */
20 | function* myGenerator() {
21 | yield "apple";
22 | yield "banana";
23 | return "done";
24 | }
25 |
26 | /**
27 | * ========================================================
28 | * Creating a Generator Object
29 | * ========================================================
30 | * You must first call the generator function to create a generator object.
31 | * This object adheres to both the iterable and iterator protocols.
32 | */
33 | const gen = myGenerator(); // Returns a generator object
34 |
35 | /**
36 | * ========================================================
37 | * Iterating through a Generator
38 | * ========================================================
39 | * Use the next() method on the generator object to iterate through the generator function's yields.
40 | * Each call to next() returns an object with 'value' and 'done' properties.
41 | */
42 | console.log(gen.next()); // Output: { value: 'apple', done: false }
43 | console.log(gen.next()); // Output: { value: 'banana', done: false }
44 | console.log(gen.next()); // Output: { value: 'done', done: true }
45 |
46 | /**
47 | * ========================================================
48 | * Nuances and Advanced Techniques
49 | * ========================================================
50 | */
51 |
52 | /**
53 | * Generator as an Iterable
54 | * ------------------------
55 | * A generator object is also an iterable, which means it can be used in a 'for...of' loop.
56 | */
57 | for (const fruit of myGenerator()) {
58 | console.log(fruit); // Output: 'apple', 'banana'
59 | }
60 |
61 | /**
62 | * yield* Expression
63 | * ----------------
64 | * The 'yield*' expression can be used to delegate to another generator function or iterable object.
65 | */
66 | function* anotherGenerator() {
67 | yield* [1, 2, 3];
68 | yield* myGenerator();
69 | }
70 | for (const val of anotherGenerator()) {
71 | console.log(val); // Output: 1, 2, 3, 'apple', 'banana'
72 | }
73 |
74 | /**
75 | * Passing Values into Generators
76 | * ------------------------------
77 | * You can pass values back into a paused generator function using the next(value) method.
78 | */
79 | function* counter() {
80 | let count = 0;
81 | while (true) {
82 | const increment = yield count;
83 | count += increment || 1;
84 | }
85 | }
86 | const cnt = counter();
87 | console.log(cnt.next().value); // Output: 0
88 | console.log(cnt.next(2).value); // Output: 2
89 |
90 | /**
91 | * Error Handling
92 | * --------------
93 | * You can handle errors within a generator function using try-catch blocks.
94 | */
95 | function* failSafeGenerator() {
96 | try {
97 | yield "OK";
98 | throw new Error("An error occurred");
99 | } catch (error) {
100 | yield "Recovered from error";
101 | }
102 | }
103 | const safeGen = failSafeGenerator();
104 | console.log(safeGen.next().value); // Output: 'OK'
105 | console.log(safeGen.next().value); // Output: 'Recovered from error'
106 |
107 | /**
108 | * Asynchronous Generators
109 | * ------------------------
110 | * ES2018 introduced asynchronous generator functions, defined using 'async function*'.
111 | * They yield Promises and can be consumed using 'for await...of' loops.
112 | */
113 | async function* asyncGenerator() {
114 | yield Promise.resolve("async value");
115 | }
116 | (async () => {
117 | for await (const val of asyncGenerator()) {
118 | console.log(val); // Output: 'async value'
119 | }
120 | })();
121 |
--------------------------------------------------------------------------------
/chapters/46_iterators.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * JavaScript Iterators
4 | * ========================================================
5 | * Iterators are a mechanism to traverse through collections (go through each item in a collection one by one),
6 | * such as arrays, strings, and more advanced data structures that we just discussed - like maps and sets.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * The Iterator Protocol
12 | * ========================================================
13 | * The iterator protocol in JavaScript requires that you implement a 'next()' method.
14 | * This method must return an object with 'value' and 'done' properties.
15 | *
16 | * The next() method serves as the core of the Iterator Protocol. It should fulfill the following conditions:
17 | *
18 | * -> Return an Object: It must return an object.
19 | *
20 | * -> Object Properties: The returned object should have two properties:
21 | * 1. value: Holds the value of the current iteration.
22 | * 2. done: A Boolean value that is true if the iterator is past the end of the iterated sequence.
23 | *
24 | * In JavaScript, it is conventional to return {value: undefined, done: true} when the sequence is exhausted.
25 | */
26 | const arrayIterator = ["a", "b", "c"][Symbol.iterator]();
27 |
28 | console.log(arrayIterator.next()); // Output: { value: 'a', done: false }
29 | console.log(arrayIterator.next()); // Output: { value: 'b', done: false }
30 | console.log(arrayIterator.next()); // Output: { value: 'c', done: false }
31 | console.log(arrayIterator.next()); // Output: { value: undefined, done: true }
32 |
33 | /**
34 | * ========================================================
35 | * Custom Iterators
36 | * ========================================================
37 | * You can make your own iterable objects by defining a [Symbol.iterator] method.
38 | * This method should return an object containing a 'next' method.
39 | */
40 | const customIterable = {
41 | [Symbol.iterator]: function () {
42 | let count = 0;
43 | return {
44 | next: function () {
45 | count++;
46 | if (count <= 3) {
47 | return { value: count, done: false };
48 | }
49 | return { value: undefined, done: true };
50 | },
51 | };
52 | },
53 | };
54 |
55 | // Using the custom iterable in a for...of loop
56 | for (const item of customIterable) {
57 | console.log(item); // Output: 1, 2, 3
58 | }
59 |
60 | /**
61 | * ========================================================
62 | * Nuances and Advanced Techniques
63 | * ========================================================
64 | */
65 |
66 | /**
67 | * Iterating Over Arguments
68 | * ------------------------
69 | * JavaScript's 'arguments' object is array-like but not an actual array.
70 | * You can make it iterable using Array.from() method.
71 | */
72 | function argumentsIterator() {
73 | const argsArray = Array.from(arguments);
74 | for (const arg of argsArray) {
75 | console.log(arg);
76 | }
77 | }
78 | argumentsIterator(1, 2, 3); // Output: 1, 2, 3
79 |
80 | /**
81 | * String Iterators
82 | * ----------------
83 | * Strings are iterable by default. When you iterate over a string,
84 | * each iteration returns a single character.
85 | */
86 | for (const char of "hello") {
87 | console.log(char); // Output: 'h', 'e', 'l', 'l', 'o'
88 | }
89 |
90 | /**
91 | * Iterating Over Maps and Sets
92 | * ----------------------------
93 | * Map and Set objects have their built-in iterators, which makes it easier to iterate through them.
94 | */
95 | const mySet = new Set([1, 2, 3]);
96 | const setIter = mySet[Symbol.iterator]();
97 | console.log(setIter.next().value); // Output: 1
98 |
99 | const myMap = new Map([
100 | ["a", 1],
101 | ["b", 2],
102 | ]);
103 | const mapIter = myMap[Symbol.iterator]();
104 | console.log(mapIter.next().value); // Output: ['a', 1]
105 |
106 | /**
107 | * Iterator Return and Throw Methods
108 | * ---------------------------------
109 | * Besides the 'next()' method, iterators can optionally implement 'return' and 'throw' methods.
110 | * These can be used to release internal resources when an iterator is no longer in use,
111 | * or to propagate errors during iteration, respectively.
112 | */
113 | const advancedIterator = {
114 | [Symbol.iterator]: function () {
115 | return {
116 | next() {
117 | return { value: "some_value", done: false };
118 | },
119 | return() {
120 | console.log("Exiting early");
121 | return { done: true };
122 | },
123 | throw(error) {
124 | console.log("An error occurred:", error);
125 | return { done: true };
126 | },
127 | };
128 | },
129 | };
130 |
--------------------------------------------------------------------------------
/chapters/47_big_int.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * BigInt
4 | * ========================================================
5 | * BigInt is a built-in object that provides a way to represent whole numbers larger
6 | * than 2^53 - 1, which is the largest number JavaScript can reliably represent using the Number primitive.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Basic Syntax
12 | * ========================================================
13 | * BigInt can be defined either by appending 'n' to the end of an integer literal or
14 | * by using the BigInt constructor function.
15 | */
16 | const bigIntLiteral = 1234567890123456789012345678901234567890n;
17 | const bigIntObject = BigInt("1234567890123456789012345678901234567890");
18 |
19 | /**
20 | * ========================================================
21 | * 2. Arithmetic Operations
22 | * ========================================================
23 | * Arithmetic operations are supported on BigInts much like they are for Numbers,
24 | * but you can't mix BigInts and Numbers in these operations.
25 | */
26 | const sum = bigIntLiteral + 1n;
27 | // const invalidSum = bigIntLiteral + 1; // This will throw a TypeError
28 |
29 | /**
30 | * ========================================================
31 | * 3. Comparison
32 | * ========================================================
33 | * BigInt can be compared using all the regular comparison operators.
34 | * Just like arithmetic operations, you can't directly compare a BigInt with a Number.
35 | */
36 | const isTrue = bigIntLiteral > 1000n; // Evaluates to true
37 |
38 | /**
39 | * ========================================================
40 | * Nuances and Advanced Techniques
41 | * ========================================================
42 | */
43 |
44 | /**
45 | * 1. No Auto-Type Conversion
46 | * ---------------------------
47 | * BigInt does not automatically convert into a string or a number.
48 | * Explicit conversion is often required when you're working with mixed types.
49 | */
50 | const bigIntStr = String(bigIntLiteral);
51 | // const invalidOperation = bigIntLiteral + " is big."; // This will throw a TypeError
52 |
53 | /**
54 | * 2. Divisions Are Floored
55 | * ------------------------
56 | * When performing division using BigInt, the division is floored,
57 | * meaning it rounds towards zero.
58 | */
59 | const flooredDivision = 11n / 3n; // Result will be 3n
60 |
61 | /**
62 | * 3. Bitwise Operations
63 | * ---------------------
64 | * You can perform bitwise operations with BigInt.
65 | * Both operands must be BigInts to perform these operations.
66 | */
67 | const bitwiseAnd = 16n & 5n; // Output will be 0n
68 |
69 | /**
70 | * 4. No Support for Math Object
71 | * -----------------------------
72 | * BigInt cannot be used with the JavaScript Math object's methods.
73 | */
74 | // const sqrtBigInt = Math.sqrt(bigIntLiteral); // This will throw a TypeError
75 |
76 | /**
77 | * 5. JSON Serialization
78 | * ----------------------
79 | * JSON.stringify() can't serialize BigInt values,
80 | * you'll need to manually convert them to strings before serialization.
81 | */
82 | // const bigIntJSON = JSON.stringify({value: bigIntLiteral}); // This will throw a TypeError
83 |
84 | /**
85 | * 6. Compatibility
86 | * ----------------
87 | * BigInt is not yet universally supported across all environments,
88 | * so you may need to check for compatibility before using it.
89 | */
90 | if (typeof BigInt !== "undefined") {
91 | const bigIntSupport = BigInt(10);
92 | console.log(`BigInt supported: ${bigIntSupport}`);
93 | }
94 |
--------------------------------------------------------------------------------
/chapters/48.1_web_apis_2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Web APIs - Part 2
4 | * ========================================================
5 | */
6 |
7 | /**
8 | * ========================================================
9 | * 1. WebSocket API
10 | * ========================================================
11 | * WebSocket provides full-duplex communication channels over a single, long-lived connection.
12 | */
13 | const ws = new WebSocket("ws://example.com/socketserver");
14 | ws.onopen = () => {
15 | ws.send("Hello Server!");
16 | };
17 | ws.onmessage = (event) => {
18 | console.log(`Server says: ${event.data}`);
19 | };
20 | ws.onclose = () => {
21 | console.log("Connection closed");
22 | };
23 |
24 | /**
25 | * ========================================================
26 | * 2. DOM API
27 | * ========================================================
28 | * The DOM API allows you to programmatically interact with HTML and XML documents.
29 | */
30 | // Adding a new element
31 | const newElement = document.createElement("div");
32 | newElement.textContent = "Hello, World!";
33 | document.body.appendChild(newElement);
34 |
35 | /**
36 | * ========================================================
37 | * 3. Drag and Drop API
38 | * ========================================================
39 | * This API allows you to make elements draggable and to capture drop events.
40 | */
41 | document.addEventListener("dragstart", (event) => {
42 | event.dataTransfer.setData("text/plain", event.target.id);
43 | });
44 | document.addEventListener("drop", (event) => {
45 | event.preventDefault();
46 | const data = event.dataTransfer.getData("text");
47 | const target = document.getElementById(data);
48 | event.target.appendChild(target);
49 | });
50 |
51 | /**
52 | * ========================================================
53 | * Nuances and Advanced Techniques
54 | * ========================================================
55 | */
56 |
57 | /**
58 | * 1. WebSocket Reconnection
59 | * --------------------------
60 | * In real-world scenarios, WebSocket connections can drop. A common practice is to implement
61 | * reconnection logic.
62 | */
63 | let ws;
64 | function connect() {
65 | ws = new WebSocket("ws://example.com/socketserver");
66 | ws.onopen = () => {
67 | ws.send("Hello Server!");
68 | };
69 | ws.onmessage = (event) => {
70 | console.log(`Server says: ${event.data}`);
71 | };
72 | ws.onclose = () => {
73 | console.log("Connection closed. Reconnecting...");
74 | setTimeout(connect, 1000);
75 | };
76 | }
77 | connect();
78 |
79 | /**
80 | * 2. Using DOM API with Fragments
81 | * --------------------------------
82 | * Document fragments let you create a subtree of elements and insert them into the DOM
83 | * as a single operation.
84 | */
85 | const fragment = document.createDocumentFragment();
86 | const elem1 = document.createElement("p");
87 | elem1.textContent = "Paragraph 1";
88 | const elem2 = document.createElement("p");
89 | elem2.textContent = "Paragraph 2";
90 | fragment.appendChild(elem1);
91 | fragment.appendChild(elem2);
92 | document.body.appendChild(fragment);
93 |
94 | /**
95 | * 3. Drag and Drop with Custom Images
96 | * -----------------------------------
97 | * You can customize the drag image by setting the `dragImage` property on the dataTransfer object.
98 | */
99 | document.addEventListener("dragstart", (event) => {
100 | const img = new Image();
101 | img.src = "path/to/image.png";
102 | event.dataTransfer.setDragImage(img, 10, 10);
103 | });
104 |
--------------------------------------------------------------------------------
/chapters/48.2_web_apis_3.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Web APIs - Part 3
4 | * ========================================================
5 | */
6 |
7 | /**
8 | * ========================================================
9 | * 1. Audio and Video API
10 | * ========================================================
11 | * The HTML5 Audio and Video APIs allow for playback of multimedia.
12 | */
13 | const audio = new Audio("audio_file.mp3");
14 | audio.play();
15 |
16 | const video = document.querySelector("video");
17 | video.play();
18 |
19 | /**
20 | * ========================================================
21 | * 2. Canvas API
22 | * ========================================================
23 | * The Canvas API provides a way to draw 2D graphics.
24 | */
25 | const canvas = document.getElementById("myCanvas");
26 | const ctx = canvas.getContext("2d");
27 | ctx.fillStyle = "#FF0000";
28 | ctx.fillRect(0, 0, 80, 80);
29 |
30 | /**
31 | * ========================================================
32 | * 3. RequestAnimationFrame API
33 | * ========================================================
34 | * Used for creating smooth animations.
35 | */
36 | function animate() {
37 | // Animation code
38 | requestAnimationFrame(animate);
39 | }
40 | animate();
41 |
42 | /**
43 | * ========================================================
44 | * Nuances and Advanced Techniques
45 | * ========================================================
46 | */
47 |
48 | /**
49 | * 1. Controlling Audio and Video Playback
50 | * ---------------------------------------
51 | * You can control the playback rates, volume, and other properties.
52 | */
53 | audio.volume = 0.5;
54 | audio.playbackRate = 1.5;
55 |
56 | /**
57 | * 2. Canvas Transformations
58 | * -------------------------
59 | * Canvas allows you to perform complex transformations like scaling and rotations.
60 | * We'll talk more about canvas, in the next chapter.
61 | */
62 | ctx.save();
63 | ctx.rotate((Math.PI / 180) * 45);
64 | ctx.fillRect(100, 0, 50, 50);
65 | ctx.restore();
66 |
67 | /**
68 | * 3. Animating with RequestAnimationFrame
69 | * ---------------------------------------
70 | * It's best to use requestAnimationFrame over setInterval for smoother animations.
71 | */
72 | let x = 0;
73 | function animate() {
74 | ctx.clearRect(0, 0, canvas.width, canvas.height);
75 | ctx.fillRect(x, 0, 50, 50);
76 | x += 1;
77 | requestAnimationFrame(animate);
78 | }
79 | animate();
80 |
--------------------------------------------------------------------------------
/chapters/49_canvas.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Canvas API
4 | * ========================================================
5 | * The Canvas API allows for dynamic, scriptable rendering of 2D and 3D graphics.
6 | */
7 |
8 | /**
9 | * ========================================================
10 | * 1. Basic Setup
11 | * ========================================================
12 | * Create a canvas element in HTML and get its context in JavaScript.
13 | */
14 | const canvas = document.getElementById("myCanvas");
15 | const ctx = canvas.getContext("2d");
16 |
17 | /**
18 | * ========================================================
19 | * 2. Drawing Shapes
20 | * ========================================================
21 | * Draw simple shapes like rectangles, circles, and lines.
22 | */
23 |
24 | // Draw a rectangle
25 | ctx.fillStyle = "red";
26 | ctx.fillRect(10, 10, 100, 50);
27 |
28 | // Draw a circle
29 | ctx.fillStyle = "blue";
30 | ctx.beginPath();
31 | ctx.arc(100, 100, 50, 0, Math.PI * 2);
32 | ctx.fill();
33 |
34 | // Draw a line
35 | ctx.strokeStyle = "green";
36 | ctx.beginPath();
37 | ctx.moveTo(10, 10);
38 | ctx.lineTo(100, 10);
39 | ctx.stroke();
40 |
41 | /**
42 | * ========================================================
43 | * 3. Text Rendering
44 | * ========================================================
45 | * Display text on the canvas. Can control font, alignment, and more.
46 | */
47 | ctx.font = "30px Arial";
48 | ctx.fillText("Hello Canvas", 50, 200);
49 | ctx.textAlign = "center"; // Other options: 'left', 'right'
50 | ctx.fillText("Centered Text", canvas.width / 2, canvas.height / 2);
51 |
52 | /**
53 | * ========================================================
54 | * Nuances and Advanced Techniques
55 | * ========================================================
56 | */
57 |
58 | /**
59 | * 1. Pixel Manipulation
60 | * ----------------------
61 | * Manipulate individual pixels for advanced effects like filters.
62 | */
63 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
64 | const data = imageData.data;
65 | for (let i = 0; i < data.length; i += 4) {
66 | // Invert colors
67 | data[i] = 255 - data[i];
68 | data[i + 1] = 255 - data[i + 1];
69 | data[i + 2] = 255 - data[i + 2];
70 | }
71 | ctx.putImageData(imageData, 0, 0);
72 |
73 | /**
74 | * 2. Transformations
75 | * -------------------
76 | * Apply transformations like scale, rotate, and translate.
77 | * Always save and restore the context when doing transformations.
78 | */
79 | ctx.save();
80 | ctx.scale(0.5, 0.5);
81 | ctx.rotate((Math.PI / 180) * 25);
82 | ctx.translate(100, 0);
83 | // Draw transformed rectangle
84 | ctx.fillStyle = "purple";
85 | ctx.fillRect(50, 50, 100, 50);
86 | ctx.restore();
87 |
88 | /**
89 | * 3. Animation
90 | * -------------
91 | * Use `requestAnimationFrame` for smooth animations.
92 | * This creates a game loop for continuous drawing.
93 | */
94 | let xPos = 0;
95 | function drawFrame() {
96 | // Clear canvas and update drawings
97 | ctx.clearRect(0, 0, canvas.width, canvas.height);
98 | ctx.fillRect(xPos, 10, 50, 50);
99 | xPos++;
100 | requestAnimationFrame(drawFrame);
101 | }
102 | requestAnimationFrame(drawFrame);
103 |
104 | /**
105 | * 4. OffscreenCanvas
106 | * -------------------
107 | * Perform canvas rendering in a web worker to offload the main thread.
108 | */
109 | if (window.OffscreenCanvas) {
110 | const offscreen = new OffscreenCanvas(100, 100);
111 | const offscreenCtx = offscreen.getContext("2d");
112 | offscreenCtx.fillStyle = "orange";
113 | offscreenCtx.fillRect(0, 0, 100, 100);
114 | }
115 |
116 | /**
117 | * 5. Path2D Object
118 | * ----------------
119 | * Use Path2D objects to cache complex paths and draw them later.
120 | * This can improve performance in animations.
121 | */
122 | const path = new Path2D();
123 | path.moveTo(10, 10);
124 | path.lineTo(100, 10);
125 | path.lineTo(100, 100);
126 | ctx.stroke(path);
127 |
--------------------------------------------------------------------------------
/chapters/50_drag_drop.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * JavaScript Drag and Drop API
4 | * ========================================================
5 | * Enables interactive Drag-and-Drop interfaces in web applications.
6 | */
7 |
8 | /**
9 | * ========================================================
10 | * 1. Making an Element Draggable
11 | * ========================================================
12 | * To make an element draggable, set its 'draggable' attribute to true.
13 | * This enables the browser's native drag-and-drop feature for the element.
14 | */
15 | const draggableElem = document.getElementById("draggable");
16 | draggableElem.setAttribute("draggable", "true");
17 |
18 | /**
19 | * ========================================================
20 | * 2. Drag Events
21 | * ========================================================
22 | * Drag events are fired on both the draggable target and the drop target.
23 | * The 'dragstart' event is essential for initiating a drag operation.
24 | */
25 | // Drag start event
26 | draggableElem.addEventListener("dragstart", (event) => {
27 | // Store some meta-data to be used during the 'drop' event
28 | event.dataTransfer.setData("text/plain", draggableElem.id);
29 | });
30 |
31 | /**
32 | * ========================================================
33 | * 3. Dropping an Element
34 | * ========================================================
35 | * To allow an element to act as a drop target, you must prevent the default handling of the element during dragover.
36 | */
37 | const dropZone = document.getElementById("dropZone");
38 |
39 | // Allow the drop by preventing default behavior
40 | dropZone.addEventListener("dragover", (event) => {
41 | event.preventDefault();
42 | });
43 |
44 | // Handle the drop
45 | dropZone.addEventListener("drop", (event) => {
46 | event.preventDefault();
47 | const data = event.dataTransfer.getData("text/plain");
48 | const draggedElem = document.getElementById(data);
49 | dropZone.appendChild(draggedElem);
50 | });
51 |
52 | /**
53 | * ========================================================
54 | * Nuances and Advanced Techniques
55 | * ========================================================
56 | */
57 |
58 | /**
59 | * 1. Custom Drag Image
60 | * --------------------
61 | * You can set a custom image to appear next to the cursor while dragging.
62 | */
63 | draggableElem.addEventListener("dragstart", (event) => {
64 | const img = new Image();
65 | img.src = "drag-image.png";
66 | event.dataTransfer.setDragImage(img, 10, 10);
67 | });
68 |
69 | /**
70 | * 2. Drag Handles
71 | * ----------------
72 | * You can make only a part of an element act as a drag handle.
73 | */
74 | const handle = document.getElementById("handle");
75 | handle.addEventListener("dragstart", (event) => {
76 | event.dataTransfer.setData("text/plain", draggableElem.id);
77 | event.stopPropagation(); // Prevent dragstart from bubbling up to parent elements
78 | });
79 |
80 | /**
81 | * 3. Nested Drop Targets
82 | * ----------------------
83 | * Handling dragover and drop events properly when there are nested drop targets can be tricky.
84 | * Stop propagation to ensure only the innermost target gets the event.
85 | */
86 | const nestedZone = document.getElementById("nestedZone");
87 | nestedZone.addEventListener("dragover", (event) => {
88 | event.stopPropagation();
89 | event.preventDefault(); // Still required to allow drop
90 | });
91 | nestedZone.addEventListener("drop", (event) => {
92 | event.stopPropagation();
93 | event.preventDefault(); // Execute your nested drop logic here
94 | });
95 |
96 | /**
97 | * 4. Drag and Drop File Upload
98 | * ----------------------------
99 | * You can use drag and drop for file uploads. It enhances user experience significantly.
100 | */
101 | dropZone.addEventListener("drop", (event) => {
102 | const files = event.dataTransfer.files;
103 | Array.from(files).forEach((file) => {
104 | // Your file processing logic here
105 | console.log(`Uploaded file: ${file.name}`);
106 | });
107 | });
108 |
109 | /**
110 | * HTML Code for testing the above -
111 |
112 |
113 |
114 |
115 | Drag and Drop Example
116 |
117 |
118 | Drag Me!
119 | Drop Here!
120 | Drag Handle
121 | Nested Drop Zone
122 |
123 |
124 |
125 |
126 | */
127 |
--------------------------------------------------------------------------------
/chapters/51_file_and_blob.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * File and Blob
4 | * ========================================================
5 | * The File and Blob APIs open up new possibilities for dealing with files and binary data,
6 | * enabling a range of functionalities from basic file I/O to advanced techniques like streaming and object serialization.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * File API
12 | * ========================================================
13 | * The File API allows you to work with files in web applications,
14 | * making it possible to read the contents of files and navigate their structure.
15 | */
16 |
17 | /**
18 | * ========================================================
19 | * 1. Creating a File
20 | * ========================================================
21 | * Creating a File object is similar to creating a Blob. The File constructor
22 | * allows you to add additional metadata, such as the file name and MIME type.
23 | */
24 | const file = new File(["Hello, again!"], "hello.txt", { type: "text/plain" });
25 |
26 | /**
27 | * ========================================================
28 | * 2. Reading a File
29 | * ========================================================
30 | * The FileReader API is commonly used to read the contents of a File object.
31 | */
32 | const fileReader = new FileReader();
33 | fileReader.onload = function (event) {
34 | console.log(event.target.result); // Outputs "Hello, again!"
35 | };
36 | fileReader.readAsText(file);
37 |
38 | /**
39 | * ========================================================
40 | * 3. Uploading a File
41 | * ========================================================
42 | * Files can be uploaded using the Fetch API or XMLHttpRequest.
43 | */
44 | fetch("https://example.com/upload", {
45 | method: "POST",
46 | body: file,
47 | });
48 |
49 | /**
50 | * ========================================================
51 | * 4. File Input Element
52 | * ========================================================
53 | * The element allows users to select files from their device,
54 | * which can then be read and manipulated using JavaScript.
55 | */
56 | const fileInput = document.getElementById("fileInput");
57 | fileInput.addEventListener("change", (event) => {
58 | const selectedFile = event.target.files[0];
59 | // Further processing of the File object
60 | });
61 |
62 | /**
63 | * ========================================================
64 | * Blob API
65 | * ========================================================
66 | * The Blob API is used for handling binary data directly,
67 | * enabling you to create, read and manipulate binary data in a performant and safe manner.
68 | */
69 |
70 | /**
71 | * ========================================================
72 | * 1. Creating a Blob
73 | * ========================================================
74 | * Blobs can be created using the Blob constructor.
75 | * The constructor takes an array of data and an optional options object.
76 | */
77 | const blob = new Blob(["Hello, world!"], { type: "text/plain" });
78 |
79 | /**
80 | * ========================================================
81 | * 2. Reading a Blob
82 | * ========================================================
83 | * The FileReader API can read Blob content just like it does with File objects.
84 | */
85 | const blobReader = new FileReader();
86 | blobReader.onload = function (event) {
87 | console.log(event.target.result); // Outputs "Hello, world!"
88 | };
89 | blobReader.readAsText(blob);
90 |
91 | /**
92 | * ========================================================
93 | * Nuances and Advanced Techniques
94 | * ========================================================
95 | */
96 |
97 | /**
98 | * 1. Blob URLs
99 | * ------------
100 | * Temporary URLs can be created for Blobs and Files for use within the application.
101 | */
102 | const url = URL.createObjectURL(blob);
103 | // Perform operations with the URL (e.g., assign it to an
or element)
104 | URL.revokeObjectURL(url); // Free up resources by releasing the URL
105 |
106 | /**
107 | * 2. Slicing a Blob
108 | * -----------------
109 | * Blobs can be sliced to create new Blob objects from a subset of their data.
110 | */
111 | const slicedBlob = blob.slice(0, 5); // Contains the text "Hello"
112 |
113 | /**
114 | * 3. Blob Streaming
115 | * -----------------
116 | * The Blob object can be streamed using the ReadableStream API, if supported.
117 | */
118 | if (blob.stream) {
119 | const stream = blob.stream();
120 | // Perform operations using the stream
121 | }
122 |
123 | /**
124 | * 4. Object Serialization
125 | * ------------------------
126 | * Blobs can store complex objects, thanks to JSON serialization.
127 | */
128 | const objBlob = new Blob([JSON.stringify({ key: "value" })], {
129 | type: "application/json",
130 | });
131 |
--------------------------------------------------------------------------------
/chapters/52_websockets.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * WebSockets in JS
4 | * ========================================================
5 | * WebSockets enable real-time, full-duplex communication between a web client and a server.
6 | * This allows for instant data transfer, making them useful for applications like chat, gaming, and live updates.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Creating a WebSocket
12 | * ========================================================
13 | * Use the WebSocket constructor to create a new WebSocket instance.
14 | * The argument is the URL of the WebSocket server you want to connect to.
15 | */
16 | const socket = new WebSocket("ws://example.com");
17 |
18 | /**
19 | * ========================================================
20 | * 2. Open Event
21 | * ========================================================
22 | * The 'open' event fires when the connection is successfully established.
23 | * You can start sending data to the server once this event fires.
24 | */
25 | socket.addEventListener("open", () => {
26 | console.log("WebSocket connection opened");
27 | socket.send("Hello, server!");
28 | });
29 |
30 | /**
31 | * ========================================================
32 | * 3. Message Event
33 | * ========================================================
34 | * The 'message' event fires when a message is received from the WebSocket server.
35 | * The message data can be accessed via `event.data`.
36 | */
37 | socket.addEventListener("message", (event) => {
38 | console.log(`Received from server: ${event.data}`);
39 | });
40 |
41 | /**
42 | * ========================================================
43 | * 4. Close Event
44 | * ========================================================
45 | * The 'close' event fires when the WebSocket connection is closed,
46 | * either due to the client's or server's request or because of an error.
47 | * The event object contains details like close code and reason.
48 | */
49 | socket.addEventListener("close", (event) => {
50 | console.log(`Connection closed: ${event.code}`);
51 | });
52 |
53 | /**
54 | * ========================================================
55 | * 5. Error Event
56 | * ========================================================
57 | * The 'error' event fires when an error occurs.
58 | * Although the event does not carry detailed information,
59 | * it is often accompanied by a 'close' event that gives additional context.
60 | */
61 | socket.addEventListener("error", (error) => {
62 | console.error(`WebSocket Error: ${error}`);
63 | });
64 |
65 | /**
66 | * ========================================================
67 | * Nuances and Advanced Techniques
68 | * ========================================================
69 | */
70 |
71 | /**
72 | * 1. Ping/Pong Frames
73 | * -------------------
74 | * Some WebSocket servers use ping/pong frames to keep the connection alive.
75 | * In most cases, the browser will automatically reply to "ping" frames with "pong" frames.
76 | * No additional client-side handling is needed.
77 | */
78 |
79 | /**
80 | * 2. Subprotocols
81 | * ---------------
82 | * You can specify one or more subprotocols while creating a WebSocket.
83 | * The server will pick one among those and will use it for further communication.
84 | */
85 | const customSocket = new WebSocket("ws://example.com", ["subprotocol-1", "subprotocol-2"]);
86 |
87 | /**
88 | * 3. Secure WebSockets (WSS)
89 | * --------------------------
90 | * Always prefer using Secure WebSockets (WSS) when dealing with sensitive or secure data.
91 | */
92 | const secureSocket = new WebSocket("wss://secure-example.com");
93 |
94 | /**
95 | * 4. Binary Data
96 | * -------------
97 | * WebSockets can also handle binary data, such as ArrayBuffer and Blob objects.
98 | */
99 | const binaryData = new ArrayBuffer(256);
100 | socket.send(binaryData);
101 |
102 | /**
103 | * 5. Buffering and Back-pressure
104 | * ------------------------------
105 | * If you're sending large or numerous pieces of data, you might run into issues with buffering.
106 | * The `bufferedAmount` property tells how many bytes are currently buffered and awaiting transmission.
107 | */
108 | if (socket.bufferedAmount === 0) {
109 | socket.send("This message will be sent immediately");
110 | } else {
111 | // Handle back-pressure, possibly by buffering data or reducing the sending rate.
112 | }
113 |
--------------------------------------------------------------------------------
/chapters/53_web_workers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Web Workers
4 | * ========================================================
5 | * Web Workers allow you to run scripts in the background.
6 | * This helps in offloading computational tasks and thus keeps the UI responsive.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Creating a Web Worker
12 | * ========================================================
13 | * Use the Worker constructor to spawn a new worker.
14 | * The argument to this constructor is the path to the worker script.
15 | */
16 | const worker = new Worker("worker-script.js");
17 |
18 | /**
19 | * ========================================================
20 | * 2. Sending Messages to the Worker
21 | * ========================================================
22 | * The `postMessage` method is used to send messages from the main thread to the worker.
23 | * The data sent can be any structure that is cloneable or transferable.
24 | */
25 | worker.postMessage("Hello, Worker!");
26 |
27 | /**
28 | * ========================================================
29 | * 3. Receiving Messages from the Worker
30 | * ========================================================
31 | * Listen for the `message` event to receive messages from the worker.
32 | * Use `event.data` to access the data sent by the worker.
33 | */
34 | worker.addEventListener("message", (event) => {
35 | console.log(`Received from worker: ${event.data}`);
36 | });
37 |
38 | /**
39 | * ========================================================
40 | * 4. Terminating a Worker
41 | * ========================================================
42 | * The `terminate` method can be used to immediately terminate a worker.
43 | * Use this method with caution as it stops all worker activities without any cleanup.
44 | */
45 | worker.terminate();
46 |
47 | /**
48 | * ========================================================
49 | * 5. Error Handling
50 | * ========================================================
51 | * The `error` event is triggered when an uncaught exception occurs within the worker.
52 | * This allows you to gracefully handle errors and possibly recover from them.
53 | */
54 | worker.addEventListener("error", (error) => {
55 | console.error(`Worker Error: ${error.message}`);
56 | });
57 |
58 | /**
59 | * ========================================================
60 | * Nuances and Advanced Techniques
61 | * ========================================================
62 | */
63 |
64 | /**
65 | * 1. Transferable Objects
66 | * -----------------------
67 | * Transferable objects are used when you want to hand off ownership of an object to another context like a worker.
68 | * This allows faster transfer of complex structures like typed arrays.
69 | */
70 | const buffer = new ArrayBuffer(8);
71 | worker.postMessage(buffer, [buffer]);
72 |
73 | /**
74 | * 2. Shared Workers
75 | * -----------------
76 | * Shared Workers allow sharing a single worker instance between multiple contexts such as different windows or even workers.
77 | */
78 | const sharedWorker = new SharedWorker("shared-worker-script.js");
79 | sharedWorker.port.start();
80 |
81 | /**
82 | * 3. Worker Scope
83 | * ---------------
84 | * Inside a Web Worker, the global scope is not the `window` object.
85 | * While you can't access the DOM, some web APIs like `fetch` and `IndexedDB` are still available.
86 | */
87 |
88 | /**
89 | * 4. Importing Scripts
90 | * --------------------
91 | * You can import external JavaScript files into your worker using `importScripts`.
92 | * This allows you to reuse code and manage dependencies.
93 | */
94 | // Inside worker-script.js
95 | importScripts("external-script.js");
96 |
97 | /**
98 | * 5. Inline Workers
99 | * -----------------
100 | * If your worker logic is small or generated dynamically, you can use inline workers created from Blob URLs or Data URLs.
101 | */
102 | const blob = new Blob(['postMessage("Hello from inline worker!");'], { type: "text/javascript" });
103 | const inlineWorker = new Worker(URL.createObjectURL(blob));
104 |
105 | /**
106 | * 6. Message Channels
107 | * -------------------
108 | * For complex, two-way communications between your main thread and worker, Message Channels can be used.
109 | */
110 | const channel = new MessageChannel();
111 | worker.postMessage("Initiate MessageChannel communication", [channel.port2]);
112 |
--------------------------------------------------------------------------------
/chapters/54_service_workers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Service Workers
4 | * ========================================================
5 | * Service Workers enable various powerful features like offline caching, background sync, and push notifications.
6 | * They run in the background and act as a proxy between web applications and the network.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Registering a Service Worker
12 | * ========================================================
13 | * To register a Service Worker, you use `navigator.serviceWorker.register`.
14 | * This returns a promise that resolves with the ServiceWorkerRegistration object when the worker gets registered successfully.
15 | */
16 | if ("serviceWorker" in navigator) {
17 | navigator.serviceWorker
18 | .register("/service-worker.js")
19 | .then((registration) => {
20 | console.log("Service Worker registered:", registration);
21 | })
22 | .catch((error) => {
23 | console.log("Service Worker registration failed:", error);
24 | });
25 | }
26 |
27 | /**
28 | * ========================================================
29 | * 2. The Install Event
30 | * ========================================================
31 | * The `install` event is fired when the Service Worker is being installed.
32 | * This is an opportunity to cache assets for offline use using the Cache API.
33 | */
34 | self.addEventListener("install", (event) => {
35 | event.waitUntil(
36 | caches.open("my-cache").then((cache) => {
37 | return cache.addAll(["/", "/index.html", "/styles.css", "/script.js"]);
38 | })
39 | );
40 | });
41 |
42 | /**
43 | * ========================================================
44 | * 3. The Activate Event
45 | * ========================================================
46 | * The `activate` event fires when the Service Worker becomes active.
47 | * This is a good place to manage old caches.
48 | */
49 | self.addEventListener("activate", (event) => {
50 | console.log("Service Worker activated");
51 | });
52 |
53 | /**
54 | * ========================================================
55 | * 4. The Fetch Event
56 | * ========================================================
57 | * The `fetch` event can be used to intercept network requests.
58 | * You can respond to this event by fetching a resource from the cache or network.
59 | */
60 | self.addEventListener("fetch", (event) => {
61 | event.respondWith(
62 | caches.match(event.request).then((response) => {
63 | return response || fetch(event.request);
64 | })
65 | );
66 | });
67 |
68 | /**
69 | * ========================================================
70 | * Nuances and Advanced Techniques
71 | * ========================================================
72 | */
73 |
74 | /**
75 | * 1. Skip Waiting
76 | * ---------------
77 | * The `skipWaiting` method allows a service worker to skip the 'waiting' lifecycle phase and move to 'active'.
78 | */
79 | self.skipWaiting();
80 |
81 | /**
82 | * 2. Clients Claim
83 | * ----------------
84 | * Using `clients.claim`, a newly activated Service Worker can immediately control all pages under its scope.
85 | */
86 | self.clients.claim();
87 |
88 | /**
89 | * 3. Background Sync
90 | * ------------------
91 | * The 'sync' event enables deferred actions to be retried when the user has network connectivity.
92 | */
93 | self.addEventListener("sync", (event) => {
94 | if (event.tag === "myBackgroundSync") {
95 | // Your background sync logic here
96 | }
97 | });
98 |
99 | /**
100 | * 4. Push Notifications
101 | * ----------------------
102 | * Service Workers can receive push messages and show local notifications to the user.
103 | */
104 | self.addEventListener("push", (event) => {
105 | const title = "New Notification";
106 | const options = {
107 | body: "This is a sample push notification!",
108 | };
109 | event.waitUntil(self.registration.showNotification(title, options));
110 | });
111 |
112 | /**
113 | * 5. Cache Versioning
114 | * -------------------
115 | * Multiple versions of cached assets can be managed by maintaining a cache version identifier.
116 | */
117 | const cacheName = "my-cache-v1";
118 | self.addEventListener("activate", (event) => {
119 | event.waitUntil(
120 | caches.keys().then((keyList) => {
121 | return Promise.all(
122 | keyList.map((key) => {
123 | if (key !== cacheName) {
124 | return caches.delete(key);
125 | }
126 | })
127 | );
128 | })
129 | );
130 | });
131 |
--------------------------------------------------------------------------------
/chapters/55_custom_events.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Custom Events
4 | * ========================================================
5 | * Custom Events enable you to define your own events in JavaScript.
6 | * This is very powerful for building modular, decoupled code.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Creating a Custom Event
12 | * ========================================================
13 | * Create a Custom Event using the 'CustomEvent' constructor.
14 | * The 'detail' field allows you to pass custom data for the event.
15 | */
16 | const myEvent = new CustomEvent("myEvent", {
17 | detail: { message: "This is a custom event" },
18 | bubbles: true,
19 | cancelable: true,
20 | });
21 |
22 | /**
23 | * ========================================================
24 | * 2. Dispatching a Custom Event
25 | * ========================================================
26 | * Dispatch the custom event using the 'dispatchEvent' method on an HTML element.
27 | * This will trigger any listeners for the custom event.
28 | */
29 | const someElement = document.getElementById("someElement");
30 | someElement.dispatchEvent(myEvent);
31 |
32 | /**
33 | * ========================================================
34 | * 3. Listening for a Custom Event
35 | * ========================================================
36 | * Use 'addEventListener' to listen to the custom event.
37 | * Event handling is similar to handling native DOM events.
38 | */
39 | someElement.addEventListener("myEvent", (event) => {
40 | console.log("Custom event fired:", event.detail.message);
41 | });
42 |
43 | /**
44 | * ========================================================
45 | * 4. Removing an Event Listener
46 | * ========================================================
47 | * To stop listening to an event, use 'removeEventListener'.
48 | */
49 | function someFunction(event) {
50 | console.log("Handled by someFunction:", event.detail.message);
51 | }
52 | someElement.removeEventListener("myEvent", someFunction);
53 |
54 | /**
55 | * ========================================================
56 | * Nuances and Advanced Techniques
57 | * ========================================================
58 | */
59 |
60 | /**
61 | * 1. Using Custom Events with Components
62 | * --------------------------------------
63 | * Custom Events are highly useful in component-based architectures like Web Components.
64 | */
65 | class MyComponent extends HTMLElement {
66 | connectedCallback() {
67 | this.dispatchEvent(new CustomEvent("componentReady", { bubbles: true }));
68 | }
69 | }
70 | customElements.define("my-component", MyComponent);
71 |
72 | /**
73 | * 2. Custom Events with Additional Data
74 | * -------------------------------------
75 | * You can attach extra information to a custom event via the 'detail' attribute.
76 | */
77 | const userDataEvent = new CustomEvent("userData", {
78 | detail: { username: "IshtmeetDoe", age: 30 },
79 | });
80 |
81 | /**
82 | * 3. Event Propagation and Bubbling
83 | * ----------------------------------
84 | * You can control the phase in which the event is captured.
85 | * Use 'true' as the third argument to 'addEventListener' to capture the event during the capture phase.
86 | */
87 | someElement.addEventListener(
88 | "myEvent",
89 | function (event) {
90 | console.log("Parent received:", event.detail.message);
91 | },
92 | true
93 | );
94 |
95 | /**
96 | * 4. Cancellable Custom Events
97 | * ------------------------------
98 | * A custom event can be made cancellable, meaning it can be stopped by an event listener.
99 | */
100 | const cancellableEvent = new CustomEvent("cancellableEvent", { cancelable: true });
101 | if (!someElement.dispatchEvent(cancellableEvent)) {
102 | console.log("Event was cancelled");
103 | }
104 |
105 | /**
106 | * 5. Polyfill for CustomEvent
107 | * ----------------------------
108 | * For compatibility with older browsers like Internet Explorer, you can use a polyfill.
109 | */
110 | (function () {
111 | if (typeof window.CustomEvent === "function") return false;
112 | function CustomEvent(event, params) {
113 | params = params || { bubbles: false, cancelable: false, detail: null };
114 | const evt = document.createEvent("CustomEvent");
115 | evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
116 | return evt;
117 | }
118 | window.CustomEvent = CustomEvent;
119 | })();
120 |
121 | /**
122 | * 6. Dynamic Event Names
123 | * ------------------------
124 | * Dynamic event names can be generated and used, providing an extra layer of flexibility.
125 | */
126 | const someDynamicValue = "Click";
127 | const eventName = "custom:" + someDynamicValue;
128 | someElement.addEventListener(eventName, someFunction);
129 |
--------------------------------------------------------------------------------
/chapters/57_dynamic_imports.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Dynamic Import in JavaScript
4 | * ========================================================
5 | * Dynamic import() syntax in JavaScript enables you to import modules on-the-fly, providing the
6 | * ability to load modules conditionally or on demand.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Basic Usage
12 | * ========================================================
13 | * Using 'await import()' allows you to import a module dynamically.
14 | * This returns a promise that resolves to the imported module.
15 | */
16 | async function basicUsage() {
17 | const module = await import("./myModule.js");
18 | module.someFunction(); // Call the function from the dynamically imported module
19 | }
20 |
21 | /**
22 | * ========================================================
23 | * 2. Using with Promises
24 | * ========================================================
25 | * Alternatively, you can use the Promise-based '.then()' syntax to handle dynamic imports.
26 | */
27 | function usingPromise() {
28 | import("./myModule.js")
29 | .then((module) => {
30 | module.someFunction(); // Call the function from the dynamically imported module
31 | })
32 | .catch((err) => {
33 | console.error("Failed to load the module:", err);
34 | });
35 | }
36 |
37 | /**
38 | * ========================================================
39 | * 3. Dynamic Path
40 | * ========================================================
41 | * The path from which to dynamically import can be constructed at runtime.
42 | */
43 | const moduleName = "module1";
44 | import(`./${moduleName}.js`).then((module) => {
45 | module.someFunction(); // Invoke function from dynamically determined module
46 | });
47 |
48 | /**
49 | * ========================================================
50 | * 4. Importing Multiple Modules
51 | * ========================================================
52 | * 'Promise.all()' can be used to import multiple modules simultaneously.
53 | */
54 | Promise.all([import("./module1.js"), import("./module2.js")]).then(([module1, module2]) => {
55 | module1.someFunction();
56 | module2.anotherFunction();
57 | });
58 |
59 | /**
60 | * ========================================================
61 | * Nuances and Advanced Techniques
62 | * ========================================================
63 | */
64 |
65 | /**
66 | * 1. Code Splitting
67 | * -----------------
68 | * Dynamic imports enable code splitting, particularly useful when used with bundlers like Webpack.
69 | */
70 | if (someCondition) {
71 | import("./heavyModule.js").then((module) => {
72 | module.heavyComputation(); // Load and use only when 'someCondition' is true
73 | });
74 | }
75 |
76 | /**
77 | * 2. Fallback Logic
78 | * -----------------
79 | * If importing a module fails, you can specify fallback behavior.
80 | */
81 | import("./optionalModule.js")
82 | .catch(() => import("./fallbackModule.js"))
83 | .then((module) => {
84 | module.someFunction(); // Use the fallback module if the original fails to import
85 | });
86 |
87 | /**
88 | * 3. Importing JSON and Other Non-JS Files
89 | * ----------------------------------------
90 | * With proper bundler configuration, you can dynamically import file types other than JavaScript.
91 | */
92 | import("./config.json").then((config) => {
93 | console.log("Config loaded:", config.default);
94 | });
95 |
96 | /**
97 | * 4. Prefetching Modules
98 | * ----------------------
99 | * Prefetching can be done by importing a module and not using it immediately.
100 | */
101 | import("./prefetchMe.js").catch(() => {
102 | // Prefetch silently and handle any load errors
103 | });
104 |
105 | /**
106 | * 5. Use With React Lazy
107 | * ----------------------
108 | * In React applications, dynamic import can be combined with React.lazy for component-level code splitting.
109 | */
110 | // const LazyComponent = React.lazy(() => import('./LazyComponent'));
111 |
112 | /**
113 | * 6. Caching
114 | * ----------
115 | * Once a module is dynamically imported, it gets cached. Further imports won't trigger additional network requests.
116 | */
117 | import("./myModule.js"); // Network request made
118 | import("./myModule.js"); // Cached, no additional network request
119 |
--------------------------------------------------------------------------------
/chapters/58_decorators.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * Decorators in JavaScript (Stage 3 Proposal - Cannot be used yet)
4 | * ========================================================
5 | * A decorator is a function that allows you to add new functionality to an object or an object method.
6 | * Although it's still a Stage 3 proposal, decorators are widely used in TypeScript and transpiler environments.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * Introduction
12 | * ========================================================
13 | * Decorators can affect:
14 | * - Classes
15 | * - Class methods
16 | * - Accessors (getters/setters)
17 | * - Class properties
18 | *
19 | * They can:
20 | * 1. Replace the value being decorated.
21 | * 2. Access the value being decorated.
22 | * 3. Initialize the value being decorated.
23 | */
24 | @defineElement("my-class")
25 | class C extends HTMLElement {
26 | @reactive accessor clicked = false;
27 | }
28 |
29 | /**
30 | * ========================================================
31 | * 2. Calling Decorators
32 | * ========================================================
33 | * Decorators receive two arguments:
34 | * 1. The value being decorated.
35 | * 2. A context object with metadata.
36 | */
37 |
38 | /**
39 | *
40 | * ========================================================
41 | * TypeScript example
42 | * ========================================================
43 | *
44 | * type Decorator = (value: Input, context: {
45 | * kind: string;
46 | * name: string | symbol;
47 | * access: {
48 | * get?(): unknown;
49 | * set?(value: unknown): void;
50 | * };
51 | * private?: boolean;
52 | * static?: boolean;
53 | * addInitializer?(initializer: () => void): void;
54 | * }) => Output | void;
55 | */
56 |
57 | /**
58 | * This function is an example of what a decorator might look like in pure JavaScript.
59 | * It does not use TypeScript 'type' keywords, but the concept remains the same.
60 | *
61 | * @param {any} value - The value being decorated.
62 | * @param {Object} context - Additional context about the decoration.
63 | */
64 | function myDecorator(value, context) {
65 | // The context object can have properties like 'kind', 'name', etc.
66 | if (context.kind === "method") {
67 | // do something
68 | }
69 | }
70 |
71 | /**
72 | * ========================================================
73 | * 3. Applying Decorators
74 | * ========================================================
75 | * Decorators are applied in a well-defined sequence:
76 | * 1. All method and non-static field decorators are applied.
77 | * 2. The class decorator is applied.
78 | * 3. Finally, static field decorators are applied.
79 | */
80 |
81 | /**
82 | * ========================================================
83 | * Basic Decorator Example: Logging
84 | * ========================================================
85 | * The following is a simple logging decorator applied to a class method.
86 | */
87 | function logged(value, { kind, name }) {
88 | if (kind === "method") {
89 | return function (...args) {
90 | console.log(`Starting ${name} with arguments ${args.join(", ")}`);
91 | const ret = value.call(this, ...args);
92 | console.log(`Ending ${name}`);
93 | return ret;
94 | };
95 | }
96 | }
97 |
98 | class C {
99 | @logged
100 | m(arg) {}
101 | }
102 |
103 | new C().m(1); // Output: Starting m with arguments 1
104 | // Ending m
105 |
106 | /**
107 | * ========================================================
108 | * Advanced Usage: Method Chaining
109 | * ========================================================
110 | * Decorators can also be used to enable method chaining by always returning 'this'.
111 | */
112 | function chainable(target, name, descriptor) {
113 | const originalMethod = descriptor.value;
114 | descriptor.value = function (...args) {
115 | originalMethod.apply(this, args);
116 | return this;
117 | };
118 | return descriptor;
119 | }
120 |
121 | class MyClass {
122 | @chainable
123 | method1() {
124 | console.log("method1");
125 | }
126 |
127 | @chainable
128 | method2() {
129 | console.log("method2");
130 | }
131 | }
132 |
133 | const myInstance = new MyClass();
134 | myInstance.method1().method2();
135 | // Output: method1
136 | // method2
137 |
--------------------------------------------------------------------------------
/chapters/62_navigator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * JavaScript Navigator Object
4 | * ========================================================
5 | * The Navigator object provides information about the user's browser and system.
6 | * It is essential for browser feature detection, analytics, and browser-specific functionalities.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. Browser Information
12 | * ========================================================
13 | * The Navigator object has several properties to help identify specific browser capabilities and information.
14 | */
15 | const appName = navigator.appName; // Get the browser's application name
16 | const appVersion = navigator.appVersion; // Get the version information of the browser
17 | const userAgent = navigator.userAgent; // Retrieve the user agent string sent by the browser to the server
18 | const platform = navigator.platform; // Get the platform on which the browser is running
19 |
20 | /**
21 | * ========================================================
22 | * 2. Feature Detection
23 | * ========================================================
24 | * It's generally better to detect specific features rather than relying on browser identification.
25 | */
26 | if ("geolocation" in navigator) {
27 | // The browser supports the Geolocation API
28 | }
29 |
30 | /**
31 | * ========================================================
32 | * 3. Online/Offline Status
33 | * ========================================================
34 | * The 'onLine' property indicates whether the browser is currently online or offline.
35 | */
36 | const isOnline = navigator.onLine; // Returns true if online, false if offline
37 |
38 | /**
39 | * ========================================================
40 | * 4. Language Settings
41 | * ========================================================
42 | * You can access the browser's language settings to localize your application accordingly.
43 | */
44 | const language = navigator.language || navigator.userLanguage; // Get the preferred language
45 |
46 | /**
47 | * ========================================================
48 | * Nuances and Advanced Techniques
49 | * ========================================================
50 | */
51 |
52 | /**
53 | * 1. Vendor Prefixes
54 | * ------------------
55 | * Be cautious of vendor-specific prefixes when dealing with certain CSS properties or JavaScript APIs.
56 | */
57 | const isWebkit = "webkitAppearance" in document.documentElement.style; // Check if the browser uses WebKit
58 |
59 | /**
60 | * 2. Do Not Rely Solely on User Agent
61 | * -----------------------------------
62 | * User agent strings can be easily spoofed, making them unreliable for feature detection.
63 | */
64 |
65 | /**
66 | * 3. Register Protocol Handlers
67 | * -----------------------------
68 | * The 'registerProtocolHandler' method allows web apps to register as handlers for specific URL schemes.
69 | */
70 | if ("registerProtocolHandler" in navigator) {
71 | navigator.registerProtocolHandler("web+custom", "handler.php?uri=%s", "My App");
72 | }
73 |
74 | /**
75 | * 4. Battery Status
76 | * -----------------
77 | * Some browsers support the Battery Status API, enabling you to retrieve information about the system's battery status.
78 | */
79 | if ("getBattery" in navigator) {
80 | navigator.getBattery().then((battery) => {
81 | console.log(`Battery level: ${battery.level * 100}%`);
82 | });
83 | }
84 |
85 | /**
86 | * 5. Clipboard Access
87 | * -------------------
88 | * The Clipboard API provides asynchronous methods to interact with the clipboard.
89 | */
90 | if (navigator.clipboard) {
91 | navigator.clipboard.writeText("Text to copy").then(() => {
92 | console.log("Text successfully copied to clipboard");
93 | });
94 | }
95 |
96 | /**
97 | * 6. Media Devices
98 | * ----------------
99 | * The 'mediaDevices' property provides methods to access connected media devices like microphones and cameras.
100 | */
101 | if ("mediaDevices" in navigator) {
102 | navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then((stream) => {
103 | // Handle the media stream
104 | });
105 | }
106 |
107 | /**
108 | * 7. Connection Information
109 | * --------------------------
110 | * The 'connection' property gives information about the system's network connection, such as type and speed.
111 | */
112 | if ("connection" in navigator) {
113 | const { type, effectiveType } = navigator.connection;
114 | console.log(`Connection type: ${type}, effective type: ${effectiveType}`);
115 | }
116 |
--------------------------------------------------------------------------------
/chapters/63_user_timing_api.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ========================================================
3 | * User Timing API
4 | * ========================================================
5 | * The User Timing API is an advanced tool that allows you to measure the duration of specific operations
6 | * within your JavaScript code. It's excellent for performance analysis, debugging, and monitoring.
7 | */
8 |
9 | /**
10 | * ========================================================
11 | * 1. mark() - Create a High-Resolution Timestamp
12 | * ========================================================
13 | * The `mark` method enables you to create a named, high-resolution timestamp that can be used for measurements.
14 | */
15 | performance.mark("startOperation"); // Mark the start
16 |
17 | // Some code you want to measure
18 | for (let i = 0; i < 100000; i++) {
19 | // Do something
20 | }
21 |
22 | performance.mark("endOperation"); // Mark the end
23 |
24 | /**
25 | * ========================================================
26 | * 2. measure() - Measure the Time Between Marks
27 | * ========================================================
28 | * The `measure` method calculates the time difference between two named marks.
29 | */
30 | performance.measure("operationDuration", "startOperation", "endOperation");
31 |
32 | /**
33 | * ========================================================
34 | * 3. getEntriesByType() - Retrieve Timing Entries
35 | * ========================================================
36 | * This function returns an array of PerformanceEntry objects based on the type specified.
37 | */
38 | const measurements = performance.getEntriesByType("measure"); // Get all 'measure' type entries
39 |
40 | /**
41 | * ========================================================
42 | * 4. Clearing Marks and Measures
43 | * ========================================================
44 | * To prevent memory leaks or unintended calculations, it's good practice to clear your marks and measures.
45 | */
46 | performance.clearMarks("startOperation"); // Clear specific mark
47 | performance.clearMarks("endOperation"); // Clear specific mark
48 | performance.clearMeasures("operationDuration"); // Clear specific measure
49 |
50 | /**
51 | * ========================================================
52 | * Nuances and Advanced Techniques
53 | * ========================================================
54 | */
55 |
56 | /**
57 | * 1. High-Resolution Time
58 | * -----------------------
59 | * The User Timing API provides microsecond-precision timestamps, offering higher resolution compared to Date.now().
60 | */
61 |
62 | /**
63 | * 2. Performance Observer
64 | * ------------------------
65 | * The Performance Observer API allows you to "listen" for specific performance events.
66 | */
67 | const observer = new PerformanceObserver((list) => {
68 | const entries = list.getEntries();
69 | for (const entry of entries) {
70 | console.log(`Name: ${entry.name}, Duration: ${entry.duration}`);
71 | }
72 | });
73 | observer.observe({ entryTypes: ["measure"] });
74 |
75 | /**
76 | * 3. User Timing in the DevTools
77 | * -------------------------------
78 | * Many browsers offer visualization tools in the DevTools for inspecting marks and measures.
79 | */
80 |
81 | /**
82 | * 4. Server Timing API Integration
83 | * --------------------------------
84 | * The User Timing API can be integrated with the Server Timing API to analyze end-to-end application performance.
85 | */
86 |
87 | /**
88 | * 5. Combining with Navigation Timing API
89 | * ----------------------------------------
90 | * The Navigation Timing API can be used alongside the User Timing API to assess the full lifecycle of a web page.
91 | */
92 |
93 | /**
94 | * 6. Resource Timing API Integration
95 | * -----------------------------------
96 | * Pair User Timing marks with Resource Timing data to gauge the performance of specific resource loads.
97 | */
98 |
99 | /**
100 | * 7. Custom Metrics
101 | * -----------------
102 | * The User Timing API is flexible enough to define custom performance metrics that are specific to your application.
103 | */
104 |
105 | /**
106 | * 8. Multiple Measurements
107 | * -------------------------
108 | * Multiple measurements can be created using the same name. You can differentiate them using other properties like `startTime`.
109 | */
110 | performance.mark("startAnotherOperation");
111 | // Some more code you want to measure
112 | performance.mark("endAnotherOperation");
113 | performance.measure("operationDuration", "startAnotherOperation", "endAnotherOperation");
114 |
115 | /**
116 | * The User Timing API is an indispensable tool for optimizing your web applications, offering precise timing
117 | * measurements and a wealth of nuances and advanced techniques for thorough performance analysis.
118 | */
119 |
--------------------------------------------------------------------------------