Источник: http://learn.javascript.ru/classes
В JavaScript есть встроенные объекты: Date
, Array
, Object
и другие. Они используют прототипы и демонстрируют концепцию «псевдоклассов», которую мы вполне можем применить и для себя.
Начнём мы с того, что создадим пустой объект и выведем его.
var obj = { };
alert( obj ); // "[object Object]" ?
В объекте, очевидно, ничего нет… Но кто же тогда генерирует строковое представление для alert(obj)
?
Встроенный прототип Object.prototype
ставится всем объектам Object
, и поэтому его методы доступны с момента создания.
В деталях, работает это так:
obj = {}
является краткой формой obj = new Object
, где Object
— встроенная функция-конструктор для объектов.new Object
, создаваемому объекту ставится __proto__
по prototype
конструктора, в данном случае это будет Object.prototype
— встроенный объект, хранящий свойства и методы, общие для объектов, в частности, есть Object.prototype.toString
.obj.toString()
— функция будет взята из прототипа.Это можно легко проверить:
var obj = { };
// метод берётся из прототипа?
alert(obj.toString == Object.prototype.toString); // true, да
// проверим протототип в Firefox, Chrome (где есть __proto__)
alert(obj.__proto__ == Object.prototype); // true
Точно такой же подход используется в массивах Array
, функциях Function
и других объектах. Встроенные методы для них находятся в Array.prototype
, Function.prototype
и т.п.
Как видно, получается иерархия наследования, которая всегда заканчивается на Object.prototype
. Объект Object.prototype
— единственный, у которого __proto__
равно null
.
Поэтому говорят, что «все объекты наследуют от Object». На самом деле ничего подобного. Это все прототипы наследуют от Object.prototype.
Некоторые методы при этом переопределяются. Например, у массива Array
есть свой toString
, который находится в Array.prototype.toString
:
var arr = [1, 2, 3]
alert( arr ); // 1,2,3 <-- результат работы Array.prototype.toString
JavaScript ищет toString
, сначала в arr, затем в arr.__proto__ == Array.prototype
. Если бы там не нашёл — пошёл бы выше в Array.prototype.__proto__
, который по стандарту (см. диаграмму выше) равен Object.prototype
.
Методы apply/call
у функций тоже берутся из встроенного прототипа Function.prototype
.
function f() { }
alert( f.call == Function.prototype.call ); // true
Термины «псевдокласс», «класс» отсутствуют в спецификации ES5. Но их используют, потому что подход, о котором мы будем говорить, похож на «классы», используемые в других языках программирования, таких как C++, Java, PHP и т.п.
Классом называют функцию-конструктор вместе с её prototype.
Например: «класс Object», «класс Date» — это примеры встроенных классов. Мы можем использовать тот же подход для объявления своих.
Чтобы задать класс, нужно:
prototype
.Опишем класс Animal
:
// конструктор
function Animal(name) {
this.name = name;
}
// методы в прототипе
Animal.prototype.run = function(speed) {
this.speed += speed;
alert(this.name + ' бежит, скорость ' + this.speed);
};
Animal.prototype.stop = function() {
this.speed = 0;
alert(this.name + ' стоит');
};
// свойство speed со значением "по умолчанию"
Animal.prototype.speed = 0;
var animal = new Animal('Зверь');
alert(animal.speed); // 0, свойство взято из прототипа
animal.run(5); // Зверь бежит, скорость 5
animal.run(5); // Зверь бежит, скорость 10
animal.stop(); // Зверь стоит
Здесь объекту animal принадлежит лишь свойство name, а остальное находится в прототипе.
Вызовы animal.run()
, animal.stop()
в примере выше изменяют this.speed
.
При этом начальное значение speed
берётся из прототипа, а новое — пишется уже в сам объект. И в дальнейшем используется. Это вполне нормально, но здесь есть важная тонкость.
Значения по умолчанию не следует хранить в прототипе в том случае, если это объекты.