JavaScript
16 November 2011

ООП в JavaScript

Хочу представить вам функцию-конструктор классов createClass.
Чем хорош этот конструктор:
1. Сам код выглядит более читабельным и понятным
2. Поддержка множественного наследования
3. Поддержка абстрактных методов
4. Наследование статических методов и свойств класса
5. Умный метод instanceOf (проверяет по всей цепочке классов)

Итак, сразу пример:
var Animal = createClass({
	abstracts: ['say'],
	construct: function(name) {
		this.name = name;
	},
	eat: function() {
		return 'ou yeah...';
	}
});

var Kitty = createClass({
	extend: Animal,
	say: function() {
		return this.name + ' said: RrrrRRR...Mau..';
	},
	eat: function() {
		return Animal.fn.eat.call(this) + 'FISH!!'
	}
});

var Doggy = createClass({
	extend: Animal,
	construct: function(name) {
		Animal.call(this, name);
		console.log('doggy have been created');
	},
	say: function() {
		return this.name + ' said: GAV!';
	},
	eat: function() {
		return Animal.fn.eat.call(this) + 'MEAT!!'
	}
});

var sharik = new Doggy('sharik'); // doggy have been created
var murka = new Kitty('murka');

console.log( sharik.eat(), sharik.say()); // ou yeah...MEAT!! sharik said: GAV!
console.log( murka.eat(), murka.say()); // ou yeah...FISH!! murka said: RrrrRRR...Mau..

Все свойства класса должны определяться в конструкторе (статические свойства в статическом конструкторе, о нём речь пойдёт ниже).
Класс Animal имеет обстрактый метод “say”. Классы Kitty и Doggy наследуется от Animal и реализуют метод “say”. При этом, поскольку в Kitty конструктор явно не задан, он заимствуется у Animal. В Doggy конструктор задан явно, поэтому родительский конструктор нужно вызвать тоже явно Animal.call(this, name). Метод eat родительского класса можно вызвать как Animal.fn.eat.call(this) это тоже самое, что Animal.prototype.eat.call(this)

Дальше:
var Singleton = createClass({
	statics: {
		construct: function() {
			this._instance = null;
		},
		getInstance: function() {
			if(this._instance === null) {
				this._instance = new this();
			}
			return this._instance;
		}
	}
});

var SuperDoggy = createClass({
	extend: [Animal, Singleton],
	say: function() {
		return this.name + ' said: GAV! GAV! GAV!';
	}
});

var dog1 = SuperDoggy.getInstance(),
dog2 = SuperDoggy.getInstance();

dog1.name = 'Bob';

console.log(dog1 === dog2); // true
console.log(dog1.eat(), dog1.say()); // ou yeah... Bob said: GAV! GAV! GAV!

Singleton имеет статическое свойство _instance и статический метод getInstance. Этот класс может применяться в качестве реализации паттерна Singleton. SuperDoggy наследуется от двух классов Animal, Singleton и реализует метод “say”.
Стоит отдельно отметить метод construct в обьекте statics. Этот метод вызывается при создании самого класса и при создании класса-наследника. Он позволяет корректно копировать при наследовании статические свойства объекта. Если в объекте prototype есть метод constructor, который создаёт объект, то почему бы не иметь constructor, создающий сам класс?

Примеры применения метода “instanceOf”:
console.log( dog2.instanceOf(Kitty) === false ); // true
console.log( dog2.instanceOf(SuperDoggy) === true );  // true
console.log( dog2.instanceOf(Animal) === true ); // true
console.log( sharik.instanceOf(Animal, Singleton) === false ); // true
console.log( dog2.instanceOf(Animal, Singleton) === true ); // true

Изначально идея была взята из книги Девида Фленагана JavaScript 5 издание: пример из книги. В итоге этот код эволюционировал в представленный в статье.

Поскольку исходник не так велик, публикую его прямо тут:
function createClass(data)
{
	var abstracts = data.abstracts || [],
	statics = data.statics || {},
	extend = data.extend || [];

	if(!(extend instanceof Array))
		extend  = [extend];

	// define constructor
	var constructor;
	if(data.construct) {
		constructor = data.construct;
	} else if(extend.length) {
		constructor = function() {
			for(var i=0; i<extend.length; i++) {
				extend[i].apply(this, arguments);
			}
		}
	} else {
		constructor = function() {};
	}

	// prototype for our class.
	var proto = {};

	delete data.construct;
	delete data.abstracts;
	delete data.statics;
	delete data.extend;


	// borrow methods from parent classes
	for(var i=0; i<extend.length; i++) {
		var parent = extend[i];

		// static constructor
		if( typeof parent.construct == "function")
			parent.construct.call(constructor);

		// copy static methods
		for(var p in parent) {
			if (typeof parent[p] != "function" || p == "construct") // copy only functions
				continue;
			constructor[p] = parent[p];
		}

		// Copy prototype methods
		for(var p in parent.prototype) {
			if (typeof parent.prototype[p] != "function" || p == "constructor")
				continue;
			proto[p] = parent.prototype[p];
		}
	}

	// build abstract static methods
	if(statics.abstracts) {
		for(var p=0; p<statics.abstracts.length; p++) {
			proto[ statics.abstracts[p] ] = function() {
				throw p + ' is static abstract method'
			};
		}
	}

	// build abstract prototype methods
	for(var p=0; p<abstracts.length; p++) {
		proto[ abstracts[p] ] = function() {
			throw p + ' is abstract method'
		};
	}

	// internal methods
	proto.instanceOf = function(_class) {
		if(arguments.length > 1) {
			var res = true;
			for(var i=0; i<arguments.length; i++)
				res = res && this.instanceOf(arguments[i]);
			return res;
		}

		if(constructor === _class)
			return true;

		for(var i=0; i<extend.length; i++) {
			if( extend[i].prototype.instanceOf.call(this, _class) )
				return true;
		}

		return _class === Object;
	};

	// rest of data are prototype methods
	for(var p in data) {
		if (typeof data[p] != "function") // copy only functions
			continue;
		proto[p] = data[p];
	}

	// static functions of class
	for(var p in statics) {
		if (typeof statics[p] != "function") // copy only functions
			continue;
		constructor[p] = statics[p];
	}

	// static constructor
	if( typeof statics.construct == "function")
		statics.construct.call(constructor);

//	proto.constructor = constructor;
	constructor.prototype = proto;
	constructor.fn = proto; // short case

	// Finally, return the constructor function
	return constructor;
}

+25
38.9k 287
Comments 84
Top of the day