Maison >interface Web >js tutoriel >Une discussion approfondie sur les techniques d'optimisation des instructions de boucle dans les techniques JavaScript_javascript
Les boucles sont l'un des mécanismes les plus importants dans tous les langages de programmation. Presque tous les programmes informatiques ayant une signification pratique (tri, requête, etc.) ne contiennent pas de boucles. Les boucles sont également un élément très gênant de l'optimisation du programme.Nous devons souvent optimiser constamment la complexité du programme, mais nous sommes empêtrés dans le choix entre la complexité temporelle et la complexité spatiale à cause des boucles.
En javascript, il existe trois types de boucles natives, for () {}, while () {} et do {} while (), parmi lesquelles for () {} est la plus couramment utilisée.
Cependant, for est le type de boucle que les ingénieurs JavaScript ignorent le plus facilement lors de l'optimisation des programmes.
Passons d’abord en revue les connaissances de base de for.
La syntaxe for de JavaScript est héritée du langage C. Il existe deux manières d'utiliser la syntaxe de base de la boucle for.
1. Tableau de boucles
Syntaxe de base de la boucle for
Nous utilisons un exemple de code pour expliquer en détail.
for (var i = 0, len = array.length; i < len; i) {
sum = array[i];
}
console.log('La somme des éléments du tableau est %d.', sum);
//=> La somme des éléments du tableau est 15.
Dans ce code, nous définissons et initialisons d'abord un tableau pour stocker les éléments à accumuler et une somme variable entière. Ensuite, nous commençons à boucler. Dans le code d'initialisation de la boucle for, nous avons également défini et initialisé deux variables : i (compteur) et len (alias pour la longueur du tableau de boucle). Lorsque i est inférieur à len, la condition de boucle est établie et le code logique. est exécuté; chaque fois que la logique Après l'exécution du code, i est incrémenté de 1.
Dans le code logique de la boucle, nous ajoutons l'élément actuel du tableau de boucle à la variable somme.
Ce cycle est représenté par un organigramme comme suit :
À partir de cet organigramme, nous pouvons facilement constater que le corps réel de la boucle dans le programme inclut non seulement notre code logique, mais inclut également le jugement d'exécution et le traitement de la boucle pour implémenter la boucle elle-même. .
De cette façon, nos idées d'optimisation sont claires et nous pouvons optimiser sous quatre aspects.
1. Code d'initialisation avant le corps de la boucle
2. Conditions de jugement d'exécution dans le corps de la boucle
3. Code logique
4. Code de traitement après le code logique
ps : Il existe une relation importante entre le premier point et le deuxième point.
1.1 Optimiser le code d'initialisation et les conditions de jugement d'exécution
Regardons d’abord un morceau de code que tout le monde connaît très bien.
1. Code d'initialisation - Cette boucle définit et initialise uniquement une variable de compteur.
2. Condition de jugement d'exécution - vraie lorsque le compteur est inférieur à la longueur de la liste.
3. Code de traitement - le compteur augmente de 1.
Revoyons à nouveau l'organigramme ci-dessus. Trouvons-nous quelque chose qui ne va pas ?
Le corps réel de la boucle contient non seulement notre code logique, mais inclut également le jugement d'exécution et le code de traitement pour implémenter la boucle elle-même. En d'autres termes, la condition de jugement i
En supposant que le tableau liste contient n éléments, le programme doit effectuer 2n opérations dans le jugement d'exécution de cette boucle.
Si nous changeons le code par ceci :
Dans ce code amélioré, nous avons ajouté une définition et initialisé une variable len dans le code d'initialisation avant l'exécution du corps de la boucle, qui est utilisée pour stocker la valeur de list.length (à propos des variables, expressions, pointeurs et valeurs). le contenu sera abordé dans la deuxième partie). De cette façon, nous n'avons pas besoin d'interroger à nouveau les attributs du tableau de liste dans le jugement d'exécution dans le corps de la boucle, et le nombre d'opérations est la moitié de celui d'origine.
Nous avons amélioré la complexité temporelle de l'algorithme dans les étapes ci-dessus, mais si nous voulons continuer à optimiser la complexité spatiale, que devons-nous faire ? Si votre code logique n'est pas limité par l'ordre des boucles, vous pouvez essayer les méthodes d'optimisation suivantes.
Ce code inverse l'ordre de la boucle, démarre le compteur i à partir du dernier index d'élément (list.length - 1) et effectue une boucle vers l'avant. Afin de réduire le nombre de variables requises pour la boucle à 1, et dans le jugement d'exécution, le nombre de requêtes de variables est réduit, et le temps nécessaire avant l'exécution de l'instruction CPU est réduit.
1.2 Optimiser le code logique
Dans la boucle, on récupère naturellement l'élément courant du tableau de la boucle afin d'effectuer certaines opérations sur lui ou de l'utiliser, ce qui entraînera inévitablement plusieurs appels à l'élément.
for (var i = array.length - 1; i >= 0; --i) {
console.log('Name: %s', array[i].name);
console.log('Il/Elle est un(n) %s', array[i].type);
console.log('rn');
}
/*=>
Nom : Vill Lin
Il/Elle est un(e) moegril
Nom : Will Wen Gunn
Il/Elle est un(e) hentai
*/
Dans ce code, le programme doit interroger les attributs de nom et de type de chaque élément du tableau. Si le tableau comporte n éléments, le programme effectue 4n recherches d'objets.
Je pense que vous devez avoir pensé à une solution à ce moment-là, qui consiste à attribuer la valeur de l'élément actuel du tableau à une variable, puis à l'utiliser dans le code logique.
for (var i = array.length - 1; i >= 0 && (person = array[i]); --i) {
console.log('Name : %s', person. name);
console.log('Il/Elle est un(e) %s', person.type);
console.log('rn');
}
personne = null;
Cela a l'air beaucoup plus beau.
C'est un peu comme foreach dans emcascript5, mais il y a une grande différence entre les deux, donc je ne l'expliquerai pas ici.
ps : Merci pour la correction de chacun. Après des expériences, nous avons appris que si les éléments du tableau sont définis par transfert direct de valeur, la valeur obtenue dans la boucle doit être une valeur, pas un pointeur. Ainsi, que vous définissiez des expressions ou des variables, des besoins en espace mémoire supplémentaires seront nécessaires.
1.3 Code de traitement optimisé
En fait, il n'y a pas grand chose qui puisse être optimisé dans le code de traitement dans le corps de la boucle, il suffit que le compteur i augmente de 1.
ps : Si vous avez de bonnes suggestions ou méthodes, veuillez les fournir. :)
2. Objet de boucle (objet)
En javascript, for peut également parcourir les propriétés et méthodes de l'objet. Il convient de noter que la boucle for ne peut pas traverser le type d'empaquetage auquel appartient l'objet ni les propriétés et méthodes du prototype (prototype) dans le constructeur.
La syntaxe est plus simple que de boucler un tableau.
Nous utilisons souvent cette méthode pour faire fonctionner des objets.
pour (var clé en personne) {
valeur = personne[clé];
// si la valeur est un tableau, convertissez-la en chaîne
if (value instanceof Array) {
value = value.join(', ');
}
console.log('%s: %s', key, value);
}
/*=>
nom : Will Wen Gunn
type : hentai
compétence : Programmation, Photographie, Prise de parole, etc
*/
Si vous avez déjà utilisé mongodb, vous connaissez certainement son mécanisme de requête. Parce que le mécanisme de requête de mongodb est comme l'âme de son API, la méthode flexible de fonctionnement du caillé a gagné à mongodb beaucoup de popularité et un élan de développement.
Dans l'implémentation de l'API mongo de nanodb, l'implémentation de la requête utilise largement les objets de boucle.
var _cursor = myColl.find({
type : 'repo',
langage : 'JavaScript'
});
_cursor
.sort({
étoile : 1
})
.toArray(function(err, rows) {
if (err)
return console.error( euh);
console.log(rows);
});
Ce que nous devons optimiser, ce n'est pas la boucle elle-même, mais l'optimisation des objets que vous devez parcourir.
Par exemple, la classe nanocollection dans nanodb, bien qu'elle ressemble à un tableau en surface, stocke tous les éléments, ou un objet, utilise l'identifiant de l'élément comme clé, puis stocke les éléments.
Mais ce n'est pas le cas. Les étudiants qui ont déjà utilisé le soulignement devraient connaître la méthode _.invert. C'est une méthode plutôt intéressante, elle inverse les clés et valeurs de l'objet passé.
var _inverted = _.invert(person);
console.log(_inverted);
/*=>
{
'Will Wen Gunn' : 'nom',
'hentai' : 'type'
}
*/
Si vous devez utiliser un objet boucle pour interroger la valeur de certaines propriétés de l'objet, vous pouvez essayer la méthode suivante.
var name = 'Will Wen Gunn';
var _inverted = _.invert(personne);
if (_inverted[name] === 'name') {
console.log('Catched!');
}
//=> Attrapé !
Cependant, il n'y a pas grand-chose qui puisse être optimisé en utilisant pour la requête d'objet. Tout doit être basé sur les besoins réels. :p
Regardons ensuite les deux autres boucles, while () {} et do {} while (). Je crois que quiconque a suivi une formation en informatique connaît ces deux cycles. La seule différence entre eux est l'ordre logique dans lequel le corps de la boucle est exécuté.
La séquence d'exécution de while () {} est similaire à celle de for () {}. Le jugement d'exécution est avant le code logique, mais le code d'initialisation et de traitement est omis.
Lorsque la condition est donnée, le code logique est exécuté jusqu'à ce que la condition ne soit plus vraie.
pendant que (somme < 10) {
somme = somme 1;
}
console.log(sum);
//=>15
do {} while () place le jugement d'exécution après le code logique, c'est-à-dire "couper d'abord et jouer plus tard".
faire {
somme = somme 1;
} while (somme < 10);
console.log(sum);
//=>15
while () {} et do {} while () ne nécessitent pas non plus de compteur, mais utilisent certaines conditions pour déterminer s'il faut exécuter ou continuer à exécuter le code logique.
3. while () {} et fais {} while ()
while () {} et do {} while () sont principalement utilisés dans la logique métier pour effectuer en continu une série d'opérations pour atteindre un certain objectif, comme les files d'attente de tâches.
Mais ces deux types de boucles sont dangereux car ils ne sont contrôlés que par les conditions d'exécution par défaut. Si le code logique n'a aucun impact sur le jugement d'exécution, une boucle infinie se produira.
// avertissement !
while (somme < 10) {
somme = 1 12
}
Un tel code équivaut à while (true) {}, donc avant de l'utiliser, vous devez clarifier les conditions d'exécution et comment affecter les conditions d'exécution.
4. Faites bon usage des instructions de contrôle de boucle
Je crois que tous les ingénieurs JavaScript ont utilisé l'instruction break, mais l'instruction continue est relativement rarement utilisée. En fait, continue peut être trouvé dans de nombreux excellents projets open source JavaScript.
Afin de mieux comprendre la fonction de l'instruction continue, jetons d'abord un coup d'œil à un exemple de code
var BroadcastServer = net.createServer();
// Magasin client
broadcastServer.clients = [];
// Clients Broadcast Method
net.Socket.prototype.broadcast = function(msg) {
var clients = BroadcastServer.clients;
// Récupère le client qui publie la diffusion dans la collection Standard
var index = clients.indexOf(this);
for (var i = clients.length - 1; i >= 0; --i) {
if (i === index) {
// Si c'est le client qui publie le diffuser, Puis terminer la boucle en cours
continuer;
}
currClient = clients[i];
if (!currClient.destroyed) {
currClient.write(
util.format(
) 'r[Echo Client %s:%d] %snInput: ',
currClient. RemoteAddress , currClient.remotePort, msg)
);
}
}
};
// Un nouveau client connecté
broadcastServer.on('connection', function(client) {
BroadcastServer.clients.push(client);
// Bienvenue
client.write('[Broadcast Server] Welcome!nInput:');
client.broadcast(client, 'Rejoint !');
// Descripteur de message
client.on('data', function(msg) {
client.broadcast(msg);
client.write('rInput:');
} );
// Déconnecter le handle
client.on('end', function() {
client.broadcast('Left!');
})
});
// Bind
broadcastServer.listen(8080, function() {
console.log('Broadcast Serverbound.');
});
Ce code implémente un serveur de diffusion basé sur le module net de node.js. Dans la méthode de diffusion, nous utilisons l'instruction continue pour envoyer des informations à toutes les connexions établies à l'exception du client qui publie le client de diffusion.
Le contenu du code est assez simple. Lorsqu'un client a besoin de publier une diffusion vers d'autres clients, la méthode de diffusion de l'objet client correspondant au client est appelée. Dans la méthode de diffusion, le programme obtiendra d'abord le cache. du client actuel. position index dans la collection de sockets client, puis publiez toutes les sockets client dans une boucle. Lorsque le compteur de boucle atteint l'index de position obtenu précédemment, le code logique dans le corps de la boucle actuelle est ignoré et la boucle suivante se poursuit. .
Je pense que les ingénieurs qui ont étudié le langage C/C recevront ce conseil de divers endroits : "N'utilisez pas d'instructions goto
."Cette instruction goto "notoire" est en fait un contrôleur de flux de code. Les détails de l'instruction goto ne seront pas expliqués en détail ici. Cependant, JavaScript n'a pas d'instruction goto évidente, mais à partir des instructions break et continue, il n'est pas difficile de trouver l'ombre de goto en JavaScript.
En effet, les instructions break et continue permettent d'accepter un nom d'étiquette défini pour le saut de code.
Jetons un coup d'œil à l'exemple de code fourni par mdn.
loop1:
for (i = 0; i < 3; i ) { //La première instruction for est étiquetée "loop1"
loop2:
for (j = 0; j < 3; j ) { //La deuxième instruction for est étiquetée "loop2"
if (i == 1 && j == 1) {
continue loop1;
console.log ("i = " i ", j = " j);
}
}
>
// "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"
// Remarquez comment il saute à la fois "i = 1, j = 1" et "i = 1, j = 2"
La première boucle est dans l'étiquette de loop1, ce qui signifie que dans le programme suivant, si l'étiquette loop1 est sélectionnée dans l'instruction continue ou l'instruction break, la boucle la plus externe sera sautée.
La boucle de deuxième niveau se trouve dans l'étiquette de loop2 dans la boucle de niveau supérieur. Si l'étiquette loop2 est sélectionnée dans l'instruction continue ou l'instruction break, elle reviendra au corps de la boucle de niveau supérieur.
5. Boucle avancée
5.1 Dérouler la boucle
Jetons d'abord un coup d'œil à deux morceaux de code. Devinez lequel a les meilleures performances.
// Cas 1
pour (var i = array.length - 1; i >= 0; i--) {
for (var j = array[i].length - 1; j >= 0; i--) {
process(array[i][j]);
}
}
// Cas 2
pour (var i = array.length - 1; i >= 0; i = i - 4) {
for (var j = array[i].length - 1 ; j >= 0; j = j - 6) {
processus(tableau[i][j]);
processus(tableau[i][j - 1]);
processus(tableau [i][j - 2]);
process(array[i][j - 3]);
process(array[i][j - 4]);
process(array[i ][j - 5]);
}
for (var j = tableau[i - 1].length - 1; j >= 0; j = j - 6) {
processus(tableau [i][j]);
processus(array[i][j - 1]);
processus(array[i][j - 2]);
processus(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) {
processus(array[i][j]);
processus(array[i][j - 1]);
processus(array[i][j - 2]);
processus(array[i][j - 3]);
processus(array[i][j - 4] );
process(array[i][j - 5]);
}
for (var j = array[i - 3].length - 1; j >= 0; j = j - 6) {
processus(array[i][j]);
processus(array[i][j - 1]);
processus(array[i][j - 2]);
processus(array[i][j - 3]);
processus(array[i][j - 4]);
processus(array[i][j - 5]);
>
}
这里我们来看看一种更给力的解决方案。 如果一个业务环节中需要对大数据集进行迭代处理,而这个数据集从开始迭代起,数据量不会再改变, 那麼可以考虑採用Il s'agit de Tom Duff. Il s'agit de Jeff Greenberg et de Javascript, ainsi que d'Andrew B. . roi 修改并提出了一种更为高效的版本。
if (restes > 0) {
do {
process(values[i ]);
} while (--leftover > 0);
}
do {
processus(valeurs[i ]);
processus(valeurs[i ]);
processus(valeurs[i ]);
processus(valeurs[i ]);
processus(valeurs [i ]);
process(values[i ]);
process(values[i ]);
process(values[i ]);
} while (--itérations > 0 );
Les valeurs de valeur de valeur 8 sont plus importantes que math.floor().证结果为整数, 然后再计算不能被 8 jours ,并对这些元素单独进行处理,其餘则 8 次为单次展开次数来进行迭代。
我将这种装置再加以封装,可以得到一种带有异步味道的 api。
var i = 0;
if (l > 0) {
do {
mapper(array[i ]);
} while (--i > 0);
}
faire {
mapper(array[i]);
mapper(array[i]);
mapper(array[i]);
mapper(array[i] );
mapper(array[i]);
mapper(array[i]);
mapper(array[i]);
mapper(array[i]);
} tandis que (--n > 0);
}
duff([...], function(item) {
//...
});
Voici un ensemble de tests de performances et de résultats pour les trois solutions itératives ci-dessus. http://jsperf.com/spreaded-loop
5.2 Boucle non native
Dans n'importe quel langage de programmation, les boucles peuvent être implémentées non seulement via les instructions de boucle natives fournies par le langage, mais aussi indirectement via d'autres méthodes.
Revoyons d'abord un peu de contenu en mathématiques au lycée - la formule générale d'une séquence.
donc
a[n] 1 = 2 * a[n - 1] 2 [n - 1] 1) = 2
puis
a[n] 1 = 2 * 2 * ... * 2 * 2
a[n] 1 = 2^n
a[n] = 2^n - 1
final
La récursion est une méthode d'application très importante en mathématiques et en informatique. Elle signifie qu'une fonction s'appelle lorsqu'elle est utilisée.
Dans la communauté node.js, la récursivité est utilisée pour implémenter une technologie très importante : la technologie middleware. Il s'agit d'un morceau de code d'implémentation de middleware dans une nouvelle version de webjs qui n'a pas encore été publiée.
var middlewares = this._usingMiddlewares;
// exécute le prochain middleware s'il existe
function next(err) {
index ;
// middleware actuel
var curr = middlewares[index];
if (curr) {
var check = new RegExp(curr.route);
// Vérifier l'itinéraire
if (check.test(url)) {
try {
function later() {
debug('Un middleware dit qu'il doit être plus tard % s', url);
// Les dépendances ne fonctionnent pas pour le moment
if (middlewares.indexOf(curr) !== middlewares.length - 1) {
_later(curr);
indice --;
next();
} else {
debug('Une dépendance de middleware incorrecte');
// Ce middleware ne peut pas s'exécuter
out();
}
}
// Exécuter le middleware
if (utils.isFunc(curr.handler)) {
// Fonction middleware normale
curr.handler(req, res, next, later);
} else if (utils.isObject(curr.handler) && utils.isFunc(curr.handler.emit)) {
// Objet serveur
curr.handler.emit('request', req, res, next, later);
} autre {
// Il y a un problème avec le middleware
next();
// Passage à l'étape suivante du pipeline
out();
}
}
// si le middleware dépend d'autres middlewares,
// il peut le laisser s'exécuter plus tard
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);
middlewares = _tmp1;
}
// premier middleware
next();
renvoyez ceci ;
};
虽然这段代码看上去狠复杂,不过如果我们对其精简之后,就清晰许多了。
复制代码
代码如下 :
var middlewares = this._usingMiddlewares;
// exécute le prochain middleware s'il existe
function next(err) {
index ;
// middleware actuel
var curr = middlewares[index];
if (curr) {
var check = new RegExp(curr.route);
// Vérifier l'itinéraire
if (check.test(url)) {
// exécuter le middleware actuel
curr.handler(req, res, next);
} else {
next();
}
} else {
// Passage à l'étape suivante du pipeline
out();
}
}
// premier middleware
next();
renvoie ceci ;
};
La raison pour laquelle la récursion peut être utilisée dans la mise en œuvre de systèmes middleware est que la récursion est la méthode de réponse de flux de programme la plus appropriée pour les E/S asynchrones dans node.js.
Dans ce code d'implémentation de middleware, this._usingmiddlewares est le tableau de boucles, la fonction next() est le corps de la boucle, check.test(url) est la condition de jugement d'exécution et le code de traitement de la boucle est le devant du corps de la boucle. . Le compteur d'index est incrémenté de 1 et la fonction suivante elle-même est appelée de manière récursive.