§15. Дополнительные сведения по объектам. ООП.
В 7 и 8 параграфах мы достаточно подробно рассмотрели вопросы создания и использования объектов. В данном параграфе мы расширим знания по этой теме.
Добавление свойств в уже существующий объект.
В JavaScript существует возможность динамического (во время выполнения программы) добавления свойств в уже существующий объект. Для этого достаточно просто в коде программы указать объект с новым свойством и присвоить этому свойству значение. Пример:
var ob = {x:23,y:4};
ob.z = 34;
Доступ к свойствам с помощью квадратных скобок.
До сих пор мы осуществляли доступ к свойствам объектов с помощью точки. В JavaScript существует возможность иметь доступ к свойствам с помощью квадратных скобок. В таком варианте идентификатор свойства необходимо заключить в кавычки. Пример:
ob['x'] = 34;
При таком подходе идентификатор свойства является строкой, а строки в свою очередь могут быть обработаны и созданы динамически. В этом заключается преимущество данного подхода. Далее пример, в котором динамически создан объект с заранее неизвестным количеством свойств:
<script>
var ob = {};
var size_of_Ob = Math.random()*20;
for (var i = 0; i<size_of_Ob; i++) ob['Свойство_'+i.toString()] = Math.random();
for (var i = 0; i<size_of_Ob; i++)
document.write('Свойство_'+i.toString(),' = ',ob['Свойство_'+i.toString()],'<br>');
</script>
Как видно из примера при таком подходе объект напоминает массив, только индексы здесь выражены не целыми числами, а строками. Такие объекты называют ассоциативными массивами.
Перебор свойств объекта. Конструкция for...in.
В JavaScript есть, можно сказать, уникальная возможность перебирать свойства любого объекта, словно это массив. Для этого существует конструкция for...in. В коде программы она выглядит следующим образом:
var ob = {x:3,y:4,z:56};
for (var prop in ob)
document.write(prop,' = ',ob[prop],'<br>');
Данная конструкция перебирает все свойства объекта и записывает их идентификаторы в переменную prop в виде строки. В теле конструкции значение этой переменной может быть использовано.
Проверка существования свойств.
Так как в JavaScript свойства объектов могут быть созданы динамически, то во время выполнения программы могут возникнуть ситуации, когда обращение будет происходить к несуществующим свойствам. Для того, что бы предотвратить подобного рода ошибки существует оператор in, с помощью которого можно проверить свойство на факт существования. Пример:
<script>
var ob = {x:3,y:4,z:56};
if ('x' in ob)
document.write('ob.x = ',ob.x,'<br>')
else
document.write('ob.x не существует');
</script>
Удаление свойств объектов. Метод delete.
Т.к. можно динамически создавать свойства, то можно динамически их и удалять. Для этого существует оператор delete. Пример:
<script>
var ob = {x:3,y:4,z:56};
delete ob.x;
if (!('x' in ob)) document.write('Свойство x не существует');
</script>
Функции как данные.
В JavaScript функции можно воспринимать так же, как и данные. В таком виде функцию можно присвоить переменной, после чего этой переменной можно пользоваться так же, как и функцией. Пример:
<script>
function F() {document.write('Привет всем!')}
var x = F;
x();
</script>
Динамическое создание методов.
На первый взгляд предыдущий пример кажется бессмысленным. Однако этот принцип имеет большое значение для динамического создания объектов. Т.к. функции можно воспринимать как данные, то так же можно воспринимать и методы. Далее пример, в котором динамически создан метод:
<script>
var ob = {};
ob.M = function(s) {document.write(s)};
ob.M('Привет всем!');
</script>
Обратите внимание на то, каким образом с помощью слова function был создан метод. В скобочках после него указываются передаваемые аргументы.
Передача данных по значениям и по ссылкам.
В программировании передача данных от переменной к переменной или от переменной к функции и наоборот осуществляется двумя способами: передача непосредственно значения и передача по ссылке. Если передаётся непосредственно значение, то создаётся копия переменной. Если передаётся по ссылке, то создаётся копия ссылки, а не самого значения. Ссылка – это информация об адресе в памяти компьютера, где находится само значение, и количество занимаемой памяти (тип значения).
Передача данных по ссылке, как правило, осуществляется в том случае, если данные занимают много места и их копирование отнимет много ресурсов компьютера. Поэтому в JavaScript по значению копируются только числа, логические переменные и строки. Все остальные типы данных, в том числе и функции, передаются по ссылке.
Примечание: как правило, в других языках по умолчанию все данные передаются по значению, а программист имеет возможность явно задавать принцип передачи путём создания переменных типа ссылка. В JavaScript такой возможности нет, тем не менее, если вам понадобиться копия объекта или массива, то сделать это не трудно.
Для того, что бы продемонстрировать принцип передачи данных по ссылке, передадим различные типы данных в функцию, где их изменим, после чего выведем их значения из самой функции и вне функции.
Пример с числом:
<script>
var number = 1;
function copy(x)
{
x++;
document.write('Значение в функции ',x);
}
copy(number);
document.write('<br>Значение в теле программы ',number);
</script>
Как видите, в теле программы переменная number своё значение не изменила, т.к. передача произошла по значению.
Пример с массивом:
<script>
var m = [1,2,3,4,5];
function copy(x)
{
for (var i = 0; i<m.length; i++) x[i] = x[i] +1;
document.write('Значение в функции ',x);
}
copy(m);
document.write('<br>Значение в теле программы ',m);
</script>
Как видите, массив в теле программы изменил значения элементов, т.к. передача произошла по ссылке.
Здесь стоит добавить, что сама переменная, содержащая в себе любой тип кроме числа, строки или логический тип, тоже является ссылкой. И если её присвоить другой переменной, то появятся две равносильные переменные ссылки на один и тот же объект. Пример:
<script>
var m = {x:1,y:23};
var p = m;
document.write(p.x,' ',m.x);
</script>
Как видно из примера, после того, как мы присвоили переменной p значение переменной m, стало без разницы через какую переменную обращаться к самому объекту.
Логично предположить, что на один и тот же элемент в течение работы программы может образоваться несколько ссылок. Поэтому что бы узнать ссылаются ли переменные на один и тот же объект их можно сравнить друг с другом оператором сравнения. Пример:
<script>
var ob_1 = {x:2,y:23};
var ob_2 = {x:2,y:23};
var p_1 = ob_1;
var p_2 = ob_1;
document.write('p_1 == p_2 - ',p_1 == p_2,'<br>');
document.write('ob_1 == ob_2 - ',ob_1 == ob_2,'<br>');
</script>
Как видно из примера две переменные ссылки p_1 и p_2 равны друг другу, т.к. при сравнении сравнивается адреса ссылок и если они равны, то и переменные ссылки то же равны. В другом случае, не смотря на то, что оба объекта ob_1 и ob_2 имеют одинаковые свойства, и эти свойства имеют одинаковые значения, при сравнении мы получили false.
Проверка объекта на то, каким конструктором он был создан.
В JavaScript есть возможность проверять, каким конструктором был создан объект. Для этого существует оператор instanceof. Пример:
var date = new Date();
date instanceof Date;//Выражение вернёт true
Свойство объектов constructor.
Существует ещё один способ узнать, каким конструктором был создан объект - это свойство constructor, которое есть у любого объекта созданного конструктором, и которое является ссылкой на этот конструктор. Далее пример демонстрирующий вышесказанное:
function myObject(x) {this.x = x};
var ob_1 = new myObject(11);
ob_1.constructor == myObject;//вернёт true
ООП.
ООП (объектно-ориентированное программирование) – совокупность трёх основных принципов построения программ:
инкапсуляция – соединение в одном объекте данных (свойств этого объекта) и функций (методов), которые, как правило, обрабатывают эти данные (свойства);
наследование – возможность создавать объекты (потомки) на основе других (предков);
полиморфизм – возможность объектов предков использовать методы своих потомков.
Все эти принципы можно реализовать и в JavaScript, правда, несколько иначе, чем в большинстве других языков. На счёт инкапсуляции, думаю, вам должно быть всё ясно, т.к. мы практически с самого начала научились создавать объекты и пользоваться ими. А объект, как раз, и содержит в себе как свойства, так и методы.
По поводу наследования стоит сделать пояснение. В JavaScript данный принцип реализован за счёт того, что есть возможность динамически добавлять в уже существующий объект свойства и методы.
Так как новый объект потомок создаётся динамически из старого объекта предка, то соответственно и принцип полиморфизма вполне соблюдается, т.к. все старые методы и свойства остаются.
Примечание: для тех, кто программировал на других языках необходимо рассказать о том, почему в JavaScript нет классов. В других объектно-ориентированных языках классом является описание, на основе которого создаётся объект. Т.е. в начале описывается то, каким должен он быть, а только потом в теле программы он создаётся. Т.к. в других языках исходный код можно скрыть, скомпилировав программу, то существует необходимость в том, что бы создавать новые классы потомки на основе уже скомпилированных классов предков.
В JavaScript исходный код не компилируется и его можно менять, поэтому для создания объекта с новыми свойствами и методами достаточно дописать их в конструктор. Соответственно в наличии классов в JavaScript необходимости нет. Можно сказать, что роль классов выполняют конструкторы.
Как правило, в другой литературе по JavaScript, если вы встретите выражение о том, что два объекта принадлежат к одному классу, значит они созданы одним конструктором. Точно так же и в данном учебнике может быть употреблено слово класс в этом же смысле.
Задачи.
1. Создать конструктор Figure (фигура) для создания и инициализации объекта с тремя свойствами x,y (координаты) и im (ссылка на рисунок, на контекст тэга canvas). Создать этим конструктором объект square (квадрат) и динамически добавить в него метод draw, рисующий квадрат в окне браузера (в тэге canvas) с длиной стороны переданной в качестве аргумента. Причём свойства x и y должны оказаться координатой левой верхней точки квадрата. Продемонстрировать работу метода draw.
Примечание: данное задание должно продемонстрировать реализацию принципов ООП. Так же стоит сказать, что при создании данного конструктора, его объекты должны рисовать сами себя в любом тэге canvas, ссылка на который должна быть передана конструктору.
2. Создать таким же образом, как в первом задании, объекты рисующие прямоугольник, треугольник (любую геометрическую фигуру на ваш выбор).
Примеры решений.
1.
<body>
<canvas id = "image_1" width = "400px" height = "200px" style = "border:solid 2px black"></canvas>
<script>
var im_1 = image_1.getContext('2d');
function Figure(x,y,im){
this.x = x;
this.y = y;
this.im = im
}
var square = new Figure(10,20,im_1);
square.draw = function(l)
{
this.im.moveTo(this.x,this.y);
this.im.lineTo(this.x+l,this.y);
this.im.lineTo(this.x+l,this.y+l);
this.im.lineTo(this.x,this.y+l);
this.im.lineTo(this.x,this.y);
this.im.stroke();
}
square.draw(30);
</script>
</body>
Примечание: обратите внимание, т.к. контекст рисования (переменная im_1) передаётся в конструктор Figure по ссылке, то объект square из за этого становится универсальным и может рисовать квадрат в любом тэге canvas любого документа.
Так же обратите внимание на использования слова this в методе draw. Оно является ссылкой на «объект хозяин» метода. Поэтому его использование является оправданным и имеет преимущество в том случае, если метод draw будет скопирован в другой объект.