Привет всем. JavaScript это весьма гибкий язык, но так получилось что классов в этом языке нет. Да, в ECMAScript 6 появятся классы, но еще не скоро наступят те времена, когда большинство пользователей будут использовать браузер с этой версией языка. А пока программисты на JavaScript используют различные фреймворки (MooTools, AtomJS и другие) для создания «классов». Прочитав эту статью вы узнаете: как устроены выше упомянутые фреймворки, и как самим на чистом JavaScript можно реализовать классы несколькими способами.
Начнем с простого. Это то, что придет на ум новичку в JavaScript сразу-же, как перед ним встанет задача написать «класс»:
function myClass(){
this.message = "Hello!";
this.alert = function(){
alert(this.message);
};
}
Конструкция вполне работоспособна, но этого мало. Вот как можно ее дополнить:
1. Контекст this
Переменная this
является контекстной, и она не всегда является тем что нужно, поэтому сделаем следующее:
function myClass(){
var _this = this;
_this.message = "Hello!";
_this.alert = function(){
alert(_this.message);
};
}
2. Конструктор
function myClass(){
var _this = this;
function _constructor(message){
_this.message = (message) ? message : "";
};
_this.alert = function(){
alert(_this.message);
};
_constructor.apply(this,arguments);
}
Здесь, после определения всех методов, мы вызываем конструктор с указанными нами контекстом и аргументами.
В данном случае можно обойтись и без _this
, так как при вызове мы указали нужный нам контекст для функции _construct
.
3. Приватные переменные, геттеры и сеттеры
Приватные переменные можно объявлять так: или классически объявлять каждую переменную по-отдельности (var a,b,c,d…
), или объявляем один объект и все переменные записываем в него. Я предпочитаю второй способ.
Про геттеры и сеттеры можно почитать в этой замечательной статье. Создание геттеров и сеттеров можно сократить способом, который показан в следующем примере:
function myClass(){
var _this = this, _pv = {}, _gt = {}, _st = {};
function _constructor(message){
_this.message = (message) ? message : "";
_pv.test = "private var for demo";
};
_this.alert = function(){
alert(_this.message);
};
_gt.test = function(){
return "getter returns: "+_pv.test;
};
_st.test = function(val){
_pv.test = val;
};
for(var i in _gt) if(_gt.hasOwnProperty(i)) _this.__defineGetter__(i,_gt[i]);
for(var i in _st) if(_st.hasOwnProperty(i)) _this.__defineSetter__(i,_st[i]);
_constructor.apply(this,arguments);
}
4. Самовызывающийся конструктор
Это позволит создавать экземпляр класса без оператора new. Про самовызывающийся конструктор можно прочитать тут.
Для класса с произвольными аргументами конструктора код будет выглядеть так:
function myClass(){
if(!(this instanceof myClass)) return new myClass({"@arguments":arguments});
var _this = this, _pv = {}, _gt = {}, _st = {};
function _constructor(message){
_this.message = (message) ? message : "";
_pv.test = "private var for demo";
};
_this.alert = function(){
alert(_this.message);
};
_gt.test = function(){
return "getter returns: "+_pv.test;
};
_st.test = function(val){
_pv.test = val;
};
for(var i in _gt) if(_gt.hasOwnProperty(i)) _this.__defineGetter__(i,_gt[i]);
for(var i in _st) if(_st.hasOwnProperty(i)) _this.__defineSetter__(i,_st[i]);
_constructor.apply(this, ( arguments.hasOwnProperty("@arguments") ) ? arguments["@arguments"] : arguments );
}
Для определенных аргументов делаем следующие изменения:
function myClass(message){
if(!(this instanceof myClass)) return new myClass(message);
...
_constructor.apply(this,arguments); // или _constructor.call(this,message); или вовсе _constructor(message);
}
5. Параметры свойств
При желании можно отрегулировать все свойства и методы так, как вам хочется при помощи Object.defineProperty
. Подробнее об этой функции читайте тут.
6. Скорость
И так, мы получили вполне хорошую реализацию классов, но есть одно но: это подойдет только тогда, когда вы будите пользоваться экземпляром, а не постоянно конструировать объект. То есть такой объект как jQuery таким способом собирать не стоит — это медленно. Метод сборки таких объектов я тоже сейчас объясню.
В предыдущей реализации классов при конструировании объекта сначала определялись все его методы с переменными — это самое затратное по времени. В «быстрой» конструкции методы определяются единожды, и их присваивают прототипу объекта с исходными данными, то есть в крации jQuery устроен так:
function $(selector){
var elements = document.querySelectorAll(selector);
elements.__proto__ = $.fn;
return elements;
}
$.fn = {
html : function(html){
if(!html) return this[0].innerHTML;
else for(var i in this) this[i].innerHTML = html;
}
};
При вызове функции создается массив с нужными элементами, и прототипу этого массива присваивается объект с методами (точнее это указатель на объект, но это не страшно, так как все методы используют исходный объект через this
).
Ну и переведем наш изначальный медленный класс на новую быструю конструкцию:
function myClass(message){
var obj = { message : (message) ? message : "", test : "private var for demo" }; // от приватных переменных придется избавиться
obj.__proto__ = myClass.methods;
return obj;
}
(function(){ // ну или сплошным объектом как в примере с jQuery, но тогда без геттеров и сеттеров
var _this = myClass.methods, _gt = {}, _st = {};
_this.alert = function(){
alert(this.message);
};
_gt.test = function(){
return "getter returns: "+this.test;
};
_st.test = function(val){
this.test = val;
};
for(var i in _gt) if(_gt.hasOwnProperty(i)) _this.__defineGetter__(i,_gt[i]);
for(var i in _st) if(_st.hasOwnProperty(i)) _this.__defineSetter__(i,_st[i]);
// для наследования: _this.__proto__ = myAnotherClass.methods;
})();
Для наследования хочу отметить одну вещь:
var a = {}, b = { num : 123 };
a.__proto__ = b; // a.num == b.num == 123;
a.num = 321; // a.num == 321; a.__proto__.num == b.num == 123;
С использованием метода родителя, когда у ребенка есть одноименный метод, проблем не будет.
Я видел еще несколько реализаций классов, пару можно найти здесь, на хабре, но в итоге основа у всех одна.
Разобравшись во всем этом для себя я сделал вывод: проще и удобней писать на языках компилируемых в JavaScript в которых есть классы, например, я решил писать сайты на Dart.
Автор: dangreen