Ето преглед на това, което ще покрием:

  • Класовете на JavaScript са синтактична захар над съществуващия модел на наследяване, базиран на прототип на JavaScript.
  • Разлика между дефинираните атрибути на ниво конструктор и клас
  • Разлика между static и нестатични атрибути на ниво клас
  • Създаване на екземпляр на клас с или без ключова дума new
  • super ключова дума
  • Извличане и настройване на Javascript
  • JavaScript класовете не поддържат истински частни членове.
  • JavaScript класовете не могат да използват ключова дума `implements`

Класовете на JavaScript са синтактична захар над съществуващия модел на наследяване, базиран на прототип на JavaScript.

Препоръчително е да имате задълбочено разбиране на основните механизми на синтаксиса на новия клас, дори ако можете да го използвате ефективно.

Наясно ли сте с разликите между създаването на обект чрез следните два метода?

// using class
class Person {
  constructor(name, place) {
    this.name = name;
    this.place = place;
  }

  greeting() {
    return `Hello ${this.name}. Welcome to ${this.place}`
  }
}

const person1 = new Person('John', 'Bangalore')

// using factory pattern
const createPerson = (name, place) => ({
  name,
  place,
  greeting: () => `Hello ${name}. Welcome to ${place}`
})

const person2 = createPerson('Jack', 'Mumbai')

Кое бихте предпочели и защо?

Основната разлика по отношение на прототипа е, че person1 наследява от прототипа на класа Person, докато person2 наследява от стандартния Object прототип.

Така че всеки екземпляр, създаден с помощта на синтаксис на клас, ще споделя общия метод greeting, който ще бъде дефиниран на Person.prototype.greeting.

Докато с фабричния шаблон всеки създаден обект ще има свое собствено специално копие на функцията greeting, дефинирана на обекта.

Разлика между дефинираните атрибути на ниво конструктор и клас

Можем да дефинираме атрибути, които трябва да бъдат създадени за всеки екземпляр в конструктора. Можете да ги инициализирате по време на създаването на екземпляр или да изберете да не го правите.

атрибутът, дефиниран в конструктора, може да има различна стойност за всеки нов екземпляр (ако стойността е картографирана чрез param на конструктора към атрибута)

// attribute defined in the constructor
class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
    this.head = null;
  }
}

В следващия пример „head“ е нестатичен атрибут на ниво клас и се дава на всяко копие на класа Person. head е подобен на data и next, които са атрибути на екземпляр. Но data и next може да се различават във всеки екземпляр, ако се инициализират със стойност при създаване на обект с помощта на new Node(1) .

атрибутът, дефиниран на ниво клас, ще има една и съща стойност за всеки нов екземпляр.

// attribute defined in the class level
class Node {
  head = null;

  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

Разлика между статични и нестатични атрибути на ниво клас

В този пример species е атрибут на ниво клас, защото е дефиниран с помощта на ключовата дума static и е прикрепен директно към класа Person.

Всеки екземпляр на класа Person ще споделя една и съща стойност на species, за разлика от нестатичните членове на ниво клас.

class Person {
  // class-level static attribute
  static species = 'Homo sapiens'; 

  constructor(name) {
    this.name = name; 
  }
}

Person.species // 'Homo sapiens'

В следващия пример species е дефиниран като нестатичен атрибут на ниво клас.

Всеки екземпляр на класа Person ще има собствено копие на атрибута species, точно както всеки друг специфичен за екземпляр атрибут, дефиниран във функцията на конструктора.

Обърнете внимание, че ако дефинирате атрибут на ниво клас без ключовата дума static, няма да имате достъп до него с помощта на името на класа (напр. Person.species). Вместо това ще трябва да получите достъп до него чрез екземпляр на класа.

class Person {
  // class-level static attribute
  species = 'Homo sapiens'; 

  constructor(name) {
    this.name = name; 
  }
}

Person.species  // undefined

const person = new Person('Jack')
person // {species: 'Homo sapiens', name: 'Jack'}

Създаване на екземпляр на клас с или без нова ключова дума

В JavaScript не можете да създадете клас без да използвате ключовата дума new. Спецификацията на ES2015 стриктно посочва, че такова извикване трябва да изведе TypeError.

Ето един пример:

class Person {
  constructor(name) {
    this.name = name;
  }
}

let person = Person('Alice');
// TypeError: Class constructor Person cannot be invoked without 'new'

супер ключова дума

Една често срещана грешка при използването на ключовата дума super е, че тя трябва да се използва преди ключовата дума this да бъде използвана в конструктора на подклас. Това е така, защото ключовата дума super трябва да бъде извикана, преди подкласът да има достъп и да променя свойствата на обекта this.

Ето един пример за илюстрация на това:

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    this.breed = breed; // ERROR: Must call super constructor first
    super(name);
  }
}

const d = new Dog('Mitzie', 'Labrador');

Така че основното правило е винаги да се извиква super() преди достъп до this вътре в конструктора на подклас. Това ще гарантира, че конструкторът на родителския клас е завършил изпълнението си, преди подкласът да се опита да получи достъп или да промени каквито и да е свойства на this обекта.

Извличане и настройване на Javascript

Използвайте гетери, когато:

  1. Искате да изчислите или трансформирате стойността на свойство, преди да го върнете. Например, може да искате да конвертирате температурна стойност от Целзий във Фаренхайт.
  2. Искате да симулирате присъствието на свойство, което всъщност не съществува. Например, може да искате да създадете свойство fullName, което свързва собственото и фамилното име на човек.
  3. Искате да направите свойство само за четене, така че да не може да се променя директно. Това може да бъде полезно за налагане на бизнес логика или предотвратяване на грешки.
class Person {
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  get ageInMonths() {
    return this.age * 12;
  }

  get canVote() {
    return this.age >= 18;
  }

  get readOnlyProperty() {
    return 'This property is read-only';
  }
}

const p = new Person('John', 'Doe', 30);

console.log(p.fullName); // Output: John Doe

console.log(p.age); // Output: 30
console.log(p.ageInMonths); // Output: 360

console.log(p.canVote); // Output: true
p.age = 17;
console.log(p.canVote); // Output: false

console.log(p.readOnlyProperty); // Output: This property is read-only
p.readOnlyProperty = 'New value'; // No effect, read-only property
console.log(p.readOnlyProperty); // Output: This property is read-only

Използвайте сетери, когато:

  1. Искате да потвърдите или дезинфекцирате стойност, преди да я зададете. Например, може да искате да гарантирате, че възрастта на дадено лице винаги е положително цяло число.
  2. Искате да задействате някакъв страничен ефект, когато свойство се промени. Например, може да искате да актуализирате свързано свойство или да уведомите други части на вашата програма.
  3. Искате да симулирате наличието на свойство, което може да бъде зададено, но всъщност не съществува. Например, може да искате да създадете свойство fullName, което може да бъде зададено, но всъщност съхранява отделните свойства за име и фамилия.
class Person {
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  get name() {
    return `${this.firstName} ${this.lastName}`;
  }

  set name(value) {
    const [firstName, lastName] = value.split(' ');
    this.firstName = firstName;
    this.lastName = lastName;
  }

  get age() {
    return this._age;
  }

  set age(value) {
    if (value < 0) {
      throw new Error('Age must be a positive integer');
    }
    this._age = value;
  }

  set fullName(value) {
    const [firstName, lastName] = value.split(' ');
    this.firstName = firstName;
    this.lastName = lastName;
  }

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const p = new Person('John', 'Doe', 30);

console.log(p.name); // Output: John Doe

p.name = 'Jane Smith';
console.log(p.firstName); // Output: Jane
console.log(p.lastName); // Output: Smith

console.log(p.age); // Output: 30
p.age = -1; // Throws an error: "Age must be a positive integer"

p.fullName = 'Bob Brown';
console.log(p.name); // Output: Bob Brown
console.log(p.firstName); // Output: Bob
console.log(p.lastName); // Output: Brown

Кога не трябва да използвате гетери и сетери:

  1. Когато не е необходимо да контролирате достъпа до собственост. Ако дадено свойство е просто и не изисква специално поведение, няма нужда да дефинирате гетер или сетер за него.
  2. Когато представянето е проблем. Getters и setters могат да добавят допълнителни разходи към вашия код, така че ако работите с големи количества данни или критичен за производителността код, може да искате да ги избегнете.

JavaScript класовете не поддържат истински частни членове.

Класовете на JavaScript не поддържат истински частни членове, които са членове, които са недостъпни извън класа, включително от производни класове.

class Person {
  constructor(name, age) {
    this._name = name;
    this._age = age;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    this._name = value;
  }
}

const person = new Person('John', 30);
console.log(person._name); // Output: "John"
person._name = 'Jane';
console.log(person._name); // Output: "Jane"

JavaScript класовете не могат да използват ключова дума `implements`

Класовете на JavaScript не поддържат ключовата дума implements, която се използва в някои други езици за програмиране, като Java, за да се определи, че даден клас изпълнява конкретен интерфейс.

Имайте предвид, че JavaScript класовете нямат пълна поддръжка за частни членове и не могат да използват ключовата дума implements. TypeScript обаче осигурява поддръжка и за двете функции.

Надявам се това да е било полезно. Уведомете ме чрез вашите коментари за повече такова съдържание.