For Loops: The Art of Iteration
Imagine you need to send New Year greeting emails to 100 employees in your company. You wouldn't manually copy and paste 100 times. Instead, you'd create an email template and have the program automatically iterate through the employee list, sending a personalized email to each person. This "repeat the same operation" scenario is where loop statements shine. In JavaScript, the for loop family is the most commonly used iteration tool.
Traditional For Loops
The most classic for loop consists of three parts: initialization, condition checking, and update expression. Its structure is like a precise clock mechanism, turning round by round according to set rules:
for (initialization; condition; increment) {
// Loop body
}Let's start with a simple example, printing numbers 1 to 5:
for (let i = 1; i <= 5; i++) {
console.log(i);
}
// Output:
// 1
// 2
// 3
// 4
// 5The execution process of this loop is:
- Initialization (
let i = 1): Executed once before the loop starts, creating counter variableiand assigning it the value 1 - Condition Check (
i <= 5): Checked before each loop iteration; if true, execute the loop body, otherwise end the loop - Execute Loop Body: Output the current value of
i - Update (
i++): After each loop body execution, incrementiby 1 - Return to step 2 and continue the next iteration
When i becomes 6, the condition i <= 5 is false, and the loop ends.
Traversing Arrays
One of the most common uses of for loops is traversing arrays. By accessing array elements through indexes, we can process each piece of data:
let fruits = ["apple", "banana", "orange", "grape", "mango"];
for (let i = 0; i < fruits.length; i++) {
console.log(`Fruit ${i + 1}: ${fruits[i]}`);
}
// Output:
// Fruit 1: apple
// Fruit 2: banana
// Fruit 3: orange
// Fruit 4: grape
// Fruit 5: mangoNote that here we use i < fruits.length instead of i <= fruits.length because array indices start at 0. If an array has 5 elements, the valid indices are 0 to 4; using <= would try to access the non-existent index 5.
Flexible Loop Control
All three parts of a for loop are optional, providing great flexibility:
// Initialize outside the loop
let i = 0;
for (; i < 5; i++) {
console.log(i);
}
// Update inside the loop body
let j = 0;
for (; j < 5; ) {
console.log(j);
j++;
}
// All parts omitted (need manual break, otherwise infinite loop)
let k = 0;
for (;;) {
if (k >= 5) break;
console.log(k);
k++;
}Although all these writing styles are legal, the standard three-part loop is clearest and should be used preferentially.
Decrement and Step Control
Loops don't necessarily have to increment; they can also decrement or use other steps:
// Counting down
for (let i = 5; i >= 1; i--) {
console.log(`Countdown: ${i}`);
}
// Output: Countdown: 5, 4, 3, 2, 1
// Skip two steps each time
for (let i = 0; i < 10; i += 2) {
console.log(i);
}
// Output: 0, 2, 4, 6, 8
// Process even indices of an array
let numbers = [10, 20, 30, 40, 50, 60];
for (let i = 0; i < numbers.length; i += 2) {
console.log(numbers[i]);
}
// Output: 10, 30, 50Reverse traversal is useful in certain scenarios, such as processing from the end of an array or deleting elements during traversal:
let items = ["a", "b", "c", "d", "e"];
// Traverse from back to front and delete to avoid index confusion
for (let i = items.length - 1; i >= 0; i--) {
if (items[i] === "c") {
items.splice(i, 1);
}
}
console.log(items); // ["a", "b", "d", "e"]Nested Loops
When dealing with multidimensional data or needing combined traversal, you can use nested loops:
// Create multiplication table
for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
console.log(`${i} × ${j} = ${i * j}`);
}
console.log("---");
}
// Output:
// 1 × 1 = 1
// 1 × 2 = 2
// 1 × 3 = 3
// ---
// 2 × 1 = 2
// 2 × 2 = 4
// 2 × 3 = 6
// ---
// 3 × 1 = 3
// 3 × 2 = 6
// 3 × 3 = 9
// ---Traverse two-dimensional arrays:
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
console.log(`matrix[${row}][${col}] = ${matrix[row][col]}`);
}
}The time complexity of nested loops grows rapidly. Two-level nesting is O(n²), three-level is O(n³). Pay special attention to performance when dealing with large datasets.
For...of Loops: Traversing Iterables
ES6 introduced for...of loops, specifically designed to traverse iterables (such as arrays, strings, Set, Map, etc.). Its syntax is more concise and doesn't require managing indexes:
let colors = ["red", "green", "blue"];
for (let color of colors) {
console.log(color);
}
// Output:
// red
// green
// blueCompared to traditional for loops, for...of focuses more on "what" rather than "how." You don't need to write colors[i]; you directly get each element. This declarative style is more readable and less error-prone.
Traversing Strings
Strings are also iterable and can be traversed character by character:
let message = "Hello";
for (let char of message) {
console.log(char);
}
// Output:
// H
// e
// l
// l
// oThis is particularly useful for handling Unicode characters because for...of correctly identifies Unicode code points without splitting some special characters into multiple parts.
Traversing Set and Map
// Set
let uniqueNumbers = new Set([1, 2, 3, 2, 1]);
for (let num of uniqueNumbers) {
console.log(num);
}
// Output: 1, 2, 3
// Map
let userRoles = new Map([
["Alice", "admin"],
["Bob", "editor"],
["Charlie", "viewer"],
]);
for (let [name, role] of userRoles) {
console.log(`${name}: ${role}`);
}
// Output:
// Alice: admin
// Bob: editor
// Charlie: viewerNote that when traversing Maps, we use destructuring assignment [name, role] to directly get key-value pairs.
Limitations of For...of
for...of cannot be directly used on plain objects because plain objects are not iterable by default:
let person = { name: "Alice", age: 30 };
// ❌ This will throw an error: person is not iterable
for (let item of person) {
console.log(item);
}
// ✅ Can traverse object values
for (let value of Object.values(person)) {
console.log(value);
}
// Output: Alice, 30
// ✅ Or traverse key-value pairs
for (let [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
// Output:
// name: Alice
// age: 30For...in Loops: Traversing Object Properties
for...in loops are used to traverse enumerable properties of an object (including inherited properties):
let car = {
brand: "Tesla",
model: "Model 3",
year: 2023,
color: "white",
};
for (let key in car) {
console.log(`${key}: ${car[key]}`);
}
// Output:
// brand: Tesla
// model: Model 3
// year: 2023
// color: whiteFor...in with Arrays
Technically, for...in can also be used with arrays, but it's not recommended:
let numbers = [10, 20, 30];
// ❌ Not recommended: for...in traversing arrays
for (let index in numbers) {
console.log(typeof index); // string!
console.log(numbers[index]);
}The problems are:
for...intraverses indices as strings, not numbers- It traverses all enumerable properties, including any custom properties you might have added to the array
- Traversal order is not guaranteed (although most engines follow index order)
For arrays, you should use traditional for loops, for...of, or array methods:
let numbers = [10, 20, 30];
// ✅ Recommended approach
for (let num of numbers) {
console.log(num);
}
// Or
numbers.forEach((num) => console.log(num));Filtering Inherited Properties
for...in traverses both the object's own and inherited enumerable properties. If you only want to process own properties, use hasOwnProperty:
let animal = { eats: true };
let rabbit = Object.create(animal);
rabbit.jumps = true;
for (let prop in rabbit) {
console.log(`${prop}: ${rabbit[prop]}`);
}
// Output:
// jumps: true
// eats: true (inherited)
// Only traverse own properties
for (let prop in rabbit) {
if (rabbit.hasOwnProperty(prop)) {
console.log(`${prop}: ${rabbit[prop]}`);
}
}
// Output:
// jumps: trueOr more concisely use Object.keys(), Object.values(), or Object.entries(), which only return own properties:
let rabbit = { jumps: true, color: "white" };
// Only traverse keys of own properties
for (let key of Object.keys(rabbit)) {
console.log(key);
}
// Only traverse values of own properties
for (let value of Object.values(rabbit)) {
console.log(value);
}
// Traverse key-value pairs
for (let [key, value] of Object.entries(rabbit)) {
console.log(`${key}: ${value}`);
}Comparison of Three For Loops
| Feature | for | for...of | for...in |
|---|---|---|---|
| Main Use | General loop, precise control | Traverse iterables (arrays, strings, Set, Map, etc.) | Traverse object properties |
| Applies To | Any scenario | Arrays, strings, Set, Map, etc. | Objects |
| Content Get | Need index access | Directly get element values | Get property keys |
| Index/Key Type | Number | - | String |
| Performance | Fastest | Slightly slower | Slower |
| Readability | Moderate | High (concise) | High (clear) |
When to use:
- for: Need precise loop control, such as custom step sizes, complex conditions, performance-critical scenarios
- for...of: Traverse arrays, strings, and other iterables, focusing on element values
- for...in: Traverse object properties
Practical Application Scenarios
1. Data Transformation
let prices = [19.99, 29.99, 39.99, 49.99];
let pricesWithTax = [];
// Calculate prices including tax
for (let i = 0; i < prices.length; i++) {
pricesWithTax[i] = prices[i] * 1.1; // 10% tax
}
console.log(pricesWithTax); // [21.989, 32.989, 43.989, 54.989]
// Using for...of is more concise
let pricesWithTax2 = [];
for (let price of prices) {
pricesWithTax2.push(price * 1.1);
}2. Finding Elements
let users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
function findUserById(id) {
for (let user of users) {
if (user.id === id) {
return user;
}
}
return null; // Not found
}
console.log(findUserById(2)); // { id: 2, name: "Bob" }
console.log(findUserById(5)); // null3. Accumulative Calculation
let scores = [85, 92, 78, 95, 88];
let total = 0;
for (let score of scores) {
total += score;
}
let average = total / scores.length;
console.log(`Average score: ${average}`); // Average score: 87.64. Building HTML
let items = ["Home", "About", "Services", "Contact"];
let navHTML = "<nav><ul>";
for (let item of items) {
navHTML += `<li><a href="#${item.toLowerCase()}">${item}</a></li>`;
}
navHTML += "</ul></nav>";
console.log(navHTML);
// <nav><ul><li><a href="#home">Home</a></li><li><a href="#about">About</a></li>...</ul></nav>5. Batch Operations
let tasks = [
{ id: 1, title: "Write report", completed: false },
{ id: 2, title: "Review code", completed: false },
{ id: 3, title: "Update docs", completed: false },
];
// Mark all tasks as completed
for (let task of tasks) {
task.completed = true;
console.log(`Marked "${task.title}" as completed`);
}Performance Optimization Tips
1. Cache Array Length
In traditional for loops, if the loop condition is i < array.length, the length property is accessed on every iteration. For large arrays, you can cache this value:
let data = new Array(1000000).fill(0);
// ❌ Access length every time
for (let i = 0; i < data.length; i++) {
// Process data[i]
}
// ✅ Cache length
let len = data.length;
for (let i = 0; i < len; i++) {
// Process data[i]
}However, modern JavaScript engines usually optimize this situation, so the performance difference might be minimal. Readability is often more important.
2. Choose Appropriate Loop Type
For simple array traversal, although for...of is slightly slower, the difference is usually negligible, and the readability improvement is tangible. Only consider using traditional for loops in performance-critical code paths.
3. Avoid Expensive Operations in Loops
// ❌ Query DOM every time
for (let i = 0; i < 100; i++) {
document.getElementById("output").innerHTML += i + "<br>";
}
// ✅ Build string first, then update DOM once
let output = "";
for (let i = 0; i < 100; i++) {
output += i + "<br>";
}
document.getElementById("output").innerHTML = output;Common Pitfalls
1. Modifying Array While Traversing
let numbers = [1, 2, 3, 4, 5];
// ❌ Dangerous: deleting elements while traversing
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
numbers.splice(i, 1); // Delete even numbers
}
}
console.log(numbers); // [1, 3, 5] but might skip certain elements
// ✅ Traverse from back to front
let numbers2 = [1, 2, 3, 4, 5];
for (let i = numbers2.length - 1; i >= 0; i--) {
if (numbers2[i] % 2 === 0) {
numbers2.splice(i, 1);
}
}
console.log(numbers2); // [1, 3, 5]2. Closure Traps (var vs let)
// ❌ Using var
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // Outputs 3, 3, 3
}, 100);
}
// ✅ Using let
for (let j = 0; j < 3; j++) {
setTimeout(function () {
console.log(j); // Outputs 0, 1, 2
}, 100);
}When using var, all iterations share the same i variable. When using let, each iteration has its own j copy.
3. For...in Traversal of Arrays Pitfall
let arr = ["a", "b", "c"];
arr.customProp = "custom";
// ❌ for...in will traverse custom properties
for (let index in arr) {
console.log(arr[index]);
}
// Output: a, b, c, custom
// ✅ for...of only traverses array elements
for (let item of arr) {
console.log(item);
}
// Output: a, b, cSummary
For loops are indispensable tools in JavaScript, and mastering them allows you to efficiently handle various iteration scenarios. Traditional for loops provide maximum flexibility and control, for...of makes traversing iterables concise and elegant, and for...in is the go-to tool for traversing object properties.
Key points:
- Traditional
forloops are suitable for scenarios requiring precise control for...ofis the preferred choice for traversing arrays, strings, and other iterables with concise syntaxfor...inis used for traversing object properties, not recommended for arrays- Pay attention to time complexity with nested loops
- Avoid modifying arrays while traversing them
- Use
letinstead ofvarto avoid closure issues - Performance optimization should be done after confirming bottlenecks; readability is equally important
Choosing appropriate loop types can make your code both efficient and readable. As you gain more practice, you'll become more proficient in using these tools.