Оглавление цикла уроков по функциям в javascript.
Как я уже оговорился в предыдущем уроке, вновь создаваемый объект не совсем пустой, у него есть, например, поле __proto__ (доступное явно не во всех браузерах), которое содержит ссылку на прототип этого объекта. Прототип — это тоже некий объект, который приходит на помощь, когда у нашего объекта нет полей и методов, нужных нашей программе. По сути, через прототипы в javascript реализуется механизм наследования. Как же задать прототип? Очень просто — через свойство prototype функции-конструктора:
1 2 3 4 5 6 7 8 9 10 |
function Constructor () {} Consructor.prototype = { test: function () { alert("Test"); } }; var obj1 = new Constructor(); var obj2 = new Constructor(); obj1.test(); // выведет "Test" obj2.test(); // тоже выведет "Test" |
Как видим, метод test(), определенный для прототипа, доступен во всех объектах, созданных этим конструктором. Более того, мы можем на лету менять содержимое прототипа (добавлять, удалять, менять поля и методы), и это сразу отражается на созданных объектах:
1 2 3 4 |
Constructor.prototype.newMethod = function () { alert("New method"); }; obj1.newMethod(); // выведет "New method" |
Можно даже так: через первый объект получаем доступ к прототипу, добавляем, а вызываем метод у второго объекта. Взгляните:
1 2 3 4 |
obj1.constructor.prototype.superNewMethod = function () { alert("Super new method"); }; obj2.superNewMethod(); // выведет "Super new method" |
Нужно учитывать, что обращение к полю в прототипе происходит только тогда, когда у самого объекта нет поля с таким именем, например:
1 2 3 4 5 |
obj1.test = function () { alert("It's my own method!"); }; obj1.test(); // выведет "It's my own method!" obj2.test(); // выведет "Test", вызывается метод прототипа |
Как же нам узнать, является ли поле собственным для объекта или заимствуется у прототипа? Для этого у каждого объекта есть метод hasOwnProperty(), принимающий в качестве параметра строку с именем поля и возвращающий true или false, продемонстрируем на примере, как должен выглядеть перебор объекта методом for-in, учитывая возможность наличия полей в прототипе:
1 2 3 4 5 6 7 8 9 10 11 |
Object.prototype.notANumber = "Not a number"; var numbers = {one: 1, two: 2, three: 3}; for (var i in numbers) { alert(numbers[i]); // некорректно, выведет "1", "2", "3", "Not a number" } for (var i in numbers) { if (numbers.hasOwnProperty(i)) { alert(numbers[i]); // теперь все ок: выведет "1", "2", "3" } } |
Как видим, попытка эмулировать ассоциативные массивы на javascript может закончиться не тем, что мы ожидали, если не учитывать «прототипную» природу этого языка. Этот пример также показывает, что мы легко можем добавлять новые поля и методы и во встроенные «типы» тоже.
То, что у всех объектов, создаваемых одним конструктором, единый объект-прототип, можно увидеть на следующем примере (учитывайте, что массив — это не примитивный тип, а тоже объект, поэтому поля разных объектов ссылаются на один и тот же массив):
1 2 3 4 5 6 7 8 |
var protoObj = {test: [0]}; // в поле test - массив с одним элементом function Constructor () {} Consructor.prototype = protoObj; var obj1 = new Constructor(); var obj2 = new Constructor(); obj1.test[0] = 1; obj2.test[0]++; alert(protoObj.test[0]); // выведет "2", работа велась с одним массивом |
Думаю, можно догадаться, что если прототип — это объект, то у него самого тоже есть прототип, который тоже объект. Приведем пример создания цепочки прототипов:
1 2 3 4 5 6 7 8 9 10 11 12 |
function Animal () {} Animal.prototype = {isAlive: true}; // все животные - живые по умолчанию function Cat (name) { this.name = name; } Cat.prototype = new Animal(); Cat.prototype.hasWhiskers = true; // у кошек есть усы var myCat = new Cat("Tom"); alert(myCat.name); // выведет "Tom" alert(myCat.hasWhiskers); // выведет "true" alert(myCat.isAlive); // выведет "true" |
Как видим, немного сложный, но все же в javascript есть механизм наследования. С этим механизмом тесно связан оператор instanceof, возвращающий true, если проверяемый объект принадлежит заданному «типу», то есть, создан ли он, его прототип, прототип прототипа и т.д. данным конструктором:
1 2 3 4 5 6 |
if (myCat instanceof Cat) { // true alert("myCat is a Cat"); } if (myCat instanceof Animal) { // true alert("myCat is an Animal"); } |
Напоследок стоит упомянуть, что мы можем применять прототипные методы встроенных типов, например, метод join(), присущий массивам, к «недомассиву» arguments:
1 2 3 4 5 |
function someFunc () { var args = Array.prototype.join.call(arguments); alert(args); // выведет "1,2,3" } someFunc(1, 2, 3); |
А еще мы можем «допилить» встроенные типы, добавив к ним новые методы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Array.prototype.inArray = function (value) { var len = this.length; // заметьте, this for (var i = 0; i < len; i++) { if (this[i] == value) { return true; } } return false; } var a = [1, 2, 3]; if (a.inArray(2)) { // true alert("2 is in the array"); } |
На этом, пожалуй, хватит, остальное придет с опытом. :)