В классических объектно-ориентированных языках объект – это коллекция данных и методов, оперирующих этими данными.
Рассмотрим объект person с полями first и last name (имя и фамилия). Предположим, надо иметь два метода для различных отображений полного имени: как "first last" или как "last, first". Это можно сделать с использованием объекта и функций следующим образом:
Но если необходимо как-то присоединять имена функций к объектам, то, как это делается в обычных объектно-ориентированных языках программирования, то можно написать следующий код:
function makePerson(first, last) {
return {
first: first,
last: last,
fullName: function() {
return this.first + ' ' + this.last;
},
fullNameReversed: function() {
return this.last + ', ' + this.first;
}
}
}
Тогда при вызове этих функций:
s = makePerson("Иван", "Петров")
s.fullName()//результат – "Иван Петров"
s.fullNameReversed()//результат – "Петров, Иван"
Здесь встречается ключевое словом "this". При использовании внутри функции "this" ссылается на текущий объект. Чему будет равно "this" на самом деле зависит от того как была вызвана функция. Если функция будет вызвана через точечную нотацию или квадратные скобки, тогда "this" указывает на этот объект. Если просто вызвать функцию, то "this" будет указывать на глобальный объект. Непонимание этого может привести к неприятным последствиям:
s = makePerson("Иван", "Петров")
var fullName = s.fullName;
fullName()//результат – "undefined undefined"
Когда вызывается fullName(), в роли "this" выступает глобальный объект. И поскольку не определены глобальные переменные first и last, для каждой из них будет получено undefined.
Можно использовать "this" для усовершенствования нашей функции makePerson:
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = function() {
return this.first + ' ' + this.last;
}
this.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
}
var s = new Person("Иван", "Петров");
Здесь присутствует ключевое слово "new". "new" связан с "this". Он создает пустой объект и вызывает указанную функцию, внутри которой "this" указывает на этот новый объект. Функции, которые будут использоваться вместе с "new" называются "конструкторами". Обычно название таких функций начинается с заглавной буквы, и напоминает о том, что функция должна быть использована вместе с "new".
Однако до сих пор остались некоторые проблемы: каждый раз при создании объекта, необходимо создавать по две новых функции внутри него. Разделим эти функции между всеми объектами:
function personFullName() {
return this.first + ' ' + this.last;
}
function personFullNameReversed() {
return this.last + ', ' + this.first;
}
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = personFullName;
this.fullNameReversed = personFullNameReversed;
}
Теперь функции будут создаваться только один раз, а ссылки на них назначаются в конструкторе. Однако существует более хороший вариант:
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function() {
return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
Person.prototype это объект общий для всех объектов, созданных с помощью Person. Он входит в "prototype chain" (цепочку поиска): каждый раз, когда происходит попытка получить свойство объекта Person, которое у него отсутствует, JavaScript проверяет, нет ли такого свойства у Person.prototype. Таким образом, все, что будет присвоено Person.prototype становится доступно всем объектам порожденным конструктором Person, в том числе и через объект this.
Это необычайно мощное средство. В JavaScript можно модифицировать prototype (прототипы) объектов в любое время и в любом месте программы, что означает, что можно добавлять методы к объектам в процессе выполнения программы:
s = new Person("Иван", "Петров");
s.firstNameCaps(); //результат – "TypeError on line 1: s.firstNameCaps is not a function"
Person.prototype.firstNameCaps = function() {
return this.first.toUpperCase()
}
s.firstNameCaps() //результат – "ИВАН"
Как уже упоминалось, прототип является частью цепочки, в конце которой находится Object.prototype, у которого есть метод toString() – он вызывается, когда объект приводится к строке. И его можно переопределить для отладки:
var s = new Person("Иван", "Петров");
s //результат – "[object Object]"
Person.prototype.toString = function() {
return '<ФИО: ' + this.fullName() + '>';
}
s //результат – "<ФИО: Иван Петров>"
Теперь вернемся к рассмотрению первого параметр метода avg.apply().Первый параметр apply() это объект, на который будет ссылаться ключевое слово "this", если оно используется в теле вызываемой функции. Например, можно сделать тривиальную реализацию "new":
function trivialNew(constructor) {
var o = {}; // создаем объект
constructor.apply(o, arguments);
return o;
}
Это конечно не совсем "new", так как не создается прототипная цепочка ( prototype chain ).
Для apply() есть похожая функция call, которая также позволяет установить "this", но вместо массива параметров принимает параметры через запятую (как при обычном вызове):