Da das Website-System immer größer wird, müssen möglicherweise Cookies von verschiedenen Domainnamen und sogar von verschiedenen Partner-Websites mehr oder weniger gemeinsam genutzt werden. In dieser Situation denkt jeder normalerweise daran, den Cookie-Status über das Login-Center zu verteilen Wenn Sie die Lösung dann synchronisieren, sind die Kosten höher und die Implementierung komplizierter und mühsamer.
Da Cookies domänenübergreifend sind, erlauben Browser überhaupt keinen gegenseitigen Zugriff. Um diese Einschränkung zu durchbrechen, wird der folgende Implementierungsplan verwendet, um Daten mithilfe von Postmessage und Localstorage domänenübergreifend auszutauschen.
Das Prinzip ist relativ einfach, aber es gibt viele Fallstricke. Lassen Sie uns das hier klären und ein Backup erstellen.
2. API-DesignWie im Hintergrund erwähnt, verwenden wir Localstorage anstelle von Cookies. Es gibt einige Unterschiede in der Verwendung von Localstorage. Beispielsweise verfügt Localstorage über eine größere Kapazität, obwohl die Kapazität groß ist Die Obergrenze des Speicherplatzes macht es leicht, abzustürzen, wenn der Betrieb nicht gut ist. Darüber hinaus bringen Sicherheitsprobleme und die asynchrone API auch einige Probleme mit sich einfacher zu bedienen?
Werfen wir einen Blick auf die API, die ich zuerst entworfen habe:
import { crosData } from 'base-tools-crossDomainData';var store = new crosData({ iframeUrl:somefile.html, //Gemeinsame Iframe-Adresse, Iframe hat besondere Anforderungen, siehe Vorlagendatei expire:'d,h,s' / /Standard-Ablaufzeit in Tagen, Stunden, Sekunden, kann beim Pflanzen auch überschrieben werden});store.set('key','val',{ expire:'d,h,s' //option Kann Ablaufzeit mitbringen, Ablaufdatum überschreiben}).then((data)=>{ //Asynchrone Methode, wenn sie fehlschlägt, wird das Catch-Ereignis eingegeben //data {val:'val',key:'key',domain :' domain'};}).catch((err)=>{ console.log(err);}); //Sie können einen Domänennamen angeben oder (.*) verwenden, um reguläre Zeichenfolgen abzugleichen. Die zurückgegebenen Wertinformationen enthalten Domäneninformationen. Wenn sie nicht ausgefüllt sind, wird die lokale Domäne zurückgegeben. }).then((vals) =>{ console.log (val) // Die gespeicherten Daten asynchron abrufen, es können mehrere sein, es ist ein Array [{},{}]}).catch((err)=>{});store.clear ('key').then(). Catch(); //Löschen Sie nur die Schlüssel unter der aktuellen Domäne. Schlüssel unter anderen Domänen dürfen nicht gelöscht werden. Sie können nur gelesen werden.Ob ein Modul schnell verwendet werden kann, hängt von der API ab. Daher halte ich es für ein Datenfreigabemodul für in Ordnung, die drei Methoden Set, Get und Clear zu unterstützen, da Postmessage selbst ein einmaliges asynchrones Verhalten ist. und es muss in ein Versprechen verpackt werden, das besser geeignet und einfacher zu verwenden ist. Da localstorage keine Ablaufzeit unterstützt, ist eine globale Ablaufzeitkonfiguration erforderlich. Natürlich können wir beim Abrufen auch festlegen, dass Daten unter einer bestimmten Domäne oder unter mehreren Domänen abgerufen werden sollen kann wiederholt werden, es gibt jedoch nur eine Domäne. Dies betrifft die Datenverwaltung. Lassen Sie uns später darüber sprechen. Schließlich können die Clear- und Set-APIs nur Daten in dieser Domäne verarbeiten und keine Daten in anderen Domänen verarbeiten.
Werfen wir einen Blick auf die Client-Einstellungen und die API:
<!DOCTYPE html><html> <head> <meta charset=utf-8> <title>crosData</title> </head> <body> <script> window.CROS = { domain:/(.*). sina.cn/, //Oder der von Ihnen zugelassene Domänenname unterstützt reguläre und * Platzhalter lz:false //Ob die LZ-Komprimierung von Val-Zeichen aktiviert werden soll}; src=http://cdn/sdk.js></script> </body></html>Sie können das js SDK des Clients flexibel in ein HTML-Dokument in einer beliebigen Domäne einführen und dann eine Domänen-Whitelist konfigurieren, die es Ihnen ermöglicht, über globale Attribute in die Domäne eingefügt zu werden, in der sich dieses Dokument befindet. Es unterstützt reguläre Ausdrücke, und dann ist lz ob Starten Sie die LZ-String-Komprimierung. Ich werde später vorstellen, was LZ-Komprimierung ist.
An diesem Punkt ist ein relativ allgemeiner API-Entwurf abgeschlossen. Werfen wir einen Blick auf die Implementierungsprinzipien und einige spezifische Probleme.
3. UmsetzungsprinzipEs klingt sehr einfach, ist aber nicht wirklich geschrieben. Dies ist eine sehr häufige API. Hier ist ein wichtiger Punkt zu beachten: PostMessage kann nur in einem Iframe verwendet werden oder die Verwendung eines Fensters ist eine Möglichkeit, eine neue Seite zu öffnen, um miteinander zu kommunizieren. Hier müssen wir natürlich zunächst einen versteckten Iframe für die domänenübergreifende Kommunikation erstellen.
Ich bin zu faul, Werkzeuge zum Zeichnen von Bildern zu verwenden, da der Prozess relativ klar ist. Hier werde ich den gesamten Kommunikationsprozess in Worten nacherzählen. Zuerst wird auf der übergeordneten Seite ein versteckter Iframe erstellt und dann werden Befehle wie „Set“, „Get“ und „Clear“ verwendet usw. ausgeführt werden, wird die Nachricht über postMessage gesendet. Nachdem die Seite die Nachricht empfangen hat, analysiert sie den Befehl, die Daten und die Rückruf-ID (postMessage kann aufgrund von Kompatibilitätsproblemen keine Funktionen und Referenzen übergeben. Es ist am besten, nur den Zeichenfolgentyp zu übergeben , also müssen die Daten stringifiziert werden). Wenn die untergeordnete Seite dann den Localstorage-Vorgang abschließt, gibt sie das entsprechende cbid und die Daten über postMessage an die übergeordnete Seite zurück. Die übergeordnete Seite hört das Nachrichtenereignis ab und verarbeitet die Ergebnisse.
4. KodierungNun, es sind nur ein paar Zeilen, beginnen wir mit dem Codieren:
Lassen Sie uns zunächst vorstellen, welche Pakete von Drittanbietern wir verwenden und warum wir sie verwenden:
1. URL-Parse analysiert die URL und verwendet hauptsächlich das darin enthaltene Ursprungsattribut, da postMessage selbst eine strenge Überprüfung des Ursprungs durchführt und wir auch die Verwaltung von Whitelists und Domänennamen unterstützen müssen.
2. ms ist eine Werkzeugbibliothek zum Umrechnen von Zeitabkürzungen in Millisekunden.
3. lz-string ist ein Toolkit zum Komprimieren von Zeichenfolgen. Um LZ zu verstehen, müssen Sie zunächst RLZ (Run Length Encoding) verstehen, einen sehr einfachen Algorithmus zur verlustfreien Komprimierung. Es ersetzt wiederholte Bytes durch eine einfache Beschreibung der wiederholten Bytes und der Anzahl der Wiederholungen. Die Idee hinter dem LZ-Komprimierungsalgorithmus besteht darin, den RLE-Algorithmus zu verwenden, um ein früheres Vorkommen eines Verweises auf dieselbe Bytesequenz zu ersetzen. Einfach ausgedrückt wird der LZ-Algorithmus als String-Matching-Algorithmus betrachtet. Beispiel: Eine bestimmte Zeichenfolge erscheint häufig in einem Textabschnitt und kann durch einen Zeichenfolgenzeiger dargestellt werden, der im vorherigen Text erscheint.
Der Vorteil von lz-string selbst besteht darin, dass es Ihre Speicherkapazität erheblich reduzieren kann. Wenn der lokale Speicher von 5 MB zur Unterstützung der Datenspeicherung mehrerer Domänennamen verwendet wird, wird er komprimiert und ist schnell aufgebraucht Langsamer und kostenintensiver. Wenn Sie große Anforderungen an die zu übertragende Datenmenge haben, können Sie versuchen, diesen Komprimierungsalgorithmus zu verwenden, um die Zeichenfolgenlänge zu optimieren.
4. Die Localstorage-API von Store2 selbst ist relativ einfach. Um die Komplexität der Codelogik zu reduzieren, wird eine beliebte Localstorage-Implementierungsbibliothek zum Ausführen von Store-Vorgängen ausgewählt.
Nachdem wir über das Drittanbieterpaket gesprochen haben, schauen wir uns an, wie man die js der übergeordneten Seite schreibt:
class crosData { constructionor(options) { supportCheck(); this.options = Object.assign({ iframeUrl: '', expire: '30d' }, Optionen .iframeBeforeFuns = []; this.parent = window; this.origin = neue URL(this.options.iframeUrl).origin; this.createIframe(this.options.iframeUrl); addEvent(this.parent, 'message', (evt) => { var data = JSON.parse(evt.data); var origin = evt.origin || evt.originalEvent .origin; //Ich erhalte nur die Nachricht des von mir geöffneten Iframes, die anderen sind illegal und es wird direkt ein Fehler gemeldet, wenn (origin !== this.origin) { Reject('illegal origin!'); return; if (data.err) { this.cbs[data.cbid].reject(data.err) else { this.cbs[data.cbid].resolve(data .ret); } delete this.cbs[data.cbid]; } createIframe(url) { addEvent(document, 'domready', () => { var frame = document.createElement('iframe'); frame.style.cssText = 'width:1px;height:1px;border:0;position:absolute;left:-9999px;top:-9999px;'; 'src', url); Frame.onload = () => { this.child = Frame.contentWindow; this.iframeBeforeFuns.forEach(item => item()); } document.body.appendChild(frame }); } postHandle(type, args) { return new Promise((resolve, depend) => { var cbid = this.cid; var message = { cbid: cbid, origin: new url(location.href).origin, action: type, args: args } this.child.postMessage(JSON.stringify(message), this.origin); this.cbs[cbid] = { auflösen, ablehnen } this.cid++ } send(type, args) { return new Promise(resolve => { if (this.child) { return this.postHandle(type, args).then(resolve } else { var self = this; this.iframeBeforeFuns.push(function() { self.postHandle(type, args).then(resolve); } } }) } set(key, val, options) { options = Object.assign({ Expire: ms (this.options.expire) }, Optionen); return this.send('set', [key, val, options]); } get(key, options) { options = Object.assign({ domain: new url(location.href) }, options); return this.send('get', [key, options]); } clear(key) { return this.send('clear ', [Schlüssel]); }}Es gibt wahrscheinlich nur wenige Methoden. Hier sind einige wichtige Punkte. Lassen Sie mich darüber sprechen.
1. Die Methoden get, set und clear werden alle einheitlich als Sendemethoden bezeichnet, der Optionsteil wird jedoch ergänzt.
2. Die send-Methode gibt ein Promise-Objekt zurück, die postHandle-Methode wird direkt aufgerufen, um den postMessage-Vorgang auszuführen. Wenn der iframe noch geladen wird, wird der aktuelle Vorgang in das iframeBeforeFuns-Array verschoben Funktion und wartet auf das Ende des Iframe-Onloads. Nach dem einheitlichen Aufruf wird die Funktion auch in die postHandle-Methode eingeschlossen.
3. Die postHandle-Methode verpackt Daten vor dem Senden der Anforderung und generiert cbid, origin, action und args. Das cbs-Objekt speichert die Auflösung und Ablehnung unter jedem cbid und wartet vor der Verarbeitung auf die Rückkehr der postMessage der Unterseite. Da postMessage keine Referenzen behalten und keine Funktionen übergeben kann, wird hier diese Methode für die Zuordnung gewählt.
4. Der Konstruktor ist leicht zu verstehen. Wenn diese Klasse initialisiert wird, definieren wir einige benötigte Optionsattribute, erstellen einen Iframe, hören dann auf das Nachrichtenereignis und verarbeiten die von der Unterseite zurückgegebene Nachricht.
5. Im Nachrichtenereignis der übergeordneten Seite müssen wir überprüfen, ob die an mich gesendete Nachricht der von mir geöffnete Fenster-Iframe sein muss. Andernfalls wird ein Fehler gemeldet und die Auflösung und Ablehnung in cbs wird entsprechend ausgeführt Fehlerkennung in den Daten.
6. In der Methode createIframe übernimmt der Rückruf in iframe onload die Zwischenspeicherung der aufrufenden Methode vor der Erstellung. Achten Sie hier auf die Verwendung von domready, da das SDK möglicherweise ausgeführt wird, bevor der Körper analysiert wird.
Hier ist der Code für den untergeordneten Teil:
class iframe { set(key, val, options, origin) { //Überprüfen Sie die Wertgröße, die 20k nicht überschreiten darf val = val.toString(); val = this.lz : val valsize = sizeof(val, 'utf16'); //localStorage speichert Bytes mit utf16-Codierung if (valsize > this.maxsize) { return { err: 'Ihr Speicherwert: ' + valstr + ' Größe ist ' + valsize + 'b, maxsize:' + this.maxsize + 'b, verwenden Sie utf16' } } key = `${this.prefix}_${key}, ${new url(origin).origin}`; var data = { val: val, lasttime: Date.now(), Expire: Date.now() + options.expire }; store.set(key, data); //Wenn es größer als die maximale Speicheranzahl ist, löschen Sie die letzte Aktualisierung if (store.size() > this.storemax) { var keys = store.keys(); keys.sort( (a, b) => { var item1 = store.get(a), item2 = store.get(b); return item2.lasttime - item1.lasttime; }); this.storemax - store.size()); while (removesize) { store.remove(keys.pop()); return { ret: data } } get(key, options) { var keys = store.keys(); var regexp = new RegExp('^' + this.prefix + '_' + key + ',' + options.domain + '$'); keys.filter((key) => { return regexp.test(key); }).map((storeKey) => { var data = store.get(storeKey); data.key = key; data.domain = storeKey .split(',')[1]; if (data.expire < Date.now()) { store.remove(storeKey) { //Update lasttime; val: data.val, lasttime: data.expire }); data.val = this.lz ? lzstring.decompressFromUTF16(data.val) : data.val; filter(item => { return !!item; //Filter undefiniert }); clear(key, origin) { store.remove(`${this.prefix}_${key},${origin}`); return {} } clearOtherKey() { //Ungültige Schlüssel löschen var keys(); var keyReg = new RegExp('^' + this.prefix); keys.forEach(key => { if (!keyReg.test(key)) { store.remove(key); } }); } constructionor(safeDomain, lz) { supportCheck(); this.safeDomain = safeDomain || /.*/; this.clearOtherKey(); safeDomain) !== '[object RegExp]') { throw new Error('safeDomain must be regexp' } this.lz = lz; this.storemax = 100; this.maxsize = 20 * 1024; //byte addEvent(window, 'message', (evt) => { var data = JSON.parse(evt.data); var originHostName = new url( evt .origin).hostname; var origin = evt.origin, action = data.action, cbid = data.cbid, args = data.args; //Legal Broadcast if (evt.origin === data.origin && this.safeDomain.test(originHostName)) { args.push(origin); var whiteAction = ['set', 'get', 'clear'] ; if (whiteAction.indexOf(action) > -1) { var message = this[action].apply(this, args); window.top.postMessage(JSON.stringify(message), origin); } } else { window.top.postMessage(JSON.stringify({ cbid: cbid, err: 'Illegal domain' } }); ; }}Es gibt nicht viel Code. Hier finden Sie eine kurze Einführung in die Verwendung und die organisatorische Beziehung der einzelnen Methoden:
1. Im Konstruktorteil überprüft die obige Klasse auch die Unterstützung von Browserfunktionen und definiert dann Attribute wie den Präfixwert des Geschäfts, die maximale Anzahl und die maximale Größe jedes Schlüssels. Dann erstellen wir einen Nachrichtenkanal und warten darauf, dass die übergeordnete Seite ihn aufruft.
2. In der Nachricht überprüfen wir den Ursprung der Übertragung, überprüfen dann die aufgerufene Methode, rufen die entsprechenden Set-, Get- und Clear-Methoden auf, rufen dann das Ausführungsergebnis ab, binden cbid und senden schließlich die übergeordnete PostMessage-Seite zurück.
3. clearOtherKey löscht einige illegale Speicherdaten und behält nur Daten bei, die dem Format entsprechen.
4. Bei der Set-Methode werden für jedes Datenelement eine Größenüberprüfung und eine LZ-Komprimierung durchgeführt. Die gespeicherten Daten umfassen Wert, Schlüssel, Ablaufzeit und Aktualisierungszeit (wird für die LRU-Berechnung verwendet).
5. Wenn bei der Set-Methode die Anzahl der gespeicherten ls den maximalen Grenzwert überschreitet, ist zu diesem Zeitpunkt ein Löschvorgang erforderlich. LRU ist die Abkürzung für Least Latest Used, also die am wenigsten kürzlich verwendete. Wir durchlaufen alle Schlüsselwerte, sortieren die Schlüsselwerte, übergeben das letzte Mal und führen dann die Pop-Operation des Schlüsselarrays durch, um die Schlüssel zu erhalten, die am Ende des Stapels gelöscht werden müssen, und löschen sie dann einzeln .
6. In der get-Methode durchlaufen wir alle Schlüsselwerte, stimmen mit dem Schlüssel der Domäne überein, die wir abrufen müssen, und zerlegen dann den Schlüssel in den Rückgabewert (wir speichern ihn im Format von Schlüssel und Domäne), weil Die API erfordert die Rückgabe mehrerer übereinstimmender Werte. Wir führen einen abschließenden Filter für die abgelaufenen Daten durch und dekomprimieren dann mit lz den Wert, um sicherzustellen, dass der Benutzer das richtige Ergebnis erhält.
Das Obige ist unser gesamter Implementierungs-Codierungsprozess und unsere Überprüfung. Lassen Sie uns über die aufgetretenen Fallstricke sprechen.
5. Einige aufgetretene FallstrickeDa oben nur der Hauptcode angegeben ist, handelt es sich nicht um den vollständigen Code. Da die Logik selbst relativ klar ist, kann sie in kurzer Zeit geschrieben werden. Lassen Sie uns weiter unten über die Fallstricke sprechen.
1. Berechnen Sie den Speicherwert von localstorage.
Da wir alle wissen, dass es ein Limit von 5 MB gibt, darf der maximale Bedarf für jedes Datenelement 20 * 1024 Bytes nicht überschreiten. Zur Berechnung der Bytes muss localstorage die utf16-Codierung zur Konvertierung verwenden. Bitte lesen Sie diesen Artikel: JS-Berechnung von Zeichen Die Anzahl der Abschnitte wird von Strings belegt
2. Kompatibilität
Es ist am besten, Zeichenfolgen in postMessage unter IE8 zu übergeben. Ereignisse müssen geglättet werden und JSON muss geglättet werden.
3. Asynchrone Verarbeitung beim Erstellen eines Iframes
Hier haben wir zuvor ein rekursives Warten auf setTimeout durchgeführt, es jedoch später in die obige Implementierungsmethode geändert. Nach dem Laden wird die Auflösung des Versprechens einheitlich verarbeitet, um die Vereinheitlichung der Versprechen-API sicherzustellen.
4. Beim Speichern von Daten räumliche Komplexität vs. zeitliche Komplexität.
Die erste Version ist nicht die obige Implementierung, ich habe 3 Versionen implementiert:
Die erste Version speichert ein LRU-Array, um die Zeitkomplexität zu reduzieren, verschwendet jedoch die Platzkomplexität. Darüber hinaus ist die Get-Methode des Speichers nach dem Testen relativ zeitaufwändig, hauptsächlich aufgrund der zeitaufwändigen Analyse.
In der zweiten Version habe ich alle Daten, einschließlich des LRU-Arrays, in einem Schlüsselwert gespeichert, um die Komprimierungsrate von lz-string zu maximieren. Daher ist der Analysezeitverbrauch bei lz-string und getItem sehr groß viele Daten, obwohl die Berechnung die zeitliche Komplexität am geringsten ist.
Bei der letzten Version habe ich etwas Zeitaufwand und Platzaufwand geopfert, aber da der Engpass in der Lese- und Schreibgeschwindigkeit von Set und Get liegt, ist die Lese- und Schreibgeschwindigkeit eines einzelnen Speichervorgangs extrem hoch Schlüssel liegt daran, dass die unterste Ebene im lokalen Speicher verwendet wird. Die Leistung ist immer noch sehr gut. 100 Einträge können in 20 KB gespeichert werden, und die Lese- und Schreibzeit beträgt etwa 1 Sekunde.
6. Zusammenfassung und VergleichNachdem ich das Modul geschrieben hatte, wurde mir klar, dass es eine solche Bibliothek gibt: zendesk/cross-storage
Aber ich habe seine API und seinen Quellcode überprüft und die Implementierungsmethoden verglichen. Ich denke, meine Version ist wichtiger.
1. Meine Version hat die Kontrolle über den Domänennamen und die Datenverwaltung.
2. Meine Version der Promise-API ist vereinfacht, mit einem onConnect weniger. Sie ist viel mehr als das, was ich geschrieben habe, und sie löst nicht das Problem des asynchronen Iframes.
3. LZ-komprimierte Daten werden nicht unterstützt.
4. Die Verwaltung des LRU-Speicherpools wird nicht unterstützt, daher ist möglicherweise zu viel Speicher vorhanden, was zu Schreibfehlern führen kann.
5. Er scheint für jede Interaktion einen Iframe zu erstellen, was eine Verschwendung von DOM-Operationen und Broadcasting darstellt. Natürlich muss er möglicherweise eine Verbindung zu mehreren Clients herstellen, damit er dies tut .
ZusammenfassenDas Obige ist die Einführung des Herausgebers in das Problem der Verwendung von Localstorage anstelle von Cookies, um den domänenübergreifenden Datenaustausch zu realisieren. Wenn Sie Fragen haben, hinterlassen Sie mir bitte eine Nachricht und der Herausgeber wird Ihnen antworten rechtzeitig. Ich möchte mich auch bei allen für die Unterstützung der VeVb-Kampfsport-Website bedanken!