Поиск…


Синтаксис

  • класс Foo {}
  • класс Foo расширяет Bar {}
  • класс Foo {constructor () {}}
  • class Foo {myMethod () {}}
  • class Foo {get myProperty () {}}
  • class Foo {set myProperty (newValue) {}}
  • class Foo {static myStaticMethod () {}}
  • class Foo {static get myStaticProperty () {}}
  • const Foo = класс Foo {};
  • const Foo = class {};

замечания

поддержка class была добавлена ​​только в JavaScript в рамках стандарта 2015 года.

Классы Javascript являются синтаксическим сахаром над уже существующим на основе прототипов на основе JavaScript. Этот новый синтаксис не представляет новую объектно-ориентированную модель наследования для JavaScript, а просто более простой способ борьбы с объектами и наследованием. Объявление class является, по существу, сокращением для ручного определения function конструктора и добавления свойств к прототипу конструктора. Важным отличием является то, что функции можно вызывать напрямую (без new ключевого слова), тогда как класс, вызываемый напрямую, будет генерировать исключение.

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"; }

Если вы используете более раннюю версию JavaScript, вам понадобится транспилер вроде или clos , чтобы скомпилировать код в версию, которую может понять целевая платформа.

Конструктор классов

Фундаментальной частью большинства классов является его конструктор, который устанавливает начальное состояние каждого экземпляра и обрабатывает любые параметры, которые были переданы при вызове new .

Он определен в блоке class как будто вы определяете метод с именем constructor , хотя он фактически обрабатывается как особый случай.

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

Пример использования:

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

Следует отметить, что конструктор класса не может быть статическим с помощью static ключевого слова, как описано ниже для других методов.

Статические методы

Статические методы и свойства определяются самим классом / конструктором , а не объектами экземпляра. Они указаны в определении класса с использованием ключевого слова static .

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

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

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

Мы видим, что статические свойства не определены в экземплярах объектов:

const myClassInstance = new MyClass();

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

Тем не менее, они определяются на подклассы:

class MySubClass extends MyClass {};

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

Геттеры и сеттеры

Getters и seters позволяют определить пользовательское поведение для чтения и записи данного свойства в вашем классе. Для пользователя они выглядят так же, как и любое типичное свойство. Однако внутренне настраиваемая функция, которую вы предоставляете, используется для определения значения при доступе к ресурсу (получателя) и для предварительного изменения любых необходимых изменений при назначении свойства (установщик).

В определении class геттер записывается как метод без аргументов с префиксом ключевого слова get . Установщик аналогичен, за исключением того, что он принимает один аргумент (назначается новое значение) и вместо него используется ключевое слово set .

Вот примерный класс, который предоставляет getter и setter для своего свойства .name . Каждый раз, когда он назначается, мы записываем новое имя во внутренний массив .names_ . При каждом обращении к нему мы вернем последнее имя.

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"]

Если вы только определяете сеттер, попытка доступа к свойству всегда будет возвращать undefined .

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

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

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

Если вы определяете только геттер, попытка присвоить свойство не будет иметь никакого эффекта.

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

classInstance.prop = 10;

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

Наследование класса

Наследование работает так же, как и в других объектно-ориентированных языках: методы, определенные на суперклассе, доступны в расширяющемся подклассе.

Если подкласс объявляет свой собственный конструктор , то он должен вызывать конструктор родителей через super() , прежде чем он может получить доступ к 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"

Частные члены

JavaScript не поддерживает техническую поддержку частных участников в качестве языковой функции. Конфиденциальность, описанная Дугласом Крокфордом, получает эмулировать вместо этого через закрытие (область сохраненных функций), которая будет генерироваться каждый с каждым вызовом инстанцирования функции-конструктора.

Пример Queue демонстрирует, как с помощью функций-конструкторов локальное состояние можно сохранить и сделать доступным также с помощью привилегированных методов.

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"]

При каждом экземпляре типа Queue конструктор генерирует замыкание.

Таким образом , оба из Queue собственных методов типа в enqueue и dequeue (см Object.keys(q) ) по- прежнему имеет доступ к list , который продолжает жить в своей области видимости , что во время строительства, не сохранились.

Используя этот шаблон - подражая частным членам через привилегированные общедоступные методы, следует иметь в виду, что с каждым экземпляром дополнительная память будет потребляться для каждого собственного метода собственности (поскольку это код, который нельзя использовать / использовать повторно). То же самое относится к количеству / размеру состояния, которое будет храниться в таком закрытии.

Имена динамических методов

Существует также возможность оценивать выражения при использовании методов именования, сходных с тем, как вы можете получить доступ к свойствам объектов с помощью [] . Это может быть полезно для динамических имен свойств, однако часто используется в сочетании с символами.

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" }

методы

Методы могут быть определены в классах для выполнения функции и необязательно возвращать результат.
Они могут принимать аргументы от вызывающего.

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" }

Управление частными данными с помощью классов

Одним из наиболее распространенных препятствий, использующих классы, является поиск правильного подхода к работе с частными государствами. Существует четыре общих решения для обработки частных состояний:

Использование символов

Символы - новый примитивный тип, введенный в ES2015, как определено в MDN

Символ - это уникальный и неизменный тип данных, который может использоваться как идентификатор свойств объекта.

При использовании символа в качестве ключа свойства он не перечислим.

Таким образом, они не будут отображаться с помощью for var in или Object.keys .

Таким образом, мы можем использовать символы для хранения частных данных.

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
        };
    }
}

Поскольку symbols уникальны, мы должны иметь ссылку на исходный символ для доступа к частной собственности.

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.

Но это не 100% личное; давайте сломаем этого агента! Мы можем использовать метод Object.getOwnPropertySymbols для получения символов объекта.

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

Использование WeakMaps

WeakMap - это новый тип объекта, который был добавлен для es6.

Как определено в MDN

Объект WeakMap представляет собой набор пар ключ / значение, в которых ключи слабо ссылаются. Ключи должны быть объектами, а значениями могут быть произвольные значения.

Другой важной особенностью WeakMap является, как определено в MDN .

Ключ в WeakMap удерживается слабо. Это означает, что, если нет других сильных ссылок на ключ, вся запись будет удалена из WeakMap сборщиком мусора.

Идея состоит в том, чтобы использовать WeakMap в качестве статической карты для всего класса, чтобы держать каждый экземпляр в качестве ключа и хранить личные данные в качестве значения для этого ключа экземпляра.

Таким образом, только внутри класса мы получим доступ к коллекции WeakMap .

Давайте дадим нашему агенту попробовать, с 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
        };
    }
}

Поскольку const topSecret определен внутри нашего закрытия модуля, и поскольку мы не привязывали его к нашим свойствам экземпляра, этот подход полностью topSecret , и мы не можем связаться с агентом topSecret .

Определить все методы внутри конструктора

Идея заключается в том, чтобы просто определить все наши методы и элементы внутри конструктора и использовать закрытие доступа к закрытым членам , не назначая им this .

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

В этом примере также данные на 100% закрыты и не могут быть доступны за пределами класса, поэтому наш агент безопасен.

Использование соглашений об именах

Мы будем решать, что любое свойство, которое является приватным, будет иметь префикс _ .

Обратите внимание, что для этого подхода данные не являются частными.

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

Связывание имени класса

Имя ClassDeclaration связано по-разному в разных областях -

  1. Область, в которой определяется класс, - let связывать
  2. Объем самого класса - внутри { и } в class {} - const binding
class Foo {
  // Foo inside this block is a const binding
}
// Foo here is a let binding

Например,

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

Это не то же самое для функции -

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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow