Przyjrzymy się bliżej cukrowi składniowemu, jakim są klasy w JavaScript i postaramy się zrozumieć jak się one mają do starszych elementów języka.
Ok, napiszmy taką klasę:
class Person {
constructor(name, age){
this.age = age;
this.name = name;
}
}
let p1 = new Person("John", 23);
console.log(p1);
Jak ona się ma do funkcji konstruującej? Cóż, funkcję konstruującą można wywołać bez new:
function Person(name, age){
if(!new.target)
return new Person(name, age);
this.name = name;
this.age = age;
}
let jim = Person("Jim", 20);
console.log(jim);
//Object { name: "Jim", age: 20 }
W klasach już się tak nie pobawimy. Nie ma new – jest error.
Ok, zobaczmy jak się mają metody klasy do tego wszystkiego, czyli metod obiektów:
class Person {
constructor(name, age){
this.age = age;
this.name = name;
}
sayHi(){
console.log("HI");
}
}
let p1 = new Person("John", 23);
console.log(p1);
console.log(Person.sayHi); //undefined
console.log(Person.prototype.sayHi); //function sayHi
p1.sayHi(); //HI
Jak widać metody Person dopisywane są do prototypu Person, nie zaś do obiektu.
Ok, a metody statyczne?
class Person {
constructor(name, age){
this.age = age;
this.name = name;
}
sayHi(){
console.log("HI");
}
static staticHi(){
console.log("HI");
}
}
let p1 = new Person("John", 23);
console.log(Person.staticHi); //function staticHi
console.log(Person.prototype.staticHi); //undefined
Person.staticHi(); //HI
console.log(typeof p1); //object
Ok, odrobina magii jak możemy zrobić mixin obiektowi:
let obj1 = function() {};
let mixin1 = {
helloWorld() {
console.log("hw");
},
hello2(){
console.log("hello 2");
}
}
Object.assign(obj1, mixin1);
console.log(obj1.helloWorld); //function...
console.log(obj1.hello2); //function...
obj1.helloWorld(); //hw
obj1.hello2(); //hello2
Idąc tym tokiem myślenia możemy napisać coś takiego:
let myMixin = {
mixinMethod(){
console.log("mixin!");
}
}
function mixin(...mixins) {
const fn = function() {};
Object.assign(fn.prototype, ...mixins);
return fn;
}
class ClsWithMixins extends mixin(myMixin) {
constructor(){
super();
this.name = "John";
}
}
let john1 = new ClsWithMixins();
john1.mixinMethod();
//mixin!
Czyli tworzymy funkcję, na jej prototypie dodajemy mixiny (obiekty z metodami/metodą), zwracamy tę funkcję. Z tej funkcji dziedziczy nasza klasa, która tworzy nowe obiekty uzbrojone już w metody z mixinów.
Jak chcemy możemy dalej to drążyć:
let john1 = new ClsWithMixins();
john1.mixinMethod();
//mixin!
console.log(john1.__proto__);
//Object
// Constructor - ClsWithMixins
console.log(john1.__proto__.constructor); //class ClsWithMixins
console.log(typeof john1.__proto__.constructor); //function
console.log(john1.__proto__.constructor.__proto__); //function fn (ma mixinMethod w prototypie)
Jak widać:
- Prototyp to Object, konstruktor to ClsWithMixins
- Próbując wypisywać __proto__.constructor dostajemy „class”, ale wiemy, że klasy w JS nie istnieją
- Próbując wypisać typ __proto__.constructor dostajemy function. To jest funkcja
- Ta funkcja ma swój prototyp, function fn, który ma mixinMethod w prototypie
Nie są to najłatwiejsze zagadnienia, ale można się przez to spokojnie przegryźć i powoli wskakiwać na wyższy poziom rozumienia JavaScript…