Home > Web Front-end > JS Tutorial > An in-depth discussion on optimization techniques for loop statements in JavaScript_javascript techniques

An in-depth discussion on optimization techniques for loop statements in JavaScript_javascript techniques

WBOY
Release: 2016-05-16 16:45:54
Original
1137 people have browsed it

Loops are one of the most important mechanisms in all programming languages. Almost any computer program with practical significance (sorting, query, etc.) does not contain loops. Loops are also a very troublesome part of program optimization. We often need to constantly optimize the complexity of programs, but we are entangled in the choice between time complexity and space complexity because of loops.

In javascript, there are three kinds of native loops, for () {}, while () {} and do {} while (), among which for () {} is the most commonly used.

However, for is the type of loop that JavaScript engineers most easily ignore when optimizing programs.

Let’s first review the basic knowledge of for.
The for syntax of JavaScript is inherited from the C language. There are two ways to use the basic syntax of for loop.

1. Loop array

Basic syntax of for loop

Copy code The code is as follows:

for ( /* Initialization*/2 /* Judgment condition */2 /* Loop processing*/ ) {
//... Logic code
}

We use a piece of example code to explain in detail.

Copy code The code is as follows:

var array = [1, 2, 3, 4, 5];
var sum = 0;

for (var i = 0, len = array.length; i < len; i) {
sum = array[i];
}

console.log('The sum of the array's items is %d.', sum);
//=> The sum of the array's items is 15.

In this code, we first define and initialize an array to store the items to be accumulated and a sum integer variable. Next, we start looping. In the initialization code of the for loop, we also defined and initialized two variables: i (counter) and len (alias for the length of the loop array). When i is less than len, the loop condition is established and the logic code is executed; each time the logic After the code is executed, i is incremented by 1.

In the loop logic code, we add the current loop array item to the sum variable.
This cycle is represented by a flow chart as follows:

An in-depth discussion on optimization techniques for loop statements in JavaScript_javascript techniques

From this flow chart, we can easily find that the real loop body in the program not only includes our logic code, but also includes the execution judgment and loop processing to implement the loop itself.
In this way, our optimization ideas are clear, and we can optimize from four aspects.

1. Initialization code before the loop body
2. Execution judgment conditions in the loop body
3. Logic code
4. Processing code after the logic code

ps: There is an important relationship between the first point and the second point.


1.1 Optimize initialization code and execution judgment conditions

Let’s first look at a piece of code that everyone is very familiar with.

Copy code The code is as follows:

// wrong!
for (var i = 02 i < list.length2 i) {
//... Logic code
}

I believe that most engineers who write JavaScript still use this seemingly normal loop. method, but why am I saying it is wrong here?
Let’s take apart everything in this loop and take a look:

1. Initialization code - This loop only defines and initializes a counter variable.
2. Execution judgment condition - true when the counter is less than the length of the list.
3. Processing code - the counter increases by 1.

Let’s review the flow chart above again. Do we find anything wrong?
The real loop body not only contains our logic code, but also includes the execution judgment and processing code to implement the loop itself. In other words, the judgment condition i < list.length must be executed before each loop. In JavaScript, when reading the properties or methods of an object, a query is required.
You seem to understand something? There are two operations for this judgment condition: 1. Query the length attribute from the list array; 2. Compare the sizes of i and list.length.
Assuming that the list array contains n elements, the program needs to perform 2n operations in the execution judgment of this loop.

If we change the code to this:

Copy code The code is as follows:

// Well
for (var i = 0 , len = list.length; i < len; i) {
//...
}

In this improved code, we added a definition and initialized a len variable in the initialization code before the loop body is executed, which is used to store the value of list.length (about variables, expressions, pointers and values The relevant content will be discussed in the second part). In this way, we do not need to query the attributes of the list array again in the execution judgment in the loop body, and the number of operations is half of the original one.

We have improved the time complexity of the algorithm in the above steps, but if we want to continue to optimize the space complexity, what should we do? If your logic code is not restricted by the loop order, you can try the following optimization methods.

Copy code The code is as follows:

for (var i = list.length - 1; i >= 0; --i) {
//...
}

This code reverses the order of the loop, starts the i counter from the last element index (list.length - 1), and loops forward. In order to reduce the number of variables required for the loop to 1, and in the execution judgment, the number of variable queries is reduced, and the time-consuming before executing the CPU instruction is reduced.

1.2 Optimize logic code

In the loop, we get the current array element of the loop naturally in order to perform some operations on it or use it, which will inevitably lead to several calls to the element.

Copy code The code is as follows:

var array = [
{ name: 'Will Wen Gunn', type: 'hentai' },
{ name: 'Vill Lin', type: 'moegril' }
];

for (var i = array.length - 1; i >= 0; --i) {
console.log('Name: %s', array[i].name);
console.log('He/She is a(n) %s', array[i].type);

console.log('rn');
}
/*=>
Name: Vill Lin
He/She is a(n) moegril

Name: Will Wen Gunn
He/She is a(n) hentai
*/

In this code, the program needs to query the name and type attributes of each array element. If the array has n elements, the program performs 4n object lookups.

Copy code The code is as follows:

1. array[i]
2. array [i].name
3. array[i]
4. array[i].type

I believe you must have thought of a solution at this time, which is to assign the value of the current array element to a variable and then use it in the logic code.

Copy code The code is as follows:

var array = [
{ name: 'Will Wen Gunn', type: 'hentai' },
{ name: 'Vill Lin', type: 'moegril' }
];
var person = null;

for (var i = array.length - 1; i >= 0 && (person = array[i]); --i) {
console.log('Name: %s', person. name);
console.log('He/She is a(n) %s', person.type);

console.log('rn');
}
person = null;

This does look a lot more beautiful.

Copy code The code is as follows:

1. array[i] => var person
2. person.name
3. person.type

It’s a bit like foreach in emcascript5, but there is a big difference between the two, so I won’t explain it here.

ps: Thank you for your corrections. After experiments, we learned that if the elements in the array are defined by direct value transfer, the value obtained in the loop must be a value, not a pointer. So whether you are defining an expression or a variable, there will be additional memory space requirements.

1.3 Optimized processing code

Actually, there is not much that can be optimized in the processing code in the loop body. It is enough for the i counter to increase by 1.
ps: If you have any good suggestions or methods, please provide them. :)

2. Loop object (object)

In javascript, for can also traverse the properties and methods of object. It should be noted that the for loop cannot traverse the packaging type to which the object belongs or the prototype properties and methods (prototype) in the constructor.

The syntax is simpler than looping an array.

Copy code The code is as follows:

for (/* initialization*/ var key in object) {
//... Logic code
}

We often use this method to operate objects.

Copy code The code is as follows:

var person = {
'name' : ' Will Wen Gunn',
'type' : 'hentai',
'skill' : ['Programming', 'Photography', 'Speaking', 'etc']
};

for (var key in person) {
value = person[key];

// if the value is array, convert it to a string
if (value instanceof Array) {
value = value.join(', ');
}

console.log('%s: %s', key, value);
}
/*=>
name: Will Wen Gunn
type: hentai
skill : Programming, Photography, Speaking, etc
*/

If you have ever used mongodb, you will definitely be familiar with its query mechanism. Because mongodb's query mechanism is like the soul of its API, the flexible curd operation method has won mongodb a lot of popularity and development momentum.

In the mongo api implementation of nanodb, the query implementation uses loop objects extensively.

Copy code The code is as follows:

var myDB = nano.db('myDB');
var myColl = myDB.collection('myColl');

var _cursor = myColl.find({
type : 'repo',
language : 'JavaScript'
});

_cursor
.sort({
star: 1
})
.toArray(function(err, rows) {
if (err)
return console.error( err);

console.log(rows);
});

What we need to optimize is not the loop itself, but the optimization of the objects you need to traverse.
For example, the nanocollection class in nanodb, although it looks like an array on the surface, stores all the elements, or an object, uses the id of the element as the key, and then stores the elements.

But this is not the case. Students who have used underscore before should know the _.invert method. This is a rather interesting method that reverses the keys and values ​​of the object passed in.

Copy code The code is as follows:

var person = {
'name' : ' Will Wen Gunn',
'type' : 'hentai'
};

var _inverted = _.invert(person);
console.log(_inverted);
/*=>
{
'Will Wen Gunn' : 'name',
'hentai' : 'type'
}
*/

If you need to use a loop object to query the value of some properties of the object, then you can try the following method.

Copy code The code is as follows:

var person = {
'name' : ' Will Wen Gunn',
'type' : 'hentai'
};

var name = 'Will Wen Gunn';

var _inverted = _.invert(person);

if (_inverted[name] === 'name') {
console.log('Catched!');
}
//=> Catched!

However, there is not much that can be optimized using for for object query. Everything needs to be based on actual needs. :p

Next let’s look at the other two loops, while () {} and do {} while (). I believe that anyone who has taken a computer science course will be familiar with these two cycles. The only difference between them is the logical order in which the loop body is executed.

The execution sequence of while () {} is similar to that of for () {}. The execution judgment is before the logic code, but the initialization and processing code is omitted.
When the condition is given, the logic code is executed until the condition is no longer true.

Copy code The code is as follows:

var sum = 0;

while (sum < 10) {
sum = sum 1;
}

console.log(sum);
//=> 15

do {} while () puts the execution judgment after the logic code, that is, "cut first and play later".

Copy code The code is as follows:

var sum = 0;

do {
sum = sum 1;
} while (sum < 10);

console.log(sum);
//=> 15

while () {} and do {} while () also do not require a counter, but use certain conditions to determine whether to execute or continue to execute the logic code.

3. while () {} and do {} while ()

while () {} and do {} while () are mainly used in business logic to continuously perform a series of operations to achieve a certain purpose, such as task queues.

But these two types of loops are dangerous because they are only controlled by execution conditions by default. If the logic code does not have any impact on the execution judgment, an infinite loop will occur.

Copy code The code is as follows:

var sum = 02

// warning!
while (sum < 10) {
sum = 1 12
}

Such code is tantamount to while (true) {}, so before using it, you must clarify the execution conditions and how to affect the execution conditions.

4. Make good use of loop control statements

I believe that all JavaScript engineers have used the break statement, but the continue statement is relatively rarely used. In fact, continue can be found in many excellent JavaScript open source projects.

In order to better understand the function of the continue statement, let’s take a look at an example code first

Copy code The code is as follows:

// Node.js Broadcast Server
var net = require('net');
var util = require('util');

var broadcastServer = net.createServer();

// Client Store
broadcastServer.clients = [];

// Clients Broadcast Method
net.Socket.prototype.broadcast = function(msg) {
var clients = broadcastServer.clients;
// Get the client that publishes the broadcast in the collection Standard
var index = clients.indexOf(this);

for (var i = clients.length - 1; i >= 0; --i) {
if (i === index) {
// If it is the client that publishes the broadcast, Then end the current loop
continue;
}

currClient = clients[i];

if (!currClient.destroyed) {
currClient.write(
util.format(
) 'r[Echo Client %s:%d] %snInput: ',
currClient.remoteAddress , currClient.remotePort, msg)
);
}
}
};

// A new client connected
broadcastServer.on('connection', function(client) {
broadcastServer.clients.push(client);

// Welcome
client.write('[Broadcast Server] Welcome!nInput:');
client.broadcast(client, 'Joined!');

// Message handle
client.on('data', function(msg) {
client.broadcast(msg);
client.write('rInput:');
} );

// Disconnect handle
client.on('end', function() {
client.broadcast('Left!');
})
});

// Bind
broadcastServer.listen(8080, function() {
console.log('Broadcast Server bound.');
});

This code implements a broadcast server based on the net module of node.js. In the broadcast method, we use the continue statement to distribute information to all established connections except the client that publishes the broadcast. client.

The content of the code is quite simple. When a client needs to publish a broadcast to other clients, the broadcast method of the client object corresponding to the client is called. In the broadcast method, the program will first obtain the cache of the current client. position index in the client socket collection, and then publish all client sockets in a loop. When the loop counter reaches the position index obtained previously, the logic code in the current loop body is skipped and the next loop is continued.

I believe that engineers who have studied C/C language will get this advice from various places: "Don't use goto statements."

This "notorious" goto statement is actually a code flow controller. The details of the goto statement will not be explained in detail here. However, JavaScript does not have an obvious goto statement, but from the break statement and continue statement, it is not difficult to find the shadow of goto in JavaScript.

This is because the break statement and continue statement allow accepting a defined label name for code jump.

Let’s take a look at the example code provided by mdn.

Copy code The code is as follows:

var i, j;

loop1:
for (i = 0; i < 3; i ) { //The first for statement is labeled "loop1"
loop2:
for (j = 0; j < 3; j ) { //The second for statement is labeled "loop2"
if (i == 1 && j == 1) {
continue loop1;
console.log ("i = " i ", j = " j);
}
}
}

// Output is:

// "i = 0, j = 0"
// "i = 0, j = 1"
// "i = 0, j = 2"
// "i = 1, j = 0"
// "i = 2, j = 0"
// "i = 2, j = 1"
// "i = 2, j = 2"
// Notice how it skips both "i = 1, j = 1" and "i = 1, j = 2"

In this example code, two levels of loops are implemented, and a label is defined outside each level of loop for subsequent calls to the continue statement.

The first loop is in the label of loop1, which means that in the subsequent program, if the loop1 label is selected in the continue statement or break statement, the outermost loop will be jumped out.

The second-level loop is in the label of loop2 in the top-level loop. If the loop2 label is selected in the continue statement or break statement, it will return to the loop body of the top-level loop.

By using loop control statements, we can interfere with the original loop execution judgment, so that we can build a very complex logic system. As an aside, there are a lot of goto statements in the Linux kernel. As for why we still often hear people saying not to use goto statements, just google them.

5. Advanced loop

5.1 Unroll loop

Let’s take a look at two pieces of code first. Guess which one has better performance.

Copy code The code is as follows:

// Setup
var array = [
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"]
];
function process(item) {
  // Do something with item
}

// Case 1
for (var i = array.length - 1; i >= 0; i--) {
  for (var j = array[i].length - 1; j >= 0; i--) {
    process(array[i][j]);
  }
}

// Case 2
for (var i = array.length - 1; i >= 0; i = i - 4) {
  for (var j = array[i].length - 1; j >= 0; j = j - 6) {
    process(array[i][j]);
    process(array[i][j - 1]);
    process(array[i][j - 2]);
    process(array[i][j - 3]);
    process(array[i][j - 4]);
    process(array[i][j - 5]);
  }
  for (var j = array[i - 1].length - 1; j >= 0; j = j - 6) {
    process(array[i][j]);
    process(array[i][j - 1]);
    process(array[i][j - 2]);
    process(array[i][j - 3]);
    process(array[i][j - 4]);
    process(array[i][j - 5]);
  }
  for (var j = array[i - 2].length - 1; j >= 0; j = j - 6) {
    process(array[i][j]);
    process(array[i][j - 1]);
    process(array[i][j - 2]);
    process(array[i][j - 3]);
    process(array[i][j - 4]);
    process(array[i][j - 5]);
  }
  for (var j = array[i - 3].length - 1; j >= 0; j = j - 6) {
    process(array[i][j]);
    process(array[i][j - 1]);
    process(array[i][j - 2]);
    process(array[i][j - 3]);
    process(array[i][j - 4]);
    process(array[i][j - 5]);
  }
}


我需要对array中的所有子数组的元素进行历遍,有两种方案,一种是我们平常所使用的方法,另一种是把循环任务展开。 答案是 case 2 性能更好,因为在每 6 个元素之间的执行判断都全部删除了,自然比往常的都要快。

这里我们来看看一种更给力的解决方案。 如果一个业务环节中需要对大数据集进行迭代处理,而这个数据集从开始迭代起,数据量不会再改变, 那麼可以考虑採用一种名为 duff 装置的技术。这项技术是以其的创造者 tom duff 的名字来命名的, 这项技术最先实现於 c 语言。后来 jeff greenberg 将其移植到 javascript 中,并经过 andrew b. king 修改并提出了一种更为高效的版本。

复制代码 代码如下:

//credit: Speed Up Up Your Site (New Riders, 2003)
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;

if (leftover > 0) {
  do {
    process(values[i ]);
  } while (--leftover > 0);
}
do {
  process(values[i ]);
  process(values[i ]);
  process(values[i ]);
  process(values[i ]);
  process(values[i ]);
  process(values[i ]);
  process(values[i ]);
  process(values[i ]);
} while (--iterations > 0);

这种技术的工作原理是通过计算values的长度除以 8 以得到需要迭代的次数,并以math.floor()函数来保证结果为整数, 然后再计算不能被 8 整除时的餘数,并对这些元素单独进行处理,其餘则 8 次为单次展开次数来进行迭代。

我将这种装置再加以封装,可以得到一种带有异步味道的 api。

复制代码 代码如下:

function duff(array, mapper) {
var n = Math.floor(array.length / 8);
var l = array.length % 8;

var i = 0;
if (l > 0) {
do {
mapper(array[i ]);
} while (--i > 0);
}
do {
mapper(array[i]);
mapper(array[i]);
mapper(array[i]);
mapper(array[i] );
mapper(array[i]);
mapper(array[i]);
mapper(array[i]);
mapper(array[i]);
} while (--n > 0);
}

duff([...], function(item) {
//...
});

Here is a set of performance tests and results for the above three iterative solutions. http://jsperf.com/spreaded-loop

5.2 Non-native loop

In any programming language, loops can be implemented not only through the native loop statements provided by the language, but also indirectly through other methods.

Let us first review some content in high school mathematics - the general formula of a sequence.

Copy code The code is as follows:

bacause
a[1] = 1
a[n] = 2 * a[n - 1] 1

so
                                                                                                                                                                                                                                           a[n] 1 = 2 * a[n - 1] 2 [n - 1] 1) = 2

then

a[n] 1 = (a[n] 1) / (a[n - 1] 1) * (a[n - 1] 1) / (a[n - 2] 1) * ... * (a[2] 1) / (a[1] 1) * (a[i] 1)

a[n] 1 = 2 * 2 * ... * 2 * 2
a[n] 1 = 2^n
a[n] = 2^n - 1

final

a[n] = 2^n - 1



After reading the simple calculation above, you may have guessed what we are going to discuss. Yes, we can also use recursion to implement loops.

Recursion is a very important application method in mathematics and computer science. It means that a function calls itself when it is used.

In the node.js community, recursion is used to implement a very important technology: middleware technology. This is a piece of middleware implementation code in a new version of webjs that has not yet been released.


Copy code The code is as follows:

/**
 * Middlewares run method
 * @param  {String} url Current request url
 * @param  {Object} req the request object
 * @param  {Object} res the response object
 * @param  {Function} out Complete Callback
 * @return {Function}     the server
 */
server.runMiddlewares = function(url, req, res, out) {
  var index = -1;

  var middlewares = this._usingMiddlewares;

  // run the next middleware if it is exists
  function next(err) {
    index ;

    // current middleware
    var curr = middlewares[index];

    if (curr) {
      var check = new RegExp(curr.route);

      // Check the route
      if (check.test(url)) {
        try {
          function later() {
            debug('A middleware says it need to be later on %s', url);
            // The dependencies do not right now
            if (middlewares.indexOf(curr) !== middlewares.length - 1) {
              _later(curr);
              index--;
              next();
            } else {
              debug('A middleware dependencies wrong');

              // This middleware can not run
              out();
            }
          }

          // Run the middleware
          if (utils.isFunc(curr.handler)) {

            // Normal middleware function
            curr.handler(req, res, next, later);

          } else if (utils.isObject(curr.handler) && utils.isFunc(curr.handler.emit)) {

            // Server object
            curr.handler.emit('request', req, res, next, later);

          } else {

            // There are something wrong about the middleware
            next();

          }
        } catch(err) {
          next();
        }
      } else {
        next();
      }
    } else {
      // Out to next step of the pipeline
      out();
    }
  }

  // if the middleware depend on other middlewares,
  // it can let it later to run
  function _later(curr) {
    var i = middlewares.indexOf(curr);
    var _tmp1 = middlewares.slice(0, i);
    _tmp1.push(middlewares[i 1], curr);
    var _tmp2 = middlewares.slice(i 2);
    [].push.apply(_tmp1, _tmp2);
    middlewares = _tmp1;
  }

  // first middleware
  next();

  return this;
};

虽然这段代码看上去狠复杂,不过如果我们对其精简之后,就清晰许多了。

复制代码 代码如下:

server.runMiddlewares = function(url, req, res, out) {
var index = -1;

var middlewares = this._usingMiddlewares;

// run the next middleware if it is exists
function next(err) {
index ;

// current middleware
var curr = middlewares[index];

if (curr) {
var check = new RegExp(curr.route);

// Check the route
if (check.test(url)) {
// run the current middleware
curr.handler(req, res, next);
} else {
next();
}
} else {
// Out to next step of the pipeline
out();
}
}

// first middleware
next();

return this;
};

The reason why recursion can be used in the implementation of middleware systems is that recursion is the most suitable program flow response method for asynchronous I/O in node.js.

In this middleware implementation code, this._usingmiddlewares is the loop array, function next() is the loop body, check.test(url) is the execution judgment condition, and the loop processing code is the front of the loop body. The index counter is incremented by 1 and the next function itself is called recursively.

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template