Functions are reusable blocks of code designed to perform specific tasks.
In Node.js, functions are a core component of JavaScript and serve as the fundamental building blocks for constructing applications.
Node.js inherits all the function features of JavaScript and plays a crucial role in its asynchronous programming model.
In JavaScript, a function can be passed as an argument to another function. We can define a function first and then pass it, or define the function directly where the argument is passed.
Importance of Functions
- Code Reuse: Avoids rewriting the same logic.
- Modularity: Breaks down complex problems into smaller functions.
- Maintainability: Makes debugging and modifying independent functional units easier.
Function Declaration
Declare a function using the function keyword.
function greet(name) {
console.log(`Hello, ${name}!`);
}
Function Expression
Assign a function to a variable.
const greet = function(name) {
console.log(`Hello, ${name}!`);
};
Arrow Functions
A concise function expression introduced in ES6.
const greet = (name) => {
console.log(`Hello, ${name}!`);
};
// Single-line arrow function
const greet = name => console.log(`Hello, ${name}!`);
Types of Functions
1. Regular Functions
The most common form of function, which can have parameters and return values.
function add(a, b) {
return a + b;
}
2. Anonymous Functions
Functions without a name, typically passed as arguments to other functions.
setTimeout(function() {
console.log('This is an anonymous function.');
}, 1000);
3. Callback Functions
Functions passed as arguments to another function and invoked after a certain operation completes.
function fetchData(callback) {
setTimeout(() => {
const data = 'Some data';
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data);
});
Asynchronous Functions
From callbacks to Promises.
Example
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
Handling asynchronous operations with async and await keywords.
Example
async function fetchUser(id) {
try {
const response = await fetch(`https://api.example.com/users/${id}`);
const user = await response.json();
return user;
} catch (error) {
console.error('Error fetching user:', error);
}
}
fetchUser(1).then(user => console.log(user));
Comparison of Invocation Methods
| Invocation Method | Example | Characteristics |
|---|---|---|
| Direct Call | greet('Alice') |
Most common method |
| Method Call | obj.method() |
this points to the calling object |
| Constructor Call | new Constructor() |
Creates a new instance |
| Indirect Call | greet.call(null, 'Bob') |
Can change the this binding |
Advanced Usage
1. Closures
A closure refers to a function that can remember and access its lexical scope, even when the function is executed outside that lexical scope.
Example
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
2. Higher-Order Functions
Functions that accept functions as arguments or return functions.
Example
function applyOperation(a, b, operation) {
return operation(a, b);
}
const sum = applyOperation(5, 3, (x, y) => x + y);
const product = applyOperation(5, 3, (x, y) => x * y);
console.log(sum); // 8
console.log(product); // 15
Function Parameter Handling
Default Parameters
Provide default values for parameters when declaring a function.
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(); // Hello, Guest!
greet('Alice'); // Hello, Alice!
Rest Parameters
Allows representing an indefinite number of arguments as an array.
Example
function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
Destructuring Parameters
Extracts data from objects or arrays and assigns them to variables.
Example
function getUserInfo({ name, age }) {
console.log(`Name: ${name}, Age: ${age}`);
}
const user = { name: 'Alice', age: 30 };
getUserInfo(user); // Name: Alice, Age: 30
Examples
Single Responsibility of Functions: Each function should do only one thing, making it easier to test and maintain.
Example
function calculateArea(width, height) {
return width * height;
}
Avoid Global Variables: Minimize the use of global variables; use local variables and function parameters instead.
Example
function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
Use Arrow Functions: Arrow functions are concise and clear, especially when handling callback functions.
Example
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob']
Error Handling: Use try...catch statements to handle potential errors.
Example
async function fetchUser(id) {
try {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const user = await response.json();
return user;
} catch (error) {
console.error('Error fetching user:', error);
}
}
Functions as Arguments: A function can be used as an argument.
Example
function say(word) {
console.log(word);
}
function execute(someFunction, value) {
someFunction(value);
}
execute(say, "Hello");
In the code above, we pass the say function as the first argument to the execute function. What is passed here is not the return value of say, but say itself!
This way, say becomes the local variable someFunction inside execute, and execute can use the say function by calling someFunction() (with parentheses).
Of course, since say has a parameter, execute can pass such a variable when calling someFunction.
Anonymous Functions: We can pass a function as a variable. However, we don't necessarily have to go through the "define first, then pass" process; we can define and pass the function directly within the parentheses of another function:
Example
function execute(someFunction, value) {
someFunction(value);
}
execute(function(word) { console.log(word); }, "Hello");
We define the function we intend to pass to execute directly where execute accepts the first argument.
In this way, we don't even need to give the function a name, which is why it's called an anonymous function.
How Function Passing Makes the HTTP Server Work
With this knowledge, let's look at our simple yet powerful HTTP server again:
Example
var http = require("http");
http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
It should now be much clearer: we passed an anonymous function to the createServer function.
The same purpose can be achieved with this code:
Example
var http = require("http");
function onRequest(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
Function Best Practices
Function Design Principles
- Single Responsibility: A function should do only one thing.
- Reasonable Naming: Use verb + noun format (e.g.,
getUserInfo). - Control Length: It's recommended not to exceed 20 lines of code.
- Avoid Side Effects: Pure functions are easier to test and maintain.
Performance Optimization Tips
Example
// Use closures to cache results
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
Practical Exercises
Exercise 1: Create a Temperature Conversion Function
Example
/**
* Implement conversion between Celsius and Fahrenheit
* @param {number} temp - Temperature value
* @param {string} unit - Original unit ('C' or 'F')
* @returns {number} Converted temperature
*/
function convertTemperature(temp, unit) {
// Your code...
}
Exercise 2: Implement a Simple Event Emitter
Example
class EventEmitter {
constructor() {
this.events = {};
}
// Implement on/emit/off methods
}
YouTip