Пример построения наследования на прототипах в javascript
В предыдущей статье я описал механизмы наследования, предоставляемые языком. В этой опишу практическое их применение для решение боевой задачи. А вот и она: описать новый класс, который наследует от стандартного Array.
Мы создаем новый класс, а не дописываем необходимые методы в прототип, так как расширение стандартных прототипов допускается в случаях, когда интерпретатор не поддерживает функции нового стандарта. Например Array.prototype.indexOf, Object.create. Если же прототипы стандартных объектов расширяются с другой целью, это может привести к поломке используемых библиотек или недоумению у человека, читающего ваш код.
В общем виде задача сводится к созданию конструктора, прототип которого содержит в __proto__ ссылку на Array.prototype. Обратите внимание, что создаваемых нами объект, хоть и выглядит как массив в консоли браузера, но таковым не является. Это именно объект с набором свойств, которые обновляются методами массива.
Собственно сам закомментариеный пример.
Мы создаем новый класс, а не дописываем необходимые методы в прототип, так как расширение стандартных прототипов допускается в случаях, когда интерпретатор не поддерживает функции нового стандарта. Например 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