Основы программирования на JavaScript

Прототипирование


Можно переписать функцию Cat таким образом, чтобы каждая функция объявлялась только один раз:

function Cat(name){ this.name = name; } Cat.prototype.species = 'Cat'; Cat.prototype.talk = function(){ alert('Meow!'); }; Cat.prototype.callOver = function(){ alert(this.name+' ignores you'); }; Cat.prototype.pet = function(){ alert('Purr!'); };

Синтаксис этого метода немного отличается от предыдущего. Вместо объявления всех свойств и методов внутри функции Cat, они теперь объявляются с помощью Cat.prototype. Это может показаться более сложным, но предлагает много преимуществ. Предположим, например, что надо добавить новую функцию sleep для каждого имеющегося cat. Это можно сделать двумя способами. Первый: можно добавить функцию sleep в felix, sam и patty. Это, однако, не только трудоемко, но также и неэффективно. Если бы имелось 500 cat, то потребовалось бы сначала отследить всех этих 500 котов, а затем добавить функцию каждому cat.

Однако с помощью прототипов можно добавить функцию sleep всем cat одновременно:

Cat.prototype.sleep = function(){ alert(this.name+' falls asleep'); };

Этот способ не только быстрее, но к тому же нам больше не требуется отслеживать каждого cat, чтобы добавить функцию sleep.

Существует более быстрый способ добавления прототипов. С помощью объектного литерала можно одновременно задать любое количество прототипов свойств или методов.

function Cat(name){ this.name = name; } Cat.prototype = { species: 'Cat', talk: function(){ alert('Meow!'); }, callOver: function(){ alert(this.name+' ignores you'); }, pet: function(){ alert('Pet!'); } }

Важно отметить, что с помощью этого метода можно задать свойства функции только один раз. После этого необходимо использовать предыдущий метод.

Object.prototype.method = function(){ ... }.

Если попробовать теперь добавить в cat метод sleep с помощью этого метода:

Cat.prototype = { sleep: function(){ alert(this.name+' falls asleep'); } }

то предыдущие прототипы, species, talk, callOver и pet, будут все удалены. Единственным имеющимся прототипом для cat будет sleep.

Прототипы можно также использовать для расширения встроенных объектов JavaScript. Можно реализовать, например, функцию String.prototype.reverse, которая будет возвращать любую созданную строку в обратном порядке:


String.prototype.reverse = function(){ var out = ''; for(var i=this.length-1; i>=0; i--){ out+=this.substr(i, 1); } return out; } alert('asdf'.reverse());

Это может быть очень полезным инструментом при правильном использовании. Можно реализовать String.prototype.trim() для удаления из строки всех пробелов, Date.prototype.dayName - для получения названия дня недели из объекта Date и т.д. Однако настоятельно рекомендуется воздержаться от добавления каких-либо прототипов в Array или Object, так как в этом случае циклы "for-in " для двух этих типов данных будут работать неправильно. Рассмотрим следующий пример:

var myArray = [1, 2, 3]; for(n in myArray) alert(n); // выводит 0, 1 и 2 - индексы массива.

Array.prototype.something = function(){ };

for(n in myArray) alert(n); // выводит 'something', 0, 1 и 2.

Как можно видеть, здесь выполнено прототипирование Array и добавлена функция 'something'. Однако теперь эта функция 'something' видна как элемент массива, результат, который определенно не ожидался и не требовался. То же самое происходит с объектами и объектными литералами, если выполнить прототипирование Object. Если можно быть абсолютно уверенным, что цикл "for-in" никогда не будет использоваться и никакой другой разработчик не будет использовать этот код JavaScript, то можно применять прототипирование для Array или Object, но надо помнить о связанных с этим проблемах. Однако существуют другие методы для достижения тех же результатов. Например, для расширения Array можно задействовать следующий метод без прототипирования:

Array.find = function(ary, element){ for(var i=0; i<ary.length; i++){ if(ary[i] == element){ return i; } } return -1; }

alert(Array.find(['a', 'b', 'c', 'd', 'e'], 'b')); // выводит 1

Как можно видеть, теперь необходимо печатать type Array.find(ary, e) вместо ary.find(e), что пришлось бы делать, если прототипировать объект Array, но стоит напечатать эти несколько дополнительных символов, чтобы избежать потери существующей функциональности JavaScript.


Содержание раздела