Javascript классы
Здравствуйте, уважаемые читатели блога. Сегодня, занимаясь оптимизацией moguta.cms я решил найти способ и структурировать свой JavaScript код.
Я давно знаком с принципами ООП и поэтому использование, классов в
языках их поддерживающих, является обычной для меня практикой.
По умолчанию такого понятия как классы в javaScript
не существует, что доставляет мелкие неудобства при проектировании
одностраничных веб-приложений, но в JavaScript есть объекты.
Для программиста знакомого с ООП, должно быть странным такое
положение дел, поскольку в рамках ООП – объекты являются экземплярами
классов. В JavaScript, классы отсутствуют, но их
экземпляры можно создать. Немного похоже на бред, но это отнюдь не так.
Чтобы понять, как такое возможно нужно изучить сущность и свойства
понятия литеральная нотация.
Литеральная нотация – это специальный формат задания объекта в JavaScript.
Не будем пока вдаваться в подробности, и зададим простой объект следующим образом.
1 2 3 4
| var apple = {
color:"green",
cost: 3
} |
Этой записью, мы определили объект "яблоко" с полями – цвет и стоимость. Тут все просто, получить свойство объекта можно так:
Кроме простых типов данных, свойства могут являться себе функциями:
1 2 3 4 5 6 7
| var apple = {
color:"green",
cost: 3,
getColor: function(){
return apple.color;
}
} |
Теперь получить цвет яблока можно еще одним способом:
1
| alert(apple.getColor()); |
Ну как? Не напоминает ничего родного и близкого из области ООП?
Если немного пофантазировать, то можно с помощью объекта задать приватные поля и функции, тем самым реализовать JavaScript классы.
Пример класса на Javascript
На самом деле существует масса теории и вариантов на тему создания классов на Javascript, и некоторые выкладки я приведу в конце статьи.
Для себя я выделил способ создания класса на JS именно при помощи
литеральной нотации совмещенный с паттерном "Модуль" И хочу попробовать
донести до вас его суть следующим примером.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| /**
* Модуль LifeexampleClass.
*/
var LifeexampleClass = (function () {
var privateField1 = "значение приватного поля1";
var privateField2 = "значение приватного поля2";
// Функция доступная только внутри класса.
function privateFunction () {
alert("Из тела класса вызван приватный метод: [privateFunction] ");
alert("Приватный метод обратился к приватному полю: [privateField1 = '"+privateField1+"']");
alert("Приватный метод обратился к публичному полю: [publicField1 = '"+this.publicField1+"']");
return true;
}
return {
// Публичные поля класса.
publicField1: "значение публичного поля1",
publicField2: "значение публичного поля2",
// Публичный метод класса.
init: function() {
alert('Вызван публичный метод класса: [init]');
// Обращение к публичному полю, доступному как внутри класса так и из вне.
alert('Метод [init] обратился к публичному полю [privateField1='+privateField1+']');
// Вызов приватной функции доступной только внутри класса.
privateFunction();
}
}
})();
// обращение к приватному полю, доступному только внутри класса.
alert("Снаружи класса произошла попытка обратиться к приватному полю [privateField1] результат = "+LifeexampleClass.privateField1);
try{
// обращение к приватному методу, доступному только внутри класса следующая строка вызовет ошибку
LifeexampleClass.privateFunction()
alert("Удалось обратиться к приватному методу класса! [Ошибка в архитектуре класса]");
} catch(err){
alert("Произошла ошибка "+err+"! Обращение к приватному методу класса запрещено!");
}
// обращение к публичному полю, доступному как внутри класса так и из вне.
alert("Снаружи класса произошла попытка обратиться к публичному полю [publicField1] результат = "+LifeexampleClass.publicField1);
try{
// обращение к публичному методу, доступному как внутри класса так и из вне.
LifeexampleClass.init()
} catch(err){
alert("Не удалось обратиться к публичному методу класса! [Ошибка в архитектуре класса]");
} |
Скомпилируйте данный код, чтобы проверить его работоспособность, но
даже визуально, полагаясь, только на комментарии можно уловить суть и
смысл задания класса на JS. Конечно это по по-прежнему не класс, а
объект LifeexampleClass, но для структурирования кода на JS, этого
достаточно.
Получившийся псевдокласс имеет приватные и публичный поля и методы.
Для полного привычного понимания класса ему не хватает только
возможности наследовать и быть родителем для дочерних объектов.
Хоть в JS и нет ООП, но наследование свойств предусмотрено свойством прототипирования: prototype.
Приведу несколько полезных аспектов, осознавая которые можно решить
для себя массу задач даже без привычного смысла классов и ООП.
Запомните:
- Классов нет, есть только объекты!
- Function – тоже является объектом , потому что в Javascript все является объектами.
- Конструктором объекта может быть только объект встроенного класса (обычно это Function).
Далее я приведу все стандартные примеры реализации javascript классов, встретившиеся мне в различных источниках и ресурсах интернета
Класс на JS заданный через function
Самый распространенный способ реализовать класс на JS – создать
функцию, внутри которой определить поля и методы. Создать экземпляр
этого класса можно будет привычным образом с помощью ключевого слова
new. При этом можно использовать конструктор.
1 2 3 4 5 6 7 8 9 10 11 12
| // A car "javascript class"
function Car( model ) {
this.model = model;
this.color = "silver";
this.year = "2012";
this.getInfo = function () {
return this.model + " " + this.year;
};
} |
Используйте данный javascript класс следующим образом:
1 2 3
| var myCar = new Car("ford");
myCar.year = "2010";
console.log( myCar.getInfo() ); |
Класс на JS как объект заданный литералами
Преимущества такого подхода в том, что мы можем инкапсулировать данные этого класса дальше в потомки класса.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| var myModule = {
myProperty: "someValue",
// object literals can contain properties and methods.
// e.g we can define a further object for module configuration:
myConfig: {
useCaching: true,
language: "en"
},
// a very basic method
myMethod: function () {
console.log( "Where in the world is Paul Irish today?" );
},
// output a value based on the current configuration
myMethod2: function () {
console.log( "Caching is:" + ( this.myConfig.useCaching ) ? "enabled" : "disabled" );
},
// override the current configuration
myMethod3: function( newConfig ) {
if ( typeof newConfig === "object" ) {
this.myConfig = newConfig;
console.log( this.myConfig.language );
}
}
};
// myModule it is class javascript
// Outputs: Where in the world is Paul Irish today?
myModule.myMethod();
// Outputs: enabled
myModule.myMethod2();
// Outputs: fr
myModule.myMethod3({
language: "fr",
useCaching: false
}); |
Класс основанный на паттерне "Модуль"
Преимущество в том, что данный паттерн предоставляет полноценное ограничение доступа к определённым компонентам объекта.
Следующие два примера были отражены в самом первом способе описанном
мной в начале статьи, но тем не менее я выложу и эти два, для
закрепления материала.
Пример с публичными полями и методами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // testModule it is class javascript
var testModule = (function () {
var counter = 0;
return {
incrementCounter: function () {
return counter++;
},
resetCounter: function () {
console.log( "counter value prior to reset: " + counter );
counter = 0;
}
};
})();
// Usage:
// Increment our counter
testModule.incrementCounter();
// Check the counter value and reset
// Outputs: 1
testModule.resetCounter();
// end example class javascript |
Пример с приватными методами и полями:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| var myNamespace = (function () {
var myPrivateVar, myPrivateMethod;
// A private counter variable
myPrivateVar = 0;
// A private function which logs any arguments
myPrivateMethod = function( foo ) {
console.log( foo );
};
return {
// A public variable
myPublicVar: "foo",
// A public function utilizing privates
myPublicFunction: function( bar ) {
// Increment our private counter
myPrivateVar++;
// Call our private method using bar
myPrivateMethod( bar );
}
};
})(); |
Паттерн синглтон в JS
Для особо жаждущих выкладываю реализацию синглтона на JS.
Однажды я описывал как устроен паттерн синглтон на PHP, теперь встречайте его реализацию на JS.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| var mySingleton = (function () {
// Instance stores a reference to the Singleton
var instance;
function init() {
// Singleton
// Private methods and variables
function privateMethod(){
console.log( "I am private" );
}
var privateVariable = "Im also private";
return {
// Public methods and variables
publicMethod: function () {
console.log( "The public can see me!" );
},
publicProperty: "I am also public"
};
};
return {
// Get the Singleton instance if one exists
// or create one if it doesn't
getInstance: function () {
if ( !instance ) {
instance = init();
}
return instance;
}
};
})();
// Usage:
var singleA = mySingleton;
var singleB = mySingleton;
console.log( singleA === singleB ); // true |
Также советую почитать примеры изложенные на этом сайте:
Для примеров описанных в той статье понадобится маленькая библиотека Oop.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| //
// Create proper-derivable "class".
//
// Version: 1.2
//
function newClass(parent, prop) {
// Dynamically create class constructor.
var clazz = function() {
// Stupid JS need exactly one "operator new" calling for parent
// constructor just after class definition.
if (clazz.preparing) return delete(clazz.preparing);
// Call custom constructor.
if (clazz.constr) {
this.constructor = clazz; // we need it!
clazz.constr.apply(this, arguments);
}
}
clazz.prototype = {}; // no prototype by default
if (parent) {
parent.preparing = true;
clazz.prototype = new parent;
clazz.prototype.constructor = parent;
clazz.constr = parent; // BY DEFAULT - parent constructor
}
if (prop) {
var cname = "constructor";
for (var k in prop) {
if (k != cname) clazz.prototype[k] = prop[k];
}
if (prop[cname] && prop[cname] != Object)
clazz.constr = prop[cname];
}
return clazz;
} |
Пример работы с библиотекой oop.js :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <script src="Oop.js"></script>
<script>
// Базовый "javascript класс".
Car = newClass(null, {
constructor: function() {
document.writeln("Вызван конструктор Car().");
},
drive: function() {
document.writeln("Вызван Car.drive()");
}
});
// Производный "javascript класс".
Zaporojets = newClass(Car, {
constructor: function() {
document.writeln("Вызван конструктор Zaporojets().");
this.constructor.prototype.constructor.call(this);
},
crack: function() {
document.writeln("Вызван Zaporojets.crack()");
},
drive: function() {
document.writeln("Вызван Zaporojets.drive()");
return this.constructor.prototype.drive.call(this);
}
});
document.writeln("Программа запущена.");
// Создаем объект производного "класса".
var vehicle = new Zaporojets();
vehicle.drive(); // вызывается функция базового объекта
// Создаем еще один объект того же класса.
var vehicle = new Zaporojets();
vehicle.crack(); // функция производного объекта
</script> |
На этом я заканчиваю статью о том, как реализовать JavaScript классы, подписывайтесь на обновления блога, у меня еще много интересных тем.
|