JavaScript (oder ECMAScript) ist die Programmiersprache, die das Web antreibt. Entstanden im Mai 1995 von Brendan Eich, hat sie sich als eine weit verbreitete und vielseitige Technologie etabliert. Trotz seines Erfolgs wurde es mit seiner fairen Dosis Kritik konfrontiert, insbesondere wegen Eigenheiten. Dinge wie Objekte, die beim Verwenden als Indizes in eine Zeichenfolgenform umgewandelt werden, 1 == "1" gibt true zurück, oder das notorisch verwirrende this Schlüsselwort. Eine besonders interessante Eigenart ist jedoch die Existenz verschiedener Techniken für den Schutz von Variablen.
In seinem aktuellen Zustand gibt es keine „direkte“ Möglichkeit, eine private Variable in JavaScript zu erstellen. In anderen Sprachen können Sie das Schlüsselwort private oder doppelte Unterstriche verwenden und alles funktioniert, aber Variablenschutz in JavaScript hat Eigenschaften, die es eher wie eine aufkommende Eigenschaft der Sprache erscheinen lassen als eine beabsichtigte Funktionalität. Lassen Sie uns einige Hintergründe zu unserem Problem einführen.
Das Schlüsselwort „var“
Vor 2015 gab es im Wesentlichen eine Möglichkeit, eine Variable zu erstellen, und das war das Schlüsselwort var. var ist funktionsbezogen, was bedeutet, dass Variablen, die mit dem Schlüsselwort instanziiert wurden, nur für Code innerhalb der Funktion zugänglich waren. Außerhalb einer Funktion oder im Wesentlichen „global“ ist die Variable für alles zugänglich, was nach der Definition der Variable ausgeführt wird. Wenn Sie versuchen, auf die Variable im selben Geltungsbereich vor ihrer Definition zuzugreifen, erhalten Sie undefined anstelle eines Fehlers. Dies liegt daran, wie das Schlüsselwort var „gehoistet“ wird.
// Define "a" in global scope
var a = 123;
// Define "b" in function scope
(function() {
console.log(b); //=> Returns "undefined" instead of an error due to hoisting.
var b = 456;
})();
console.log(a); // => 123
console.log(b); // Throws "ReferenceError" exception, because "b" cannot be accessed from outside the function scope.
Die Geburt der ES6-Variablen
Im Jahr 2015 wurde ES6/ES2015 offiziell, und damit kamen zwei neue Schlüsselwörter für Variablen: let und const. Beide waren blockbezogen, was bedeutet, dass Variablen, die mit den Schlüsselwörtern erstellt wurden, aus allem innerhalb desselben geschweiften Klammernpaares zugänglich waren. Wie bei var konnten let- und const-Variablen jedoch nicht außerhalb des Blockumfangs mit Schleifen, Funktionen, if-Anweisungen, geschweiften Klammern usw. zugegriffen werden.
const a = 123;
// Block scope example #1
if (true) {
const b = 345;
}
// Block scope example #2
{
const c = 678;
}
console.log(a); // 123
console.log(b); // Throws "ReferenceError" because "b" cannot be accessed from outside the block scope.
console.log(c); // Throws "ReferenceError" because "b" cannot be accessed from outside the block scope.
Da Code außerhalb des Geltungsbereichs nicht auf die Variablen zugreifen kann, erhalten wir eine aufkommende Eigenschaft der Privatsphäre. Wir werden einige Techniken behandeln, um dies auf verschiedene Weise zu implementieren.
Verwendung von Funktionen
Da Funktionen in JavaScript ebenfalls Blöcke sind, funktionieren alle Variablenschlüsselwörter mit ihnen. Darüber hinaus können wir ein sehr nützliches Entwurfsmuster namens „Modul“ implementieren.
Das Modul-Entwurfsmuster
Google stützt sich auf das Oxford Dictionary, um ein „Modul“ zu definieren
Jede einer Reihe von unterschiedlichen, aber zusammenhängenden Einheiten, aus denen ein Programm aufgebaut werden kann oder in die eine komplexe Aktivität analysiert werden kann.
– „Module“ Definition 1.2
Das Modul-Entwurfsmuster ist in JavaScript sehr nützlich, da es öffentliche und private Komponenten kombiniert und es uns ermöglicht, ein Programm in kleinere Komponenten aufzuteilen, wobei nur das offengelegt wird, auf das ein anderer Teil des Programms über einen Prozess namens „Kapselung“ zugreifen kann. Durch diese Methode legen wir nur das offen, was verwendet werden muss, und können den Rest der Implementierung, die nicht gesehen werden muss, verbergen. Wir können den Funktionsbereich nutzen, um dies zu implementieren.
const CarModule = () => {
let milesDriven = 0;
let speed = 0;
const accelerate = (amount) => {
speed += amount;
milesDriven += speed;
}
const getMilesDriven = () => milesDriven;
// Using the "return" keyword, you can control what gets
// exposed and what gets hidden. In this case, we expose
// only the accelerate() and getMilesDriven() function.
return {
accelerate,
getMilesDriven
}
};
const testCarModule = CarModule();
testCarModule.accelerate(5);
testCarModule.accelerate(4);
console.log(testCarModule.getMilesDriven());
Damit können wir die Anzahl der gefahrenen Meilen sowie die Beschleunigung erhalten, aber da der Benutzer in diesem Fall keinen Zugriff auf die Geschwindigkeit benötigt, können wir sie verbergen, indem wir nur die Methode accelerate() und getMilesDriven() offenlegen. Im Wesentlichen ist speed eine private Variable, da sie nur für Code innerhalb desselben Blockumfangs zugänglich ist. Der Vorteil von privaten Variablen wird in dieser Situation klar. Wenn Sie die Fähigkeit, auf eine Variable, eine Funktion oder eine andere interne Komponente zuzugreifen, entfernen, reduzieren Sie die Angriffsfläche für Fehler, die dadurch entstehen, dass jemand anderes versehentlich etwas verwendet, das nicht dazu bestimmt war.
Der alternative Weg
Im zweiten Beispiel werden Sie die Ergänzung des Schlüsselworts this bemerken. Es gibt einen Unterschied zwischen der ES6-Pfeilfunktion ( => ) und der traditionellen function(){}. Mit dem Schlüsselwort function können Sie this verwenden, das an die Funktion selbst gebunden ist, während Pfeilfunktionen keinerlei Verwendung des Schlüsselworts this zulassen. Beide sind gleichwertige Möglichkeiten, das Modul zu erstellen. Die Kernidee ist, Teile offenzulegen, die zugegriffen werden sollen, und andere Teile, mit denen nicht interagiert werden soll, zurückzulassen, also sowohl öffentliche als auch private Daten.
function CarModule() {
let milesDriven = 0;
let speed = 0;
// In this case, we instead use the "this" keyword,
// which refers to CarModule
this.accelerate = (amount) => {
speed += amount;
milesDriven += speed;
}
this.getMilesDriven = () => milesDriven;
}
const testCarModule = new CarModule();
testCarModule.accelerate(5);
testCarModule.accelerate(4);
console.log(testCarModule.getMilesDriven());
Einführung in ES6-Klassen
Klassen waren eine weitere Ergänzung, die mit ES6 kam. Klassen sind im Wesentlichen syntaktischer Zucker – mit anderen Worten, immer noch eine Funktion, aber potenziell „versüßen“ sie in eine Form, die einfacher auszudrücken ist. Mit Klassen ist Variablenschutz (derzeit) fast unmöglich, ohne einige größere Änderungen am Code vorzunehmen.
Betrachten wir ein Beispiel für eine Klasse.
class CarModule {
/*
milesDriven = 0;
speed = 0;
*/
constructor() {
this.milesDriven = 0;
this.speed = 0;
}
accelerate(amount) {
this.speed += amount;
this.milesDriven += this.speed;
}
getMilesDriven() {
return this.milesDriven;
}
}
const testCarModule = new CarModule();
testCarModule.accelerate(5);
testCarModule.accelerate(4);
console.log(testCarModule.getMilesDriven());
Eines der ersten Dinge, die auffallen, ist, dass die Variablen milesDriven und speed innerhalb einer constructor()-Funktion stehen. Beachten Sie, dass Sie die Variablen auch außerhalb des Konstruktors definieren können (wie im Code-Kommentar gezeigt), aber sie sind funktional gleich, unabhängig davon. Das Problem ist, dass diese Variablen öffentlich und für Elemente außerhalb der Klasse zugänglich sein werden.
Betrachten wir einige Möglichkeiten, dies zu umgehen.
Verwendung eines Unterstrichs
In Fällen, in denen der Datenschutz verhindern soll, dass Kollaborateure katastrophale Fehler machen, kann das Präfixieren von Variablen mit einem Unterstrich (_), obwohl sie immer noch „sichtbar“ für die Außenwelt sind, ausreichen, um einem Entwickler zu signalisieren: „Fassen Sie diese Variable nicht an.“ Wir haben also zum Beispiel Folgendes:
// This is the new constructor for the class. Note that it could
// also be expressed as the following outside of constructor().
/*
_milesDriven = 0;
_speed = 0;
*/
constructor() {
this._milesDriven = 0;
this._speed = 0;
}
Während dies für seinen spezifischen Anwendungsfall funktioniert, ist es immer noch sicher zu sagen, dass es auf vielen Ebenen weniger als ideal ist. Sie können immer noch auf die Variable zugreifen, aber Sie müssen auch den Variablennamen zusätzlich dazu ändern.
Alles in den Konstruktor packen
Technisch gesehen gibt es eine Methode für Variablenschutz in einer Klasse, die Sie derzeit verwenden können, und das ist, alle Variablen und Methoden innerhalb der constructor()-Funktion zu platzieren. Betrachten wir das.
class CarModule {
constructor() {
let milesDriven = 0;
let speed = 0;
this.accelerate = (amount) => {
speed += amount;
milesDriven += speed;
}
this.getMilesDriven = () => milesDriven;
}
}
const testCarModule = new CarModule();
testCarModule.accelerate(5);
testCarModule.accelerate(4);
console.log(testCarModule.getMilesDriven());
console.log(testCarModule.speed); // undefined -- We have true variable privacy now.
Diese Methode erreicht echten Variablenschutz in dem Sinne, dass es keine Möglichkeit gibt, direkt auf Variablen zuzugreifen, die nicht absichtlich offengelegt werden. Das Problem ist, dass wir nun Code haben, der im Vergleich zu dem, was wir vorher hatten, nicht besonders gut aussieht, zusätzlich zu der Tatsache, dass er die Vorteile des synthetischen Zuckers, den wir mit Klassen hatten, zunichte macht. An diesem Punkt könnten wir genauso gut die function()-Methode verwenden.
Verwendung von WeakMap
Es gibt eine weitere, kreativere Möglichkeit, eine private Variable zu erstellen, und das ist die Verwendung von WeakMap(). Obwohl es ähnlich wie Map klingen mag, sind die beiden sehr unterschiedlich. Während Maps jeden Wertetyp als Schlüssel nehmen können, nimmt eine WeakMap nur Objekte und löscht die Werte in der WeakMap, wenn der Objekt-Schlüssel garbage collected wird. Darüber hinaus kann eine WeakMap nicht durchlaufen werden, was bedeutet, dass Sie Zugriff auf die Referenz zu einem Objekt-Schlüssel haben müssen, um auf einen Wert zuzugreifen. Dies macht sie für die Erstellung von privaten Variablen eher nützlich, da die Variablen effektiv unsichtbar sind.
class CarModule {
constructor() {
this.data = new WeakMap();
this.data.set(this, {
milesDriven: 0,
speed: 0
});
this.getMilesDriven = () => this.data.get(this).milesDriven;
}
accelerate(amount) {
// In this version, we instead create a WeakMap and
// use the "this" keyword as a key, which is not likely
// to be used accidentally as a key to the WeakMap.
const data = this.data.get(this);
const speed = data.speed + amount;
const milesDriven = data.milesDriven + data.speed;
this.data.set({ speed, milesDriven });
}
}
const testCarModule = new CarModule();
testCarModule.accelerate(5);
testCarModule.accelerate(4);
console.log(testCarModule.getMilesDriven());
console.log(testCarModule.data); //=> WeakMap { [items unknown] } -- This data cannot be accessed easily from the outside!
Diese Lösung ist gut darin, eine versehentliche Nutzung der Daten zu verhindern, ist aber nicht wirklich privat, da sie immer noch von außerhalb des Geltungsbereichs durch Ersetzen von this durch CarModule zugegriffen werden kann. Darüber hinaus fügt sie eine erhebliche Komplexität hinzu und ist daher nicht die eleganteste Lösung.
Verwendung von Symbolen zur Vermeidung von Kollisionen
Wenn die Absicht darin besteht, Namenskollisionen zu verhindern, gibt es eine nützliche Lösung mit Symbol. Dies sind im Wesentlichen Instanzen, die sich wie eindeutige Werte verhalten können, die niemals gleich irgendetwas anderem sind, außer ihrer eigenen eindeutigen Instanz. Hier ist ein Beispiel dafür in Aktion
class CarModule {
constructor() {
this.speedKey = Symbol("speedKey");
this.milesDrivenKey = Symbol("milesDrivenKey");
this[this.speedKey] = 0;
this[this.milesDrivenKey] = 0;
}
accelerate(amount) {
// It's virtually impossible for this data to be
// accidentally accessed. By no means is it private,
// but it's well out of the way of anyone who would
// be implementing this module.
this[this.speedKey] += amount;
this[this.milesDrivenKey] += this[this.speedKey];
}
getMilesDriven() {
return this[this.milesDrivenKey];
}
}
const testCarModule = new CarModule();
testCarModule.accelerate(5);
testCarModule.accelerate(4);
console.log(testCarModule.getMilesDriven());
console.log(testCarModule.speed); // => undefined -- we would need to access the internal keys to access the variable.
Like the underscore solution, this method more or less relies on naming conventions to prevent confusion.
TC39 Vorschlag für private Klassenfelder
Vor kurzem wurde ein neuer Vorschlag eingeführt, der private Variablen in Klassen einführen würde. Es ist ziemlich einfach: Stellen Sie ein # vor den Namen einer Variablen, und sie wird privat. Keine zusätzlichen strukturellen Änderungen erforderlich.
class CarModule {
#speed = 0
#milesDriven = 0
accelerate(amount) {
// It's virtually impossible for this data to be
// accidentally accessed.
this.#speed += amount;
this.#milesDriven += speed;
}
getMilesDriven() {
return this.#milesDriven;
}
}
const testCarModule = new CarModule();
testCarModule.accelerate(5);
testCarModule.accelerate(4);
console.log(testCarModule.getMilesDriven());
console.log(testCarModule.speed); //=> undefined -- we would need to access the internal keys to access the variable.
Der Vorschlag für private Klassenfelder ist kein Standard und kann zum Zeitpunkt der Erstellung dieses Textes nicht ohne die Verwendung von Babel verwendet werden. Sie müssen also noch etwas warten, bis er in großen Browsern, Node usw. verwendet werden kann.
Private Klassenmerkmale sind Realität geworden und haben bereits ziemlich gute Browserunterstützung.
Fazit
Das fasst die verschiedenen Möglichkeiten zusammen, wie Sie private Variablen in JavaScript implementieren können. Es gibt keinen einzigen „richtigen“ Weg, dies zu tun. Diese funktionieren für unterschiedliche Bedürfnisse, bestehende Codebasen und andere Einschränkungen. Während jede Methode Vor- und Nachteile hat, sind letztendlich alle Methoden gleichwertig, solange sie Ihr Problem effektiv lösen.
Danke fürs Lesen! Ich hoffe, dies gibt Ihnen einige Einblicke, wie Geltungsbereich und Variablenschutz angewendet werden können, um Ihren JavaScript-Code zu verbessern. Dies ist eine mächtige Technik und kann so viele verschiedene Methoden unterstützen und Ihren Code benutzerfreundlicher und fehlerfreier machen. Probieren Sie einige neue Beispiele für sich selbst aus und gewinnen Sie ein besseres Gefühl dafür.
Ihre WeakMap- und Symbolbeispiele lassen immer noch alles offen… Sicher, es ist durch eine Indirektionsebene verschleiert, aber trotzdem.
Versuchen Sie es eher so
Dies erzwingt tatsächlich den Datenschutz der Daten, da die Weakmap nur für Instanzen von CarModule sichtbar ist.
Dasselbe würde für die Version mit Symbolen gelten – definieren Sie sie in einem Bereich, der nicht global ist, und nicht als Eigenschaften des Objekts, auf dem sie verwendet werden. (Es gibt keinen Grund, das Symbol jedes Mal neu zu definieren: Symbol(“Foo”) !== Symbol(“Foo”).)
Die Art und Weise, wie ich private Variablen in einer ES6-Klasse gesehen habe, ist mit Symbolen, aber sie nicht auf der Instanz offenzulegen.
var Person = Class.create(“Person”, {
ctor: function(fn, ln){
this.fn = fn;
this.ln = ln;
},
firstName: function (){
if(arguments.length){
this.fn = arguments [0];}
return this.fn;
}
});
var p = new Person(‘John’, ‘Doe’);
p.firstName();
p.fn ist nicht auf der Objektinstanz verfügbar
Tolle JavaScript-Daten-Code-Informationsimplementierung und -ausführung. Verschiedene Ebenen unterschiedlicher Arten von Bedingungen und Situationen.
Bezüglich der vorgeschlagenen Syntax befindet sie sich seit fast 2 Jahren in Phase 3 und wird bereits in Chrome und Node.js unterstützt. Scheint mir eine ausgemachte Sache zu sein. Wer diese Funktion benötigt, sollte sie heute problemlos über Babel nutzen können.
Verwenden Sie es-Proxy, um jede Art von Schreiben und Lesen von/zu dem von Ihnen erstellten Objekt zu proxieren.
Warum nicht einfach ein Schlüsselwort wie private haben? # wird in vielen anderen Sprachen für Kommentare verwendet.
Danke dafür! Ich konnte einiges lernen, besonders mit der WeakMap und dem TC39-Vorschlag. In der
accellerate-Methode Ihres WeakMap-Beispiels haben Sie meiner Meinung nach einen Tippfehler. Sollten Sie nichtthisals erstes Argument inthis.data.setübergeben?Warum neue Syntax einführen?
TypeScript, C++, C#, PHP, Java usw. verwenden alle das Schlüsselwort „private/protected“ für diese Funktion.
Bitte schlagen Sie keine weitere Möglichkeit vor, dies zu tun. Wenn wir bereits so viele Sprachen zur Auswahl haben, wäre es schön, mehr Konsistenz in der Syntax zu haben, nicht weniger.
TypeScript bietet das Schlüsselwort
private, das Sie zur Kompilierzeit warnt, wenn Sie versuchen, eine private Eigenschaft zu verwenden.+1
Mein Muster für private Variablen ist so
Konvention ist, dass eine private Klassen-Eigenschaft mit einem Unterstrich beginnt, was eine einfache Unterscheidung zwischen klasseninternen und funktionslokalen Variablen ermöglicht, und die Änderung öffentlicher Eigenschaften erfolgt über _this.state anstelle von this.state – wodurch die Verwendung sowohl in privaten als auch in öffentlichen Funktionen vereinheitlicht wird.