From 4a538132238f34ac6a715df91c2082c327100d4f Mon Sep 17 00:00:00 2001 From: Ishtmeet Date: Fri, 22 Sep 2023 07:04:42 +0530 Subject: [PATCH] adds more chapters --- .DS_Store | Bin 0 -> 6148 bytes README.md | 140 ++++++------- chapters/04.1_ternary.js | 82 ++++++++ chapters/04.2_nullish_coalescing.js | 0 chapters/09_functions.js | 2 +- chapters/12.1_error_object.js | 188 ++++++++++++++++++ chapters/12.1_optional_chaining.js | 89 +++++++++ chapters/12_objects.js | 8 +- chapters/16_destructuring.js | 6 +- chapters/18_this.js | 8 +- chapters/19_call_apply_bind.js | 2 +- chapters/20_error_handling.js | 2 +- chapters/23.1_async_await.js | 105 ++++++++++ chapters/27_storage.js | 12 +- chapters/31_modules.js | 2 + chapters/32_template_literals.js | 12 +- chapters/33_date_time.js | 2 +- chapters/34_math.js | 2 +- chapters/37_set_timeout.js | 85 ++++++++ chapters/38_setInterval.js | 85 ++++++++ chapters/39_json_stringify.js | 96 +++++++++ chapters/40_json_parse.js | 57 ++++++ chapters/41_map.js | 106 ++++++++++ chapters/42_weak_map.js | 102 ++++++++++ chapters/43_set.js | 117 +++++++++++ chapters/44_weak_map.js | 91 +++++++++ chapters/45_generators.js | 120 +++++++++++ chapters/46_iterators.js | 129 ++++++++++++ chapters/47_big_int.js | 93 +++++++++ chapters/48.0_web_apis.js | 138 +++++++++++++ chapters/48.1_web_apis_2.js | 103 ++++++++++ chapters/48.2_web_apis_3.js | 79 ++++++++ chapters/49_canvas.js | 126 ++++++++++++ chapters/50_drag_drop.js | 126 ++++++++++++ chapters/51_file_and_blob.js | 130 ++++++++++++ chapters/52_websockets.js | 112 +++++++++++ chapters/53_web_workers.js | 111 +++++++++++ chapters/54_service_workers.js | 130 ++++++++++++ chapters/55_custom_events.js | 128 ++++++++++++ chapters/56_webrtc.js | 143 +++++++++++++ chapters/57_dynamic_imports.js | 118 +++++++++++ chapters/58_decorators.js | 136 +++++++++++++ chapters/59_proxy.js | 166 ++++++++++++++++ chapters/60_reflect.js | 129 ++++++++++++ .../{48_performance.js => 61_performance.js} | 2 +- chapters/{49_navigator.js => 62_navigator.js} | 0 ...er_timing_api.js => 63_user_timing_api.js} | 0 ...tion_timing.js => 64_navigation_timing.js} | 0 chapters/65_lazy_loading.js | 171 ++++++++++++++++ 49 files changed, 3695 insertions(+), 96 deletions(-) create mode 100644 .DS_Store create mode 100644 chapters/04.1_ternary.js create mode 100644 chapters/04.2_nullish_coalescing.js create mode 100644 chapters/12.1_error_object.js create mode 100644 chapters/12.1_optional_chaining.js create mode 100644 chapters/23.1_async_await.js create mode 100644 chapters/37_set_timeout.js create mode 100644 chapters/38_setInterval.js create mode 100644 chapters/39_json_stringify.js create mode 100644 chapters/40_json_parse.js create mode 100644 chapters/41_map.js create mode 100644 chapters/42_weak_map.js create mode 100644 chapters/43_set.js create mode 100644 chapters/44_weak_map.js create mode 100644 chapters/45_generators.js create mode 100644 chapters/46_iterators.js create mode 100644 chapters/47_big_int.js create mode 100644 chapters/48.0_web_apis.js create mode 100644 chapters/48.1_web_apis_2.js create mode 100644 chapters/48.2_web_apis_3.js create mode 100644 chapters/49_canvas.js create mode 100644 chapters/50_drag_drop.js create mode 100644 chapters/51_file_and_blob.js create mode 100644 chapters/52_websockets.js create mode 100644 chapters/53_web_workers.js create mode 100644 chapters/54_service_workers.js create mode 100644 chapters/55_custom_events.js create mode 100644 chapters/56_webrtc.js create mode 100644 chapters/57_dynamic_imports.js create mode 100644 chapters/58_decorators.js create mode 100644 chapters/59_proxy.js create mode 100644 chapters/60_reflect.js rename chapters/{48_performance.js => 61_performance.js} (99%) rename chapters/{49_navigator.js => 62_navigator.js} (100%) rename chapters/{50_user_timing_api.js => 63_user_timing_api.js} (100%) rename chapters/{51_navigation_timing.js => 64_navigation_timing.js} (100%) create mode 100644 chapters/65_lazy_loading.js diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 50 ? "passed" : "failed"} the exam.`); // Output: 'You passed the exam.' + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Type Coercion + * ---------------- + * Be cautious about type coercion when using the ternary operator, as it follows the same rules as other JavaScript operators. + */ +const value = "5"; +const number = value == 5 ? "Loose equality" : "Strict inequality"; +console.log(`Type Coercion: ${number}`); // Output: 'Loose equality' + +/** + * 2. Avoid Side Effects + * ---------------------- + * Avoid using ternary operators for operations that produce side effects, like assignments or function calls with side effects. + */ + +/** + * 3. Readability + * -------------- + * While chaining or nesting ternary operators can be powerful, it can also make code harder to read and maintain. + */ diff --git a/chapters/04.2_nullish_coalescing.js b/chapters/04.2_nullish_coalescing.js new file mode 100644 index 0000000..e69de29 diff --git a/chapters/09_functions.js b/chapters/09_functions.js index 3779935..3126ddb 100644 --- a/chapters/09_functions.js +++ b/chapters/09_functions.js @@ -9,7 +9,7 @@ function functions() { function sayHello(name) { return `Hello, ${name}`; } - console.log(sayHello("Alice")); // Outputs: "Hello, Alice" + console.log(sayHello("Ishtmeet")); // Outputs: "Hello, Ishtmeet" /** * ======================================================== diff --git a/chapters/12.1_error_object.js b/chapters/12.1_error_object.js new file mode 100644 index 0000000..2a7df8c --- /dev/null +++ b/chapters/12.1_error_object.js @@ -0,0 +1,188 @@ +/** + * ======================================================== + * Error Object in JavaScript + * ======================================================== + * The Error object is a built-in object in JavaScript that provides information about errors that occur while + * a script is running. It's a way to handle both custom and system-generated exceptions. + */ + +/** + * ======================================================== + * Creating a Simple Error + * ======================================================== + * You can create an instance of the Error object using the Error constructor. + */ + +const simpleError = new Error("Something went wrong"); + +/** + * The Error object contains a message property that describes the error. The above code creates a new error object + * with the message "Something went wrong". + */ + +/** + * ======================================================== + * Throwing Errors + * ======================================================== + * Throwing errors halts the normal execution of code and directs the flow to the nearest catch block. + */ + +function throwError() { + throw new Error("An error occurred"); +} + +try { + throwError(); +} catch (error) { + console.error(error.message); +} + +/** + * Here, the function 'throwError' throws an error, which is caught by the catch block. The message is then logged + * to the console. + */ + +/** + * ======================================================== + * Handling Errors with Try-Catch + * ======================================================== + * The try-catch statement allows you to handle errors gracefully without breaking your entire code. + */ + +try { + // Potentially error-prone code + const sum = add(5, "ten"); +} catch (error) { + console.error("An error occurred:", error.message); +} + +/** + * The 'add' function is supposed to be a numerical addition, but a string is passed instead. This would typically + * result in an error, but the catch block catches it and prints the error message. + */ + +/** + * ======================================================== + * Custom Errors + * ======================================================== + * You can create custom error types by extending the Error class to handle specific kinds of errors. + */ + +class ValidationError extends Error { + constructor(message) { + super(message); + this.name = "ValidationError"; + } +} + +// Usage +try { + throw new ValidationError("Invalid input"); +} catch (error) { + if (error instanceof ValidationError) { + console.error(`Custom error caught: ${error.message}`); + } else { + console.error(`Unknown error: ${error.message}`); + } +} + +/** + * The custom 'ValidationError' class extends the native Error class. It can be thrown and caught just like any + * other error but allows for more specific error handling. + */ + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Stack Trace + * --------------- + * The stack trace is a property of the Error object that provides information about the function calls leading up to + * the error. + */ + +try { + throw new Error("An error occurred"); +} catch (error) { + console.error(error.stack); +} + +/** + * Output: This will display the error message along with the function calls that led to this error. + * It can be immensely helpful for debugging. + */ + +/** + * 2. Catching Multiple Error Types + * --------------------------------- + * You can catch and handle multiple types of errors using instanceof in a catch block. + */ + +try { + // Potentially error-prone code +} catch (error) { + if (error instanceof TypeError) { + console.error("Type Error:", error.message); + } else if (error instanceof ReferenceError) { + console.error("Reference Error:", error.message); + } +} + +/** + * Output: Depending on the type of error thrown, a specific error message will be logged. + */ + +/** + * 3. Error Handling with Promises + * ------------------------------- + * Errors can be caught in a Promise chain using the 'catch' method. + */ + +fetch("invalid_url") + .then((response) => response.json()) + .catch((error) => console.error("Fetch Error:", error.message)); + +/** + * Output: 'Fetch Error:' followed by the error message will be displayed, indicating that the fetch operation failed. + */ + +/** + * 4. Finally Block + * ----------------- + * The 'finally' block will be executed regardless of whether an error was thrown or not. + */ + +try { + // Code that may throw an error +} catch (error) { + console.error(error.message); +} finally { + console.log("Cleanup code"); +} + +/** + * Output: If an error occurs, the error message will be logged. The 'Cleanup code' message will always be logged + * afterwards. + */ + +/** + * 5. Async/Await Error Handling + * ----------------------------- + * With async/await, you can use try/catch just like you would with synchronous code. + */ + +async function fetchData() { + try { + const response = await fetch("some_url"); + const data = await response.json(); + } catch (error) { + console.error("Fetch Error:", error.message); + } +} + +/** + * Output: If an error occurs during the fetch operation, 'Fetch Error:' followed by the error message will be logged. + */ diff --git a/chapters/12.1_optional_chaining.js b/chapters/12.1_optional_chaining.js new file mode 100644 index 0000000..c25fbb2 --- /dev/null +++ b/chapters/12.1_optional_chaining.js @@ -0,0 +1,89 @@ +/** + * ======================================================== + * Optional Chaining (?.) + * ======================================================== + * The optional chaining operator (?.) permits reading the value of a property located deep within a chain + * of connected objects without having to expressly validate that each reference in the chain is valid. + * It short-circuits if it encounters a null or undefined, returning undefined as the evaluation result. + */ + +/** + * ======================================================== + * Basic Usage + * ======================================================== + * Optional chaining is used to safely access deeply nested properties of an object. + */ +const user = { + profile: { + name: "John", + age: 30, + }, +}; + +const userName = user?.profile?.name; +console.log(`User Name: ${userName}`); // Output: 'User Name: John' + +/** + * ======================================================== + * Accessing Array Items + * ======================================================== + * You can use optional chaining when attempting to access an index of an array that might be undefined. + */ +const arr = [1, 2, 3]; +const value = arr?.[4]; +console.log(`Array Value: ${value}`); // Output: 'Array Value: undefined' + +/** + * ======================================================== + * Function or Method Calls + * ======================================================== + * You can also use optional chaining when calling a function or method that might not exist. + */ +const greet = user?.profile?.greet?.(); +console.log(`Greeting: ${greet}`); // Output: 'Greeting: undefined' + +/** + * ======================================================== + * Combined with Nullish Coalescing + * ======================================================== + * Optional chaining can be combined with the nullish coalescing operator for setting defaults. + */ +const city = user?.profile?.address?.city ?? "Unknown"; +console.log(`City: ${city}`); // Output: 'City: Unknown' + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ +/** + * 1. Short-Circuiting + * -------------------- + * If optional chaining encounters null or undefined, it short-circuits the rest of the evaluation. + */ +const obj = { prop: null }; +const shortCircuit = obj?.prop?.nonExistentMethod?.(); +console.log(`Short-Circuit Result: ${shortCircuit}`); // Output: 'Short-Circuit Result: undefined' + +/** + * 2. Operator Precedence + * ---------------------- + * To avoid any ambiguity when combining optional chaining with other operators, use parentheses. + */ +const mixed = { value: 5 }; +const ambiguous = mixed?.value + 1; // This will result in 6, as you would expect +const clarified = mixed?.value + 1; // Use parentheses to make the operation order explicit +console.log(`Ambiguous: ${ambiguous}`); // Output: 'Ambiguous: 6' +console.log(`Clarified: ${clarified}`); // Output: 'Clarified: 6' + +/** + * 3. Functionality Limitation + * --------------------------- + * The operator only checks for null or undefined, but not for other falsy values like 0 or ''. + */ +const zeroCheck = { zero: 0 }; +const zeroValue = zeroCheck?.zero ?? "No value"; +console.log(`Zero Value: ${zeroValue}`); // Output: 'Zero Value: 0' + +// const user: User = { id: 1, name: { first: 'John' } }; +// const lastName = user?.name?.last; // No TypeScript error here diff --git a/chapters/12_objects.js b/chapters/12_objects.js index a6787c5..911b6c7 100644 --- a/chapters/12_objects.js +++ b/chapters/12_objects.js @@ -7,13 +7,13 @@ function objects() { * Objects can have properties and methods (functions as properties). */ const person = { - name: "Alice", + name: "Ishtmeet", age: 30, greet() { return `Hello, ${this.name}`; }, }; - console.log(person.greet()); // Outputs: "Hello, Alice" + console.log(person.greet()); // Outputs: "Hello, Ishtmeet" /** * ======================================================== @@ -32,7 +32,7 @@ function objects() { * New properties can be added and existing properties can be updated. * This showcases the dynamic nature of JavaScript objects. */ - person.email = "alice@example.com"; + person.email = "ishtmeet@example.com"; person["age"] = 31; console.log(person); // Outputs updated object @@ -77,7 +77,7 @@ function objects() { * This feature was introduced in ES6 and is often used for better readability. */ const { name, age } = person; - console.log(name, age); // Outputs: "Alice 31" + console.log(name, age); // Outputs: "Ishtmeet 31" /** * ======================================================== diff --git a/chapters/16_destructuring.js b/chapters/16_destructuring.js index 2d7f0f8..afa0f28 100644 --- a/chapters/16_destructuring.js +++ b/chapters/16_destructuring.js @@ -19,8 +19,8 @@ function destructuring() { * ======================================================== * You can provide default values for both array and object destructuring. */ - const { name = "John" } = {}; - console.log(name); // Outputs 'John' + const { name = "Ishtmeet" } = {}; + console.log(name); // Outputs 'Ishtmeet' /** * ======================================================== @@ -57,7 +57,7 @@ function destructuring() { function greet({ name, age }) { console.log(`Hello, ${name}. You are ${age} years old.`); } - greet({ name: "Alice", age: 25 }); // Outputs "Hello, Alice. You are 25 years old." + greet({ name: "Ishtmeet", age: 25 }); // Outputs "Hello, Ishtmeet. You are 25 years old." /** * ======================================================== diff --git a/chapters/18_this.js b/chapters/18_this.js index 0c65d31..78a2f23 100644 --- a/chapters/18_this.js +++ b/chapters/18_this.js @@ -30,9 +30,9 @@ function thisKeyword() { * In a method, which is a function stored as an object property, `this` refers to the object. */ const obj = { - name: "Alice", + name: "Ishtmeet", greet: function () { - console.log(`Hello, my name is ${this.name}`); // Outputs "Hello, my name is Alice" + console.log(`Hello, my name is ${this.name}`); // Outputs "Hello, my name is Ishtmeet" }, }; obj.greet(); @@ -69,8 +69,8 @@ function thisKeyword() { function showName(label) { console.log(`${label}: ${this.name}`); } - const alice = { name: "Alice" }; - showName.call(alice, "User"); // Outputs "User: Alice" + const ishtmeet = { name: "Ishtmeet" }; + showName.call(ishtmeet, "User"); // Outputs "User: Ishtmeet" /** * ======================================================== diff --git a/chapters/19_call_apply_bind.js b/chapters/19_call_apply_bind.js index 05318f4..434261f 100644 --- a/chapters/19_call_apply_bind.js +++ b/chapters/19_call_apply_bind.js @@ -13,7 +13,7 @@ function callApplyBind() { console.log(`${this.title} ${firstname} ${lastname}`); } const person1 = { title: "Mr." }; - showFullName.call(person1, "John", "Doe"); // Outputs: "Mr. John Doe" + showFullName.call(person1, "Ishtmeet", "Doe"); // Outputs: "Mr. Ishtmeet Doe" /** * ======================================================== diff --git a/chapters/20_error_handling.js b/chapters/20_error_handling.js index b21a0a6..ee158b8 100644 --- a/chapters/20_error_handling.js +++ b/chapters/20_error_handling.js @@ -11,7 +11,7 @@ function errorHandling() { */ try { // Code that could possibly throw an error goes here - const x = JSON.parse('{"name": "John"}'); + const x = JSON.parse('{"name": "Ishtmeet"}'); } catch (e) { // Code to execute if an exception occurs in the try block console.error("An error occurred:", e); diff --git a/chapters/23.1_async_await.js b/chapters/23.1_async_await.js new file mode 100644 index 0000000..6d27335 --- /dev/null +++ b/chapters/23.1_async_await.js @@ -0,0 +1,105 @@ +/** + * ======================================================== + * Async/Await + * ======================================================== + * 'async/await' is a modern way to write asynchronous code in JavaScript. + * It provides a more readable and maintainable syntax over callbacks and Promises. + */ + +/** + * ======================================================== + * 1. Basic Syntax + * ======================================================== + * Declaring a function as 'async' makes it return a Promise implicitly. + * You can use 'await' within an 'async' function to pause the execution until a Promise is resolved or rejected. + */ +async function fetchData() { + const response = await fetch("https://api.example.com/data"); + const data = await response.json(); + return data; +} +// Invoke the function to see it in action +fetchData().then((data) => console.log(data)); + +/** + * ======================================================== + * 2. Handling Errors + * ======================================================== + * It's crucial to handle errors when dealing with asynchronous operations. + * 'try/catch' blocks are commonly used within 'async' functions for this purpose. + */ +async function fetchDataWithErrorHandling() { + try { + const response = await fetch("https://api.example.com/data"); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + const data = await response.json(); + return data; + } catch (error) { + console.error("Fetch Error:", error); + } +} +// Invoke the function to see error handling in action +fetchDataWithErrorHandling().catch((error) => console.error(error)); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * Error Propagation + * ----------------- + * Throwing an exception within an 'async' function causes it to return a Promise that is rejected. + */ +async function failAsync() { + throw new Error("Failed"); + // This is equivalent to: return Promise.reject(new Error('Failed')); +} + +/** + * Concurrency with Promise.all() + * ------------------------------ + * 'async/await' can work in tandem with Promise.all() to execute multiple asynchronous operations concurrently. + */ +async function fetchAllData() { + const [data1, data2] = await Promise.all([ + fetch("https://api.example.com/data1").then((res) => res.json()), + fetch("https://api.example.com/data2").then((res) => res.json()), + ]); + return { data1, data2 }; +} + +/** + * Using 'for-await-of' with Async Iterables + * ----------------------------------------- + * The 'for-await-of' loop enables you to traverse through async iterables as though they were synchronous. + */ +async function handleAsyncIterable(asyncIterable) { + for await (const item of asyncIterable) { + console.log(item); + } +} + +/** + * Non-Promise Asynchronous Operations + * ---------------------------------- + * While 'await' is designed to work with Promises, it can also be used with non-Promise values. + * When you 'await' a non-Promise value, it's returned instantly. + */ +async function notReallyAsync() { + const value = await 42; + return value; // 42, instantly +} + +/** + * Running Async Functions Immediately + * ----------------------------------- + * You can immediately invoke an async function using an IIFE (Immediately Invoked Function Expression). + */ +(async function () { + const data = await fetchData(); + console.log(data); +})(); diff --git a/chapters/27_storage.js b/chapters/27_storage.js index eb39431..179e7ef 100644 --- a/chapters/27_storage.js +++ b/chapters/27_storage.js @@ -12,9 +12,9 @@ * ======================================================== * localStorage allows you to store data with no expiration time. This data will persist even after closing the browser. */ -localStorage.setItem("username", "JohnDoe"); +localStorage.setItem("username", "IshtmeetDoe"); const username = localStorage.getItem("username"); -console.log(`Username: ${username}`); // Output: Username: JohnDoe +console.log(`Username: ${username}`); // Output: Username: IshtmeetDoe localStorage.removeItem("username"); /** @@ -34,8 +34,8 @@ sessionStorage.removeItem("sessionId"); * ======================================================== * Both localStorage and sessionStorage provide a way to iterate over stored items. */ -localStorage.setItem("username", "JohnDoe"); -localStorage.setItem("email", "john.doe@example.com"); +localStorage.setItem("username", "IshtmeetDoe"); +localStorage.setItem("email", "ishtmeet.doe@example.com"); for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); const value = localStorage.getItem(key); @@ -63,10 +63,10 @@ window.addEventListener("storage", function (event) { * ----------------------- * Both localStorage and sessionStorage can only directly store strings. To store objects or arrays, serialize them to JSON. */ -const user = { name: "JohnDoe", age: 30 }; +const user = { name: "IshtmeetDoe", age: 30 }; localStorage.setItem("user", JSON.stringify(user)); const retrievedUser = JSON.parse(localStorage.getItem("user")); -console.log(retrievedUser); // Output: { name: 'JohnDoe', age: 30 } +console.log(retrievedUser); // Output: { name: 'IshtmeetDoe', age: 30 } /** * 2. Storage Limit diff --git a/chapters/31_modules.js b/chapters/31_modules.js index cc288a8..accc800 100644 --- a/chapters/31_modules.js +++ b/chapters/31_modules.js @@ -67,6 +67,8 @@ import myDefaultFunction from "./myModule.js"; * ------------------ * Dynamic imports enable you to load modules on demand using `import()`. * This returns a promise that resolves into the imported module. + * + * We have a dedicated chapter on dynamic imports. */ const modulePath = "./myModule.js"; import(modulePath) diff --git a/chapters/32_template_literals.js b/chapters/32_template_literals.js index 4f7c62d..651ce31 100644 --- a/chapters/32_template_literals.js +++ b/chapters/32_template_literals.js @@ -1,6 +1,6 @@ /** * ======================================================== - * Template Literals in JavaScript + * Template Literals * ======================================================== */ @@ -16,8 +16,8 @@ const str = `Hello, world!`; // String with variables -const name = "John"; -const greeting = `Hello, ${name}!`; // Output: "Hello, John!" +const name = "Ishtmeet"; +const greeting = `Hello, ${name}!`; // Output: "Hello, Ishtmeet!" // String with expressions const x = 10, @@ -46,7 +46,7 @@ const multiLineStr = ` function myTag(strings, ...values) { console.log(strings); // Outputs: Array of string literals like ["Hello ", ", the sum is ", "."] - console.log(values); // Outputs: Array of evaluated expressions like ["John", 30] + console.log(values); // Outputs: Array of evaluated expressions like ["Ishtmeet", 30] return "Modified String"; } @@ -62,14 +62,14 @@ const tagged = myTag`Hello ${name}, the sum is ${x + y}.`; // Calls myTag functi * 1. Nesting Template Literals */ // Template literals can be nested within another template literal. -const nested = `outer ${`inner ${name}`}`; // Output: "outer inner John" +const nested = `outer ${`inner ${name}`}`; // Output: "outer inner Ishtmeet" /** * 2. Raw Strings * * The String.raw tag function allows you to treat backslashes as literal characters. */ -const rawStr = String.raw`This is a raw string \n ${name}.`; // Output: "This is a raw string \n John" +const rawStr = String.raw`This is a raw string \n ${name}.`; // Output: "This is a raw string \n Ishtmeet" /** * 3. Custom Interpolators diff --git a/chapters/33_date_time.js b/chapters/33_date_time.js index ce314cf..b92b1f5 100644 --- a/chapters/33_date_time.js +++ b/chapters/33_date_time.js @@ -81,7 +81,7 @@ const utcHours = now.getUTCHours(); * * NOTE: Some browsers may return the below as a valid date, setting date to the 00:00:00 Jan 1, 2014 */ -const invalidDate = new Date("john-doe 2014").toString(); +const invalidDate = new Date("ishtmeet-doe 2014").toString(); /** * 4. Leap Year Considerations diff --git a/chapters/34_math.js b/chapters/34_math.js index 70f9b77..815e806 100644 --- a/chapters/34_math.js +++ b/chapters/34_math.js @@ -1,6 +1,6 @@ /* * ======================================================== - * Math Object in JavaScript + * Math Object * ======================================================== * The Math object provides static properties and methods for mathematical constants and functions. * It doesn't need to be instantiated. diff --git a/chapters/37_set_timeout.js b/chapters/37_set_timeout.js new file mode 100644 index 0000000..e8b1e8d --- /dev/null +++ b/chapters/37_set_timeout.js @@ -0,0 +1,85 @@ +function _setTimeout() { + /** + * ======================================================== + * Basic Syntax and Usage of setTimeout + * ======================================================== + * The setTimeout method allows you to schedule code execution after + * a specified delay. The delay is not guaranteed but approximate, due to + * the single-threaded nature of JavaScript. + * + * Syntax: setTimeout(callback, delayInMilliseconds, ...additionalArguments) + * - callback: The function that will be executed after the delay. + * - delayInMilliseconds: The delay in milliseconds. + * - ...additionalArguments: Optional arguments that are passed to the callback. + */ + setTimeout(() => { + console.log("Basic Usage: This message will appear after approximately 1 second."); + }, 1000); + + /** + * ======================================================== + * Canceling a Scheduled Timeout + * ======================================================== + * The setTimeout function returns a TimeoutID, which can be used to cancel + * the timeout using clearTimeout method. + */ + const timeoutID = setTimeout(() => { + console.log("This message will never appear."); + }, 2000); + clearTimeout(timeoutID); + + /** + * ======================================================== + * Passing Parameters to the Callback + * ======================================================== + * setTimeout allows passing additional arguments to the callback function. + * These arguments follow the delay parameter in the function signature. + */ + setTimeout( + (param1, param2) => { + console.log(`Passed Parameters: ${param1}, ${param2}`); + }, + 1500, + "Parameter1", + "Parameter2" + ); + + /** + * ======================================================== + * Handling 'this' Context within setTimeout + * ======================================================== + * If you're using an arrow function as the callback, the value of 'this' + * will be inherited from the enclosing scope. + * + * For regular functions, the 'this' value will be either the window object + * (in a browser) or undefined (in strict mode). + */ + const exampleObject = { + name: "Ishtmeet", + greet: function () { + setTimeout(() => { + console.log(`Hello, ${this.name}`); + }, 500); + }, + }; + exampleObject.greet(); // Output: "Hello, Ishtmeet" + + /** + * ======================================================== + * Nuances and Edge Cases + * ======================================================== + * 1. Zero Delay: Even if you set zero milliseconds as the delay, the actual + * execution may take longer because the callback enters the event queue. + * 2. Maximum Delay: Delay can't exceed 2147483647 milliseconds (~24.8 days). + * Any value longer will be truncated. + */ + setTimeout(() => { + console.log("Zero delay doesn't mean immediate execution."); + }, 0); + + setTimeout(() => { + console.log("This message will display after 1 millisecond, not 25 days."); + }, 2147483648); +} + +_setTimeout(); diff --git a/chapters/38_setInterval.js b/chapters/38_setInterval.js new file mode 100644 index 0000000..77d5c56 --- /dev/null +++ b/chapters/38_setInterval.js @@ -0,0 +1,85 @@ +// Function to explore the intricacies and features of setInterval +function _setInterval() { + /** + * ======================================================== + * Basic Syntax and Usage of setInterval + * ======================================================== + * The setInterval method schedules repeated execution of code + * at specific intervals. Just like setTimeout, the actual intervals are approximate. + * + * Syntax: setInterval(callback, intervalInMilliseconds, ...additionalArguments) + * - callback: The function that will be executed at each interval. + * - intervalInMilliseconds: The interval time in milliseconds. + * - ...additionalArguments: Optional arguments that are passed to the callback. + */ + const basicIntervalID = setInterval(() => { + console.log("Basic Usage: This message will repeat every 2 seconds."); + }, 2000); + + // To stop the above basic example after 6 seconds + setTimeout(() => { + clearInterval(basicIntervalID); + }, 6000); + + /** + * ======================================================== + * Canceling a Scheduled Interval + * ======================================================== + * The setInterval function returns an IntervalID, which can be used to cancel + * the interval using the clearInterval method. + */ + const cancelableIntervalID = setInterval(() => { + console.log("This message will display only once."); + }, 3000); + clearInterval(cancelableIntervalID); + + /** + * ======================================================== + * Passing Parameters to the Callback + * ======================================================== + * setInterval allows passing additional arguments to the callback function. + * These arguments follow the interval parameter in the function signature. + */ + setInterval( + (param1, param2) => { + console.log(`Passed Parameters: ${param1}, ${param2}`); + }, + 4000, + "Parameter1", + "Parameter2" + ); + + /** + * ======================================================== + * Handling 'this' Context within setInterval + * ======================================================== + * The rules for the 'this' context within the callback function are similar + * to those in setTimeout. Arrow functions inherit 'this' from the surrounding + * code, while regular functions don't. + */ + const exampleObject = { + name: "Bob", + greet: function () { + setInterval(() => { + console.log(`Hello, ${this.name}`); + }, 5000); + }, + }; + exampleObject.greet(); // Output: "Hello, Bob" + + /** + * ======================================================== + * Nuances and Edge Cases + * ======================================================== + * 1. Zero Interval: Even with zero milliseconds as the interval, the actual + * execution may not be immediate due to JavaScript's single-threaded nature. + * 2. Maximum Interval: The maximum length is 2147483647 milliseconds (~24.8 days), + * similar to setTimeout. + */ + setInterval(() => { + console.log("Zero interval doesn't mean immediate repetition."); + }, 0); +} + +// Run the exploreSetInterval function to observe the behavior of setInterval +_setInterval(); diff --git a/chapters/39_json_stringify.js b/chapters/39_json_stringify.js new file mode 100644 index 0000000..a37518d --- /dev/null +++ b/chapters/39_json_stringify.js @@ -0,0 +1,96 @@ +function jsonStringify() { + /** + * ======================================================== + * Basic Syntax and Usage of JSON.stringify() + * ======================================================== + * The JSON.stringify() method converts a JavaScript value (object, array, string, + * number, boolean, or null) to a JSON-formatted string. + * + * Syntax: JSON.stringify(value, replacer?, space?) + * - value: The JavaScript value to convert to a JSON string. + * - replacer: Either a function or an array used to filter or modify the results. + * - space: Specifies the indentation for readability. + */ + const person = { + name: "Ishtmeet", + age: 25, + }; + const jsonString = JSON.stringify(person); + console.log(`Basic Usage: ${jsonString}`); // Output: '{"name":"Ishtmeet","age":25}' + + /** + * ======================================================== + * Using Replacer Function + * ======================================================== + * The replacer function can be used to filter out values or to transform the values + * before they get stringified. The replacer function takes two arguments, the 'key' and 'value'. + */ + const replacerFunction = (key, value) => { + if (typeof value === "number") { + return value * 2; + } + return value; + }; + console.log(`With Replacer Function: ${JSON.stringify(person, replacerFunction)}`); // Output: '{"name":"Ishtmeet","age":50}' + + /** + * ======================================================== + * Using Space for Indentation + * ======================================================== + * The 'space' parameter specifies the number of spaces to use for indentation. + * This makes the output JSON string more readable. + */ + const prettyJsonString = JSON.stringify(person, null, 4); + console.log(`Pretty Printed JSON: \n${prettyJsonString}`); + + /** + * ======================================================== + * Nuances and Edge Cases + * ======================================================== + */ + + /** + * Handling Undefined and Functions + * -------------------------------- + * JSON.stringify() will omit properties with undefined values, functions, or Symbol types. + */ + const objWithUndefined = { name: "Ishtmeet", greet: undefined, sayHi: function () {} }; + console.log(`Omitting Undefined and Functions: ${JSON.stringify(objWithUndefined)}`); // Output: '{"name":"Ishtmeet"}' + + /** + * Handling Circular References + * ---------------------------- + * JSON.stringify() throws an error when there are circular references in the object. + */ + const circularObj = { name: "Ishtmeet" }; + circularObj.self = circularObj; + // Uncomment the following line will result in an error + // console.log(JSON.stringify(circularObj)); + + /** + * toJSON Method + * ------------ + * If an object has a toJSON method, JSON.stringify() calls it and stringifies + * the value returned by toJSON(). + */ + const objWithToJSON = { + name: "Ishtmeet", + age: 25, + toJSON() { + return { + name: this.name, + }; + }, + }; + console.log(`Using toJSON method: ${JSON.stringify(objWithToJSON)}`); // Output: '{"name":"Ishtmeet"}' + + /** + * Handling Dates + * -------------- + * JSON.stringify() will convert Date objects to their ISO string representation. + */ + const objWithDate = { name: "Ishtmeet", birthDate: new Date() }; + console.log(`Handling Dates: ${JSON.stringify(objWithDate)}`); +} + +jsonStringify(); diff --git a/chapters/40_json_parse.js b/chapters/40_json_parse.js new file mode 100644 index 0000000..19569b4 --- /dev/null +++ b/chapters/40_json_parse.js @@ -0,0 +1,57 @@ +function jsonParse() { + /** + * ======================================================== + * Basic Syntax and Usage of JSON.parse() + * ======================================================== + * The JSON.parse() method converts a JSON-formatted string into a JavaScript object or value. + * + * Syntax: JSON.parse(text, reviver?) + * - text: The JSON string to parse. + * - reviver: A function to transform the resulting object. + */ + const jsonString = '{"name": "Ishtmeet", "age": 25}'; + const parsedObject = JSON.parse(jsonString); + console.log(`Basic Usage:`, parsedObject); // Output: { name: 'Ishtmeet', age: 25 } + + /** + * ======================================================== + * Using Reviver Function + * ======================================================== + * The reviver function is used for post-processing the result. + * It receives two arguments: 'key' and 'value'. + */ + const reviverFunction = (key, value) => { + if (key === "age") { + return value + 1; + } + return value; + }; + const parsedWithReviver = JSON.parse(jsonString, reviverFunction); + console.log(`With Reviver Function:`, parsedWithReviver); // Output: { name: 'Ishtmeet', age: 26 } + + /** + * ======================================================== + * Nuances and Edge Cases + * ======================================================== + */ + + /** + * Malformed JSON Strings + * ---------------------- + * If JSON.parse() encounters a malformed JSON string, it throws a SyntaxError. + */ + // Uncomment the next line to see the error + // const malformedJSON = JSON.parse("{'name': 'Ishtmeet'}"); // Single quotes are not valid + + /** + * Parsing Dates + * ------------- + * JSON.parse() doesn't automatically convert date strings into Date objects. + * You need to manually convert them. + */ + const parsedDateObject = JSON.parse('{"date": "2022-01-01T12:00:00Z"}'); + parsedDateObject.date = new Date(parsedDateObject.date); + console.log(`Parsing Dates:`, parsedDateObject.date instanceof Date); // Output: true +} + +jsonParse(); diff --git a/chapters/41_map.js b/chapters/41_map.js new file mode 100644 index 0000000..2fede5b --- /dev/null +++ b/chapters/41_map.js @@ -0,0 +1,106 @@ +function map() { + /** + * ======================================================== + * Initializing a Map + * ======================================================== + * The Map object is initialized using the new Map() constructor. + * You can optionally pass an array of key-value pairs to initialize the map. + */ + const myMap = new Map([ + ["key1", "value1"], + ["key2", "value2"], + [1, "one"], + ]); + + /** + * ======================================================== + * Adding Elements + * ======================================================== + * The set() method is used to add key-value pairs to the map. + * It replaces the value if the key already exists. + */ + myMap.set("key3", "value3"); + myMap.set({}, "emptyObject"); // Note: Object keys are supported + + /** + * ======================================================== + * Accessing Values + * ======================================================== + * The get() method is used to retrieve the value corresponding to a given key. + */ + console.log(`Accessing Values:`, myMap.get("key1")); // Output: 'value1' + console.log(`Accessing Values:`, myMap.get(1)); // Output: 'one' + + /** + * ======================================================== + * Removing Elements + * ======================================================== + * The delete() method removes a key-value pair from the map. + * It returns true if the item exists and has been removed, otherwise false. + */ + myMap.delete("key1"); + + /** + * ======================================================== + * Checking for Existence + * ======================================================== + * The has() method checks if a key exists in the map. + */ + console.log(`Checking Existence:`, myMap.has("key2")); // Output: true + + /** + * ======================================================== + * Iterating Over a Map + * ======================================================== + * The Map object can be iterated using forEach(), for...of, and its built-in iterators. + */ + myMap.forEach((value, key) => { + console.log(`Iterating using forEach: Key: ${key}, Value: ${value}`); + }); + + /** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + + /** + * Order of Elements + * ----------------- + * Maps maintain the insertion order, unlike regular JavaScript objects. + */ + + /** + * Key Equality + * ------------ + * Maps use the "SameValueZero" equality algorithm. For example, NaN is considered equal to NaN. + */ + myMap.set(NaN, "notANumber"); + console.log(`Key Equality:`, myMap.get(NaN)); // Output: 'notANumber' + + /** + * Chaining + * -------- + * Methods like set() return the Map object itself, allowing for chained calls. + */ + myMap.set("key4", "value4").set("key5", "value5"); + + /** + * Built-in Iterators + * ------------------ + * Maps have built-in iterators and can be looped using for...of. + */ + for (const [key, value] of myMap) { + console.log(`Iterating using for...of: Key: ${key}, Value: ${value}`); + } + + /** + * Converting to Array + * ------------------- + * Maps can be converted to arrays for easier manipulation and traversal. + */ + const mapArray = Array.from(myMap); + console.log(`Converting to Array:`, mapArray); // Output: Array of key-value pairs +} + +map(); diff --git a/chapters/42_weak_map.js b/chapters/42_weak_map.js new file mode 100644 index 0000000..09ea84d --- /dev/null +++ b/chapters/42_weak_map.js @@ -0,0 +1,102 @@ +/** + * ======================================================== + * WeakMap + * ======================================================== + * A WeakMap is a collection of key-value pairs where the keys must be objects + * and the values can be arbitrary values. WeakMaps hold "weak" references to the keys, + * which means that they do not prevent garbage collection in case there are no other references to the object. + */ + +/** + * ======================================================== + * Initializing a WeakMap + * ======================================================== + * WeakMaps are initialized using the `new WeakMap()` constructor. + * You can also initialize it with an iterable of key-value pairs, where keys must be objects. + */ +const myWeakMap = new WeakMap([ + [{}, "firstValue"], + [{}, "secondValue"], +]); + +/** + * ======================================================== + * Adding Elements + * ======================================================== + * The set() method adds a new key-value pair into the WeakMap. + * Remember, the key must be an object. + */ +const obj1 = {}; +const obj2 = {}; + +myWeakMap.set(obj1, "obj1Value"); +myWeakMap.set(obj2, "obj2Value"); + +/** + * ======================================================== + * Accessing Values + * ======================================================== + * The get() method retrieves the value associated with a given object key. + */ +console.log(myWeakMap.get(obj1)); // Output: 'obj1Value' + +/** + * ======================================================== + * Removing Elements + * ======================================================== + * The delete() method removes the key-value pair associated with a given object key. + */ +myWeakMap.delete(obj1); // Will remove obj1 and its associated value + +/** + * ======================================================== + * Checking for Existence + * ======================================================== + * The has() method checks if a WeakMap contains a specific key. + */ +console.log(myWeakMap.has(obj2)); // Output: true + +/** + * ======================================================== + * WeakMap Nuances and Limitations + * ======================================================== + */ + +/** + * No Iteration Support + * -------------------- + * WeakMaps can't be iterated using methods like `forEach` or for...of loops. + * This is because they are designed to be 'garbage-collection friendly' and do not have enumerable keys. + */ + +/** + * Garbage Collection + * ------------------ + * WeakMaps don't prevent the keys from being garbage collected. + * If there are no other references to the object, the garbage collector will remove it and its associated value from the WeakMap. + */ + +/** + * No Primitive Data Types as Keys + * ------------------------------- + * Primitive data types (numbers, strings, etc.) cannot be used as keys. + */ + +/** + * No `size` Property + * ------------------ + * Unlike Maps, WeakMaps do not have a `size` property. + */ + +/** + * No Clear Method + * --------------- + * WeakMaps do not have a `clear()` method to remove all key-value pairs. + */ + +/** + * Chainable Methods + * ----------------- + * Methods like set() return the WeakMap object itself, allowing for chained calls. + */ +myWeakMap.set(obj2, "newValue").set(obj1, "obj1NewValue"); diff --git a/chapters/43_set.js b/chapters/43_set.js new file mode 100644 index 0000000..5ed3f7f --- /dev/null +++ b/chapters/43_set.js @@ -0,0 +1,117 @@ +/** + * ======================================================== + * Set + * ======================================================== + * A Set is a data structure that allows you to store unique values of any type, + * whether they are primitive types or objects. Sets are particularly useful when you + * want to avoid duplicate values. + */ + +/** + * ======================================================== + * 1. Initializing a Set + * ======================================================== + * You can initialize a set with the `new Set()` constructor. + * You can also pass an iterable object (like an array) to the constructor to initialize the set with values. + */ +const mySet = new Set([1, 2, 3, 4, 5]); + +/** + * ======================================================== + * 2. Adding Elements + * ======================================================== + * The add() method is used to insert a new element into the set. + */ +mySet.add(6); +mySet.add("six"); + +/** + * ======================================================== + * 3. Removing Elements + * ======================================================== + * The delete() method removes a specific element from the set. + * It returns true if the element is found and removed, otherwise it returns false. + */ +const wasDeleted = mySet.delete(1); // Returns true because '1' was in the set and has been removed + +/** + * ======================================================== + * 4. Checking for Existence + * ======================================================== + * The has() method returns a boolean indicating whether an element is present in the set. + */ +console.log(mySet.has(2)); // Output will be true + +/** + * ======================================================== + * 5. Clearing All Elements + * ======================================================== + * The clear() method is used to remove all elements from the set. + */ +// Uncomment the following line to clear the set +// mySet.clear(); + +/** + * ======================================================== + * 6. Iterating Over a Set + * ======================================================== + * The forEach method and for...of loop can be used to iterate over the Set. + */ +mySet.forEach((value) => { + console.log(value); // Logs each value in the set +}); + +// Using for...of +for (const value of mySet) { + console.log(value); +} + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * Uniqueness is Strict + * -------------------- + * Sets enforce strict uniqueness; even two objects with the same shape are considered different. + */ +mySet.add({}); +mySet.add({}); // Both objects will be added since they are different references + +/** + * Primitive Uniqueness + * --------------------- + * In a set, NaN is considered equal to NaN, which is different from the Array behavior. + */ +mySet.add(NaN); +mySet.add(NaN); // Won't add another NaN as it is already present + +/** + * Order of Elements + * ----------------- + * Sets maintain the insertion order, which means items will be iterated in the order in which they were added. + */ + +/** + * Initialization Shortcuts + * ------------------------ + * Sets can be initialized from arrays or even other sets, effectively removing any duplicates from arrays. + */ +const arraySet = new Set([1, 2, 2, 3, 4]); // Will remove the duplicate '2' +const anotherSet = new Set(mySet); // Creates a new set from an existing one + +/** + * Set Size + * -------- + * You can check the size (number of unique elements) of the set using the size property. + */ +console.log(mySet.size); // Outputs the number of unique elements + +/** + * Chaining Methods + * ---------------- + * Since add() returns the Set object, you can chain multiple add() calls together. + */ +mySet.add(7).add(8); diff --git a/chapters/44_weak_map.js b/chapters/44_weak_map.js new file mode 100644 index 0000000..eacbb50 --- /dev/null +++ b/chapters/44_weak_map.js @@ -0,0 +1,91 @@ +/** + * ======================================================== + * WeakSet in JavaScript + * ======================================================== + * A WeakSet is a special kind of set that holds only objects (not primitive types) + * and does not prevent them from being garbage-collected. This makes it particularly + * useful for scenarios that require temporary storage of object references. + */ + +/** + * ======================================================== + * 1. Initializing a WeakSet + * ======================================================== + * You can initialize a WeakSet using the `new WeakSet()` constructor. + * The constructor optionally accepts an iterable of objects. + */ +const weakSet = new WeakSet([{ a: 1 }, { b: 2 }]); + +/** + * ======================================================== + * 2. Adding Elements + * ======================================================== + * To add an object to a WeakSet, use the add() method. + * Note that only objects can be added, not primitive values. + */ +const myObj = { c: 3 }; +weakSet.add(myObj); + +/** + * ======================================================== + * 3. Removing Elements + * ======================================================== + * The delete() method removes a specific object from the WeakSet. + * It returns true if the object is found and successfully removed. + */ +const wasDeleted = weakSet.delete(myObj); // Returns true as 'myObj' was in the set and has been removed + +/** + * ======================================================== + * 4. Checking for Existence + * ======================================================== + * The has() method returns a boolean indicating whether an object exists in the WeakSet. + */ +console.log(weakSet.has(myObj)); // Output will be false because it's been removed + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * Garbage Collection + * ------------------ + * Since WeakSets hold weak references to objects, the JavaScript engine can safely garbage-collect them. + * This is useful when you want a collection of objects that does not prevent those objects from being garbage-collected. + */ + +/** + * Non-Enumerable + * -------------- + * Unlike Sets, WeakSets are non-enumerable, which means you cannot iterate over them. + * This lack of enumerability is by design to allow the JavaScript engine to perform garbage collection. + */ + +/** + * Limited API + * ----------- + * WeakSets do not have methods like size, keys, values, or forEach. They also lack a clear() method. + * This is intentional, again for allowing better garbage collection. + */ + +/** + * No Primitive Values + * ------------------- + * WeakSets can only store objects. Adding primitive values like numbers or strings will throw a TypeError. + */ + +/** + * Use-Cases + * --------- + * WeakSets are commonly used for scenarios where you want a collection that won't prevent its items from being garbage-collected. + * This makes it useful for things like storing DOM elements that may be removed from the DOM at any time. + */ + +/** + * No Duplicates + * ------------- + * Just like Sets, WeakSets also enforce uniqueness among their elements. + * Adding the same object more than once will have no effect. + */ diff --git a/chapters/45_generators.js b/chapters/45_generators.js new file mode 100644 index 0000000..052dd69 --- /dev/null +++ b/chapters/45_generators.js @@ -0,0 +1,120 @@ +/** + * ======================================================== + * JavaScript Generators + * ======================================================== + * Generators are special functions that allow you to pause and resume their execution, + * preserving their internal state between pauses. This is particularly useful for things + * like iteration, asynchronous operations, and more. + * + * Generators offer a unique way to handle iterative or asynchronous logic + * in a cleaner, more manageable manner. + */ + +/** + * ======================================================== + * Defining a Generator Function + * ======================================================== + * A generator function is defined using the 'function*' syntax. + * When this function is called, it returns a generator object. + */ +function* myGenerator() { + yield "apple"; + yield "banana"; + return "done"; +} + +/** + * ======================================================== + * Creating a Generator Object + * ======================================================== + * You must first call the generator function to create a generator object. + * This object adheres to both the iterable and iterator protocols. + */ +const gen = myGenerator(); // Returns a generator object + +/** + * ======================================================== + * Iterating through a Generator + * ======================================================== + * Use the next() method on the generator object to iterate through the generator function's yields. + * Each call to next() returns an object with 'value' and 'done' properties. + */ +console.log(gen.next()); // Output: { value: 'apple', done: false } +console.log(gen.next()); // Output: { value: 'banana', done: false } +console.log(gen.next()); // Output: { value: 'done', done: true } + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * Generator as an Iterable + * ------------------------ + * A generator object is also an iterable, which means it can be used in a 'for...of' loop. + */ +for (const fruit of myGenerator()) { + console.log(fruit); // Output: 'apple', 'banana' +} + +/** + * yield* Expression + * ---------------- + * The 'yield*' expression can be used to delegate to another generator function or iterable object. + */ +function* anotherGenerator() { + yield* [1, 2, 3]; + yield* myGenerator(); +} +for (const val of anotherGenerator()) { + console.log(val); // Output: 1, 2, 3, 'apple', 'banana' +} + +/** + * Passing Values into Generators + * ------------------------------ + * You can pass values back into a paused generator function using the next(value) method. + */ +function* counter() { + let count = 0; + while (true) { + const increment = yield count; + count += increment || 1; + } +} +const cnt = counter(); +console.log(cnt.next().value); // Output: 0 +console.log(cnt.next(2).value); // Output: 2 + +/** + * Error Handling + * -------------- + * You can handle errors within a generator function using try-catch blocks. + */ +function* failSafeGenerator() { + try { + yield "OK"; + throw new Error("An error occurred"); + } catch (error) { + yield "Recovered from error"; + } +} +const safeGen = failSafeGenerator(); +console.log(safeGen.next().value); // Output: 'OK' +console.log(safeGen.next().value); // Output: 'Recovered from error' + +/** + * Asynchronous Generators + * ------------------------ + * ES2018 introduced asynchronous generator functions, defined using 'async function*'. + * They yield Promises and can be consumed using 'for await...of' loops. + */ +async function* asyncGenerator() { + yield Promise.resolve("async value"); +} +(async () => { + for await (const val of asyncGenerator()) { + console.log(val); // Output: 'async value' + } +})(); diff --git a/chapters/46_iterators.js b/chapters/46_iterators.js new file mode 100644 index 0000000..2b2357a --- /dev/null +++ b/chapters/46_iterators.js @@ -0,0 +1,129 @@ +/** + * ======================================================== + * JavaScript Iterators + * ======================================================== + * Iterators are a mechanism to traverse through collections (go through each item in a collection one by one), + * such as arrays, strings, and more advanced data structures that we just discussed - like maps and sets. + */ + +/** + * ======================================================== + * The Iterator Protocol + * ======================================================== + * The iterator protocol in JavaScript requires that you implement a 'next()' method. + * This method must return an object with 'value' and 'done' properties. + * + * The next() method serves as the core of the Iterator Protocol. It should fulfill the following conditions: + * + * -> Return an Object: It must return an object. + * + * -> Object Properties: The returned object should have two properties: + * 1. value: Holds the value of the current iteration. + * 2. done: A Boolean value that is true if the iterator is past the end of the iterated sequence. + * + * In JavaScript, it is conventional to return {value: undefined, done: true} when the sequence is exhausted. + */ +const arrayIterator = ["a", "b", "c"][Symbol.iterator](); + +console.log(arrayIterator.next()); // Output: { value: 'a', done: false } +console.log(arrayIterator.next()); // Output: { value: 'b', done: false } +console.log(arrayIterator.next()); // Output: { value: 'c', done: false } +console.log(arrayIterator.next()); // Output: { value: undefined, done: true } + +/** + * ======================================================== + * Custom Iterators + * ======================================================== + * You can make your own iterable objects by defining a [Symbol.iterator] method. + * This method should return an object containing a 'next' method. + */ +const customIterable = { + [Symbol.iterator]: function () { + let count = 0; + return { + next: function () { + count++; + if (count <= 3) { + return { value: count, done: false }; + } + return { value: undefined, done: true }; + }, + }; + }, +}; + +// Using the custom iterable in a for...of loop +for (const item of customIterable) { + console.log(item); // Output: 1, 2, 3 +} + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * Iterating Over Arguments + * ------------------------ + * JavaScript's 'arguments' object is array-like but not an actual array. + * You can make it iterable using Array.from() method. + */ +function argumentsIterator() { + const argsArray = Array.from(arguments); + for (const arg of argsArray) { + console.log(arg); + } +} +argumentsIterator(1, 2, 3); // Output: 1, 2, 3 + +/** + * String Iterators + * ---------------- + * Strings are iterable by default. When you iterate over a string, + * each iteration returns a single character. + */ +for (const char of "hello") { + console.log(char); // Output: 'h', 'e', 'l', 'l', 'o' +} + +/** + * Iterating Over Maps and Sets + * ---------------------------- + * Map and Set objects have their built-in iterators, which makes it easier to iterate through them. + */ +const mySet = new Set([1, 2, 3]); +const setIter = mySet[Symbol.iterator](); +console.log(setIter.next().value); // Output: 1 + +const myMap = new Map([ + ["a", 1], + ["b", 2], +]); +const mapIter = myMap[Symbol.iterator](); +console.log(mapIter.next().value); // Output: ['a', 1] + +/** + * Iterator Return and Throw Methods + * --------------------------------- + * Besides the 'next()' method, iterators can optionally implement 'return' and 'throw' methods. + * These can be used to release internal resources when an iterator is no longer in use, + * or to propagate errors during iteration, respectively. + */ +const advancedIterator = { + [Symbol.iterator]: function () { + return { + next() { + return { value: "some_value", done: false }; + }, + return() { + console.log("Exiting early"); + return { done: true }; + }, + throw(error) { + console.log("An error occurred:", error); + return { done: true }; + }, + }; + }, +}; diff --git a/chapters/47_big_int.js b/chapters/47_big_int.js new file mode 100644 index 0000000..e1fef58 --- /dev/null +++ b/chapters/47_big_int.js @@ -0,0 +1,93 @@ +/** + * ======================================================== + * BigInt + * ======================================================== + * BigInt is a built-in object that provides a way to represent whole numbers larger + * than 2^53 - 1, which is the largest number JavaScript can reliably represent using the Number primitive. + */ + +/** + * ======================================================== + * 1. Basic Syntax + * ======================================================== + * BigInt can be defined either by appending 'n' to the end of an integer literal or + * by using the BigInt constructor function. + */ +const bigIntLiteral = 1234567890123456789012345678901234567890n; +const bigIntObject = BigInt("1234567890123456789012345678901234567890"); + +/** + * ======================================================== + * 2. Arithmetic Operations + * ======================================================== + * Arithmetic operations are supported on BigInts much like they are for Numbers, + * but you can't mix BigInts and Numbers in these operations. + */ +const sum = bigIntLiteral + 1n; +// const invalidSum = bigIntLiteral + 1; // This will throw a TypeError + +/** + * ======================================================== + * 3. Comparison + * ======================================================== + * BigInt can be compared using all the regular comparison operators. + * Just like arithmetic operations, you can't directly compare a BigInt with a Number. + */ +const isTrue = bigIntLiteral > 1000n; // Evaluates to true + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. No Auto-Type Conversion + * --------------------------- + * BigInt does not automatically convert into a string or a number. + * Explicit conversion is often required when you're working with mixed types. + */ +const bigIntStr = String(bigIntLiteral); +// const invalidOperation = bigIntLiteral + " is big."; // This will throw a TypeError + +/** + * 2. Divisions Are Floored + * ------------------------ + * When performing division using BigInt, the division is floored, + * meaning it rounds towards zero. + */ +const flooredDivision = 11n / 3n; // Result will be 3n + +/** + * 3. Bitwise Operations + * --------------------- + * You can perform bitwise operations with BigInt. + * Both operands must be BigInts to perform these operations. + */ +const bitwiseAnd = 16n & 5n; // Output will be 0n + +/** + * 4. No Support for Math Object + * ----------------------------- + * BigInt cannot be used with the JavaScript Math object's methods. + */ +// const sqrtBigInt = Math.sqrt(bigIntLiteral); // This will throw a TypeError + +/** + * 5. JSON Serialization + * ---------------------- + * JSON.stringify() can't serialize BigInt values, + * you'll need to manually convert them to strings before serialization. + */ +// const bigIntJSON = JSON.stringify({value: bigIntLiteral}); // This will throw a TypeError + +/** + * 6. Compatibility + * ---------------- + * BigInt is not yet universally supported across all environments, + * so you may need to check for compatibility before using it. + */ +if (typeof BigInt !== "undefined") { + const bigIntSupport = BigInt(10); + console.log(`BigInt supported: ${bigIntSupport}`); +} diff --git a/chapters/48.0_web_apis.js b/chapters/48.0_web_apis.js new file mode 100644 index 0000000..bf487b1 --- /dev/null +++ b/chapters/48.0_web_apis.js @@ -0,0 +1,138 @@ +/** + * ======================================================== + * Web APIs - Part 1 + * ======================================================== + * Web APIs augment the capabilities of JavaScript, allowing developers to interact + * with the web browser or other hosting environments. Web APIs often provide a way + * to perform operations that aren't possible with JavaScript alone. + */ + +/** + * ======================================================== + * 1. XMLHttpRequest API + * ======================================================== + * The traditional method for making HTTP requests in JavaScript. + * While it's mostly superseded by the Fetch API, it's still widely used in legacy codebases. + */ +const xhr = new XMLHttpRequest(); +xhr.open("GET", "https://api.example.com/data", true); +xhr.send(); +xhr.onload = () => { + if (xhr.status === 200) { + console.log(xhr.response); + } else { + console.log(`Error: ${xhr.status}`); + } +}; + +/** + * ======================================================== + * 2. Fetch API + * ======================================================== + * A modern way to fetch resources over the network. It returns Promises and + * is much cleaner and more powerful than XMLHttpRequest. + */ +fetch("https://api.example.com/data") + .then((response) => { + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }) + .then((data) => console.log(data)) + .catch((error) => console.error("Fetch Error:", error)); + +/** + * ======================================================== + * 3. Geolocation API + * ======================================================== + * This API is used to get the geographic position of a user. Note that it requires user consent. + */ +navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude } = position.coords; + console.log(`Latitude: ${latitude}, Longitude: ${longitude}`); + }, + (error) => console.error("Geolocation Error:", error) +); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. AbortController for Fetch API + * -------------------------------- + * The AbortController is used to terminate fetch requests. + * This can be helpful to abort requests based on certain conditions, like a timeout. + */ +const controller = new AbortController(); +const { signal } = controller; +fetch("https://api.example.com/data", { signal }) + .then((response) => response.json()) + .catch((err) => { + if (err.name === "AbortError") { + console.log("Fetch aborted"); + } + }); +// To abort the fetch +controller.abort(); + +/** + * 2. Using the Cache API + * ---------------------- + * This API allows you to cache network resources. This can significantly improve + * the performance of web applications by reducing load times. + */ +caches.open("my-cache").then((cache) => { + cache + .add("https://api.example.com/data") + .then(() => console.log("Data cached")) + .catch((error) => console.error("Cache Error:", error)); +}); + +/** + * 3. Clipboard API + * ---------------- + * The Clipboard API is used for interacting with the clipboard to read and write text. + */ +navigator.clipboard + .writeText("Copied text") + .then(() => console.log("Text copied to clipboard")) + .catch((err) => console.error("Clipboard Error:", err)); + +/** + * 4. Notification API + * ------------------- + * Allows you to display native system notifications. + * This can enhance the user experience by providing real-time updates. + */ +if (Notification.permission === "granted") { + new Notification("Hello, world!"); +} else { + Notification.requestPermission().then((permission) => { + if (permission === "granted") { + new Notification("Hello, world!"); + } + }); +} + +/** + * 5. Intersection Observer API + * ---------------------------- + * Useful for detecting when an element enters or exits the viewport. + * Common use-cases include lazy-loading images and infinite scrolling. + */ +const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + // Perform action, such as lazy-loading an image + console.log("Element is in the viewport!"); + } + }); +}); + +// To observe a specific element +observer.observe(document.querySelector(".some-element")); diff --git a/chapters/48.1_web_apis_2.js b/chapters/48.1_web_apis_2.js new file mode 100644 index 0000000..62b8390 --- /dev/null +++ b/chapters/48.1_web_apis_2.js @@ -0,0 +1,103 @@ +/** + * ======================================================== + * Web APIs - Part 2 + * ======================================================== + */ + +/** + * ======================================================== + * 1. WebSocket API + * ======================================================== + * WebSocket provides full-duplex communication channels over a single, long-lived connection. + */ +const ws = new WebSocket("ws://example.com/socketserver"); +ws.onopen = () => { + ws.send("Hello Server!"); +}; +ws.onmessage = (event) => { + console.log(`Server says: ${event.data}`); +}; +ws.onclose = () => { + console.log("Connection closed"); +}; + +/** + * ======================================================== + * 2. DOM API + * ======================================================== + * The DOM API allows you to programmatically interact with HTML and XML documents. + */ +// Adding a new element +const newElement = document.createElement("div"); +newElement.textContent = "Hello, World!"; +document.body.appendChild(newElement); + +/** + * ======================================================== + * 3. Drag and Drop API + * ======================================================== + * This API allows you to make elements draggable and to capture drop events. + */ +document.addEventListener("dragstart", (event) => { + event.dataTransfer.setData("text/plain", event.target.id); +}); +document.addEventListener("drop", (event) => { + event.preventDefault(); + const data = event.dataTransfer.getData("text"); + const target = document.getElementById(data); + event.target.appendChild(target); +}); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. WebSocket Reconnection + * -------------------------- + * In real-world scenarios, WebSocket connections can drop. A common practice is to implement + * reconnection logic. + */ +let ws; +function connect() { + ws = new WebSocket("ws://example.com/socketserver"); + ws.onopen = () => { + ws.send("Hello Server!"); + }; + ws.onmessage = (event) => { + console.log(`Server says: ${event.data}`); + }; + ws.onclose = () => { + console.log("Connection closed. Reconnecting..."); + setTimeout(connect, 1000); + }; +} +connect(); + +/** + * 2. Using DOM API with Fragments + * -------------------------------- + * Document fragments let you create a subtree of elements and insert them into the DOM + * as a single operation. + */ +const fragment = document.createDocumentFragment(); +const elem1 = document.createElement("p"); +elem1.textContent = "Paragraph 1"; +const elem2 = document.createElement("p"); +elem2.textContent = "Paragraph 2"; +fragment.appendChild(elem1); +fragment.appendChild(elem2); +document.body.appendChild(fragment); + +/** + * 3. Drag and Drop with Custom Images + * ----------------------------------- + * You can customize the drag image by setting the `dragImage` property on the dataTransfer object. + */ +document.addEventListener("dragstart", (event) => { + const img = new Image(); + img.src = "path/to/image.png"; + event.dataTransfer.setDragImage(img, 10, 10); +}); diff --git a/chapters/48.2_web_apis_3.js b/chapters/48.2_web_apis_3.js new file mode 100644 index 0000000..755c145 --- /dev/null +++ b/chapters/48.2_web_apis_3.js @@ -0,0 +1,79 @@ +/** + * ======================================================== + * Web APIs - Part 3 + * ======================================================== + */ + +/** + * ======================================================== + * 1. Audio and Video API + * ======================================================== + * The HTML5 Audio and Video APIs allow for playback of multimedia. + */ +const audio = new Audio("audio_file.mp3"); +audio.play(); + +const video = document.querySelector("video"); +video.play(); + +/** + * ======================================================== + * 2. Canvas API + * ======================================================== + * The Canvas API provides a way to draw 2D graphics. + */ +const canvas = document.getElementById("myCanvas"); +const ctx = canvas.getContext("2d"); +ctx.fillStyle = "#FF0000"; +ctx.fillRect(0, 0, 80, 80); + +/** + * ======================================================== + * 3. RequestAnimationFrame API + * ======================================================== + * Used for creating smooth animations. + */ +function animate() { + // Animation code + requestAnimationFrame(animate); +} +animate(); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Controlling Audio and Video Playback + * --------------------------------------- + * You can control the playback rates, volume, and other properties. + */ +audio.volume = 0.5; +audio.playbackRate = 1.5; + +/** + * 2. Canvas Transformations + * ------------------------- + * Canvas allows you to perform complex transformations like scaling and rotations. + * We'll talk more about canvas, in the next chapter. + */ +ctx.save(); +ctx.rotate((Math.PI / 180) * 45); +ctx.fillRect(100, 0, 50, 50); +ctx.restore(); + +/** + * 3. Animating with RequestAnimationFrame + * --------------------------------------- + * It's best to use requestAnimationFrame over setInterval for smoother animations. + */ +let x = 0; +function animate() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillRect(x, 0, 50, 50); + x += 1; + requestAnimationFrame(animate); +} +animate(); diff --git a/chapters/49_canvas.js b/chapters/49_canvas.js new file mode 100644 index 0000000..2b70b9e --- /dev/null +++ b/chapters/49_canvas.js @@ -0,0 +1,126 @@ +/** + * ======================================================== + * Canvas API + * ======================================================== + * The Canvas API allows for dynamic, scriptable rendering of 2D and 3D graphics. + */ + +/** + * ======================================================== + * 1. Basic Setup + * ======================================================== + * Create a canvas element in HTML and get its context in JavaScript. + */ +const canvas = document.getElementById("myCanvas"); +const ctx = canvas.getContext("2d"); + +/** + * ======================================================== + * 2. Drawing Shapes + * ======================================================== + * Draw simple shapes like rectangles, circles, and lines. + */ + +// Draw a rectangle +ctx.fillStyle = "red"; +ctx.fillRect(10, 10, 100, 50); + +// Draw a circle +ctx.fillStyle = "blue"; +ctx.beginPath(); +ctx.arc(100, 100, 50, 0, Math.PI * 2); +ctx.fill(); + +// Draw a line +ctx.strokeStyle = "green"; +ctx.beginPath(); +ctx.moveTo(10, 10); +ctx.lineTo(100, 10); +ctx.stroke(); + +/** + * ======================================================== + * 3. Text Rendering + * ======================================================== + * Display text on the canvas. Can control font, alignment, and more. + */ +ctx.font = "30px Arial"; +ctx.fillText("Hello Canvas", 50, 200); +ctx.textAlign = "center"; // Other options: 'left', 'right' +ctx.fillText("Centered Text", canvas.width / 2, canvas.height / 2); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Pixel Manipulation + * ---------------------- + * Manipulate individual pixels for advanced effects like filters. + */ +const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); +const data = imageData.data; +for (let i = 0; i < data.length; i += 4) { + // Invert colors + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; +} +ctx.putImageData(imageData, 0, 0); + +/** + * 2. Transformations + * ------------------- + * Apply transformations like scale, rotate, and translate. + * Always save and restore the context when doing transformations. + */ +ctx.save(); +ctx.scale(0.5, 0.5); +ctx.rotate((Math.PI / 180) * 25); +ctx.translate(100, 0); +// Draw transformed rectangle +ctx.fillStyle = "purple"; +ctx.fillRect(50, 50, 100, 50); +ctx.restore(); + +/** + * 3. Animation + * ------------- + * Use `requestAnimationFrame` for smooth animations. + * This creates a game loop for continuous drawing. + */ +let xPos = 0; +function drawFrame() { + // Clear canvas and update drawings + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillRect(xPos, 10, 50, 50); + xPos++; + requestAnimationFrame(drawFrame); +} +requestAnimationFrame(drawFrame); + +/** + * 4. OffscreenCanvas + * ------------------- + * Perform canvas rendering in a web worker to offload the main thread. + */ +if (window.OffscreenCanvas) { + const offscreen = new OffscreenCanvas(100, 100); + const offscreenCtx = offscreen.getContext("2d"); + offscreenCtx.fillStyle = "orange"; + offscreenCtx.fillRect(0, 0, 100, 100); +} + +/** + * 5. Path2D Object + * ---------------- + * Use Path2D objects to cache complex paths and draw them later. + * This can improve performance in animations. + */ +const path = new Path2D(); +path.moveTo(10, 10); +path.lineTo(100, 10); +path.lineTo(100, 100); +ctx.stroke(path); diff --git a/chapters/50_drag_drop.js b/chapters/50_drag_drop.js new file mode 100644 index 0000000..69b329c --- /dev/null +++ b/chapters/50_drag_drop.js @@ -0,0 +1,126 @@ +/** + * ======================================================== + * JavaScript Drag and Drop API + * ======================================================== + * Enables interactive Drag-and-Drop interfaces in web applications. + */ + +/** + * ======================================================== + * 1. Making an Element Draggable + * ======================================================== + * To make an element draggable, set its 'draggable' attribute to true. + * This enables the browser's native drag-and-drop feature for the element. + */ +const draggableElem = document.getElementById("draggable"); +draggableElem.setAttribute("draggable", "true"); + +/** + * ======================================================== + * 2. Drag Events + * ======================================================== + * Drag events are fired on both the draggable target and the drop target. + * The 'dragstart' event is essential for initiating a drag operation. + */ +// Drag start event +draggableElem.addEventListener("dragstart", (event) => { + // Store some meta-data to be used during the 'drop' event + event.dataTransfer.setData("text/plain", draggableElem.id); +}); + +/** + * ======================================================== + * 3. Dropping an Element + * ======================================================== + * To allow an element to act as a drop target, you must prevent the default handling of the element during dragover. + */ +const dropZone = document.getElementById("dropZone"); + +// Allow the drop by preventing default behavior +dropZone.addEventListener("dragover", (event) => { + event.preventDefault(); +}); + +// Handle the drop +dropZone.addEventListener("drop", (event) => { + event.preventDefault(); + const data = event.dataTransfer.getData("text/plain"); + const draggedElem = document.getElementById(data); + dropZone.appendChild(draggedElem); +}); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Custom Drag Image + * -------------------- + * You can set a custom image to appear next to the cursor while dragging. + */ +draggableElem.addEventListener("dragstart", (event) => { + const img = new Image(); + img.src = "drag-image.png"; + event.dataTransfer.setDragImage(img, 10, 10); +}); + +/** + * 2. Drag Handles + * ---------------- + * You can make only a part of an element act as a drag handle. + */ +const handle = document.getElementById("handle"); +handle.addEventListener("dragstart", (event) => { + event.dataTransfer.setData("text/plain", draggableElem.id); + event.stopPropagation(); // Prevent dragstart from bubbling up to parent elements +}); + +/** + * 3. Nested Drop Targets + * ---------------------- + * Handling dragover and drop events properly when there are nested drop targets can be tricky. + * Stop propagation to ensure only the innermost target gets the event. + */ +const nestedZone = document.getElementById("nestedZone"); +nestedZone.addEventListener("dragover", (event) => { + event.stopPropagation(); + event.preventDefault(); // Still required to allow drop +}); +nestedZone.addEventListener("drop", (event) => { + event.stopPropagation(); + event.preventDefault(); // Execute your nested drop logic here +}); + +/** + * 4. Drag and Drop File Upload + * ---------------------------- + * You can use drag and drop for file uploads. It enhances user experience significantly. + */ +dropZone.addEventListener("drop", (event) => { + const files = event.dataTransfer.files; + Array.from(files).forEach((file) => { + // Your file processing logic here + console.log(`Uploaded file: ${file.name}`); + }); +}); + +/** + * HTML Code for testing the above - + + + + + Drag and Drop Example + + +
Drag Me!
+
Drop Here!
+
Drag Handle
+
Nested Drop Zone
+ + + + + */ diff --git a/chapters/51_file_and_blob.js b/chapters/51_file_and_blob.js new file mode 100644 index 0000000..0795a6a --- /dev/null +++ b/chapters/51_file_and_blob.js @@ -0,0 +1,130 @@ +/** + * ======================================================== + * File and Blob + * ======================================================== + * The File and Blob APIs open up new possibilities for dealing with files and binary data, + * enabling a range of functionalities from basic file I/O to advanced techniques like streaming and object serialization. + */ + +/** + * ======================================================== + * File API + * ======================================================== + * The File API allows you to work with files in web applications, + * making it possible to read the contents of files and navigate their structure. + */ + +/** + * ======================================================== + * 1. Creating a File + * ======================================================== + * Creating a File object is similar to creating a Blob. The File constructor + * allows you to add additional metadata, such as the file name and MIME type. + */ +const file = new File(["Hello, again!"], "hello.txt", { type: "text/plain" }); + +/** + * ======================================================== + * 2. Reading a File + * ======================================================== + * The FileReader API is commonly used to read the contents of a File object. + */ +const fileReader = new FileReader(); +fileReader.onload = function (event) { + console.log(event.target.result); // Outputs "Hello, again!" +}; +fileReader.readAsText(file); + +/** + * ======================================================== + * 3. Uploading a File + * ======================================================== + * Files can be uploaded using the Fetch API or XMLHttpRequest. + */ +fetch("https://example.com/upload", { + method: "POST", + body: file, +}); + +/** + * ======================================================== + * 4. File Input Element + * ======================================================== + * The element allows users to select files from their device, + * which can then be read and manipulated using JavaScript. + */ +const fileInput = document.getElementById("fileInput"); +fileInput.addEventListener("change", (event) => { + const selectedFile = event.target.files[0]; + // Further processing of the File object +}); + +/** + * ======================================================== + * Blob API + * ======================================================== + * The Blob API is used for handling binary data directly, + * enabling you to create, read and manipulate binary data in a performant and safe manner. + */ + +/** + * ======================================================== + * 1. Creating a Blob + * ======================================================== + * Blobs can be created using the Blob constructor. + * The constructor takes an array of data and an optional options object. + */ +const blob = new Blob(["Hello, world!"], { type: "text/plain" }); + +/** + * ======================================================== + * 2. Reading a Blob + * ======================================================== + * The FileReader API can read Blob content just like it does with File objects. + */ +const blobReader = new FileReader(); +blobReader.onload = function (event) { + console.log(event.target.result); // Outputs "Hello, world!" +}; +blobReader.readAsText(blob); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Blob URLs + * ------------ + * Temporary URLs can be created for Blobs and Files for use within the application. + */ +const url = URL.createObjectURL(blob); +// Perform operations with the URL (e.g., assign it to an or element) +URL.revokeObjectURL(url); // Free up resources by releasing the URL + +/** + * 2. Slicing a Blob + * ----------------- + * Blobs can be sliced to create new Blob objects from a subset of their data. + */ +const slicedBlob = blob.slice(0, 5); // Contains the text "Hello" + +/** + * 3. Blob Streaming + * ----------------- + * The Blob object can be streamed using the ReadableStream API, if supported. + */ +if (blob.stream) { + const stream = blob.stream(); + // Perform operations using the stream +} + +/** + * 4. Object Serialization + * ------------------------ + * Blobs can store complex objects, thanks to JSON serialization. + */ +const objBlob = new Blob([JSON.stringify({ key: "value" })], { + type: "application/json", +}); diff --git a/chapters/52_websockets.js b/chapters/52_websockets.js new file mode 100644 index 0000000..377a9bd --- /dev/null +++ b/chapters/52_websockets.js @@ -0,0 +1,112 @@ +/** + * ======================================================== + * WebSockets in JS + * ======================================================== + * WebSockets enable real-time, full-duplex communication between a web client and a server. + * This allows for instant data transfer, making them useful for applications like chat, gaming, and live updates. + */ + +/** + * ======================================================== + * 1. Creating a WebSocket + * ======================================================== + * Use the WebSocket constructor to create a new WebSocket instance. + * The argument is the URL of the WebSocket server you want to connect to. + */ +const socket = new WebSocket("ws://example.com"); + +/** + * ======================================================== + * 2. Open Event + * ======================================================== + * The 'open' event fires when the connection is successfully established. + * You can start sending data to the server once this event fires. + */ +socket.addEventListener("open", () => { + console.log("WebSocket connection opened"); + socket.send("Hello, server!"); +}); + +/** + * ======================================================== + * 3. Message Event + * ======================================================== + * The 'message' event fires when a message is received from the WebSocket server. + * The message data can be accessed via `event.data`. + */ +socket.addEventListener("message", (event) => { + console.log(`Received from server: ${event.data}`); +}); + +/** + * ======================================================== + * 4. Close Event + * ======================================================== + * The 'close' event fires when the WebSocket connection is closed, + * either due to the client's or server's request or because of an error. + * The event object contains details like close code and reason. + */ +socket.addEventListener("close", (event) => { + console.log(`Connection closed: ${event.code}`); +}); + +/** + * ======================================================== + * 5. Error Event + * ======================================================== + * The 'error' event fires when an error occurs. + * Although the event does not carry detailed information, + * it is often accompanied by a 'close' event that gives additional context. + */ +socket.addEventListener("error", (error) => { + console.error(`WebSocket Error: ${error}`); +}); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Ping/Pong Frames + * ------------------- + * Some WebSocket servers use ping/pong frames to keep the connection alive. + * In most cases, the browser will automatically reply to "ping" frames with "pong" frames. + * No additional client-side handling is needed. + */ + +/** + * 2. Subprotocols + * --------------- + * You can specify one or more subprotocols while creating a WebSocket. + * The server will pick one among those and will use it for further communication. + */ +const customSocket = new WebSocket("ws://example.com", ["subprotocol-1", "subprotocol-2"]); + +/** + * 3. Secure WebSockets (WSS) + * -------------------------- + * Always prefer using Secure WebSockets (WSS) when dealing with sensitive or secure data. + */ +const secureSocket = new WebSocket("wss://secure-example.com"); + +/** + * 4. Binary Data + * ------------- + * WebSockets can also handle binary data, such as ArrayBuffer and Blob objects. + */ +const binaryData = new ArrayBuffer(256); +socket.send(binaryData); + +/** + * 5. Buffering and Back-pressure + * ------------------------------ + * If you're sending large or numerous pieces of data, you might run into issues with buffering. + * The `bufferedAmount` property tells how many bytes are currently buffered and awaiting transmission. + */ +if (socket.bufferedAmount === 0) { + socket.send("This message will be sent immediately"); +} else { + // Handle back-pressure, possibly by buffering data or reducing the sending rate. +} diff --git a/chapters/53_web_workers.js b/chapters/53_web_workers.js new file mode 100644 index 0000000..c6203d8 --- /dev/null +++ b/chapters/53_web_workers.js @@ -0,0 +1,111 @@ +/** + * ======================================================== + * Web Workers + * ======================================================== + * Web Workers allow you to run scripts in the background. + * This helps in offloading computational tasks and thus keeps the UI responsive. + */ + +/** + * ======================================================== + * 1. Creating a Web Worker + * ======================================================== + * Use the Worker constructor to spawn a new worker. + * The argument to this constructor is the path to the worker script. + */ +const worker = new Worker("worker-script.js"); + +/** + * ======================================================== + * 2. Sending Messages to the Worker + * ======================================================== + * The `postMessage` method is used to send messages from the main thread to the worker. + * The data sent can be any structure that is cloneable or transferable. + */ +worker.postMessage("Hello, Worker!"); + +/** + * ======================================================== + * 3. Receiving Messages from the Worker + * ======================================================== + * Listen for the `message` event to receive messages from the worker. + * Use `event.data` to access the data sent by the worker. + */ +worker.addEventListener("message", (event) => { + console.log(`Received from worker: ${event.data}`); +}); + +/** + * ======================================================== + * 4. Terminating a Worker + * ======================================================== + * The `terminate` method can be used to immediately terminate a worker. + * Use this method with caution as it stops all worker activities without any cleanup. + */ +worker.terminate(); + +/** + * ======================================================== + * 5. Error Handling + * ======================================================== + * The `error` event is triggered when an uncaught exception occurs within the worker. + * This allows you to gracefully handle errors and possibly recover from them. + */ +worker.addEventListener("error", (error) => { + console.error(`Worker Error: ${error.message}`); +}); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Transferable Objects + * ----------------------- + * Transferable objects are used when you want to hand off ownership of an object to another context like a worker. + * This allows faster transfer of complex structures like typed arrays. + */ +const buffer = new ArrayBuffer(8); +worker.postMessage(buffer, [buffer]); + +/** + * 2. Shared Workers + * ----------------- + * Shared Workers allow sharing a single worker instance between multiple contexts such as different windows or even workers. + */ +const sharedWorker = new SharedWorker("shared-worker-script.js"); +sharedWorker.port.start(); + +/** + * 3. Worker Scope + * --------------- + * Inside a Web Worker, the global scope is not the `window` object. + * While you can't access the DOM, some web APIs like `fetch` and `IndexedDB` are still available. + */ + +/** + * 4. Importing Scripts + * -------------------- + * You can import external JavaScript files into your worker using `importScripts`. + * This allows you to reuse code and manage dependencies. + */ +// Inside worker-script.js +importScripts("external-script.js"); + +/** + * 5. Inline Workers + * ----------------- + * If your worker logic is small or generated dynamically, you can use inline workers created from Blob URLs or Data URLs. + */ +const blob = new Blob(['postMessage("Hello from inline worker!");'], { type: "text/javascript" }); +const inlineWorker = new Worker(URL.createObjectURL(blob)); + +/** + * 6. Message Channels + * ------------------- + * For complex, two-way communications between your main thread and worker, Message Channels can be used. + */ +const channel = new MessageChannel(); +worker.postMessage("Initiate MessageChannel communication", [channel.port2]); diff --git a/chapters/54_service_workers.js b/chapters/54_service_workers.js new file mode 100644 index 0000000..cc23d50 --- /dev/null +++ b/chapters/54_service_workers.js @@ -0,0 +1,130 @@ +/** + * ======================================================== + * Service Workers + * ======================================================== + * Service Workers enable various powerful features like offline caching, background sync, and push notifications. + * They run in the background and act as a proxy between web applications and the network. + */ + +/** + * ======================================================== + * 1. Registering a Service Worker + * ======================================================== + * To register a Service Worker, you use `navigator.serviceWorker.register`. + * This returns a promise that resolves with the ServiceWorkerRegistration object when the worker gets registered successfully. + */ +if ("serviceWorker" in navigator) { + navigator.serviceWorker + .register("/service-worker.js") + .then((registration) => { + console.log("Service Worker registered:", registration); + }) + .catch((error) => { + console.log("Service Worker registration failed:", error); + }); +} + +/** + * ======================================================== + * 2. The Install Event + * ======================================================== + * The `install` event is fired when the Service Worker is being installed. + * This is an opportunity to cache assets for offline use using the Cache API. + */ +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open("my-cache").then((cache) => { + return cache.addAll(["/", "/index.html", "/styles.css", "/script.js"]); + }) + ); +}); + +/** + * ======================================================== + * 3. The Activate Event + * ======================================================== + * The `activate` event fires when the Service Worker becomes active. + * This is a good place to manage old caches. + */ +self.addEventListener("activate", (event) => { + console.log("Service Worker activated"); +}); + +/** + * ======================================================== + * 4. The Fetch Event + * ======================================================== + * The `fetch` event can be used to intercept network requests. + * You can respond to this event by fetching a resource from the cache or network. + */ +self.addEventListener("fetch", (event) => { + event.respondWith( + caches.match(event.request).then((response) => { + return response || fetch(event.request); + }) + ); +}); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Skip Waiting + * --------------- + * The `skipWaiting` method allows a service worker to skip the 'waiting' lifecycle phase and move to 'active'. + */ +self.skipWaiting(); + +/** + * 2. Clients Claim + * ---------------- + * Using `clients.claim`, a newly activated Service Worker can immediately control all pages under its scope. + */ +self.clients.claim(); + +/** + * 3. Background Sync + * ------------------ + * The 'sync' event enables deferred actions to be retried when the user has network connectivity. + */ +self.addEventListener("sync", (event) => { + if (event.tag === "myBackgroundSync") { + // Your background sync logic here + } +}); + +/** + * 4. Push Notifications + * ---------------------- + * Service Workers can receive push messages and show local notifications to the user. + */ +self.addEventListener("push", (event) => { + const title = "New Notification"; + const options = { + body: "This is a sample push notification!", + }; + event.waitUntil(self.registration.showNotification(title, options)); +}); + +/** + * 5. Cache Versioning + * ------------------- + * Multiple versions of cached assets can be managed by maintaining a cache version identifier. + */ +const cacheName = "my-cache-v1"; +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((keyList) => { + return Promise.all( + keyList.map((key) => { + if (key !== cacheName) { + return caches.delete(key); + } + }) + ); + }) + ); +}); diff --git a/chapters/55_custom_events.js b/chapters/55_custom_events.js new file mode 100644 index 0000000..b2e45b9 --- /dev/null +++ b/chapters/55_custom_events.js @@ -0,0 +1,128 @@ +/** + * ======================================================== + * Custom Events + * ======================================================== + * Custom Events enable you to define your own events in JavaScript. + * This is very powerful for building modular, decoupled code. + */ + +/** + * ======================================================== + * 1. Creating a Custom Event + * ======================================================== + * Create a Custom Event using the 'CustomEvent' constructor. + * The 'detail' field allows you to pass custom data for the event. + */ +const myEvent = new CustomEvent("myEvent", { + detail: { message: "This is a custom event" }, + bubbles: true, + cancelable: true, +}); + +/** + * ======================================================== + * 2. Dispatching a Custom Event + * ======================================================== + * Dispatch the custom event using the 'dispatchEvent' method on an HTML element. + * This will trigger any listeners for the custom event. + */ +const someElement = document.getElementById("someElement"); +someElement.dispatchEvent(myEvent); + +/** + * ======================================================== + * 3. Listening for a Custom Event + * ======================================================== + * Use 'addEventListener' to listen to the custom event. + * Event handling is similar to handling native DOM events. + */ +someElement.addEventListener("myEvent", (event) => { + console.log("Custom event fired:", event.detail.message); +}); + +/** + * ======================================================== + * 4. Removing an Event Listener + * ======================================================== + * To stop listening to an event, use 'removeEventListener'. + */ +function someFunction(event) { + console.log("Handled by someFunction:", event.detail.message); +} +someElement.removeEventListener("myEvent", someFunction); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Using Custom Events with Components + * -------------------------------------- + * Custom Events are highly useful in component-based architectures like Web Components. + */ +class MyComponent extends HTMLElement { + connectedCallback() { + this.dispatchEvent(new CustomEvent("componentReady", { bubbles: true })); + } +} +customElements.define("my-component", MyComponent); + +/** + * 2. Custom Events with Additional Data + * ------------------------------------- + * You can attach extra information to a custom event via the 'detail' attribute. + */ +const userDataEvent = new CustomEvent("userData", { + detail: { username: "IshtmeetDoe", age: 30 }, +}); + +/** + * 3. Event Propagation and Bubbling + * ---------------------------------- + * You can control the phase in which the event is captured. + * Use 'true' as the third argument to 'addEventListener' to capture the event during the capture phase. + */ +someElement.addEventListener( + "myEvent", + function (event) { + console.log("Parent received:", event.detail.message); + }, + true +); + +/** + * 4. Cancellable Custom Events + * ------------------------------ + * A custom event can be made cancellable, meaning it can be stopped by an event listener. + */ +const cancellableEvent = new CustomEvent("cancellableEvent", { cancelable: true }); +if (!someElement.dispatchEvent(cancellableEvent)) { + console.log("Event was cancelled"); +} + +/** + * 5. Polyfill for CustomEvent + * ---------------------------- + * For compatibility with older browsers like Internet Explorer, you can use a polyfill. + */ +(function () { + if (typeof window.CustomEvent === "function") return false; + function CustomEvent(event, params) { + params = params || { bubbles: false, cancelable: false, detail: null }; + const evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + } + window.CustomEvent = CustomEvent; +})(); + +/** + * 6. Dynamic Event Names + * ------------------------ + * Dynamic event names can be generated and used, providing an extra layer of flexibility. + */ +const someDynamicValue = "Click"; +const eventName = "custom:" + someDynamicValue; +someElement.addEventListener(eventName, someFunction); diff --git a/chapters/56_webrtc.js b/chapters/56_webrtc.js new file mode 100644 index 0000000..179ce79 --- /dev/null +++ b/chapters/56_webrtc.js @@ -0,0 +1,143 @@ +/** + * ======================================================== + * WebRTC + * ======================================================== + * WebRTC allows real-time communication including audio, video, and data to be transferred + * between browsers without requiring any plugins. + */ + +/** + * ======================================================== + * 1. Accessing Media Devices + * ======================================================== + * Use 'navigator.mediaDevices.getUserMedia()' to request access to a user's camera and microphone. + * This returns a promise that resolves to a MediaStream object. + */ +async function getMedia() { + try { + const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); + const videoElement = document.querySelector("video"); + videoElement.srcObject = stream; + } catch (error) { + console.error("Error accessing media devices:", error); + } +} +getMedia(); + +/** + * ======================================================== + * 2. Creating a Peer Connection + * ======================================================== + * The 'RTCPeerConnection' interface allows you to establish a peer-to-peer connection. + */ +const peerConnection = new RTCPeerConnection(); + +/** + * ======================================================== + * 3. Exchanging Offer and Answer + * ======================================================== + * The initiator creates an offer and the recipient responds with an answer. + * The offer and answer mechanism is crucial for establishing the media configuration. + */ +async function createOffer() { + const offer = await peerConnection.createOffer(); + await peerConnection.setLocalDescription(offer); + // Send offer to remote peer through signaling +} + +async function createAnswer() { + const offer = await peerConnection.createAnswer(); + await peerConnection.setLocalDescription(answer); + // Send answer to remote peer through signaling +} + +/** + * ======================================================== + * 4. Handling ICE Candidates + * ======================================================== + * ICE (Interactive Connectivity Establishment) candidates handle the exchange of network + * information. This enables NAT traversal, allowing the connection to be made. + */ +peerConnection.onicecandidate = (event) => { + if (event.candidate) { + // Typically, you'd send this candidate information to the remote peer over your signaling channel + } +}; + +/** + * ======================================================== + * 5. Handling Data Channels + * ======================================================== + * Data Channels allow for the transfer of arbitrary data between peers. + */ +const dataChannel = peerConnection.createDataChannel("myChannel"); +dataChannel.onmessage = (event) => { + console.log("Received data:", event.data); +}; + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. NAT Traversal + * ---------------- + * STUN (Session Traversal Utilities for NAT) and TURN (Traversal Using Relays around NAT) servers help + * in situations where peers are behind firewalls or NATs. + */ +const configuration = { + iceServers: [ + { urls: "stun:stun.l.google.com:19302" }, + { + urls: "turn:turn.example.org", + username: "username", + credential: "credential", + }, + ], +}; +const natPeerConnection = new RTCPeerConnection(configuration); + +/** + * 2. Simulcast + * ------------ + * Simulcasting is the ability to send multiple resolutions of the video to the other peer. + */ +const transceiver = peerConnection.addTransceiver("video", { + direction: "sendrecv", + streams: [stream], + sendEncodings: [ + { rid: "high", active: true, maxBitrate: 900000 }, + { rid: "medium", active: true, maxBitrate: 300000 }, + { rid: "low", active: true, maxBitrate: 100000 }, + ], +}); + +/** + * 3. Security Concerns + * --------------------- + * Although WebRTC traffic is encrypted by default, always be cautious about security vulnerabilities. + */ + +/** + * 4. Adaptivity and Bandwidth Estimation + * -------------------------------------- + * WebRTC can adapt video quality according to the current network conditions. + * Advanced algorithms estimate the available bandwidth. + */ +// (No direct code example, this is handled by the WebRTC internals) + +/** + * 5. Use of WebRTC Libraries + * --------------------------- + * Libraries like SimplePeer or PeerJS can simplify the WebRTC implementation. + * These libraries handle signaling, media capture, and various WebRTC features. + */ +// Example using PeerJS +const peer = new Peer(); +peer.on("connection", (conn) => { + conn.on("data", (data) => { + console.log("Received", data); + }); +}); diff --git a/chapters/57_dynamic_imports.js b/chapters/57_dynamic_imports.js new file mode 100644 index 0000000..31b6f9a --- /dev/null +++ b/chapters/57_dynamic_imports.js @@ -0,0 +1,118 @@ +/** + * ======================================================== + * Dynamic Import in JavaScript + * ======================================================== + * Dynamic import() syntax in JavaScript enables you to import modules on-the-fly, providing the + * ability to load modules conditionally or on demand. + */ + +/** + * ======================================================== + * 1. Basic Usage + * ======================================================== + * Using 'await import()' allows you to import a module dynamically. + * This returns a promise that resolves to the imported module. + */ +async function basicUsage() { + const module = await import("./myModule.js"); + module.someFunction(); // Call the function from the dynamically imported module +} + +/** + * ======================================================== + * 2. Using with Promises + * ======================================================== + * Alternatively, you can use the Promise-based '.then()' syntax to handle dynamic imports. + */ +function usingPromise() { + import("./myModule.js") + .then((module) => { + module.someFunction(); // Call the function from the dynamically imported module + }) + .catch((err) => { + console.error("Failed to load the module:", err); + }); +} + +/** + * ======================================================== + * 3. Dynamic Path + * ======================================================== + * The path from which to dynamically import can be constructed at runtime. + */ +const moduleName = "module1"; +import(`./${moduleName}.js`).then((module) => { + module.someFunction(); // Invoke function from dynamically determined module +}); + +/** + * ======================================================== + * 4. Importing Multiple Modules + * ======================================================== + * 'Promise.all()' can be used to import multiple modules simultaneously. + */ +Promise.all([import("./module1.js"), import("./module2.js")]).then(([module1, module2]) => { + module1.someFunction(); + module2.anotherFunction(); +}); + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Code Splitting + * ----------------- + * Dynamic imports enable code splitting, particularly useful when used with bundlers like Webpack. + */ +if (someCondition) { + import("./heavyModule.js").then((module) => { + module.heavyComputation(); // Load and use only when 'someCondition' is true + }); +} + +/** + * 2. Fallback Logic + * ----------------- + * If importing a module fails, you can specify fallback behavior. + */ +import("./optionalModule.js") + .catch(() => import("./fallbackModule.js")) + .then((module) => { + module.someFunction(); // Use the fallback module if the original fails to import + }); + +/** + * 3. Importing JSON and Other Non-JS Files + * ---------------------------------------- + * With proper bundler configuration, you can dynamically import file types other than JavaScript. + */ +import("./config.json").then((config) => { + console.log("Config loaded:", config.default); +}); + +/** + * 4. Prefetching Modules + * ---------------------- + * Prefetching can be done by importing a module and not using it immediately. + */ +import("./prefetchMe.js").catch(() => { + // Prefetch silently and handle any load errors +}); + +/** + * 5. Use With React Lazy + * ---------------------- + * In React applications, dynamic import can be combined with React.lazy for component-level code splitting. + */ +// const LazyComponent = React.lazy(() => import('./LazyComponent')); + +/** + * 6. Caching + * ---------- + * Once a module is dynamically imported, it gets cached. Further imports won't trigger additional network requests. + */ +import("./myModule.js"); // Network request made +import("./myModule.js"); // Cached, no additional network request diff --git a/chapters/58_decorators.js b/chapters/58_decorators.js new file mode 100644 index 0000000..02de47e --- /dev/null +++ b/chapters/58_decorators.js @@ -0,0 +1,136 @@ +/** + * ======================================================== + * Decorators in JavaScript (Stage 3 Proposal - Cannot be used yet) + * ======================================================== + * A decorator is a function that allows you to add new functionality to an object or an object method. + * Although it's still a Stage 3 proposal, decorators are widely used in TypeScript and transpiler environments. + */ + +/** + * ======================================================== + * Introduction + * ======================================================== + * Decorators can affect: + * - Classes + * - Class methods + * - Accessors (getters/setters) + * - Class properties + * + * They can: + * 1. Replace the value being decorated. + * 2. Access the value being decorated. + * 3. Initialize the value being decorated. + */ +@defineElement("my-class") +class C extends HTMLElement { + @reactive accessor clicked = false; +} + +/** + * ======================================================== + * 2. Calling Decorators + * ======================================================== + * Decorators receive two arguments: + * 1. The value being decorated. + * 2. A context object with metadata. + */ + +/** + * + * ======================================================== + * TypeScript example + * ======================================================== + * + * type Decorator = (value: Input, context: { + * kind: string; + * name: string | symbol; + * access: { + * get?(): unknown; + * set?(value: unknown): void; + * }; + * private?: boolean; + * static?: boolean; + * addInitializer?(initializer: () => void): void; + * }) => Output | void; + */ + +/** + * This function is an example of what a decorator might look like in pure JavaScript. + * It does not use TypeScript 'type' keywords, but the concept remains the same. + * + * @param {any} value - The value being decorated. + * @param {Object} context - Additional context about the decoration. + */ +function myDecorator(value, context) { + // The context object can have properties like 'kind', 'name', etc. + if (context.kind === "method") { + // do something + } +} + +/** + * ======================================================== + * 3. Applying Decorators + * ======================================================== + * Decorators are applied in a well-defined sequence: + * 1. All method and non-static field decorators are applied. + * 2. The class decorator is applied. + * 3. Finally, static field decorators are applied. + */ + +/** + * ======================================================== + * Basic Decorator Example: Logging + * ======================================================== + * The following is a simple logging decorator applied to a class method. + */ +function logged(value, { kind, name }) { + if (kind === "method") { + return function (...args) { + console.log(`Starting ${name} with arguments ${args.join(", ")}`); + const ret = value.call(this, ...args); + console.log(`Ending ${name}`); + return ret; + }; + } +} + +class C { + @logged + m(arg) {} +} + +new C().m(1); // Output: Starting m with arguments 1 +// Ending m + +/** + * ======================================================== + * Advanced Usage: Method Chaining + * ======================================================== + * Decorators can also be used to enable method chaining by always returning 'this'. + */ +function chainable(target, name, descriptor) { + const originalMethod = descriptor.value; + descriptor.value = function (...args) { + originalMethod.apply(this, args); + return this; + }; + return descriptor; +} + +class MyClass { + @chainable + method1() { + console.log("method1"); + } + + @chainable + method2() { + console.log("method2"); + } +} + +const myInstance = new MyClass(); +myInstance.method1().method2(); +// Output: method1 +// method2 diff --git a/chapters/59_proxy.js b/chapters/59_proxy.js new file mode 100644 index 0000000..a05dab6 --- /dev/null +++ b/chapters/59_proxy.js @@ -0,0 +1,166 @@ +/** + * ======================================================== + * Proxy + * ======================================================== + * The Proxy object in JavaScript is used to define custom behavior for fundamental + * operations (e.g., property lookup, assignment, enumeration, function invocation, etc.). + */ + +/** + * ======================================================== + * 1. Basic Object Proxy + * ======================================================== + * Using Proxy to intercept 'get' and 'set' operations on an object. + */ +const person = { + name: "Ishtmeet", + age: 25, +}; + +const personProxy = new Proxy(person, { + get(target, prop) { + if (prop in target) { + return target[prop]; + } else { + return "Property not found"; + } + }, + set(target, prop, value) { + if (prop === "age" && typeof value !== "number") { + throw new TypeError("Age must be a number"); + } + target[prop] = value; + }, +}); + +// Usage: Invocation of Proxy +console.log(personProxy.name); // Output: "Ishtmeet" +console.log(personProxy.unknownProperty); // Output: "Property not found" + +/** + * ======================================================== + * 2. Array Proxy + * ======================================================== + * Proxies can also be used to intercept operations on arrays. + */ +const numbers = [1, 2, 3]; +const arrayProxy = new Proxy(numbers, { + get(target, prop) { + if (isNaN(prop)) { + return target[prop]; + } else { + return target[parseInt(prop, 10)]; + } + }, +}); + +// Usage: Invocation of Array Proxy +console.log(arrayProxy[1]); // Output: 2 +console.log(arrayProxy.length); // Output: 3 + +/** + * ======================================================== + * 3. Function Proxy + * ======================================================== + * Using Proxy to intercept the 'apply' operation on a function. + */ +function add(a, b) { + return a + b; +} + +const addProxy = new Proxy(add, { + apply(target, thisArg, args) { + console.log(`Function is called with arguments: ${args}`); + return target.apply(thisArg, args); + }, +}); + +// Usage: Invocation of Function Proxy +console.log(addProxy(1, 2)); // Output: Function is called with arguments: 1,2 +// Output: 3 + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Validation + * ------------- + * Proxies can validate object structure when a property is set. + */ +const user = {}; +const userProxy = new Proxy(user, { + set(target, prop, value) { + if (prop === "age" && (value < 0 || value > 150)) { + throw new RangeError("Invalid age"); + } + target[prop] = value; + }, +}); + +// Usage: Invocation of Validation Proxy +try { + userProxy.age = 200; // Throws RangeError: Invalid age +} catch (e) { + console.log(e.message); // Output: Invalid age +} + +/** + * 2. Property Enumeration + * ----------------------- + * Proxies can modify property enumeration behavior. + */ +const noEnumProxy = new Proxy(person, { + ownKeys(target) { + return Object.keys(target).filter((key) => key !== "age"); + }, +}); + +// Usage: Invocation of Property Enumeration Proxy +console.log(Object.keys(noEnumProxy)); // Output: ["name"] + +/** + * 3. Revocable Proxies + * -------------------- + * Proxies can be revoked, disabling the target object. + */ +const { proxy, revoke } = Proxy.revocable(person, {}); +revoke(); + +// Usage: Invocation of Revocable Proxy +try { + console.log(proxy.name); // Throws TypeError: proxy is revoked +} catch (e) { + console.log(e.message); // Output: proxy is revoked +} + +/** + * 4. Meta-Programming + * ------------------- + * Proxies enable you to perform meta-programming tasks such as modifying 'instanceof' behavior. + */ +const instanceProxy = new Proxy(function () {}, { + hasInstance(target, instance) { + return false; // Every object is not an instance of this function + }, +}); + +/** + * 5. Conditional Logic Based on Context + * ------------------------------------ + * The 'get' handler can use different logic based on the receiver. + */ +const conditionalProxy = new Proxy(person, { + get(target, prop, receiver) { + if (receiver === person) { + return target[prop]; + } else { + return "Unauthorized"; + } + }, +}); + +// Usage: Invocation of Conditional Logic Proxy +console.log(conditionalProxy.name); // Output: "Ishtmeet" diff --git a/chapters/60_reflect.js b/chapters/60_reflect.js new file mode 100644 index 0000000..cd4b0ec --- /dev/null +++ b/chapters/60_reflect.js @@ -0,0 +1,129 @@ +/** + * ======================================================== + * Reflect + * ======================================================== + * The Reflect object provides various methods that encapsulate common JavaScript operations + * in a more function-like syntax, making them interceptable and manipulable. + */ + +/** + * ======================================================== + * 1. Property Access using Reflect + * ======================================================== + * Reflect allows property access similar to traditional JavaScript but in a more function-like syntax. + * This is particularly useful when you're working with Proxy handlers. + */ +const person = { + name: "Ishtmeet", + age: 25, +}; + +// Using Reflect.get to access a property +const age = Reflect.get(person, "age"); +console.log(`Age: ${age}`); // Output: Age: 25 + +// Using Reflect.set to modify a property +Reflect.set(person, "name", "Bob"); +console.log(`Name: ${person.name}`); // Output: Name: Bob + +/** + * ======================================================== + * 2. Property Existence Checks + * ======================================================== + * The Reflect.has method allows you to check if a property exists on an object. + */ +const hasAge = Reflect.has(person, "age"); +console.log(`Has age property: ${hasAge}`); // Output: Has age property: true + +/** + * ======================================================== + * 3. Property Deletion + * ======================================================== + * Reflect.deleteProperty allows for the deletion of properties in a function-like syntax. + */ +Reflect.deleteProperty(person, "age"); +console.log(`Age property deleted: ${"age" in person}`); // Output: Age property deleted: false + +/** + * ======================================================== + * 4. Object Creation + * ======================================================== + * Reflect.construct is used to create new instances of objects. + * This can replace the 'new' keyword in certain scenarios. + */ +const newObj = Reflect.construct(Array, [1, 2, 3]); +console.log(newObj); // Output: [1, 2, 3] + +/** + * ======================================================== + * 5. Function Invocation + * ======================================================== + * Reflect.apply allows for invoking functions with specified context and arguments. + */ +function greet(name) { + return `Hello, ${name}!`; +} +const greeting = Reflect.apply(greet, null, ["Ishtmeet"]); +console.log(greeting); // Output: Hello, Ishtmeet! + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Error Handling + * ----------------- + * Many Reflect methods return boolean values, allowing for built-in error handling. + */ +const success = Reflect.defineProperty(person, "name", { value: "Charlie" }); +if (success) { + console.log('Property "name" defined successfully.'); +} else { + console.error('Failed to define property "name".'); +} + +/** + * 2. Property Enumeration + * ----------------------- + * Reflect.ownKeys allows for easy enumeration of all object properties, both enumerable and non-enumerable. + */ +const propertyNames = Reflect.ownKeys(person); +console.log(`Property names: ${propertyNames}`); // Output: Property names: name + +/** + * 3. Object Extensibility Control + * ------------------------------ + * Reflect.preventExtensions and Reflect.isExtensible allow you to control and check an object's extensibility. + */ +Reflect.preventExtensions(person); +console.log(`Is extensible: ${Reflect.isExtensible(person)}`); // Output: Is extensible: false + +/** + * 4. Proxy and Reflect + * -------------------- + * Reflect methods can be used in Proxy handlers to provide custom behavior. + * They complement each other especially well in meta-programming tasks. + */ +const handler = { + get(target, prop) { + return Reflect.get(target, prop); + }, +}; +const proxyPerson = new Proxy(person, handler); +console.log(proxyPerson.name); // Output: Bob + +/** + * 5. Function Context and Arguments + * -------------------------------- + * Reflect.apply allows you to specify both the function's context and its arguments array, providing more control over invocation. + */ +const context = { + domain: "example.com", +}; +function logDomain(name) { + return `Hello, ${name} from ${this.domain}!`; +} +const contextualGreeting = Reflect.apply(logDomain, context, ["Ishtmeet"]); +console.log(contextualGreeting); // Output: Hello, Ishtmeet from example.com! diff --git a/chapters/48_performance.js b/chapters/61_performance.js similarity index 99% rename from chapters/48_performance.js rename to chapters/61_performance.js index 14f4fba..574b3d5 100644 --- a/chapters/48_performance.js +++ b/chapters/61_performance.js @@ -1,6 +1,6 @@ /* * ======================================================== - * Performance Optimization in JavaScript + * Performance Optimization * ======================================================== * JavaScript performance optimization is crucial for responsive and efficient web apps. * Here, we look at various techniques to improve the performance of your JavaScript code. diff --git a/chapters/49_navigator.js b/chapters/62_navigator.js similarity index 100% rename from chapters/49_navigator.js rename to chapters/62_navigator.js diff --git a/chapters/50_user_timing_api.js b/chapters/63_user_timing_api.js similarity index 100% rename from chapters/50_user_timing_api.js rename to chapters/63_user_timing_api.js diff --git a/chapters/51_navigation_timing.js b/chapters/64_navigation_timing.js similarity index 100% rename from chapters/51_navigation_timing.js rename to chapters/64_navigation_timing.js diff --git a/chapters/65_lazy_loading.js b/chapters/65_lazy_loading.js new file mode 100644 index 0000000..0d8bc95 --- /dev/null +++ b/chapters/65_lazy_loading.js @@ -0,0 +1,171 @@ +/** + * ======================================================== + * Lazy Loading + * ======================================================== + * Lazy loading is a design pattern commonly used to defer the loading of an object or resource until the time it is needed. + * This can be particularly helpful in improving performance for web applications. + */ + +/** + * ======================================================== + * Basic Lazy Loading of Images + * ======================================================== + * The most common use case is lazy loading of images. Below is a simple example using JavaScript and HTML's native + * 'loading' attribute set to 'lazy'. + */ + +// HTML code +// example image + +/** + * This will defer the loading of the image until the user scrolls to the point where the image is visible. + * You don't need any extra JavaScript to make this work. + */ + +/** + * ======================================================== + * Lazy Loading with Intersection Observer + * ======================================================== + * A more robust approach to lazy loading involves the Intersection Observer API. This enables you to dynamically + * load content as it becomes visible within the viewport. + */ + +// HTML code +// example image + +const observer = new IntersectionObserver((entries, observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.getAttribute("data-src"); + observer.unobserve(img); + } + }); +}); + +document.querySelectorAll(".lazy").forEach((img) => observer.observe(img)); + +/** + * In this example, the Intersection Observer observes all images with the class "lazy". When an image intersects + * with the viewport, it sets the image source and unobserves the image to stop watching it. + */ + +/** + * ======================================================== + * Lazy Loading JavaScript Modules + * ======================================================== + * Lazy loading can also be applied to JavaScript modules. This helps to reduce the initial load time of a web application. + */ + +// Using dynamic imports +async function loadModule() { + const { myFunction } = await import("./myModule.js"); + myFunction(); +} + +// Invoking the function +loadModule(); + +/** + * The 'import()' function dynamically loads the module only when the 'loadModule' function is invoked. + * This can be helpful in reducing the initial bundle size of your application. + */ + +/** + * ======================================================== + * Nuances and Advanced Techniques + * ======================================================== + */ + +/** + * 1. Fallback for Older Browsers + * ------------------------------ + * Native lazy loading is not supported in all browsers. Having a fallback mechanism ensures that + * lazy loading still works on older browsers. + */ + +// Using Intersection Observer as a fallback +if ("loading" in HTMLImageElement.prototype) { + const images = document.querySelectorAll('img[loading="lazy"]'); + images.forEach((img) => { + img.src = img.dataset.src; + }); +} else { + // Fallback to Intersection Observer +} + +/** + * In this code snippet, we check if the browser supports native lazy loading. If it does, we set the image + * source directly. Otherwise, we fallback to using Intersection Observer. + */ + +/** + * 2. Loading Threshold + * --------------------- + * With Intersection Observer, you can set a threshold that defines when to start loading content before it + * actually comes into the viewport. + */ + +const observerWithThreshold = new IntersectionObserver( + (entries, observer) => { + // Logic here + }, + { rootMargin: "200px" } +); + +/** + * The rootMargin option sets a 200px margin outside of the viewport, effectively starting the image loading + * 200px before the user actually scrolls to the image. + */ + +/** + * 3. Placeholder Images + * ---------------------- + * You can display a low-quality placeholder or a loading spinner until the actual content is loaded. + */ + +// HTML code +// + +/** + * The low-quality image will display first and will be replaced by the high-quality image once it is loaded. + */ + +/** + * 4. Lazy Loading Iframes + * ------------------------ + * Lazy loading can be applied to iframes as well, and it follows a similar pattern as that of images. + */ + +// HTML code +// + +/** + * The iframe content will only start loading when it gets near the viewport, thereby improving page load speed. + */ + +/** + * 5. Lazy Loading Background Images + * --------------------------------- + * Background images set via CSS can also be lazy-loaded using Intersection Observer. + */ + +const bgObserver = new IntersectionObserver((entries, observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.classList.add("bg-loaded"); + observer.unobserve(entry.target); + } + }); +}); + +document.querySelectorAll(".bg-lazy").forEach((el) => bgObserver.observe(el)); + +// CSS +// .bg-loaded { +// background-image: url('high-quality.jpg'); +// } + +/** + * The 'bg-loaded' class contains the actual background image that will be applied once the element comes into view. + */