Notes about JS Closures
1. What is Closures.
A closure in JavaScript is a function that has access to variables from its surrounding lexical environment (or outer function scope), even after the outer function has returned. In essence, a closure "remembers" the environment in which it was created. This is possible because functions in JavaScript are first-class objects, meaning they can be treated like any other variable (passed as arguments, returned from functions, assigned to variables). So, functions 'remember' the environment in which they were declared/created. Closures ties in very closely with Lexical Scope, can be seen when returning a function reference. Every closure has three scopes: Local scope (Own scope), Enclosing scope (can be block, function, or module scope), Global scope.
Closures allow you save states inside function (that already is object in JS), something like this:
1: function outerFunction() {
2: let outerVar = "I'm outside!";
3: function innerFunction() {
4: console.log(outerVar); // This accesses outerVar *after* outerFunction has finished
5: }
6: return innerFunction; // Returning the inner function
7: }
8:
9: let myClosure = outerFunction();
10: myClosure(); // Output: "I'm outside!" (Even though outerFunction has completed)
You can program with JS by years and never used this patten.
And secondary, if you are Angular programmer, you never will use Closures, because dependency injection with Angular doesn't create closures in the traditional JavaScript way. The service instances are managed by Angular's injector, not closed over by a function.
2. C# and Java.
- Definition:
- Closure is a function that has access to variables in its outer (enclosing) scope, even after the outer function has returned.
- Closures "remember" the environment in which they were created. They can access variables from their outer scope, even if those variables are no longer in scope in the usual sense.
- Closures are often used to create private variables, maintain state, and implement higher-order functions.
- Why Java and C# Don't Use Closures in the same way? The reasons why Java and C# don't have closures as a core language feature are rooted in their design philosophies and the way they handle scoping and memory management.
- Scoping and Lifetime:
- Java and C# Scoping: Java and C# have a more rigid scoping model than JavaScript. Variables have well-defined scopes (e.g., class scope, method scope, block scope). The lifetime of variables is also more strictly managed.
- JavaScript Scoping: JavaScript's scoping rules are more flexible. Variables can be declared anywhere, and their lifetime is less strictly defined. This flexibility is what makes closures possible.
- Memory Management:
- Java and C# Memory Management: Java and C# have garbage collection mechanisms that automatically reclaim memory. This means that when a variable is no longer reachable, the memory it occupied can be freed.
- JavaScript Memory Management: JavaScript also has garbage collection, but closures can sometimes create situations where objects are kept alive longer than expected. This can lead to memory leaks if not handled carefully.
- Design Philosophy:
- Java and C# Design: Java and C# are designed to be more structured and less prone to errors. They emphasize type safety and clear scoping rules.
- JavaScript Design: JavaScript is more dynamic and flexible. It's designed to be used in a wide range of applications, including web development.
- Alternative Approaches:
- Java and C# Alternatives: Java and C# have alternative ways to achieve similar functionality to closures. For example, you can use inner classes, anonymous classes, or lambda expressions to create private variables or maintain state.
- JavaScript Alternatives: JavaScript has closures as a core language feature.
- Language Evolution:
- Java: Java has evolved over time to include features like lambda expressions, which provide a way to achieve similar functionality to closures.
- C#: C# has also evolved to include features like lambda expressions, which provide a way to achieve similar functionality to closures.
- JavaScript: JavaScript has had closures as a core language feature since its inception.
- Specific Differences:
- Lexical Scoping: JavaScript uses lexical scoping, which means that the scope of a variable is determined by where it is declared in the code. Java and C# use block scoping, which means that the scope of a variable is determined by the block of code in which it is declared.
- Variable Lifetime: In JavaScript, variables can exist even after the function in which they were declared has returned. In Java and C#, variables are typically destroyed when the function in which they were declared returns.
- Garbage Collection: JavaScript has garbage collection, which means that memory is automatically reclaimed when it is no longer needed. Java and C# also have garbage collection, but they also have other mechanisms for managing memory.
- In Summary:
- Java and C#: These languages prioritize type safety, structured programming, and explicit memory management. They offer alternative ways to achieve similar functionality to closures, such as inner classes, anonymous classes, and lambda expressions.
- JavaScript: JavaScript embraces closures as a core language feature, which aligns with its dynamic and flexible nature.
- In essence:
- JavaScript's design philosophy embraces flexibility and dynamic behavior, making closures a natural fit.
- Java and C#'s design philosophy emphasizes structure, type safety, and explicit memory management, making closures less of a natural fit. I hope this explanation helps clarify why closures are a core feature of JavaScript but not of Java or C#.
3. My examples.
I without haste try to prepare to lecture about JS Closures, this is base of my code to that lecture:
4. My examples.
Look to my workable solution with MySQL mysqlpool.js, mysqlasync.js, mysql.js and postgreSQL postgres.js, postgrespool.js - as you can see I never hide connection inside object, why? This is looking more clear for me, but other programmer thinking in other way.
And other programmers prefer more sophisticated solution with closures, because this way can use a closure to manage the database connection lifecycle, ensuring that it's opened once and closed when all operations are complete, preventing the need to repeatedly open and close the connection. Look please, how is possible to make refactoring my simplest solution:
1: import { Client } from 'pg';
2: import {} from "dotenv/config";
3:
4: function createDatabaseManager() {
5: let client; // Client is scoped to the outer function
6:
7: return {
8: connect: async () => {
9: if (!client) { // Avoid reconnecting if already connected
10: client = new Client(process.env.localPostgresCN);
11: await client.connect();
12: console.log('Connected to PostgreSQL database');
13: }
14: return client; // Always return existing client if connected.
15: },
16: query: async (sql, params) => {
17: const client = await this.connect(); // use the current client.
18: const result = await client.query(sql, params); // no need to pass client every time.
19: return result.rows;
20: },
21:
22: insert: async (sql, params, callback) => {
23: const client = await this.connect();
24: return await client.query(sql, params).then(async (x) => {
25: callback(x.rowCount, sql);
26: });
27: },
28: disconnect: async () => {
29: if (client) { // checks if a client is initialised
30: await client.end();
31: client = null; // Release the client
32: console.log('Connection to PostgreSQL closed');
33: }
34: },
35: };
36: }
37:
38: const dbManager = createDatabaseManager(); // Create the manager instance
39:
40: // Example usage in your other modules (e.g., uploadNewTopic.js):
41: async function processData() {
42:
43: try {
44: const result1 = await dbManager.query('SELECT * FROM entry WHERE i > $1 ORDER BY i', [1000]);
45: console.log('Results 1:', result1);
46:
47: // ... other database operations using dbManager.query, dbManager.insert, etc.
48: await dbManager.insert( `INSERT INTO public.entry (parent, id, kind, name, mime) VALUES ($1, $2, $3, $4,$5);`,
49: ["parentValue", "idValue", "kindValue", "nameValue", "mimeTypeValue"], async (rowCount, sql) => {
50: console.log(`Inserted ${rowCount} row(s). SQL: ${sql}`);
51: });
52:
53: } finally {
54: await dbManager.disconnect(); // close connection when done
55: }
56: }
57:
58: processData();
In this case the database connection is managed within the closure's scope, and your other modules interact with it through the dbManager object's methods, ensuring clean connection handling.
Also in this case we can Tracking Asynchronous Operations with Counters, something like this:
1: function createAsyncCounter(totalOperations, completionCallback) {
2: let completedOperations = 0;
3: return function operationComplete() { // This inner function is the closure
4: completedOperations++;
5: if (completedOperations === totalOperations) {
6: completionCallback(); // Call the completion callback when all are done
7: }
8: };
9: }
10:
11: async function processMultipleInserts(inserts) {
12: const numInserts = inserts.length;
13: const onComplete = createAsyncCounter(numInserts, () => {
14: console.log('All insert operations complete!');
15: dbManager.disconnect(); // close when all inserts are done.
16: }); // Create a counter
17:
18: try {
19: for (const insert of inserts) {
20: dbManager.insert(insert.sql, insert.params, (rowCount, sql) => {
21: console.log(`Inserted ${rowCount} row(s). SQL: ${sql}`);
22: onComplete(); // increment and check the counter each insert is finished
23: })
24: }
25: } catch (error) {
26: console.error("Error during inserts:", error);
27: // Handle errors, potentially rollback changes if necessary
28: }
29: }
30:
31: const inserts = [
32: { sql: 'INSERT INTO my_table (col1, col2) VALUES ($1, $2)', params: ['val1', 'val2'] },
33: // ... more insert objects
34: ];
35: processMultipleInserts(inserts);
Sorry, I have no time, will continue soon.
ES6 context:

|