Пример построения наследования на прототипах в javascript

В предыдущей статье я описал механизмы наследования, предоставляемые языком. В этой опишу практическое их применение для решение боевой задачи. А вот и она: описать новый класс, который наследует от стандартного Array.

Мы создаем новый класс, а не дописываем необходимые методы в прототип, так как расширение стандартных прототипов допускается в случаях, когда интерпретатор не поддерживает функции нового стандарта. Например Array.prototype.indexOf, Object.create. Если же прототипы стандартных объектов расширяются с другой целью, это может привести к поломке используемых библиотек или недоумению у человека, читающего ваш код.

В общем виде задача сводится к созданию конструктора, прототип которого содержит в __proto__ ссылку на Array.prototype. Обратите внимание, что создаваемых нами объект, хоть и выглядит как массив в консоли браузера, но таковым не является. Это именно объект с набором свойств, которые обновляются методами массива.

Собственно сам закомментариеный пример. 
/*
 Возвращает пустой объект, у которого
 __proto__ ссылается на obj

 Тоже самое делает метод Object.create
 из новой редакции стандарта ECMA. Вместо
 того, чтобы писать такую реализацию, можно
 взять готовую и дополнить ею стандартный Object
*/
function create_object (obj) {
	var F = function () {};
	F.prototype = obj;
	return new F();
}

/*
 Функция, которая будет использоваться как конструктор экземпляров класса, наследующих
 от стандартного Array
*/
function SuperArray () {}

/*
 Экземпляры SuperArray получают в __proto__
 ссылку на Array.prototype. Следовательно,
 наследуют от Array.prototype
*/
SuperArray.prototype = create_object(Array.prototype);
/*
 Так как конструктором Array.prototype является
 Array, подменяем свойство конструктор на тот,
 который будет являться конструктором де-факто.

 Если бы мы этого не сделали, то равеснтво
 s.constructor === SuperArray; // false
 было бы неверным
*/
SuperArray.prototype.constructor = SuperArray;

/*
 Создаем методы в прототипе. Они будут доступны
 для каждого экземпляра класса SuperArray
*/

/*
 Принимает аргументом массив или массивоподобный
 объект, элементы массива будут являться ключами
 возвращаемого объекта. Значениями — значения
 экземпляра класса SuperArray
*/
SuperArray.prototype.to_object = function (keys) {
	var res, i;

	for (i = 0; i < keys.length; i += 1) {
		res[ keys[i] ] = this[i];
	}
	return res;
};

/*
 Принимает аргументом функцию. Если для всех элементов экземпляра SuperArray она возвращает
 true, то методы возвращает true.
*/
SuperArray.prototype.all = function (test) {
	var i;

	for (i = 0; i < this.length; i += 1) {
		if (!test(this[i])) {
			return false;
		}
	}
	return true;
};

/*
 Принимает аргументом функцию. Если для одного
 элемента экземпляра SuperArray она возвращает
 true, то метод вернет true
*/
SuperArray.prototype.any = function (test) {
	var i;

	for (i = 0; i < this.length; i += 1) {
		if (test(this[i])) {
			return true;
		}
	}
	return false;
};

var s = new SuperArray();
/*
 Метод push берется из цепочки прототипов.
 Он обновляет значение объекта а так-же его
 свойство length
*/
s.push(5);
s.push(3);
s.push(8);
s.push(4);
s.push(1);
s.push(7);
s.push('foo');

s.all(function (el) {
	return typeof el === 'number';
}); // false

s.any(function (el) {
	return typeof el === 'string';
}); // true

s.any(function (el) {
	return typeof el === 'number';
}); // true

/*
 ВАЖНО:
 созданный объект не является масивом,
 а только наследует его методы. Это
 значит, что для него не сработают
 конструкции, которые бы сработали для
 массива
*/
s.length === 7; // true
/*
 После такого присвоения, длина массива
 изменяется. В нашем случае она остается
 неизменной.
*/
s[99] = true;
s.length === 100; // false
s[99] === true; // true
 
Share
Send
Popular