Классы 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.