Pull to refresh

Обёртки для создания классов: зло или добро?

JavaScript *
Раз за разом я читаю, что удобные библиотеки для создания классов на Javascript, видите ли, не соответствуют идеологии языка и тем, кто их использует просто необходимо учить язык. Такое говорят невежды, которые и сами толком не разобрались ни в самом языке ни в библиотеках, которые они критикуют. И так часто говорят, что я решил написать этот топик и просто давать ссылку
var Foo = new Class({
	Extends: Bar,
	initialize: function(firstname, lastname) {
		this.parent(firstname);
		this.lastname = lastname;
	},
	sayHello: function(){
		alert(this.lastname || this.firstname);
	}
});


Обёртки в нормальных библиотеках совершенно не противоречат идеологии Javascript и прототипно-ориентированного программирования. Они просто предоставляют удобный интерфейс, алиас для его использования. Конечно, есть библиотеки, которые реализуют «ООП в Javascript» через какие-то черезжопные методы, но сейчас такие во внимание не берем, а рассматриваем их на примере Мутулз.

Например, для конструкции
MyClass = function() {/* constructor */};
MyClass.prototype.firstMethod  = function() {/**/};
MyClass.prototype.secondMethod = function() {/**/};
MyClass.prototype.thirdMethod  = function() {/**/};


Введен алиас, который позволяет не повторять MyObject.prototype. Не обойтись без этой конструкции, а просто не повторять её. Внутри библиотеки будет всё тот же
MyClass = new Class({
	initialize  : function() {/* constructor */},
	firstMethod : function() {/**/},
	secondMethod: function() {/**/},
	thirdMethod : function() {/**/}
});


Следующая конструкция для наследования (заметьте, она полностью соотсветствует идеологии Javascript и «ловится» с помощью instanceof)
MyAnotherClass = function() {/* constructor */};
var Tmp = function() { }
Tmp.prototype = MyClass.prototype
MyAnotherClass.prototype = new Tmp()
MyAnotherClass.prototype.constructor = MyAnotherClass;

Инкапсулируется в такую простую запись:
MyAnotherClass = new Class({
	Extends: MyClass
});


И еще много-много-много. Суть в том, что оно не нарушает идеологию JavaScript. Оно под неё подстраивается и предоставляет удобный интерфейс, а внутри это всё то же самое, что делали бы профессиональные. Ведь идеология прототипно-ориентированного программирования — это не многословность и постоянные упоминания слова «prototype», а невероятная расширяемость.

Перегруженый конструктор



А идеологию нарушает как раз другой, очень вредный и очень популярный подход — перегруженный конструктор:
var MyClass = function() {
	/* Начало конструктора */
	this.firstMethod  = function() {/**/};
	this.secondMethod = function() {/**/};
	this.thirdMethod  = function() {/**/};
	/* Конец конструктора, вот такой у нас зашибезный конструктор */
};
var MyAnotherClass = function() {
	// Наследование
	MyClass.apply(this, arguments);
	this.elseOneMethod  = function() {/**/};
};
var myAC = new MyAnotherClass();
console.log(myAC instanceof MyClass); // false


Который нельзя наследовать так, чтобы работал instanceof, в которой функции создаются каждый раз при создании объекта, из-за чего память течет рекой и рыдает процессор. Вместо того, чтобы создать функцию в одном месте и дать на неё ссылку — будем создавать её каждый раз. Этих функций нету в прототипе (а во встроенных объектах, таких как Array, Number и остальных всё находится именно в прототипе), их нельзя переопределить или получить вне контекста, они неправильно определяются с помощью hasOwnProperty и работают непозволительно медленно (в сотни раз):
var MyClass = function() {
	this.method1 = function(){};
};
MyClass.prototype.method2 = function(){};

var myClass = new MyClass;
console.log(myClass.hasOwnProperty('method1')); // true
console.log(myClass.hasOwnProperty('method2')); // false

var Foo = function() {
	this.method1 = function(){};
	this.method2 = function(){};
	this.method3 = function(){};
	this.method4 = function(){};
	this.method5 = function(){};
	this.method6 = function(){};
	this.method7 = function(){};
	this.method8 = function(){};
	this.method9 = function(){};
}

var Bar = function() {};
Bar.prototype.method1 = function(){};
Bar.prototype.method2 = function(){};
Bar.prototype.method3 = function(){};
Bar.prototype.method4 = function(){};
Bar.prototype.method5 = function(){};
Bar.prototype.method6 = function(){};
Bar.prototype.method7 = function(){};
Bar.prototype.method8 = function(){};
Bar.prototype.method9 = function(){};

/**
 * Chrome 8
 *  Foo: 260
 *  Bar:  26
 * Firefox 3.5
 *  Foo: 22081
 *  Bar:   158
 */
console.time('Foo');
for (var i = 1000000; i--;) new Foo();
console.timeEnd('Foo');

console.time('Bar');
for (var i = 1000000; i--;) new Bar();
console.timeEnd('Bar');

Я уж молчу про то, что будет в Internet Explorer. Сами проверьте. Можно оставить компьютер включённым на ночь =)
Даже то, что он позволяет реализовать псевдо-приватные методы и переменные не оправдывает этот подход ни на секунду.

Еще раз недостатки перегруженого конструктора, списком:
* не работает instanceof в детях
* значительно больший расход памяти
* работает в десятки-сотни раз медленнее
* методы отсутствуют в прототипе
* методы неправильно определяются через hasOwnProperty

Вывод


Пользуйтесь тем, что вам удобно и приятно пользователям. Если считаете, что MooTools.Class или любая другая подобная библиотека сделает ваше приложение читабельнее и облегчит поддержку — используйте её! И не обращайте внимание на всяких сами знаете кого, которые кричат про идеологию ДжаваСкрипта (а она, несомненно, прекрасна)

UPD: Этот топик был написан как ответ на сообщение aux:
JS проповедут прототипное ООП, вставлять классовые костыли — не гут. Учите язык и его возможности.

Я не утверждаю, что подход в фреймворках единственно-верный. Просто не следует избегать таких обёрток, как MooTools.Class, если кто-то сказал вам, что она, видите ли, не соответствует идеологии Javascript или что-то подобное. _
Tags:
Hubs:
Total votes 77: ↑67 and ↓10 +57
Views 22K
Comments Comments 113