adWords Amazon Analytics BABYExpo books.ru call-центр clx.ru CRM css Flex Futbolka.ua Google Google Analytics iForum javascript MailChimp Expert money.ua Nadavi nginx Optimization 2011 OWOX OZON.ru php price.ua promodo Prototype Raketa RIA Rozetka seo SiteHeart sms SoftKey Stat24 UAMaster usability Visa/MasterCard webmoney webprojects Авторизация аналитика баннеры бренды деньги дизайн Законодательство ИМУ интервью Исследования рынка Каталоги КИБ клиенты Книжный Клуб конверсия контекст конференция конференция 2007 конференция 2008 конференция 2009 конференция 2010 конференция 2011 копирайтинг Корзина Кризис Логистика маркетинг маркеты мерчандайзинг мобильный интернет обзоры, конференции обмен опытом Операторы связи/ISP офис персональные рекомендации поздравление покупка ссылок ПриватБанк процессинг Регистрация реклама РИТ Семинар в Днепропетровске семинар для клиентов Скорость софт Социальные сети статистика телемаркетин Украина Интернет форум УПЦ эквайринг Электронная торговля-2011 Яндекс

Прототипно-орієнтований JavaScript

Григорий Гребинюк
02 марта 2011
Григорий Гребинюк,
программист компании OWOX

Досить часто в роботі використовую властивість «prototype» JavaScript об’єктів для зміни значень властивостей одразу у всіх примірників (instances) конкретного типу. Але прочитавши статтю «Разбираемся с prototype, __proto__, constructor и их цепочками в картинках», вирішив провести низку власних дослідів, щоб більш поглиблено розібратися яким саме чином примірники «класів» пов’язані з базовими об’єктами. І як каже один мій друг: «Наступний рівень самонавчання — навчання когось іншого». Тому пост сформую у вигляді лекції.


Перш за все, що ж таке «прототипне програмування»?

Згідно Wikipedia:

Прототипне програмування — стиль об’єктно-орієнтованого програмування, при якому відсутнє поняття класу, а повторне використання (успадкування) проводиться шляхом клонування існуючого примірника об’єкта — прототипу.

У випадку JavaScript: набування властивостей і методів батьківського типу відбувається через посилання на прототип конструктора. Саме час надати визначення властивостям constructor, prototype, __proto__.

  • constructor — властивість об’єкта прототипу, зберігає посилання на функцію, яка створила примірник.
  • prototype — властивість об’єкта конструктора, зберігає посилання на об’єкт, який має властивості і методи притаманні всім примірникам, створених цим конструкторoм.
  • __proto__ — властивість примірника, зберігає посилання на прототип конструктора, яким був створений.

Так як всі об’єкти в JavaScript походять від Object, то всі об’єкти успадковують властивості і методи від Object.prototype.


Розглянемо приклад

Створимо примірник Object:

Прототипно-орієнтований JavaScript - примірник Object

Ми викликали конструктор var obj = new Object() і отримали об’єкт, який має одну єдину властивість __proto__. Але для звернення доступні всі властивості і методи, які є у прототипі конструктора. JavaScript інтерпретатор спочатку шукає властивість чи метод, за яким було звернення, серед власних в obj. У випадку невдалого результату через посилання __proto__ переходить до прототипу і перевіряє його на наявність властивості чи метода, за яким було звернення. І так аж поки звернення не буде знайдене, або не буде досягнутий прототип, який не має посилання __proto__. В такому випадку отримаємо «undefined» (якщо зверталися до властивості), чи помилку «has no method» (якщо намагалися викликати метод).

Зверніть увагу, що конструктор Object є функцією і, як всі примірники створені конструктором Function, має посилання Function.prototype, той в свою чергу має в якості прототипа Object.prototype. Крім того, конструктор Function також є функцією, тому одночасно має посилання __proto__ на Function.prototype (як примірник створений «самим-собою» :) і властивість prototype, яка теж посилається на Function.prototype (щоб всі нащадки мали доступ до властивостей і методів цього прототипу).

Зі зв’язками об’єктів і принципом наслідування розібралися. Але на цьому вся краса реалізації прототипів в JavaScript лише починається. Всі три властивості можна динамічно перевизначити в залежності від потреб.


Почнемо з властивості __proto__

Припустимо ми створили власний тип Lifeform і додатково два типи Animal і Plant, які за логікою повинні успадковувати всі властивості і методи від типу Lifeform. Для цього посилання на батьківський прототип в прототипах типів Animal і Plant (Animal.prototype.__proto__ і Plant.prototype.__proto__) замінимо на прототип Lifeform (Lifeform.prototype)


function extend(child, supertype)
{
   child.prototype.__proto__ = supertype.prototype;
}

extend(Animal, Lifeform);
extend(Plant, Lifeform);

var anOnion = new Plant();

Таким чином примірник типу Plant — «цибулина» успадкує всі властивості і методи від Plant.prototype, Lifeform.prototype і Object.prorotype.

Варто також згадати що є можливість за бажанням (за потреби) заборонити розширювати властивості об’єкта за допомогою функції Object.preventExtensions(obj).


Зміна властивості prototype

За допомогою зміни властивості prototype у об’єкта конструктора, можна досягнути того, що об’єкти створені одним і тим самим конструктором будуть мати різні набори властивостей і методів. Через те, що в них властивість __proto__ посилається на різні об’єкти прототипів.


І нарешті зміна властивості constructor

Навіщо може виникнути потреба змінювати значення властивості constructor у об’єкта прототипа, чесно кажучи, не знаю. Але така можливість є для всіх типів (окрім примітивних Boolean, Number чи String).

Наступний приклад покаже, що не треба стовідсотково довіряти значенню властивості constructor, так як воно може бути змінено. Краще перевіряти чи є об’єкт примірником конкретного типу за допомогою конструкції: object instanceof Type.


function Type(){};
var types = [
 new Array, [],
 new Boolean, true,
 new Date,
 new Error,
 new Function, function(){},
 Math, 
 new Number, 1,
 new Object, {},
 new RegExp, /(?:)/,
 new String, "test"
];
for(var i = 0; i < types.length; i++){
 types[i].constructor = Type;
 types[i] = [types[i].constructor, types[i] instanceof Type, types[i].toString()];
};
alert(types.join("\n"));

Виведе:


function Type(){},false,
function Type(){},false,
function Type(){},false,false
function Boolean() { [native code] },false,true
function Type(){},false,Sun Dec 26 2010 22:35:47 GMT+0200 (Eastern Europe Standard Time)
function Type(){},false,Error
function Type(){},false,function anonymous() {

}
function Type(){},false,function (){}
function Type(){},false,[object Math]
function Type(){},false,0
function Number() { [native code] },false,1
function Type(){},false,[object Object]
function Type(){},false,[object Object]
function Type(){},false,/(?:)/
function Type(){},false,/(?:)/
function Type(){},false,
function String() { [native code] },false,test


Це і все що я хотів розповісти. Буду радий якщо комусь ця стаття допоможе.

Джерело: Блоґ звичайного програміста

Метки: javascript, Prototype

Добавить комментарий