Классы JavaScript, представленные в ECMAScript 2015, представляют собой синтаксический сахар по сравнению с существующим в JavaScript наследованием на основе прототипов. Итак, JS больше похож на другие языки ООП, но что на самом деле происходит внутри? В этой статье я хотел бы углубиться в JavaScript и попытаться объяснить его с помощью примеров кода и журналов консоли.

Давайте объявим класс.

class Car { 
    constructor(model, color) {
        this.model = model
        this.color = color
    }

    changeModel(newModel) {
        this.model = newModel
    }

    changeColor(newColor) {
        this.color = newColor
    }
}

const car = new Car("BMW", "black")
car.changeModel("Mercedes")

Это очень знакомо, если у вас есть базовые знания ООП. Чтобы понять, как это работает, давайте сделаем то же самое, что и выше, используя «старый способ».

// создаем объект с точечной нотацией

const car = {}
car.model = "BMW"
car.color = "black"
car.changeColor = function (newColor) {
    car.color = newColor
}
car.changeModel = function (newModel) {
    car.model = newModel
}

Как мы видим, было бы крайне неэффективно организовывать наш код таким образом. Так как каждый раз, когда нам нужно создать объект, мы должны вручную писать все свойства и присваивать ему значения. Представьте суету. Итак, что же является второй «лучшей» вещью, которую мы можем сделать?

function Car(model, color) {
    const newCar = {}
    newCar.model = model
    newCar.color = color

    newCar.changeColor = function(newColor) {
        newCar.color = newColor
    }
    newCar.changeModel = function(newModel) {
        newCar.model = newModel
    }
    return newCar
}

const bmw = Car("BMW", "black")
bmw.changeColor("white")

Это выглядит намного лучше, наш код организован. Если вы понимаете до этого момента, поздравляю, вы почти у цели. С этого момента все шаги будут просто делать наш последний код более эффективным. Как видите, выглядит неплохо, однако с этим есть небольшая проблема. Каждый раз, когда мы создаем объект, мы сохраняем в памяти одну и ту же функциональность. Также, если нам нужно добавить новую функцию, мы должны добавить ее к каждому объекту вручную. Представьте, что у нас есть 3000 объектов. Итак, что мы можем сделать. Было бы лучше, если бы у вас было «специальное» место для функций, и каждый раз, когда мы создаем объект, он автоматически наследовал бы эти функции.

// создать объект с помощью Object.create

Давайте создадим объект, в котором мы можем хранить функциональные возможности объекта.

const carRelatedFunctions = {
    changeColor : function(newModel) {
        this.newModel = newModel
    },
    change_color: function (new_color) {
        this.newColor = newColor
    }
}

function Car(model, color) {
    const newCar = Object.create(carRelatedFunctions)
    newCar.newColor = color
    newCar.newModel = model

    return newCar
}

const bmw = Car("BMW", "black")
const mercedes = Car("Mercedes", 'white')
bmw.changeColor("white")
mercedes.changeColor("black")

// console.log bwm
=> {newColor: "white", newModel: "BMW"}

// console.log mercedes 
=> {newColor: "black", newModel: "Mercedes"}

Как мы видим, это работает, теперь, если нужно добавить новую функциональность, мы просто добавим ее в carRelatedFunctions. Но подождите минутку, если мы посмотрим на объект Car, мы не сможем найти функциональность changeColor, так как же только что созданный объект знает, где искать. Когда мы создали объект с помощью Object.create(carRelatedFunctions), он фактически создал связь между объектом Car и carRelatedFunctions через свойство __proto__.

Посмотрим в консоли:

=> {newColor: "black", newModel: "Mercedes"}
newColor: "black"
newModel: "Mercedes"
__proto__:
changeColor: ƒ (newColor)
changeModel: ƒ (newModel)
__proto__: Object

Пока все выглядит хорошо, однако, если мы подумаем об оптимизации, есть ли способ автоматизировать новые операции?

// создать объект с новым ключевым словом.

function Car(model, color) {
    this.model = model
    this.color = color
    console.log(this)

}

const bmw = new Car("BMW", "black");

Этот код немного короткий, что происходит? Поэтому, когда мы создаем объект с новым ключевым словом, он создает пустой объект и присваивает ему неявную переменную this и возвращает этот новый созданный объект.

Давайте console.log mercedes.

const mercedes = new Car("Mercedes", "white")
=> Car{color: "white", model: "Mercedes"}

Что насчет функций? Все функции в их объектном формате автоматически имеют свойство, называемое прототипом. Таким образом, мы поместим все функции туда.

Car.prototype.changeColor = function(newColor) {
    this.color = newColor
}

Car.prototype.changeModel = function(newModel) {
    this.model = newModel
}

bmw.changeColor("red")

=> Car {model: "BMW", color: "red"}
color: "red"
model: "BMW"
__proto__:
changeColor: ƒ (newColor)
changeModel: ƒ (newModel)
constructor: ƒ Car(model, color)
__proto__: Object
class Car { 
    
    constructor(model, color) {
        this.model = model
        this.color = color
    }

    // same as 
    function Car(model, color) {
        this.model = model
        this.color = color
    }

    changeModel(newModel) {
        this.model = newModel
    }

    changeColor(newColor) {
        this.color = newColor
    }

    // same as  
    Car.prototype.changeColor = function(newColor) {
        this.color = newColor
    }

    Car.prototype.changeModel = function(newModel) {
        this.model = newModel
    }
}

Вот и все, надеюсь, вы лучше понимаете, как работают классы в Javascript.