Třídy JavaScriptu – přátelský úvod Pt.1

Třídy JavaScriptu jsou jednou z nejžhavějších funkcí představených v ECMAScript 2015. Je to také jedna z funkcí, o kterých se nejvíce diskutuje. Naučte se vše, co potřebujete vědět o JavaScriptových třídách a o tom, jak fungují, abyste je mohli začít používat se skálopevnou jistotou.

Třídy JavaScriptu – přátelský úvod, část 2.

Vytváření objektů starým způsobem s konstruktory funkcí

Pěkně popořádku. Jak jste mohli jako vývojáři vytvářet objekty před zavedením tříd JavaScriptu? Mohli byste to udělat a stále můžete pomocí konstruktorů funkcí. Když jste pak chtěli k objektu přidat nějaké vlastnosti nebo metody, mohli jste to udělat dvěma způsoby. Za prvé, můžete to udělat přímo v konstruktoru pomocí this .

Druhá možnost? Můžete také přidat vlastnosti nebo metody k objektu mimo konstruktor. V takovém případě byste použili prototype objekt. Když chcete vytvořit novou instanci objektu, definovali jste ji jako novou proměnnou a použijte new klíčové slovo následované názvem objektu a závorkou. Například let myInstance = new MyObj() .

Podívejme se na jeden jednoduchý příklad. Nejprve vytvořte nový objekt Person , se čtyřmi vlastnostmi, name , age , height , weight a jednu metodu pomocí konstruktoru funkcí (krok 1). Dále použijeme tento objekt a vytvoříme dvě instance, joe a samantha , oba s některými daty pro výchozí vlastnosti (krok 2).

Nyní řekněme, že chcete joe mít pohlaví (krok 3). Jak vidíte, pokoušíte se přihlásit gender vlastnost na samantha nebude fungovat (krok 4). Pouze joe má tuto vlastnost. Pokud chcete všechny instance Person mít gender vlastnost standardně, můžete ji přidat, jak jsem již zmínil, na začátku pomocí this uvnitř konstruktoru nebo později mimo něj pomocí prototype objekt.

Jak můžete vidět, protokolování vlastnosti pohlaví na samantha bude nyní fungovat (krok 5). Nakonec můžete do Person přidat další metodu objekt. Opět pomocí prototype objekt. Například metoda, která vrátí hodnotu age vlastnictví. Stejně jako u gender vlastnost, všechny instance také automaticky zdědí tuto metodu (krok 6).

///
// Step 1:
// Use function constructor to create Person object
function Person(name, age, height, weight) {
  // Add default object properties using 'this'
  // 'this' refers to the Person object
  this.name = name
  this.age = age
  this.height = height
  this.weight = weight

  // Add method to get the name property
  this.getName = function () {
    // Use 'this' keyword to refer to the 'name' property
    // of Person object.
    return this.name
  }
}


///
// Step 2:
// Create two instances of Person object, joe and samantha
let joe = new Person('Joe', 32, 180, 75)
let samantha = new Person('Samantha', 27, 176, 57)

// Log names of joe and samantha
console.log(joe.getName())
// Outputs: 'Joe'
console.log(samantha.getName())
// Outputs: 'Samantha'


///
// Step 3:
// Add gender property only to the joe instance and log it
joe.gender = 'male'
console.log(joe.gender)
// Outputs: male


///
// Step 4:
// Try to log gender of samantha instance
console.log(samantha.gender)
// Outputs: undefined
// Reason: 'gender' property exists only on joe instance


///
// Step 5:
// Use object prototype to add gender property to Person
Person.prototype.gender = '¯\_(ツ)_/¯'

// Try to log gender of samantha instance again
console.log(samantha.gender)
// Outputs: '¯_(ツ)_/¯'
// Reason: adding 'gender' to Person prototype automatically added it to all instances


///
// Step 6:
// Use object prototype to add method to get age property to the Person object prototype
Person.prototype.getAge = function () {
  // 'this' refers to the Person object
  return this.age
}

// Log age of joe and samantha
console.log(joe.getAge()) // 32
console.log(samantha.getAge()) // 27

Vytváření objektů novým způsobem pomocí tříd JavaScript

Pokud jste již slyšeli o třídách JavaScript, můžete také slyšet, jak vývojáři říkají, že třídy JavaScript jsou jen syntaktický cukr. mají pravdu. Přestože třídy JavaScriptu mohou vypadat jako něco zcela nového, pod kapotou jsou stále konstruktory funkcí. Navrchu je jen trochu… cukru.

Nyní přepišme předchozí příklad do třídy JavaScript. Jak vidíte, jediný rozdíl je v „Kroku 1“. Zde jsme definovali person jako třída. Vlastnosti, které chceme předat jako argumenty při vytváření nové instance, jsou nyní definovány pomocí třídy constructor . Všimněte si také nedostatku this když definujeme getName() metoda.

A co zbytek kódu? Jak můžete vidět a vyzkoušet, vše ostatní je v podstatě stejné a funguje stejně jako dříve. To platí také pro způsob vytváření nových instancí. Stále používáte proměnné spolu s new klíčové slovo a název objektu, dobře, nyní class.

///
// Step 1:
// Use JavaScript class to create Person object
class Person {
  constructor(name, age, height, weight) {
    // Add default object properties
    this.name = name
    this.age = age
    this.height = height
    this.weight = weight
  }

  // Add method to get name property
  getName() {
    return this.name
  }
}


///
// Step 2:
// Create two instances of Person object, joe and samantha
let joe = new Person('Joe', 32, 180, 75)
let samantha = new Person('Samantha', 27, 176, 57)

// Log names of joe and samantha
console.log(joe.getName())
// Outputs: 'Joe'
console.log(samantha.getName())
// Outputs: 'Samantha'


///
// Step 3:
// Add gender property only to the joe instance and log it
joe.gender = 'male'
console.log(joe.gender)
// Outputs: male


///
// Step 4:
// Try to log gender of samantha instance
console.log(samantha.gender)
// Outputs: undefined
// Reason: 'gender' property exists only on joe instance


///
// Step 5:
// Use object prototype to add gender property to Person
Person.prototype.gender = '¯\_(ツ)_/¯'

// Try to log gender of samantha instance again
console.log(samantha.gender)
// Outputs: '¯_(ツ)_/¯'
// Reason: adding 'gender' to Person prototype automatically added it to all instances


///
// Step 6:
// Use object prototype to add method to get age property to the Person object prototype
Person.prototype.getAge = function () {
  // 'this' refers to the Person object
  return this.age
}

// Log age of joe and samantha
console.log(joe.getAge()) // 32
console.log(samantha.getAge()) // 27

Konstruktor

Jednou z věcí, které mají třídy JavaScriptu společnou, je constructor metoda. Jedná se o speciální metodu ve třídě. Je to metoda, která vytváří a inicializuje objekt vytvořený pomocí třídy. To znamená, že kdykoli vytvoříte novou instanci třídy, JavaScript automaticky zavolá constructor metoda.

Pár věcí, které byste měli vědět o třídách JavaScript a constructor metoda. Jedna, každá třída může mít pouze jeden constructor . Použití constructor je jednoduchý. Typickým použitím je vytvoření výchozích vlastností třídy. Tyto vlastnosti pak můžete předat při vytváření nových instancí třídy. Nebo je můžete deklarovat s některými výchozími hodnotami nebo obojím.

Dva, constructor metoda je volitelná. Třídy můžete definovat pomocí constructor (Příklad 1) nebo bez něj (Příklad 2). Tři, pokud zahrnete constructor ve třídě jej musíte jako první definovat na vrcholu třídy. V opačném případě JavaScript vyvolá chybu.

///
// Example 1: Class with constructor
class MyClass {
  // Use constructor to add class properties
  constructor(message = 'Hello world!') {
    this.message = message
  }

  // Add class method
  printMessage() {
    return this.message
  }
}

// Create instance of 'MyClass' class
const instanceOfMyClass = new MyClass()

// Print the message
console.log(instanceOfMyClass.printMessage())
// Outputs: 'Hello world!'


///
// Example 2: Class without constructor
class MyClass {
  // Add class method
  printMessage() {
    return 'Hello world!'
  }
}

// Create instance of 'MyClass' class
const instanceOfMyClass = new MyClass()

// Print the message
console.log(instanceOfMyClass.printMessage())
// Outputs: 'Hello world!'

Vlastnosti a metody třídy

Atributy a chování ve třídách JavaScriptu se nazývají vlastnosti tříd a metody tříd. Příklady obou jste již viděli na předchozích příkladech. Jen málo věcí k zapamatování. Za prvé, když chcete přidat vlastnost do třídy, uděláte to v constructor metoda. Za druhé, když chcete přidat metodu, uděláte to uvnitř třídy, ale mimo constructor .

Za třetí, když chcete odkazovat na jakoukoli vlastnost nebo metodu uvnitř třídy, musíte použít this klíčové slovo. Zde můžete přemýšlet o this jako krátká alternativa pro „na této třídě“. Takže byste v podstatě mohli říct this.property jako „majetek této třídy“, abych tak řekl. Pojďme vytvořit NewClass třída se dvěma vlastnostmi, classPropOne , classPropTwo a dvě metody, someClassMethod a anotherClassMethod .

// Create new class called MyClass
class NewClass {
  // Add two class properties, 'classPropOne', 'classPropTwo'
  constructor(classPropOne, classPropTwo) {
    this.classPropOne = classPropOne
    this.classPropTwo = classPropTwo
  }

  // Add class method called 'someClassMethod'
  someClassMethod() {
    return this.classPropOne
  }

  // Add class method called 'anotherClassMethod'
  anotherClassMethod() {
    return this.classPropTwo
  }
}

Práce s JavaScriptovými třídami a jejich vlastnostmi a metodami je velmi snadná. To jste mohli vidět na příkladu na samém začátku tohoto článku, ale stojí za to to znovu zmínit. Do tříd JavaScript můžete také přidat nové vlastnosti a metody později, aniž byste museli provádět změny přímo v definici třídy.

Můžete to udělat pomocí prototype objekt. To funguje jak s vlastnostmi třídy, tak s metodami. Syntaxe je jednoduchá. První je název třídy. Následuje prototype klíčové slovo následované názvem metody nebo vlastnosti s tečkami mezi názvem třídy prototype a název metody nebo vlastnosti. Poté následuje úkol.

// Add new method called 'newClassMethod' to 'NewClass' class
NewClass.prototype.newClassMethod = function() {
  return this.classPropOne + ' & ' + this.classPropTwo
}

// Create instance of NewClass called 'foo'
let foo = new NewClass('foo', 'bar')

// Test that new works
console.log(foo.newClassMethod())
// Outputs: 'foo & bar'

Dědičnost třídy (rozšiřující se)

To byly základní věci o třídách JavaScriptu. Nyní pojďme mluvit o dědičnosti nebo rozšiřování tříd. Rozšíření tříd v podstatě znamená, že vytvoříte jednu třídu, podřízenou třídu nebo podtřídu na základě jiné třídy, nadřazené třídy nebo nadtřídy. Podřízená třída nebo podtřída dědí vlastnosti a metody z nadřazené třídy nebo nadtřídy.

Hlavní výhodou toho je, že můžete přidat funkce bez změny původní třídy. To je zvláště důležité, když nechcete měnit instance této třídy. Pokud do třídy přidáte funkce pomocí prototype jakákoli změna, kterou ve třídě provedete, se automaticky rozšíří do všech jejích instancí.

Představte si, že máte třídu s názvem Vehicle . Tato třída má některé vlastnosti, například name , condition a speed . Nyní řekněme, že tuto třídu chcete použít k vytvoření letadla, auta a lodi. Všechna tato vozidla mohou mít specifické vlastnosti, jako je počet kol, počet motorů, počet vrtulí atd.

Jedna, velmi špatná, možnost je přidat všechny tyto vlastnosti do Vehicle třída. Problém je v tom, že by to zaplnilo všechny instance Vehicle třídy s vlastnostmi nebo metodami, které by nikdy nepoužili. Další a mnohem lepší možností je využití dědičnosti. To znamená, že vytvoříte podtřídy pro plane , car a ship pomocí Vehicle jako supertřída.

To vám umožní přidávat specifické vlastnosti pouze do tříd nebo podtříd, které je budou používat. A co víc, protože všechny tyto nové třídy budou podtřídami Vehicle superclass, všichni budou moci sdílet některé vlastnosti a metody, ty zděděné z Vehicle .

Způsob, jak vytvořit podtřídy nadtřídy nebo rozšířit třídy, je jednoduchý. Deklarujete třídu jako obvykle, ale přidáte extends a název nadtřídy mezi názvem třídy a složenými závorkami. Například class MySubclass extends SuperClass {} . Poté můžete přidat vlastnosti a metody jako u normální třídy.

// Create superclass Vehicle
class Vehicle {
  constructor(name, condition, speed) {
    this.name = name
    this.condition = condition
    this.speed = speed
  }
}


// Create Car subclass
class Car extends Vehicle {
  constructor(name, condition, speed, numOfWheels) {
    // Call super() with all parameters required for Vehicle class
    super(name, condition, speed)

    this.numOfWheels = numOfWheels
  }

  // Add method to print all properties
  printInfo() {
    return `Name: ${this.name}, Condition: ${this.condition}, Max. speed: ${this.speed}, Number of Wheels: ${this.numOfWheels}`
  }
}


// Create Plane subclass
class Plane extends Vehicle {
  constructor(name, condition, speed, numOfEngines) {
    // Call super() with all parameters required for Vehicle class
    super(name, condition, speed)

    this.numOfEngines = numOfEngines
  }

  // Add method to print all properties
  printInfo() {
    return `Name: ${this.name}, Condition: ${this.condition}, Max. speed: ${this.speed}, Number of Engines: ${this.numOfEngines}`
  }
}


// Create Ship subclass
class Ship extends Vehicle {
  constructor(name, condition, speed, numOfPropellers) {
    // Call super() with all parameters required for Vehicle class
    super(name, condition, speed)

    this.numOfPropellers = numOfPropellers
  }

  // Add method to print all properties
  printInfo() {
    return `Name: ${this.name}, Condition: ${this.condition}, Max. speed: ${this.speed}, Number of Propellers: ${this.numOfPropellers}`
  }
}


// Create instance of Car class
const tesla = new Car('Tesla', 'new', 280, 2)
console.log(tesla.printInfo())
// Outputs: 'Name: Tesla, Condition: new, Max. speed: 280, Number of Wheels: 2'


// Create instance of Ship class
const catamaran = new Ship('Catamaran', 'new', 140, 2)
console.log(catamaran.printInfo())
// Outputs: 'Name: Catamaran, Condition: new, Max. speed: 140, Number of Propellers: 2'


// Create instance of Plane class
const cesna = new Plane('Cesna', 'new', 234, 2)
console.log(cesna.printInfo())
// Outputs: 'Name: Cesna, Condition: new, Max. speed: 234, Number of Engines: 2'

Dědičnost a podtřídy nebo podtřídy

Jedna věc, kterou byste měli vědět o dědictví. Není omezen na supertřídy. Můžete také nechat jednu podtřídu dědit z jiné podtřídy, která může také dědit z další podtřídy, která může dědit z nadtřídy. Dovedeno do extrému, můžete vytvořit řetězec stovek podtříd dědících jednu od druhé s jednou nadtřídou nahoře.

// Create superclass Animal
class Animal {
  // Some code
}

// Create subclass Mammal that inherits from superclass Animal
class Mammal extends Animal {
  // Some code
}

// Create subclass Cat that inherits from subclass Mammal
class Cat extends Mammal {
  // Some code
}

// Create subclass Kitten that inherits from subclass Cat
class Kitten extends Cat {
  // Some code
}

// Create subclass Tomcat that inherits from subclass Kitten
class Tomcat extends Kitten {
  // Some code
}

Konstruktor přepisující třídy

Jak jste mohli vidět v příkladu výše, všechny podtřídy měly své vlastní constructor metoda. To znamená, že přepisovaly nadtřídu constructor . Když k tomu dojde, když podtřída přepíše constructor nadtřídy, musíte zavolat super() metoda se všemi počátečními constructor parametry.

Volání super() uvnitř constructor volá constructor nadtřídy, v tomto případě Vehicle . To pak umožňuje podtřídám používat vlastnosti definované v nadtřídě constructor . Jedna důležitá věc k zapamatování je, že musíte volat super() metoda musí být volána v horní části constructor .

Než přidáte jakékoli vlastnosti, musíte jej zavolat. Pokud jej zapomenete, this , a jeho odkaz na třídu, nebude existovat a JavaScript vyvolá chybu. A co když podtřída nemá svůj vlastní konstruktor, nepřepisuje nadtřídu constructor ? Pak se nemusíte o nic starat, constructor nebo super() .

// Create superclass MyClass
class MyClass {
  constructor(name) {
    this.name = name
  }
}


// Create subclass of MyClass superclass
// that doesn't override the constructor of the superclass
class MySubClass extends MyClass {
  getName() {
    return this.name
  }
}

// Create instance of MySubClass
let instanceOfMySubClass = new MySubClass('Johny')

// Test that subclass can access the 'name' property
console.log(instanceOfMySubClass.getName())
// Outputs: 'Johny'


// Create subclass of MyClass superclass
// that overrides the constructor of the superclass
class AnotherSubClass extends MyClass {
  constructor(name, mood) {
    // Call super() with all initial parameters - the 'name'
    // Allows to use 'this' and gives access to 'name' property
    super(name)

    // Add new property
    this.mood = mood
  }
}

// Create instance of AnotherSubClass
let instanceOfAnotherSubClass = new AnotherSubClass('Tom', 'happy')
// Log Tom's mood
console.log(instanceOfAnotherSubClass.mood)
// Outputs: 'happy'

Přepsání vlastností a metod třídy

Nyní víte, jak přepsat constructor ze supertřídy. Přepsání vlastností a metod nadtřídy je stejně snadné. Když chcete přepsat vlastnost třídy v podtřídě, přidejte constructor , zavolejte super() se všemi počátečními parametry vyberte vlastnost, kterou chcete změnit, a jednoduše ji změňte, její hodnotu.

Když chcete přepsat metodu třídy, proces je ještě jednodušší. Jediné, co musíte udělat, je použít stejný název metody v podtřídě a změnit to, co dělá, kód uvnitř.

// Create superclass Entity
class Entity {
  // Create class constructor
  constructor() {
    // Add class property
    this.isHuman = null
  }

  // Add class method
  identifyYourself() {
    return 'I am neither a human nor a robot.'
  }
}


// Create subclass of Entity superclass
// This subclass overrides both, class property and method.
class Man extends Entity {
  // Add subclass' own constructor
  constructor() {
    // Call super() - allows to use 'this'
    // and gives access to 'isHuman' property
    super()

    // Override class property
    this.isHuman = true
  }

  // Override the 'identifyYourself()' method
  identifyYourself() {
    return 'I am a human.'
  }
}

// Create instance of Man subclass
let jake = new Man()
console.log(jake.isHuman)
// Outputs: true
console.log(jake.identifyYourself())
// Outputs: 'I am a human.'


// Create subclass of Entity superclass
// This subclass overrides only class property.
class Woman extends Entity {
  // Add subclass' own constructor
  constructor() {
    // Call super() - allows to use 'this'
    // and gives access to 'isHuman' property
    super()

    // Override class property
    this.isHuman = true
  }
}

// Create instance of Robot subclass
let melissa = new Woman()
console.log(melissa.isHuman)
// Outputs: true


// Create subclass of Entity superclass
// This subclass overrides only class method.
class Robot extends Entity {
  // Override the 'identifyYourself()' method
  identifyYourself() {
    return 'I am a robot.'
  }
}

// Create instance of Robot subclass
let android = new Robot()
console.log(android.identifyYourself())
// Outputs: 'I am a robot.'

Rozšíření metod tříd o nadtřídy a podtřídy

Dobře, ale co když nechcete úplně nahradit metodu supertřídy? Co když na ní chcete stavět, rozšiřovat ji nebo vylepšovat? Zde můžete použít super znovu. Dříve jste používali super() jako metodu k volání nadtřídy‘ constructor (pouze uvnitř konstruktoru). Můžete však použít super také jako klíčové slovo.

Když použijete super jako klíčové slovo spolu s názvem metody zavoláte původní verzi metody, která existuje uvnitř nadtřídy. Jinými slovy, můžete použít volání původní metody pomocí super klíčové slovo a poté přidejte další kód. Výsledkem je, že podtřída nenahradí metodu, ale bude na ní stavět a rozšiřovat ji.

// Create superclass Human
class Human {
  // Add class method
  sayHi() {
    console.log('I am a human.')
  }
}


// Create subclass of Human superclass
class Man extends Human {
  // Extend the 'sayHi()' method
  sayHi() {
    // Call the superclass' 'sayHi()' method
    super.sayHi()

    console.log('I am also a man.')
  }
}

// Create instance of Man subclass
let timothy = new Man()
timothy.sayHi()
// Outputs:
// 'I am a human.' (result of calling super.sayHi() in Man)
// 'I am also a man.'

Rozšíření metod tříd o podtřídy a podtřídy

Jak víte, jedna podtřída může dědit z jiné podtřídy. Co když použijete super klíčové slovo pro volání nějaké metody v podtřídě, která dědí z jiné podtřídy? Zavolá metodu tak, jak existuje v podtřídě, kterou vaše aktuální podtřída rozšiřuje, nebo o úroveň výš.

Jinými slovy, řekněme, že foo rozšiřuje bar a bar rozšiřuje bazz a bazz rozšiřuje fuzz . Poté zavolejte super.someMethod() v foo bude to volat someMethod() v bar , protože foo rozšiřuje bar . Pokud zavoláte super.someMethod() v bar zavolá to someMethod() v bazz , protože bar rozšiřuje bazz .

Co když zavoláte nějakou metodu pomocí super v každé podtřídě je to řetězec? Jak můžete hádat, výsledkem bude pěkná řetězová reakce.

// Create superclass Human
class Human {
  // Add class method
  sayHi() {
    console.log('I am a human.')
  }
}


// Create subclass of Human superclass
class Man extends Human {
  // Extend the 'sayHi()' method
  sayHi() {
    // Call the superclass' 'sayHi()' method
    super.sayHi()

    console.log('I am also a man.')
  }
}

// Create subclass of Man subclass
class Boy extends Man {
  // Extend the 'sayHi()' method
  sayHi() {
    // Call the superclass' 'sayHi()' method
    super.sayHi()

    console.log('I am also a boy.')
  }
}

// Create subclass of Boy subclass
class Baby extends Boy {
  // Extend the 'sayHi()' method
  sayHi() {
    // Call the superclass' 'sayHi()' method
    super.sayHi()

    console.log('And I am also a baby.')
  }
}

// Create instance of Robot subclass
let timothy = new Baby()
timothy.sayHi()
// Outputs:
// 'I am a human.'
// 'I am also a man.' (result of calling super.sayHi() in Man)
// 'And I am also a boy.' (result of calling super.sayHi() in Boy)
// 'And I am also a baby.'

Epilolog:Třídy JavaScriptu – přátelský úvod Pt.1

Gratulujeme! Udělejme si rychlou rekapitulaci. Na začátku jste se dozvěděli o rozdílu mezi starým způsobem vytváření objektů pomocí konstruktorů funkcí a novým způsobem pomocí tříd JavaScriptu. Potom jste se dozvěděli o základech, částech tříd JavaScriptu a souvisejících konceptech, které je důležité znát.

Tyto části a koncepty zahrnují konstruktor, vlastnosti a metody třídy, dědičnost, nadtřídy a podtřídy a také to, jak přepsat vlastnosti a metody konstruktoru a třídy. To byly základy. V další části se dozvíte o pokročilých tématech, jako jsou statické vlastnosti a metody, pole tříd, mixiny a další.