Сегодня хочу поговорить про ООП(объектно-ориентированное программирование).
А именно ООП в JavaScript.
Небольшое лирическое отступление:
— Помню, как год или два назад писал статьи про тоже, но для Java, сейчас, к сожалению, этих статей не осталось, хотя если постараться можно найти, правда, Вы там ничего не поймёте поскольку тогда я был жутким школяром, который углубляясь во что-то, всё до мелочей описывал, ни капли не заботясь о том, что кому-то это должно быть понятно. Посмотрим, что будет сейчас.
Изучая конкретный язык программирования, каждый должен знать, как в нём поддерживается ООП, вернее не так, как писать объектно-ориентированный код.
ООП — способ организации представления данных в виде классов, а те, в свою очередь, — объектов. Методология проектирования данных.
Так, давайте по порядку.
Класс — это некий шаблон, описывающий общую структуру чего-то. Приведу аналогию. Представьте инженера по кораблестроению. Инженер — Вы. Вы рисуете чертежи(шаблон, класс), на основе которых строятся, создаются корабли(объекты). В чертежах(классах) Вы описываете строение корабля, то есть некие элементы(свойства), присутствующие у всех кораблей(объектов).
Вот ещё парочка примеров:
— инженер автомобилей — Вы. Вы конструируете машину на чертежах, то есть описываете класс машины, отдаёте эти чертежи на завод или ещё куда-то, где на его основе будут создавать машины, некоторые их которых будут отличаться, разные модели, например. Одна будет красного цвета, другая — синего. Такие характеристики, как цвет машины — некие свойства класса(чертежа машины), которые имеют разные значения для определенного объекта(машины).
Так, думаю, теоретически Вы поняли самую главную суть ООП. Теперь давайте посмотрим… стоп! Что-то я углубился, судя по заголовку статьи, мы должны рассмотреть как это всё устроенно в JS. Ну что же давайте посмотрим.
Поехали!
Всем уже должно быть известно как определяется объект в JS. Вы скажите, с помощью литералов и это так! То есть, например.
Ок, это понятно, а как тогда классы!? В JS нету классов! Ага, вообще.
Ну, ладно, есть, только не в явном виде. Это как? Очень просто.
Наверное, Вы слышали, что всё в JS — объекты, так? Конечно, слышали.
Так вот функции это тоже объекты. То есть у них тоже есть свойства, и даже можно определять функции в качестве свойства, тем самым образуя ЗАМЫКАНИЯ, но это тема другой статьи, поэтому не будем заострять на этом внимание.
У всех классов есть конструкторы, то есть функции, которые инициализируют объект, описывают все(или не все)
Если попытаться обратиться к свойству name через функцию Person, и вывести его значение в консоль вот так:
то получим undefined!
Но почему? Дело в том, что this не на что ссылаться. Об этом чуть позже. Нужно создать объект, иначе как экземпляр функции-конструктора Person. Для этого нам понадобиться оператор new.
Ок, создали, теперь если обратиться к свойству name по только что созданному объекту objPerson
Но как создаётся объект на самом деле? Давайте разбираться.
Вот алгоритм интерпретатора JS, встретив на своём пути оператор new
Вот, пожалуй, и всё.
То есть теперь переменная objPerson ссылается на экземпляр функции Person(), то есть конструктор.
Как я уже сказал функции являются объектами, а с помощью new мы можем клонировать его(функцию) и хранить в переменной, и дальше соответственно работать как с объектом.
А теперь вот полноценный пример:
Теперь давайте подробнее рассмотрим создание объекта класса Person.
У каждой функции есть свойство prototype, которое хранит в себе объект следующего вида:
И при создании объекта через new интерпретатор, сразу после выполнения ранее описанного алгоритма преобразует итоговое значение в ClassName.prototype, в данном случаи — Person.prototype.
А если быть более точным, то Person.prototype преобразуется в объект следующего вида
Если попробовать обратиться к этому свойству через objPerson, то мы получим код объявления функции-конструктора Person
Один из китов, на котором держится ООП.
Вот краткое определение:
— это возможность переопределять свойства одного класса другим.
Хм…и всё!? На самом деле, да, то есть основный принцип наследования — это переопределить общую характеристику класса с добавлением чего-то уникального для конкретного класса.
Давайте приведу аналогию
Допустим, что у нас есть чертёж дома(класс будущего объекта), у этого дома будет, например, сколько-то: этажей, комнат; и возможное наличие подвала.
Но потом Вы нарисовали новый чертёж для дома, в нём будут присущи все остальные характеристики, что у предыдущего, но с несколькими различиями, например, возможное наличие чердака, веранды.
То есть создаётся некая иерархия чертежей домов. Новый чертёж(класс) наследует, переменяет свойства предыдущего чертежа(класса).
Давайте теперь посмотрим как это всё выглядит в коде. Вот у нас есть класс, функция-конструктор House
А вот реализация класса ModernHouse
Ок, мы описали два класса, из которых второй переопределяет реализацию первого с помощью вот такого выражения House.apply(this, arguments) — искренне надеюсь, что Вы знаете, что это такое, но тем не менее, читатель может и не знать, поэтому краткое описание:
Примечание:
Если вспомнить алгоритм интерпретатора JS, то переменная modern будет хранить значение следующего вида ModernHouse.prototype, а то в свою очередь литерал объекта:
Инкапсуляция — если кратко, приватизация данных, то есть отделение от общедоступных данных в определенную оболочку, доступ к которой происходит через общедоступные функции.
Допустим, мы хотим, чтобы не было прямого доступа к определенному свойству объекта, чтоб любой другой человек, работающий с Вашим кодом случайно не изменил значение этого свойства.
Для этого нужно знать что такое область определения.
Вот пример:
Мы хотим, чтоб обращение к свойству rows, cols не было, то есть было запрещено, или попросту не срабатывало.
Для этого нужно немного, просто объявить эти свойства как переменные через var.
undefined! Да, то, что нам нужно! Так как наши переменные локальны, поэтому обращение к ним из других областей видимости попросту невозможно!
Но как же теперь получить значение этих свойств!? Для этого нужно объявить так называемые функции-геттеры и -сеттеры.
Это сработает поскольку функции-геттеры -сеттеры, находятся в той же области видимости, что и функция-конструктор Database.
А именно ООП в 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 — она принимает в качестве аргументов отдельные значения, разделённые запятой.Теперь же давайте попробуем создать объект класса ModernHouse и обратиться к какому-нибудь свойству.
Например, House.call(this, ctFloors, ctRooms, isBasement) — эквивалентное выражение House.apply(this, arguments);
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.