Sök…


Syntax

  • klass Foo {}
  • klass Foo utökar Bar {}
  • klass Foo {konstruktör () {}}
  • klass Foo {myMethod () {}}
  • klass Foo {get myProperty () {}}
  • klass Foo {set myProperty (newValue) {}}
  • klass Foo {static myStaticMethod () {}}
  • klass Foo {static get myStaticProperty () {}}
  • const Foo = klass Foo {};
  • const Foo = klass {};

Anmärkningar

class stöd endast läggas till JavaScript som en del av 2015 standard.

Javascript-klasser är syntaktiskt socker över JavaScript: s redan existerande prototypbaserade arv. Den nya syntaxen introducerar inte en ny objektorienterad arvsmodell till JavaScript, bara ett enklare sätt att hantera objekt och arv. En class deklaration är i huvudsak en förkortning för manuellt definiera en konstruktör function och addera egenskaperna till prototypen för konstruktören. En viktig skillnad är att funktioner kan kallas direkt (utan det new nyckelordet), medan en klass som kallas direkt kommer att kasta ett undantag.

class someClass {
    constructor () {}
    someMethod () {}
}
 
console.log(typeof someClass);               
console.log(someClass);
console.log(someClass === someClass.prototype.constructor);                         
console.log(someClass.prototype.someMethod);
 
// Output:
// function
// function someClass() { "use strict"; }
// true
// function () { "use strict"; }

Om du använder en tidigare version av JavaScript behöver du en transpilerare som eller för att sammanställa koden till en version som målplattformen kan förstå.

Klasskonstruktör

Den grundläggande delen av de flesta klasser är dess konstruktör, som ställer in varje instans ursprungliga tillstånd och hanterar alla parametrar som har vidtagits när man ringde new .

Det är definierad i en class blocket som om du definierar en metod som heter constructor , men det är faktiskt hanteras som ett specialfall.

class MyClass {
    constructor(option) {
        console.log(`Creating instance using ${option} option.`);
        this.option = option;
    }
}

Exempel på användning:

const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"

En liten sak att notera är att en klasskonstruktör inte kan göras statisk via det static nyckelordet, som beskrivs nedan för andra metoder.

Statiska metoder

Statiska metoder och egenskaper definieras i klassen / konstruktören själv , inte på instansobjekt. Dessa anges i en klassdefinition med hjälp av det static nyckelordet.

class MyClass {
    static myStaticMethod() {
        return 'Hello';
    }

    static get myStaticProperty() {
        return 'Goodbye';
    }
}

console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"

Vi kan se att statiska egenskaper inte definieras i objektfall:

const myClassInstance = new MyClass();

console.log(myClassInstance.myStaticProperty); // logs: undefined

Men de definieras klasser:

class MySubClass extends MyClass {};

console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"

Getters and Setters

Getters and seters låter dig definiera anpassat beteende för att läsa och skriva en viss egenskap i din klass. För användaren verkar de samma som alla typiska egenskaper. Emellertid används en anpassad funktion som du tillhandahåller för att bestämma värdet när egenskapen nås (getter) och för att förforma alla nödvändiga ändringar när egenskapen tilldelas (setter).

I en class definition, är en getter skriven som en no-argument metod föregås av get sökord. En setter är liknande, förutom att den accepterar ett argument (det nya värdet som tilldelas) och det set nyckelordet används istället.

Här är en exempelklass som ger en getter och setter för sin .name egenskap. Varje gång det tilldelas registrerar vi det nya namnet i en intern .names_ matris. Varje gång det öppnas kommer vi tillbaka det senaste namnet.

class MyClass {
    constructor() {
        this.names_ = [];
    }

    set name(value) {
        this.names_.push(value);
    }

    get name() {
        return this.names_[this.names_.length - 1];
    }
}

const myClassInstance = new MyClass();
myClassInstance.name = 'Joe';
myClassInstance.name = 'Bob';

console.log(myClassInstance.name); // logs: "Bob"
console.log(myClassInstance.names_); // logs: ["Joe", "Bob"]

Om du bara definierar en setter, försöker du komma åt egenskapen alltid undefined .

const classInstance = new class {
    set prop(value) {
        console.log('setting', value);
    }
};

classInstance.prop = 10; // logs: "setting", 10

console.log(classInstance.prop); // logs: undefined

Om du bara definierar en getter har försök att tilldela egenskapen ingen effekt.

const classInstance = new class {
    get prop() {
        return 5;
    }
};

classInstance.prop = 10;

console.log(classInstance.prop); // logs: 5

Klass ärft

Arv fungerar precis som på andra objektorienterade språk: metoder definierade på superklassen är tillgängliga i den utvidgade underklassen.

Om underklassen förklarar sin egen konstruktör måste den åberopa föräldrarnas konstruktör via super() innan den kan komma åt this .

class SuperClass {

    constructor() {
        this.logger = console.log;
    }

    log() {
        this.logger(`Hello ${this.name}`);
    }

}

class SubClass extends SuperClass {

    constructor() {
        super();
        this.name = 'subclass';
    }

}

const subClass = new SubClass();

subClass.log(); // logs: "Hello subclass"

Privata medlemmar

JavaScript stöder inte tekniskt privata medlemmar som språkfunktion. Sekretess - beskrivet av Douglas Crockford - emuleras istället via stängningar (bevarad funktionsomfång) som kommer att genereras var och en med varje inställningsanrop i en konstruktörsfunktion.

Exemplet i Queue visar hur lokal konstruktion kan bevaras och göras tillgängliga via konstruktorfunktioner via privilegierade metoder.

class Queue {

  constructor () {                    // - does generate a closure with each instantiation.

    const list = [];                  // - local state ("private member").

    this.enqueue = function (type) {  // - privileged public method
                                      //   accessing the local state
      list.push(type);                //   "writing" alike.
      return type;
    };
    this.dequeue = function () {      // - privileged public method
                                      //   accessing the local state
      return list.shift();            //   "reading / writing" alike.
    };
  }
}


var q = new Queue;            //
                              //
q.enqueue(9);                 // ... first in ...
q.enqueue(8);                 //
q.enqueue(7);                 //
                              //
console.log(q.dequeue());     // 9 ... first out.
console.log(q.dequeue());     // 8
console.log(q.dequeue());     // 7
console.log(q);               // {}
console.log(Object.keys(q));  // ["enqueue","dequeue"]

Med varje instans av en Queue typ skapar konstruktören en stängning.

Således både en Queue typ egna metoder enqueue och dequeue (se Object.keys(q) ) fortfarande har tillgång till list som fortsätter att leva i sin omslutande omfattning som vid byggtiden, har bevarats.

Med hjälp av detta mönster - emulering av privata medlemmar via privilegierade offentliga metoder - bör man komma ihåg att med varje instans kommer ytterligare minne att konsumeras för varje egen egendomsmetod (för det är kod som inte kan delas / återanvändas). Detsamma gäller för mängden / storleken på tillstånd som kommer att lagras inom en sådan stängning.

Dynamiska metodnamn

Det finns också förmågan att utvärdera uttryck när man namnger metoder som liknar hur du kan komma åt ett objekts egenskaper med [] . Detta kan vara användbart för att ha dynamiska egendomsnamn, men används ofta i samband med symboler.

let METADATA = Symbol('metadata');

class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
  
  // example using symbols
  [METADATA]() {
    return {
      make: this.make,
      model: this.model
    };
  }

  // you can also use any javascript expression

  // this one is just a string, and could also be defined with simply add()
  ["add"](a, b) {
    return a + b;
  }

  // this one is dynamically evaluated
  [1 + 2]() {
    return "three";
  }
}

let MazdaMPV = new Car("Mazda", "MPV");
MazdaMPV.add(4, 5); // 9
MazdaMPV[3](); // "three"
MazdaMPV[METADATA](); // { make: "Mazda", model: "MPV" }

metoder

Metoder kan definieras i klasser för att utföra en funktion och eventuellt returnera ett resultat.
De kan få argument från den som ringer.

class Something {
    constructor(data) {
        this.data = data
    }

    doSomething(text) {
        return {
            data: this.data,
            text
        }
    }
}

var s = new Something({})
s.doSomething("hi") // returns: { data: {}, text: "hi" }

Hantera privata data med klasser

Ett av de vanligaste hinder som använder klasser är att hitta rätt sätt att hantera privata stater. Det finns fyra vanliga lösningar för att hantera privata stater:

Använda symboler

Symboler är en ny primitiv typ som introducerades i ES2015, enligt definitionen på MDN

En symbol är en unik och oföränderlig datatyp som kan användas som en identifierare för objektegenskaper.

När du använder symbol som en egendomsnyckel är det inte många.

Som sådan kommer de inte att avslöjas med for var in eller i Object.keys .

Således kan vi använda symboler för att lagra privat data.

const topSecret = Symbol('topSecret'); // our private key; will only be accessible on the scope of the module file
export class SecretAgent{
    constructor(secret){
        this[topSecret] = secret; // we have access to the symbol key (closure)
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(topSecret[topSecret]); // we have access to topSecret
        };
    }
}

Eftersom symbols är unika måste vi ha referens till den ursprungliga symbolen för att få åtkomst till den privata egenskapen.

import {SecretAgent} from 'SecretAgent.js'
const agent = new SecretAgent('steal all the ice cream');
// ok lets try to get the secret out of him!
Object.keys(agent); // ['coverStory'] only cover story is public, our secret is kept.
agent[Symbol('topSecret')]; // undefined, as we said, symbols are always unique, so only the original symbol will help us to get the data.

Men det är inte 100% privat; låt oss bryta ner agenten! Vi kan använda metoden Object.getOwnPropertySymbols för att få objektsymbolerna.

const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.

Använda WeakMaps

WeakMap är en ny typ av objekt som har lagts till för es6.

Som definierat på MDN

WeakMap-objektet är en samling nyckel- / värdepar där knapparna är svagt refererade till. Nycklarna måste vara objekt och värdena kan vara godtyckliga värden.

En annan viktig egenskap hos WeakMap är, enligt definitionen på MDN .

Nyckeln i en WeakMap hålls svagt. Vad detta betyder är att om det inte finns några andra starka referenser till nyckeln kommer hela posten att tas bort från WeakMap av avfallssamlaren.

Tanken är att använda WeakMap, som en statisk karta för hela klassen, för att hålla varje instans som nyckel och behålla den privata informationen som ett värde för den instansnyckeln.

Således är det bara inom klassen att vi har tillgång till WeakMap kollektionen.

Låt oss försöka vår agent med WeakMap :

const topSecret = new WeakMap(); // will hold all private data of all instances.
export class SecretAgent{
    constructor(secret){
        topSecret.set(this,secret); // we use this, as the key, to set it on our instance private data
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(topSecret.get(this)); // we have access to topSecret
        };
    }
}

Eftersom const topSecret definieras i vår modulstängning, och eftersom vi inte binder den till våra instansegenskaper, är denna strategi helt privat och vi kan inte nå agenten topSecret .

Definiera alla metoder inuti konstruktören

Idén här är helt enkelt att definiera alla våra metoder och medlemmar i konstruktören och använda stängningen för att få åtkomst till privata medlemmar utan att tilldela dem till this .

   export class SecretAgent{
        constructor(secret){
            const topSecret = secret;
            this.coverStory = 'just a simple gardner';
            this.doMission = () => {
                figureWhatToDo(topSecret); // we have access to topSecret
            };
        }
    }

Även i detta exempel är uppgifterna 100% privata och kan inte nås utanför klassen, så vår agent är säker.

Använda namnkonventioner

Vi kommer att bestämma att alla egendomar som är privata kommer att prefixeras med _ .

Observera att för denna strategi är uppgifterna inte riktigt privata.

export class SecretAgent{
    constructor(secret){
        this._topSecret = secret; // it private by convention
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(this_topSecret); 
        };
    }
}

Klassnamn bindande

ClassDeclarations namn är bundet på olika sätt i olika tillämpningsområden -

  1. Omfattningen i vilken klassen definieras - let bindande
  2. Omfattningen av klassen själv - inom { och } i class {} - const bindande
class Foo {
  // Foo inside this block is a const binding
}
// Foo here is a let binding

Till exempel,

class A {
  foo() {
    A = null; // will throw at runtime as A inside the class is a `const` binding
  }
}
A = null; // will NOT throw as A here is a `let` binding

Detta är inte detsamma för en funktion -

function A() {
  A = null; // works
}
A.prototype.foo = function foo() {
  A = null; // works
}
A = null; // works


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow