Сегодня хочу поговорить про ООП(объектно-ориентированное программирование).
А именно ООП в JavaScript.
Небольшое лирическое отступление:
— Помню, как год или два назад писал статьи про тоже, но для Java, сейчас, к сожалению, этих статей не осталось, хотя если постараться можно найти, правда, Вы там ничего не поймёте поскольку тогда я был жутким школяром, который углубляясь во что-то, всё до мелочей описывал, ни капли не заботясь о том, что кому-то это должно быть понятно. Посмотрим, что будет сейчас.
Изучая конкретный язык программирования, каждый должен знать, как в нём поддерживается ООП, вернее не так, как писать объектно-ориентированный код.
Андрей, а что это за ООП такое вообще!?
ООП — способ организации представления данных в виде классов, а те, в свою очередь, — объектов. Методология проектирования данных.
Эм-м.., что простите!?
Так, давайте по порядку.
Класс — это некий шаблон, описывающий общую структуру чего-то. Приведу аналогию. Представьте инженера по кораблестроению. Инженер — Вы. Вы рисуете чертежи(шаблон, класс), на основе которых строятся, создаются корабли(объекты). В чертежах(классах) Вы описываете строение корабля, то есть некие элементы(свойства), присутствующие у всех кораблей(объектов).
Вот ещё парочка примеров:
— инженер автомобилей — Вы. Вы конструируете машину на чертежах, то есть описываете класс машины, отдаёте эти чертежи на завод или ещё куда-то, где на его основе будут создавать машины, некоторые их которых будут отличаться, разные модели, например. Одна будет красного цвета, другая — синего. Такие характеристики, как цвет машины — некие свойства класса(чертежа машины), которые имеют разные значения для определенного объекта(машины).
Так, думаю, теоретически Вы поняли самую главную суть ООП. Теперь давайте посмотрим… стоп! Что-то я углубился, судя по заголовку статьи, мы должны рассмотреть как это всё устроенно в JS. Ну что же давайте посмотрим.
Поехали!
Всем уже должно быть известно как определяется объект в JS. Вы скажите, с помощью литералов и это так! То есть, например.
var obj = {};
Ок, это понятно, а как тогда классы!? В JS нету классов! Ага, вообще.
Ну, ладно, есть, только не в явном виде. Это как? Очень просто.
Наверное, Вы слышали, что всё в JS — объекты, так? Конечно, слышали.
Так вот функции это тоже объекты. То есть у них тоже есть свойства, и даже можно определять функции в качестве свойства, тем самым образуя ЗАМЫКАНИЯ, но это тема другой статьи, поэтому не будем заострять на этом внимание.
У всех классов есть конструкторы, то есть функции, которые инициализируют объект, описывают все(или не все)
function Person(name, age) {
this.name = name;
this.age = age;
}
Боже мой, что эта за извращения?!
Если попытаться обратиться к свойству name через функцию Person, и вывести его значение в консоль вот так:
console.log(Person.name);
console: undefined
то получим undefined!
Но почему? Дело в том, что this не на что ссылаться. Об этом чуть позже. Нужно создать объект, иначе как экземпляр функции-конструктора Person. Для этого нам понадобиться оператор new.
var objPerson = new Person("Pety", 25);//создаём объект класса Person
Ок, создали, теперь если обратиться к свойству name по только что созданному объекту objPerson
console.log(objPerson.name)
console: «Pety»
Но как создаётся объект на самом деле? Давайте разбираться.
Вот алгоритм интерпретатора JS, встретив на своём пути оператор new
- ищет объявление функции-конструктора, название которой указано сразу после оператора new в конструкторе
- переменная this становиться равна новому созданному объекту, в данном случае objPerson
Вот, пожалуй, и всё.
То есть теперь переменная objPerson ссылается на экземпляр функции Person(), то есть конструктор.
Как я уже сказал функции являются объектами, а с помощью new мы можем клонировать его(функцию) и хранить в переменной, и дальше соответственно работать как с объектом.
Что!? Так функция тоже объект!
Да, но она предназначена для вызова некоторого фрагмента кода. Просто она сама по себе является объектом.
А теперь вот полноценный пример:
function Person(name, age) { // объявляем функцию-конструктор
this.name = name;
this.age = age;
}
var objPerson = new Person("Pety", 5); // создаём объект класса Person
objPerson.age = 25;
objPerson.name = "Pety Ivanon";
objPerson.name += "Sergeevich";
console.log(objPerson.name + ", " + objPerson.age + " years old"); // "Pety Ivanov Sergeevich, 25 yers old"
Свойство constructor
Теперь давайте подробнее рассмотрим создание объекта класса Person.
У каждой функции есть свойство prototype, которое хранит в себе объект следующего вида:
{
constructor: Person
}
И при создании объекта через new интерпретатор, сразу после выполнения ранее описанного алгоритма преобразует итоговое значение в ClassName.prototype, в данном случаи — Person.prototype.
var objPerson = new Person("Pety", 25);
console.log(objPerson == Person.prototype); // true
А если быть более точным, то Person.prototype преобразуется в объект следующего вида
{
constructor: Person
}
Если попробовать обратиться к этому свойству через objPerson, то мы получим код объявления функции-конструктора Person
console.log(objPerson.constructor);// то есть Person.prototype.constructor
function Person(name, age) {
this.name = name;
this.age = age;
}
Знакомьтесь, Наследование
Один из китов, на котором держится ООП.
Вот краткое определение:
— это возможность переопределять свойства одного класса другим.
Хм…и всё!? На самом деле, да, то есть основный принцип наследования — это переопределить общую характеристику класса с добавлением чего-то уникального для конкретного класса.
Давайте приведу аналогию
Допустим, что у нас есть чертёж дома(класс будущего объекта), у этого дома будет, например, сколько-то: этажей, комнат; и возможное наличие подвала.
Но потом Вы нарисовали новый чертёж для дома, в нём будут присущи все остальные характеристики, что у предыдущего, но с несколькими различиями, например, возможное наличие чердака, веранды.
То есть создаётся некая иерархия чертежей домов. Новый чертёж(класс) наследует, переменяет свойства предыдущего чертежа(класса).
Давайте теперь посмотрим как это всё выглядит в коде. Вот у нас есть класс, функция-конструктор House
function House(ctFloors, ctRooms, isBasement){
this.floors = ctFloors; // количество этажей
this.rooms = ctRooms; // количество комнат
this.basement = isBasement; // наличие подвала
}
А вот реализация класса ModernHouse
function ModernHouse(ctFloors, ctRooms, isBasement, isAttic, ctVeranda) {
House.apply(this, arguments); //вызываем конструктор House в контексте текущего
конструктора, ModernHouse
this.attic = isAttic;
this.veranda = ctVeranda;
}
Ок, мы описали два класса, из которых второй переопределяет реализацию первого с помощью вот такого выражения House.apply(this, arguments) — искренне надеюсь, что Вы знаете, что это такое, но тем не менее, читатель может и не знать, поэтому краткое описание:
С помощью функции apply() мы можем вызывать различные функции в определенном контексте, указанном в первом параметре, в данном случаи this, а также указать аргументы в виде массива.
Примечание:
Помимо функции apply, существует ещё call, разница которой относительно apply — она принимает в качестве аргументов отдельные значения, разделённые запятой.
Например, House.call(this, ctFloors, ctRooms, isBasement) — эквивалентное выражение House.apply(this, arguments);
Теперь же давайте попробуем создать объект класса ModernHouse и обратиться к какому-нибудь свойству.
var modern = new ModernHouse(3, 23, true, true, false); // создаём объект класса ModernHouse
console.log(modern.floors); // 23
Если вспомнить алгоритм интерпретатора JS, то переменная modern будет хранить значение следующего вида ModernHouse.prototype, а то в свою очередь литерал объекта:
{
constructor: ModernHouse;
}
Знакомьтесь, Инкапсуляци
Что это такое!?
Инкапсуляция — если кратко, приватизация данных, то есть отделение от общедоступных данных в определенную оболочку, доступ к которой происходит через общедоступные функции.
Допустим, мы хотим, чтобы не было прямого доступа к определенному свойству объекта, чтоб любой другой человек, работающий с Вашим кодом случайно не изменил значение этого свойства.
Для этого нужно знать что такое область определения.
Вот пример:
function Database() {
this.rows=20;
this.cols = 20;
}
var db = new Database();
console.log(db.rows + " : " + db.cols);// "20 : 20"
Мы хотим, чтоб обращение к свойству rows, cols не было, то есть было запрещено, или попросту не срабатывало.
Для этого нужно немного, просто объявить эти свойства как переменные через var.
function Database() {
var rows=20;
var cols = 20;
}
var db = new Database();
console.log(db.rows + " : " + db.cols);// "undefined : undefined"
undefined! Да, то, что нам нужно! Так как наши переменные локальны, поэтому обращение к ним из других областей видимости попросту невозможно!
Но как же теперь получить значение этих свойств!? Для этого нужно объявить так называемые функции-геттеры и -сеттеры.
function Database() {
var _rows=20;
var _cols = 20;
this.getTable = function() { //задаём геттер
return this.rows + ' : ' + this.cols; // поскольку она находиться в той же области видимости, что и переменные _rows и _cols, то доступ разрешен.
}
this.setTable = function(row, col) { //задаём сеттер
this.rows = row; // аналогичная ситуация с областью видимости.
this.cols = col;
}
}
var db = new Database(); // создание объекта класса Database
console.log(db._rows + ' : ' + db._cols); // "undefined : undefined"
console.log(db.getTable());// "20 : 20"
db.setTable(35, 26); // изменяем значения свойств
console.log(db.getTable());// "35 : 26"
Это сработает поскольку функции-геттеры -сеттеры, находятся в той же области видимости, что и функция-конструктор Database.
Автор: new player