Поиск…
Синтаксис
- класс 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 в рамках стандарта es6 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, вам понадобится транспилер вроде babel или google- clos -compiler , чтобы скомпилировать код в версию, которую может понять целевая платформа.
Конструктор классов
Фундаментальной частью большинства классов является его конструктор, который устанавливает начальное состояние каждого экземпляра и обрабатывает любые параметры, которые были переданы при вызове 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 связано по-разному в разных областях -
- Область, в которой определяется класс, -
let
связывать - Объем самого класса - внутри
{
и}
в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