Эта статья написана «по мотивам» одной из глав книги «jQuery – подробное руководство по продвинутому JavaScript». Информация, содержащаяся в этой главе, показалась мне настолько полезной, что я не мог не поделиться ей. Здесь не освещаются все концепции языка – внимание уделено лишь тем, понимание которых поможет эффективнее овладеть библиотекой jQuery.

Итак, первое на чем мы остановим свое внимание – это определение и работа с функциями, с использованием подхода при котором они являются обычными объектами JavaScript. Но для того, чтобы к этому подойти, давайте сначала посмотрим, как создаются

Объекты в JavaScript

var card = new Object();

Весьма просто, но совсем неинтересно, поскольку наш объект не содержит никакой информации. Давайте исправим это:

var card = new Object();
card.firstname = 'Ivan';
card.lastname = 'Ivanov';
card.year = '1974';
card.employed = new Date(2004,1,12);

Это уже что-то полезное – создав новый экземпляр объекта, мы присвоили его переменной card и наполнили свойствами различных типов: строка, число, и дата.

Что же получается? Получается, что объект JavaScript – это набор свойств, каждое из которых состоит из имени и значения. При этом в качестве имени может выступать только строка, а вот значением (и это важный момент!!!) может быть и строка, и число, и массив, и конечно функция…

Вывод: основная цель экземпляра объекта – служить контейнером для именованных наборов других объектов.

Давайте немного усложним наш код, который мы используем в качестве примера:

var card = new Object();
card.firstname = 'Ivan';
card.lastname = 'Ivanov';
card.year = '1974';
card.employed = new Date(2004,1,12);
var phone = new Object();
phone.home = '(495)353-5353';
phone.mobile = '(903)261-1777';
card.phone = phone;

мы создали еще один объект, который содержит номера телефонов и сделали этот объект значением свойства нашего первого объекта.

А теперь попробуем записать все тоже самое в литеральной нотации, плавно двигаясь к тому синтаксису, который присутствует в jQuery-коде практически повсеместно.

var card = {
  firstname : 'Ivan',
  lastname  : 'Ivanov',
  year      : '1974',
  employed  : new Date(2004,1,12),
  phone     : {
    home   : '(495)353-5353',
    mobile : '(903)261-1777'
  }
};

Вот собственно такая форма записи и называется JSON – посмотрите, насколько проста и понятна структура:

- объект – пара фигурных скобок
- свойства перечислены внутри скобок, через запятую
- каждое свойство – это пара состоящая из имени и значения, разделенных двоеточием.

Давайте теперь отметим для себя еще один очень важный момент, воспользовавшись следующим простым примером:

var someVar = 'В лесу родилась елочка!';

Простейшая инструкция, в которой переменной присваивается экземпляр объекта String. Если мы попробуем выполнить:

alert(someVar);

увидим то, что и ожидали – сенсационное сообщение о том, что в лесу родилась елочка. Как Вы думаете, что мы увидим после выполнения следующего кода?

alert(window.someVar);

Можете попробовать, но сразу скажу, что в результате получится абсолютно тоже самое сообщение! Поскольку инструкции эквивалентны, то какой же вывод из этого можно сделать?

Правильно! Когда ключевое слово var используется за пределами какой-либо функции, на глобальном уровне, оно является всего лишь ссылкой на свойство объекта window.

Ну вот, с объектами мы более-менее разобрались. Но давайте кратко сформулируем то, о чем мы говорили. Итак:

- объект в JavaScript – это неупорядоченный набор свойств;
- свойство состоит из имени и значения;
- объекты можно объявлять с помощью литералов;
- глобальные переменные – есть свойства объекта window;

Функция как объект JavaScript

Вот теперь мы готовы к тому, чтобы вернуться к тому утверждению, что функции в JavaScript можно рассматривать как обычные объекты. Почему же в JavaScript функцию можно считать обычным объектом? Давайте возьмем для сравнения объекты других типов, например String или Number.

Если объекты String или Number определяются соответствующими конструкторами, то функции также определяются конструктором, в данном случае конструктором Function. Точно также, как и другие объекты функции могут:

- присваиваться переменным и свойствам объектов
- передаваться в виде параметров
- возвращаться как результат других функций
- создаваться с использованием литералов

Но есть на первый взгляд одно очень существенное отличие от тех же упомянутых объектов String или Number – у функции есть имя! Но это только на первый взгляд.

Посмотрим простенький пример:

function hello() {
  alert('Hello, world!');
}

такая запись очень часто используется для создания глобальных функций, но это совершенно не означает, что мы только что создали функцию с именем hello. Вспомните наши примеры с ключевым словом var. Тут практически тоже самое, только в примере с функцией ключевое слово function автоматически создает экземпляр объекта и присваивает его свойству объекта window. А имя этого свойства совпадает с именем нашей функции. Т.е. мы могли бы написать так:

hello = function () {
  alert('Hello, world!');
}

или даже так:

window.hello = function () {
  alert('Hello, world!');
}

Это есть синтаксис литерала функции – привыкайте, в jQuery-коде Вы будете использовать такую форму записи очень часто. Но самый главный вывод, который мы должны сделатьэкземпляры Function являются значениями!!!, которые можно присвоить переменным, свойствам или параметрам, через которые на них можно ссылаться.

Функции обратного вызова

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

function hello() {
  alert('Hello, word!');
}
setTimeout(hello,5000);

Объявляем функцию с именем hello и устанавливаем таймер на 5 секунд. Смотрите, передача функции в качестве параметра ничем не отличается от передачи любого другого значения – в первом параметре мы передали функцию, а во втором – число. Когда время таймера истечет, будет вызвана функция hello. Поскольку метод setTimeout() делает вызов функции в нашем собственном программном коде – эту функцию называют функцией обратного вызова.

Однако есть более изящный способ записи этого кода:

setTimeout(function(){ alert('Hello, word!'); },5000);

если функцию не требуется вызывать где-то в другом месте страницы, нет никакой необходимости создавать свойство hello в объекте window. Такой подход будет очень часто встречаться в программном коде jQuery.

Реализация this в JavaScript

В объектно-ориентированных языках основанных на классах, указатель this, как правило, ссылается на экземпляр класса, в пределах которого был объявлен метод. Реализация this в JavaScript отличается едва различимым, но очень существенным образом. В JavaScript, где функции являются обычными объектами (мы говорили об этом выше), они не объявляются как часть чего-либо. Объект, на который ссылается this, называется контекстом функции и определяется не тем, как функция объявляется, а тем как она вызывается. Это значит, что функция может иметь различный контекст в зависимости от того, как она была вызвана.

Давайте вернемся к примеру и немного дополним его:

var card = {
  firstname : 'Ivan',
  lastname  : 'Ivanov',
  year      : '1974',
  employed  : new Date(2004,1,12),
  phone     : {
    home    : '(495)353-5353',
    mobile  : '(903)261-1777'
  },
  showData   : function() {
    return this.lastname+'
            '+this.firstname+'
            '+this.phone.mobile;
  }
};

К первоначальному коду мы добавили свойство с именем showData, которое ссылается на экземпляр Function. Если теперь вызвать функцию через свойство:

alert(card.showData());

то в качестве контекста функции будет установлен экземпляр объекта на который указывает card. В результате мы увидим следующее:

Ivanov Ivan (903)261-1777

Ну это все вполне естественно, гораздо более интересен следующий пример. JavaScript позволяет четко установить, что будет использоваться в качестве контекста функции. Вызывая функцию с помощью методов call() или apply() экземпляра Function, мы можем передать в качестве контекста все, что угодно.

var obj1 = { myProperty: 'object 1' };
var obj2 = { myProperty: 'object 2' };
var obj3 = { myProperty: 'object 3' };
window.myProperty = 'object window';

function showProperty() {
  return this.myProperty;
}

obj1.identifyMe = showProperty;

alert(showProperty());
alert(obj1.identifyMe());
alert(showProperty.call(obj2));
alert(showProperty.apply(obj3));

Определили три простых объекта, у каждого из которых есть свойство myProperty. Добавили свойство myProperty в экземпляр объекта window. Определили глобальную функцию, которая возвращает значение свойства myProperty для любого объекта, используемого в качестве контекста этой функции. И еще присвоили эту же самую функцию свойству с именем identifyMe объекта obj1.

Дальше самое интересное – четыре раза мы вызываем один и тот же экземпляр функции другим способом и посмотрите, что получается:
- в первом случае вызываем функцию как глобальную и контекстом функции является экземпляр объекта window о чем свидетельствует текст в окне предупреждения – «object window«.
- во втором случае вызываем функцию как свойство объекта obj1 и контекстом функции становится этот объект. Текст в окне предупреждения – «object 1«.
- в третьем случае используем метод call() объекта Function и контекстом функции становится объект, переданный методу call() в качестве первого параметра. Текст в окне предупреждения – «object 2«.
- наконец в четвертом случае используем метод apply(), передавая ему в качестве первого аргумента obj3, и получаем текст в окне предупреждения – «object 3«.

Пример можно открыть в отдельном окне.

Вывод такой: функция fn действует как метод объекта ob, когда объект ob выступает в качестве контекста при вызове функции fn.

Что такое замыкание?

Замыкание (closure) – это экземпляр Function вместе с локальными переменными из его окружения, необходимыми для выполнения.

Функция при объявлении может ссылаться на любые переменные, находящиеся в области ее видимости на момент объявления. Эти переменные будут достижимы для функции даже после того, как текущее положение в объявлении выйдет из области видимости, замыкая объявление.

Лучше всего пояснить это на простом примере (здесь уже используем библиотеку jQuery):

$(function(){
  var local = 1;
  window.setInterval(function(){
    $('#result').append(new Date()+
                ' значение local = '+local+'   ');
    local++;
  },5000);
});

После загрузки DOM объявляем переменную local и присваиваем ей числовое значение 1. С помощью метода setInterval() взводим таймер, который будет срабатывать каждые 5 секунд. В качестве функции обратного вызова для таймера, определяем функцию, которая каждые пять секунд будет добавлять текущее время и значение переменной local. Кроме того, при каждом срабатывании таймера значение переменной local должно увеличиваться на единицу.

Пример можно открыть в отдельном окне.

Можно было подумать, что поскольку функция обратного вызова запустится только лишь спустя 5 секунд после готовности DOM, то во время выполнения функции переменная local окажется неопределенной, ведь код в котором определяется local выходит из области видимости, как только обработчик события готовности DOM заканчивает свою работу.

Тем не менее этот код прекрасно работает, благодаря замыканию, созданному при объявлении функции и включающему в себя переменную local. Замыкание остается в области видимости функции на протяжении всего жизненного цикла.

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

Посмотрите такой код:

this.id = 'someID';
$('*').each(function(){
  alert(this.id);
});

Мы могли бы ожидать, что в окне предупреждения для каждого элемента будет раз за разом отображаться значение someID, однако это совсем не так. Мы увидим окно предупреждений столько раз, сколько элементов будет отобрано в объект jQuery и каждый раз будет отображаться значение атрибута id следующего элемента (или пустое окно предупреждения, если такого атрибута нет). Это происходит потому, что у каждого вызова функции собственный контекст, и в данном случае контекстом является элемент из объекта jQuery.

Давайте немного исправим пример:

this.id = 'someID';
var someVar = this;
$('*').each(function(){
  alert(someVar.id);
});

Переменная someVar становится частью замыкания и будет доступна внутри функции обратного вызова. Такой код будет раз за разом выдавать в окне предупреждения одно и тоже значение – someID.

Ну, вот вроде бы и все. Осталось сказать, что если твердо усвоить все эти понятия, то создавать эффектные и эффективные сценарии на JavaScript с использованием jQuery будет гораздо проще…

Поделиться в FaceBookПоделиться ВКонтактеДобавить в TwitterПоделиться в Моём МиреСохранить закладку в GoogleОтправить в Живую ленту GoogleДобавить в Яндекс.ЗакладкиПоделиться в ОдноклассникахОпубликовать в LiveJournal