Schleifen sind einer der wichtigsten Mechanismen in allen Programmiersprachen. Fast jedes Computerprogramm mit praktischer Bedeutung (Sortieren, Abfragen usw.) enthält keine Schleifen. Schleifen sind auch ein sehr problematischer Teil der Programmoptimierung. Wir müssen häufig die Komplexität des Programms ständig optimieren, sind jedoch aufgrund von Schleifen in die Wahl zwischen zeitlicher Komplexität und räumlicher Komplexität verstrickt.
In Javascript gibt es drei Arten von nativen Schleifen: for () {}, while () {} und do {} while (), von denen for () {} am häufigsten verwendet wird.
For ist jedoch die Art von Schleife, die JavaScript-Ingenieure bei der Optimierung von Programmen am leichtesten ignorieren.
Lassen Sie uns zunächst die Grundkenntnisse von for überprüfen.
Die for-Syntax von JavaScript wird von der C-Sprache übernommen. Es gibt zwei Möglichkeiten, die grundlegende Syntax der for-Schleife zu verwenden.
1. Schleifenarray
Grundlegende Syntax der for-Schleife
Wir verwenden einen Beispielcode, um dies im Detail zu erklären.
for (var i = 0, len = array.length; i < len; i) {
sum = array[i];
}
console.log('Die Summe der Array-Elemente ist %d.', sum);
//=> Die Summe der Array-Elemente beträgt 15.
In diesem Code definieren und initialisieren wir zunächst ein Array zum Speichern der zu akkumulierenden Elemente und einer Summen-Ganzzahlvariablen. Als nächstes beginnen wir mit der Schleife. Im Initialisierungscode der for-Schleife haben wir außerdem zwei Variablen definiert und initialisiert: i (Zähler) und len (Alias für die Länge des Schleifenarrays). Wenn i kleiner als len ist, wird die Schleifenbedingung erstellt und der Logikcode erstellt wird ausgeführt; jedes Mal, wenn die Logik ausgeführt wird, wird i um 1 erhöht.
Im Schleifenlogikcode fügen wir das aktuelle Schleifenarrayelement zur Summenvariablen hinzu.
Dieser Zyklus wird durch ein Flussdiagramm wie folgt dargestellt:
Anhand dieses Flussdiagramms können wir leicht erkennen, dass der eigentliche Schleifenkörper im Programm nicht nur unseren Logikcode enthält, sondern auch die Ausführungsbeurteilung und Schleifenverarbeitung zur Implementierung der Schleife selbst .
Auf diese Weise sind unsere Optimierungsideen klar und wir können aus vier Aspekten optimieren.
1. Initialisierungscode vor dem Schleifenkörper
2. Ausführungsbeurteilungsbedingungen im Schleifenkörper
3. Logikcode
4. Verarbeitungscode nach dem Logikcode
ps: Es gibt eine wichtige Beziehung zwischen dem ersten und dem zweiten Punkt.
1.1 Initialisierungscode und Ausführungsbeurteilungsbedingungen optimieren
Schauen wir uns zunächst einen Code an, mit dem jeder sehr vertraut ist.
2. Ausführungsbeurteilungsbedingung – wahr, wenn der Zähler kleiner als die Länge der Liste ist.
3. Verarbeitungscode – der Zähler erhöht sich um 1.
Der eigentliche Schleifenkörper enthält nicht nur unseren Logikcode, sondern auch die Ausführungsbeurteilung und den Verarbeitungscode zur Implementierung der Schleife selbst. Mit anderen Worten: Die Beurteilungsbedingung i < list.length muss vor jeder Schleife ausgeführt werden. In JavaScript ist beim Lesen der Eigenschaften oder Methoden eines Objekts eine Abfrage erforderlich.
Du scheinst etwas zu verstehen? Für diese Beurteilungsbedingung gibt es zwei Operationen: 1. Fragen Sie das Längenattribut aus dem Listenarray ab. 2. Vergleichen Sie die Größen von i und list.length.
Angenommen, das Listenarray enthält n Elemente, muss das Programm bei der Ausführungsbeurteilung dieser Schleife 2n Operationen ausführen.
In diesem verbesserten Code haben wir eine Definition hinzugefügt und eine len-Variable im Initialisierungscode initialisiert, bevor der Schleifenkörper ausgeführt wird, die zum Speichern des Werts von list.length (über Variablen, Ausdrücke, Zeiger und Werte) verwendet wird Der Inhalt wird im zweiten Teil besprochen. Auf diese Weise müssen wir die Attribute des Listenarrays bei der Ausführungsbeurteilung im Schleifenkörper nicht erneut abfragen, und die Anzahl der Operationen beträgt die Hälfte der ursprünglichen.
Wir haben die zeitliche Komplexität des Algorithmus in den obigen Schritten verbessert, aber was sollten wir tun, wenn wir die räumliche Komplexität weiter optimieren möchten? Wenn Ihr Logikcode nicht durch die Schleifenreihenfolge eingeschränkt ist, können Sie die folgenden Optimierungsmethoden ausprobieren.
Dieser Code kehrt die Reihenfolge der Schleife um, startet den i-Zähler ab dem letzten Elementindex (list.length - 1) und führt eine Schleife vorwärts durch. Um die Anzahl der für die Schleife erforderlichen Variablen auf 1 und in der Ausführungsbeurteilung zu reduzieren, wird die Anzahl der Variablenabfragen reduziert und der Zeitaufwand vor der Ausführung des CPU-Befehls verringert.
1.2 Logikcode optimieren
In der Schleife erhalten wir auf natürliche Weise das aktuelle Array-Element der Schleife, um einige Operationen daran auszuführen oder es zu verwenden, was unweigerlich zu mehreren Aufrufen des Elements führt.
for (var i = array.length - 1; i >= 0; --i) {
console.log('Name: %s', array[i].name);
console.log('Er/Sie ist ein(n) %s', array[i].type);
console.log('rn');
}
/*=>
Name: Vill Lin
Er/Sie ist ein/e Moegril
Name: Will Wen Gunn
Er/Sie ist ein(e) Hentai
*/
In diesem Code muss das Programm die Namens- und Typattribute jedes Array-Elements abfragen. Wenn das Array n Elemente hat, führt das Programm 4n Objektsuchen durch.
Ich glaube, Sie müssen zu diesem Zeitpunkt über eine Lösung nachgedacht haben, die darin besteht, den Wert des aktuellen Array-Elements einer Variablen zuzuweisen und ihn dann im Logikcode zu verwenden.
for (var i = array.length - 1; i >= 0 && (person = array[i]); --i) {
console.log('Name: %s', person. name);
console.log('Er/Sie ist ein/e %s', person.type);
console.log('rn');
}
person = null;
Das sieht tatsächlich viel schöner aus.
Es ist ein bisschen wie foreach in emcascript5, aber es gibt einen großen Unterschied zwischen den beiden, deshalb werde ich es hier nicht erklären.
ps: Vielen Dank für die Korrektur. Nach Experimenten haben wir gelernt, dass der in der Schleife erhaltene Wert ein Wert und kein Zeiger sein muss, wenn die Elemente im Array durch direkte Werteübertragung definiert werden. Unabhängig davon, ob Sie Ausdrücke oder Variablen definieren, wird zusätzlicher Speicherplatz benötigt.
1.3 Optimierter Verarbeitungscode
Eigentlich gibt es im Verarbeitungscode im Schleifenkörper nicht viel zu optimieren. Es reicht aus, wenn der i-Zähler um 1 erhöht wird.
ps: Wenn Sie gute Vorschläge oder Methoden haben, geben Sie diese bitte an. :)
2. Schleifenobjekt (Objekt)
In Javascript kann for auch die Eigenschaften und Methoden von Objekten durchlaufen. Es ist zu beachten, dass die for-Schleife weder den Verpackungstyp, zu dem das Objekt gehört, noch die Prototypeigenschaften und -methoden (Prototyp) im Konstruktor durchlaufen kann.
Die Syntax ist einfacher als das Schleifen eines Arrays.
Wir verwenden diese Methode häufig zum Bedienen von Objekten.
for (var key in person) {
value = person[key];
// Wenn der Wert ein Array ist, konvertieren Sie ihn in einen String
if (value instanceof Array) {
value = value.join(', ');
}
console.log('%s: %s', key, value);
}
/*=>
Name: Will Wen Gunn
Typ: Hentai
Fähigkeit : Programmieren, Fotografieren, Sprechen usw.
*/
Wenn Sie Mongodb jemals verwendet haben, sind Sie mit dem Abfragemechanismus auf jeden Fall vertraut. Da der Abfragemechanismus von Mongodb wie die Seele seiner API ist, hat die flexible Curd-Operationsmethode Mongodb große Popularität und Entwicklungsdynamik eingebracht.
In der Mongo-API-Implementierung von Nanodb verwendet die Abfrageimplementierung häufig Schleifenobjekte.
var _cursor = myColl.find({
type : 'repo',
language : 'JavaScript'
});
_cursor
.sort({
star: 1
})
.toArray(function(err, rows) {
if (err)
return console.error( ähm);
console.log(rows);
});
Was wir optimieren müssen, ist nicht die Schleife selbst, sondern die Optimierung der Objekte, die Sie durchlaufen müssen.
Zum Beispiel speichert die Nanocollection-Klasse in Nanodb, obwohl sie auf der Oberfläche wie ein Array aussieht, alle Elemente oder ein Objekt, verwendet die ID des Elements als Schlüssel und speichert dann die Elemente.
Dies ist jedoch nicht der Fall. Schüler, die bereits Unterstriche verwendet haben, sollten die Methode _.invert kennen. Dies ist eine ziemlich interessante Methode, sie kehrt die Schlüssel und Werte des übergebenen Objekts um.
var _inverted = _.invert(person);
console.log(_inverted);
/*=>
{
'Will Wen Gunn' : 'name',
'hentai' : 'type'
}
*/
Wenn Sie ein Schleifenobjekt verwenden müssen, um den Wert einiger Eigenschaften des Objekts abzufragen, können Sie die folgende Methode ausprobieren.
var name = 'Will Wen Gunn';
var _inverted = _.invert(person);
if (_inverted[name] === 'name') {
console.log('Catched!');
}
//=> Catched!
Es gibt jedoch nicht viel, was für die Objektabfrage optimiert werden kann. Alles muss auf den tatsächlichen Anforderungen basieren. :p
Als nächstes schauen wir uns die anderen beiden Schleifen an, while () {} und {} while (). Ich glaube, dass jeder, der ein Informatikstudium absolviert hat, diese beiden Zyklen kennt. Der einzige Unterschied zwischen ihnen besteht in der logischen Reihenfolge, in der der Schleifenkörper ausgeführt wird.
Die Ausführungssequenz von while () {} ähnelt der von for () {}. Die Ausführungsbeurteilung erfolgt vor dem Logikcode, der Initialisierungs- und Verarbeitungscode wird jedoch weggelassen.
Wenn die Bedingung gegeben ist, wird der Logikcode ausgeführt, bis die Bedingung nicht mehr wahr ist.
while (sum < 10) {
sum = sum 1;
}
console.log(sum);
//=> 15
do {} while () setzt die Ausführungsbeurteilung nach dem Logikcode, d. h. „Zuerst schneiden und später abspielen“.
do {
sum = sum 1;
} while (sum < 10);
console.log(sum);
//=> 15
while () {} und do {} while () erfordern ebenfalls keinen Zähler, verwenden jedoch bestimmte Bedingungen, um zu bestimmen, ob der Logikcode ausgeführt oder weiterhin ausgeführt werden soll.
3. while () {} und {} while ()
while () {} und do {} while () werden hauptsächlich in der Geschäftslogik verwendet, um kontinuierlich eine Reihe von Vorgängen auszuführen, um einen bestimmten Zweck zu erreichen, z. B. Aufgabenwarteschlangen.
Aber diese beiden Arten von Schleifen sind gefährlich, da sie standardmäßig nur durch Ausführungsbedingungen gesteuert werden. Wenn der Logikcode keinen Einfluss auf die Ausführungsbeurteilung hat, entsteht eine Endlosschleife.
// Warnung!
while (sum < 10) {
sum = 1 12
}
Ein solcher Code ist gleichbedeutend mit while (true) {}. Bevor Sie ihn verwenden, müssen Sie daher die Ausführungsbedingungen und die Auswirkungen auf die Ausführungsbedingungen klären.
4. Nutzen Sie Schleifenkontrollanweisungen sinnvoll
Ich glaube, dass alle JavaScript-Ingenieure die Break-Anweisung verwendet haben, die Continue-Anweisung jedoch relativ selten verwendet wird. Tatsächlich ist continue in vielen hervorragenden JavaScript-Open-Source-Projekten zu finden.
Um die Funktion der continue-Anweisung besser zu verstehen, schauen wir uns zunächst einen Beispielcode an
var BroadcastServer = net.createServer();
// Client Store
broadcastServer.clients = [];
// Clients Broadcast Method
net.Socket.prototype.broadcast = function(msg) {
var client = BroadcastServer.clients;
// Holen Sie sich den Client, der die Übertragung in der Sammlung Standard veröffentlicht
var index = client.indexOf(this);
for (var i = client.length - 1; i >= 0; --i) {
if (i === index) {
// Wenn es der Client ist, der das veröffentlicht Broadcast, dann die aktuelle Schleife beenden
weitermachen;
}
currClient = client[i];
if (!currClient.destroyed) {
currClient.write(
util.format(
) 'r[Echo Client %s:%d] %snInput: ',
currClient. remoteAddress , currClient.remotePort, msg)
);
}
}
};
// Ein neuer Client ist verbunden
broadcastServer.on('connection', function(client) {
BroadcastServer.clients.push(client);
// Willkommen
client.write('[Broadcast Server] Welcome!nInput:');
client.broadcast(client, 'Joined!');
// Nachrichtenhandle
client.on('data', function(msg) {
client.broadcast(msg);
client.write('rInput:');
} );
// Handle trennen
client.on('end', function() {
client.broadcast('Left!');
})
});
// Bind
broadcastServer.listen(8080, function() {
console.log('Broadcast Serverbound.');
});
Dieser Code implementiert einen Broadcast-Server basierend auf dem Net-Modul von node.js. In der Broadcast-Methode verwenden wir die continue-Anweisung, um Informationen an alle hergestellten Verbindungen mit Ausnahme des Clients zu senden, der den Broadcast-Client veröffentlicht.
Der Inhalt des Codes ist recht einfach. Wenn ein Client eine Übertragung an andere Clients veröffentlichen muss, wird die Broadcast-Methode des Client-Objekts aufgerufen. Bei der Broadcast-Methode ruft das Programm zuerst den Cache ab des aktuellen Clients in der Client-Socket-Sammlung und veröffentlichen Sie dann alle Client-Sockets in einer Schleife. Wenn der Schleifenzähler den zuvor erhaltenen Positionsindex erreicht, wird der Logikcode im aktuellen Schleifenkörper übersprungen und die nächste Schleife fortgesetzt .
Ich glaube, dass Ingenieure, die C/C-Sprache studiert haben, von verschiedenen Seiten diesen Rat erhalten werden: „Verwenden Sie keine Goto-Anweisungen.“
Diese „berüchtigte“ goto-Anweisung ist eigentlich ein Codefluss-Controller. Die Details der goto-Anweisung werden hier nicht im Detail erläutert. JavaScript verfügt jedoch nicht über eine offensichtliche goto-Anweisung, aber anhand der break-Anweisung und der continue-Anweisung ist es nicht schwierig, den Schatten von goto in JavaScript zu finden.
Das liegt daran, dass die break-Anweisung und die continue-Anweisung das Akzeptieren eines definierten Labelnamens für den Codesprung ermöglichen.
Werfen wir einen Blick auf den von mdn bereitgestellten Beispielcode.
loop1:
for (i = 0; i < 3; i ) { //Die erste for-Anweisung trägt die Bezeichnung „loop1“
loop2:
for (j = 0; j < 3; j ) { //Die zweite for-Anweisung trägt die Bezeichnung „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"
// Beachten Sie, dass sowohl „i = 1, j = 1" als auch „i = 1, j = 2" übersprungen wird
Die erste Schleife befindet sich in der Beschriftung von Schleife1. Dies bedeutet, dass im nachfolgenden Programm die äußerste Schleife herausgesprungen wird, wenn die Beschriftung Schleife1 in der Continue-Anweisung oder der Break-Anweisung ausgewählt wird.
Die Schleife der zweiten Ebene befindet sich in der Beschriftung von Schleife2 in der Schleife der obersten Ebene. Wenn die Beschriftung Schleife2 in der Continue-Anweisung oder Break-Anweisung ausgewählt wird, kehrt sie zum Schleifenkörper der Schleife der obersten Ebene zurück.
5. Erweiterte Schleife
5.1 Schleife abrollen
Schauen wir uns zunächst zwei Codeteile an. Erraten Sie, welcher Code die bessere Leistung bietet.
// Fall 1
for (var i = array.length - 1; i >= 0; i--) {
for (var j = array[i].length - 1; j >= 0; i--) {
process(array[i][j]);
}
}
// Fall 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]);
}
}
这里我们来看看一种更给力的解决方案。 环节中需要对大数据集进行迭代处理,而这个数据集从开始迭代起,数据量不会再改变, 那麼可以考虑採用一种名为 duff 装置的技术.这项技术是以其的创造者 tom duff 的名字来命名的, 这项技术最先实现於Beschreibung: Jeff Greenberg und Andrew B . König 修改并提出了一种更为高效的版本.
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
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) {
//...
});
Hier finden Sie eine Reihe von Leistungstests und Ergebnissen für die oben genannten drei iterativen Lösungen. http://jsperf.com/spreaded-loop
5.2 Nicht-native Schleife
In jeder Programmiersprache können Schleifen nicht nur durch die von der Sprache bereitgestellten nativen Schleifenanweisungen, sondern auch indirekt durch andere Methoden implementiert werden.
Lassen Sie uns zunächst einige Inhalte der Oberstufenmathematik durchgehen – die allgemeine Formel einer Folge.
also
a[n] 1 = 2 * a[n - 1] 2 [n - 1] 1) = 2
dann
a[n] 1 = 2 * 2 * ... * 2 * 2
a[n] 1 = 2^n
a[n] = 2^n - 1
final
Rekursion ist eine sehr wichtige Anwendungsmethode in der Mathematik und Informatik. Sie bedeutet, dass eine Funktion sich selbst aufruft, wenn sie verwendet wird.
In der Node.js-Community wird Rekursion verwendet, um eine sehr wichtige Technologie zu implementieren: Middleware-Technologie. Dies ist ein Teil des Middleware-Implementierungscodes in einer neuen Version von webjs, der noch nicht veröffentlicht wurde.
var middlewares = this._usingMiddlewares;
// die nächste Middleware ausführen, falls vorhanden
function next(err) {
index ;
// aktuelle Middleware
var curr = middlewares[index];
if (curr) {
var check = new RegExp(curr.route);
// Überprüfen Sie die Route
if (check.test(url)) {
try {
function later() {
debug('Eine Middleware sagt, dass es später sein muss % s', url);
// Die Abhängigkeiten funktionieren derzeit nicht
if (middlewares.indexOf(curr) !== middlewares.length - 1) {
. _later(curr);
Index --;
next();
} else {
debug('Eine Middleware-Abhängigkeit ist falsch');
// Diese Middleware kann nicht ausgeführt werden
out();
}
}
// Middleware ausführen
if (utils.isFunc(curr.handler)) {
// Normale Middleware-Funktion
curr.handler(req, res, next, later);
} else if (utils.isObject(curr.handler) && utils.isFunc(curr.handler.emit)) {
// Serverobjekt
curr.handler.emit('request', req, res, next, later);
} sonst {
// Mit der Middleware stimmt etwas nicht
next();
} } sonst {
// Zum nächsten Schritt der Pipeline
out();
}
}
// wenn die Middleware von anderen Middlewares abhängt,
// kann sie später ausgeführt werden
function _later(curr) {
var i = middlewares.indexOf(curr);
var _tmp1 = middlewares.slice(0, i);
_tmp1.push(middlewares[i 1], curr);
[].push. apply(_tmp1, _tmp2);
middlewares = _tmp1;
}
// erste Middleware
next();
gib dies zurück;
};
虽然这段代码看上去狠复杂,不过如果我们对其精简之后,就清晰许多了.
代码如下:
var middlewares = this._usingMiddlewares;
// die nächste Middleware ausführen, falls vorhanden
function next(err) {
index ;
// aktuelle Middleware
var curr = middlewares[index];
if (curr) {
var check = new RegExp(curr.route);
// Route prüfen
if (check.test(url)) {
// aktuelle Middleware ausführen
curr.handler(req, res, next);
} else {
next();
}
} else {
// Zum nächsten Schritt der Pipeline
out();
}
}
// erste Middleware
next();
gib dies zurück;
};
Der Grund, warum Rekursion bei der Implementierung von Middleware-Systemen verwendet werden kann, liegt darin, dass Rekursion die am besten geeignete Programmfluss-Antwortmethode für asynchrone E/A in node.js ist.
In diesem Middleware-Implementierungscode ist this._usingmiddlewares das Schleifenarray, die Funktion next() ist der Schleifenkörper, check.test(url) ist die Ausführungsbeurteilungsbedingung und der Schleifenverarbeitungscode ist die Vorderseite des Schleifenkörpers . Der Indexzähler wird um 1 erhöht und die nächste Funktion selbst wird rekursiv aufgerufen.