SoFunction
Updated on 2025-03-01

Learn javascript loops from me

1. Use arrays instead of Object types to represent sequential collections

The ECMAScript standard does not specify the storage order of properties in JavaScript's Object type.

However, when using the for..in loop to traverse the properties in the Object, it does need to rely on some order. Because ECMAScript does not explicitly standardize this order, each JavaScript execution engine can be implemented according to its own characteristics, so the behavior consistency of the for..in loop cannot be guaranteed in different execution environments.

For example, the result of the following code when calling the report method is uncertain:

function report(highScores) { 
  var result = ""; 
  var i = 1; 
  for (var name in highScores) { // unpredictable order 
    result += i + ". " + name + ": " + 
    highScores[name] + "\n"; 
    i++; 
  } 
  return result; 
} 
report([{ name: "Hank", points: 1110100 }, 
{ name: "Steve", points: 1064500 }, 
{ name: "Billy", points: 1050200 }]); 
// ? 

If you really need to ensure that the results of the run are based on the order of data, use array types to represent the data first, rather than directly using the Object type. At the same time, try to avoid using for..in loops, and use explicit for loops:

function report(highScores) { 
  var result = ""; 
  for (var i = 0, n = ; i < n; i++) { 
    var score = highScores[i]; 
    result += (i + 1) + ". " + 
     + ": " +  + "\n"; 
  } 
  return result; 
} 
report([{ name: "Hank", points: 1110100 }, 
{ name: "Steve", points: 1064500 }, 
{ name: "Billy", points: 1050200 }]); 
// "1. Hank: 1110100 2. Steve: 1064500 3. Billy: 1050200\n" 

Another behavior that relies specifically on order is the calculation of floating point numbers:

var ratings = { 
  "Good Will Hunting": 0.8, 
  "Mystic River": 0.7, 
  "21": 0.6, 
  "Doubt": 0.9 
}; 

In Item 2, it is mentioned that the addition operation of floating-point numbers cannot even satisfy the commutational law:
The results of (0.1 + 0.2) + 0.3 and the results of 0.1 + (0.2 + 0.3) are respectively
0.60000000000000001 and 0.6

Therefore, for floating-point arithmetic operations, it is even more impossible to use any order:

var total = 0, count = 0; 
for (var key in ratings) { // unpredictable order 
  total += ratings[key]; 
  count++; 
} 
total /= count; 
total; // ? 

When the traversal order of for..in is different, the final total result is also different. The following are the two calculation orders and their corresponding results:

(0.8 + 0.7 + 0.6 +0.9) / 4 // 0.75
(0.6 + 0.8 + 0.7 +0.9) / 4 // 0.7499999999999999

Of course, for the calculation of floating-point numbers, there is a solution to the problem of using integer numbers to represent them. For example, we first enlarge the above floating-point number 10 times into integer data, and then shrink it by 10 times after the calculation is finished:

(8+ 7 + 6 + 9) / 4 / 10  // 0.75
(6+ 8 + 7 + 9) / 4 / 10  // 0.75

2. Never add enumerable attributes to it

If your code depends on the for..in loop to iterate over properties in the Object type, do not add any enumerable properties to iterate.

However, when enhancing the JavaScript execution environment, new attributes or methods are often needed to add to the object. For example, you can add a method to get all the attribute names in an object:

 = function() { 
  var result = []; 
  for (var key in this) { 
    (key); 
  } 
  return result; 
}; 

But the result is like this:

({ a: 1, b: 2, c: 3}).allKeys(); // ["allKeys", "a", "b","c"]

A viable solution is to use functions instead of defining new methods on:

function allKeys(obj) { 
  var result = []; 
  for (var key in obj) { 
    (key); 
  } 
  return result; 
} 

But if you really need to add a new property upwards and don't want the property to be traversed in the for..in loop, you can use the methods provided by the ES5 environment:

(, "allKeys", { 
  value: function() { 
    var result = []; 
    for (var key in this) { 
      (key); 
    } 
    return result; 
  }, 
  writable: true, 
  enumerable: false, 
  configurable: true 
}); 

The key part of the above code is to set the enumerable property to false. In this way, the property cannot be traversed in the for..in loop.

3. For array traversal, use for loops instead of for..in loops.

Although the previous Item has already mentioned this issue, can you tell the final average for the following code?

var scores = [98, 74, 85, 77, 93, 100, 89]; 
var total = 0; 
for (var score in scores) { 
  total += score; 
} 
var mean = total / ; 
mean; // ? 

Through calculation, the final result should be 88.

But don't forget that in the for..in loop, the key is always traversed, not value, and the same is true for arrays. Therefore, the score in the for..in loop above is not the expected series of values ​​such as 98 and 74, but a series of indexes such as 0 and 1.

So you might think the final result is:
(0 + 1+ …+ 6) / 7 = 21

But this answer is also wrong. Another key point is that the type of key in the for..in loop is always string type, so the + operator here actually performs string splicing operations:

The final total obtained is actually the string 00123456. The value after converting this string to a numeric type is 123456, and then dividing it by the number of elements 7, you will get the final result: 17636.571428571428

Therefore, for array traversal, it is better to use a standard for loop

4. Use traversal methods instead of loops

When using loops, it is easy to violate the DRY (Don't Repeat Yourself) principle. This is because we usually choose the copy-paste method to avoid handwriting segments of loop statements. But doing this makes it possible for a lot of duplicate code to appear in the code, and developers are meaninglessly "reproducing the wheel". More importantly, it is easy to ignore the details in the loop when copying and pasting, such as the starting index value, the termination judgment condition, etc.

For example, the following for loop has this problem, assuming n is the length of the collection object:

for (var i = 0; i &lt;= n; i++) { ... }
// Termination condition error, it should be i < nfor (var i = 1; i &lt; n; i++) { ... }
// The starting variable is incorrect, it should be i = 0for (var i = n; i &gt;= 0; i--) { ... }
// The starting variable is incorrect, it should be i = n - 1for (var i = n - 1; i &gt; 0; i--) { ... }
// The termination condition is incorrect, it should be i >= 0

It can be seen that errors are prone to processing some details of the loop. Using the closure provided by JavaScript (see Item 11), the details of the loop can be encapsulated and reused. In fact, ES5 provides some ways to deal with this problem. One of them is the simplest one. Using it, we can write the loop like this:

// Use a for loopfor (var i = 0, n = ; i &lt; n; i++) {
  players[i].score++;
}

// Use forEach(function(p) {
  ++;
});

In addition to traversing the collection object, another common pattern is to perform some operation on each element in the original collection and then obtain a new collection. We can also use the forEach method to implement the following:

// Use a for loopvar trimmed = [];
for (var i = 0, n = ; i &lt; n; i++) {
  (input[i].trim());
}

// Use forEachvar trimmed = [];
(function(s) {
  (());
});

But because this pattern of converting one set to another is very common, ES5 also provides methods to make the code simpler and more elegant:

var trimmed = (function(s) {
  return ();
});

In addition, there is another common pattern that filters the set according to certain conditions and then obtains a subset of the original set. This mode is provided in ES5. This method accepts a Predicate as an argument, which is a function that returns true or false: returning true means that the element will be retained in the new set; returning false means that the element will not appear in the new set. For example, we use the following code to filter the price of the product, and only the products with prices in the [min, max] range are retained:

(function(listing) {
  return  >= min &&  <= max;
});

Of course, the above method is available in ES5-enabled environments. In other environments, we have two options: 1. Use third-party libraries such as underscore or lodash, which provide quite a few common methods to manipulate objects and collections. 2. Define it as needed.

For example, define the following method to obtain the previous elements of the set according to a certain condition:

function takeWhile(a, pred) {
  var result = [];
  for (var i = 0, n = ; i < n; i++) {
    if (!pred(a[i], i)) {
      break;
    }
    result[i] = a[i];
  }
  return result;
}

var prefix = takeWhile([1, 2, 4, 8, 16, 32], function(n) {
  return n < 10;
}); // [1, 2, 4, 8]

In order to better reuse this method, we can define it on the object. For specific impact, please refer to Item 42.

 = function(pred) {
  var result = [];
  for (var i = 0, n = ; i < n; i++) {
    if (!pred(this[i], i)) {
      break;
    }
    result[i] = this[i];
  }
  return result; 
};

var prefix = [1, 2, 4, 8, 16, 32].takeWhile(function(n) {
  return n < 10;
}); // [1, 2, 4, 8]

It is better to use loops in only one occasion than to use traversal functions: when you need to use break and continue. For example, when using forEach to implement the above takeWhile method, there will be problems. How should it be implemented when predicate is not satisfied?

function takeWhile(a, pred) {
  var result = [];
  (function(x, i) {
    if (!pred(x)) {
      // ?
    }
    result[i] = x;
  });
  return result;
}

We can use an internal exception to make the judgment, but it is also a bit clumsy and inefficient:

function takeWhile(a, pred) {
  var result = [];
  var earlyExit = {}; // unique value signaling loop break
  try {
    (function(x, i) {
      if (!pred(x)) {
        throw earlyExit;
      }
      result[i] = x;
    });
  } catch (e) {
    if (e !== earlyExit) { // only catch earlyExit
      throw e;
    }
  }
  return result;
}

But after using forEach, the code is even longer than before using it. This is obviously a problem. For this problem, ES5 provides some and every methods to deal with loops with premature termination, and their usage is as follows:

[1, 10, 100].some(function(x) { return x > 5; }); // true
[1, 10, 100].some(function(x) { return x < 0; }); // false

[1, 2, 3, 4, 5].every(function(x) { return x > 0; }); // true
[1, 2, 3, 4, 5].every(function(x) { return x < 3; }); // false

Both methods are short-circuiting: as long as any element returns true in the predicate of some method, some will return; if any element returns false in the predicate of every method, then the every method will also return false.

Therefore, takeWhile can be implemented as follows:

function takeWhile(a, pred) {
  var result = [];
  (function(x, i) {
    if (!pred(x)) {
      return false; // break
    }
    result[i] = x;
    return true; // continue
  });
  return result;
}

In fact, this is the idea of ​​functional programming. In functional programming, you rarely see explicit for loops or while loops. The details of the loop are well encapsulated.

5. Summary

  • When using for..in loops, do not depend on the order of traversals.
  • When using the Object type to save data, it is necessary to ensure that the data in it is disordered.
  • When you need to represent a collection with order, use an array type instead of an Object type.
  • Avoid adding any attributes to it.
  • If it is indeed necessary to add method properties to it, consider using a standalone function instead.
  • Use to add properties that can not be traversed by the for..in loop.
  • When iterating through an array, use a standard for loop instead of for..in loop.
  • Consider pre-save the length of the array when necessary to improve performance.
  • Use traversal methods and instead of loops to make the code clearer and more readable.
  • For repeated loops, consider abstracting them. Through methods provided by third parties or by yourself.
  • Explicit loops are useful in some cases, and some or every method can be used accordingly.

The above is the entire content of this article. I hope that through this article, you will understand the principles of javascript loops better and make progress together.