Объектно-ориентированное программирование (ООП) - это парадигма программирования, основанная на концепции «объектов», где данные и функции (атрибуты и методы) связаны внутри объекта.
Объект в JavaScript - это набор пар ключ-значение. Эти пары ключ-значение являются свойствами объекта. Свойство может быть массивом, функцией, самим объектом или любым примитивным типом данных, например строкой или числом.
Какие же существуют способы создания объектов в JavaScript?
Предположим, мы создаем пользователей для игры, которую только что разработали. Как мы будем хранить такие сведения о пользователях, как имя и уровень, и реализовывать такие методы, как увеличение уровня? Вот два базовых способа создания объекта.
Способ 1 - Литеральная запись
let user = {
name: 'Иван',
points: 5,
increment: function() {
user.points++;
}
};
Литерал объекта JavaScript - это список пар ключ-значение, заключенный в фигурные скобки. В приведенном выше примере создается объект user и в нем хранятся связанные данные.
Способ 2 - Object.create()
Object.create(proto, [ propertiesObject ])
Метод Object.create принимает два аргумента:
- proto: объект, который должен быть прототипом вновь созданного объекта. Это должен быть объект или null.
- propertiesObject: свойства нового объекта. Это необязательный аргумент.
По сути, вы указываете в Object.create объект, от которого хотите наследоваться, и вам соответственно возвращается новый объект, унаследованный от объекта, который вы ему передали.
let user = Object.create(null);
user.name = 'Иван';
user.points = 8;
user.increment = function() {
user.points++;
}
Вышеуказанные способы создания объекта скучноватые. К тому же, используя такие подходы, нам придётся каждого нового пользователя создавать вручную.
Как нам это преодолеть?
Решение 1 - Генерировать объекты с помощью функции
Простое решение - написать функцию для создания новых пользователей.
function createUser(name, points) {
let newUser = {};
newUser.name = name;
newUser.points = points;
newUser.increment = function() {
newUser.points++;
};
return newUser;
}
Теперь чтобы создать пользователя, нам нужно просто ввести данные в параметрах функции:
let user = createUser('Иван', 5);
user.increment();
Однако функция increment в приведенном примере - это та же копия нашей базовой функции increment. Это не лучший способ написания кода, так как любые потенциальные изменения функции необходимо будет вносить вручную для каждого объекта.
Решение 2 - Использовать особенности prototype
В отличие от объектно-ориентированных языков, таких как Python и Java, в JavaScript нет классов. Он использует концепцию прототипов и цепочки прототипов для наследования.
Когда вы, допустим, создаете новый массив, вы автоматически получаете доступ к встроенным методам, таким как Array.join, Array.sort и Array.filter. Это связано с тем, что объекты массива наследуют свойства от Array.prototype.
Каждая функция JavaScript имеет свойство prototype, которое по умолчанию пусто. Вы можете добавлять функции к этому свойству, которые будут называться методами. При наследовании этой функции значение this будет указывать на созданный объект.
function createUser(name, points) {
let newUser = Object.create(userFunction);
newUser.name = name;
newUser.points = points;
return newUser;
}
let userFunction = {
increment: function() {this.points++};
}
let user = createUser('Иван', 5);
user.increment();
При создании объекта user была сформирована связь прототипной цепочки с userFunction.
Если вызвать метод user.increment(), интерпретатор будет искать user в глобальной памяти. Дальше, как только он его обнаружит, внутри user он будет искать функцию increment(), но не найдет ее. Интерпретатор начнёт двигаться вверх по цепочке прототипов и смотреть на следующий объект и там уже найдет функцию increment().
Решение 3 - ключевые слова new и this
Оператор new используется для создания экземпляра объекта, который имеет функцию конструктора.
Когда мы вызываем функцию-конструктор с помощью new, происходят следующие действия:
- Создаётся новый объект;
- К нему подвязывается this;
- prototype функции-конструктора становится свойством __proto__ нового объекта;
- Возвращает объект из функции.
Такая автоматизация приводит к менее повторяющемуся коду!
function User(name, points) {
this.name = name;
this.points = points;
}
User.prototype.increment = function(){
this.points++;
}
let user = new User('Иван', 6);
user.increment();
При использовании шаблона прототипа каждый метод и свойство добавляются непосредственно к прототипу объекта.
Интерпретатор поднимется по цепочке прототипов и найдет функцию increment в свойстве прототипа User, которое также является объектом с данными внутри. Помните - все функции в JavaScript также являются объектами. Теперь, когда интерпретатор нашел то, что ему нужно, он может создать новый локальный контекст выполнения для запуска user.increment().
Разница между __proto__ и prototype
Если вы уже запутались в __proto__ и prototype, не волнуйтесь! Вы далеко не единственный, кого это смущает.
Prototype - это свойство функции-конструктора, которое определяет, что станет свойством __proto__ для сконструированного объекта.
А __proto__ - это созданная ссылка, которая является связью цепи прототипа.
Решение 4 - синтаксический сахар ES6
В ECMAScript6 введено ключевое слово class, которое позволяет нам писать классы (похожие на обычные классы других классических языков). Для разработчиков это синтаксический сахар над прототипным поведением JavaScript.
class User {
constructor(name, points) {
this.name = name;
this.points = points;
}
increment () {
this.points++;
}
}
let user = new User('Иван', 12);
user.increment();
В решении 3 связанные методы были реализованы с использованием User.prototype.functionName. В этом решении достигаются те же результаты, но синтаксис выглядит чище.
Комментарии (0)