├── .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 | --------------------------------------------------------------------------------