пятница, 25 июня 2010 г.

Базовые знания Javasript необходмые для использования JQuery

Одно из главных премуществ применения jQuery в веб-приложениях - возможность выполнять много сценариев без необходимости писать эти сценарии. jQuery позволяет нам сконцентрироваться на создании своих приложений и заставлять их делать то, что они должны делать!
Чтобы писать и понимать селекторы jQuery достаточно было лишь самых элементарных навыков работы c Java-Script и CSS. Для более сложных задач, таким как обра-ботка событии, анимация и Ajax, требуется понимать основные концепции JavaScript, направленные на эффективное использование библиотеки jQuery.
Наиболее важные из этих концепций касаются того, как JavaScript определяет и работает с функциями, а именно - подхода, при котором функции являются обычными объектами JavaScript. Чтобы понять, как функция может быть объектом, мы должны прежде всего убедиться в понимании того, что представляет из себя собственно объект JavaScript. Итак, приступим.



1. Основные сведения об объекте Object языка JavaScript
Большинство объектно-ориентированных (ОО) языков определяют базовый тип Object, как порождающий все другие объекты. В языке JavaScript тоже есть базовый объект Object, который служит основой для всех других объектов, но этим сходство и ограничивается. По сути, у объекта Object мало общего с базовыми объектами, определяемыми в родственных объектно-ориентированных языках.
На первый взгляд. Object - скучный и обыденный элемент. После создания он не содержит никаких данных и предоставляет минимум семантических действий. Но такая ограниченная семантика в действительности дает ему большой потенциал.
Давайте посмотрим, каким образом.

1.1. Как создаются объекты
Новый объект создается с помощью оператора new в паре с конструктором Object. Объект создается просто:
    var shinyAndNew = new Object();
Можно сделать это еще проще (как мы вскоре увидим), но пока будем поступать именно так.
Но что можно сделать с этим новым объектом? У него, казалось бы, ничего нет: ни информации, ни сложной семантики, ничего. Наш совершенно новый, чистый объект не представляет интереса до тех пор, пока мы не начнем добавлять к нему некоторые атрибуты, называемые свойствами.

1.2. Свойства объектов
Подобно своим серверным аналогам, объекты JavaScript могут содержать данные и обладать методами (например, сортировки, но не будем спешить). В отличие от своих серверных собратьев, эти элементы в объекте заранее не объявлены, мы создаем их динамически, по мере необходимости.
Взгляните на следующий фрагмент:
    var ride = new Object();
    ride make = 'Yamaha';
    ride model = 'V-Star Silverado 1100';
    ride year = 2005;
    ride purchased = new Date(2005,3,12);
Мы создаем новый экземпляр Object и присваиваем его переменной ride. Затем наполняем эту переменную рядом свойств различных типов: два строковых свойства, числовое свойство и свойство типа Date.

Нам не надо объявлять эти свойства до операции присваивания, они появляются автоматически, в момент присваивания им значений. Это чрезвычайно мощная особенность, дающая нам большую гибкость. Но не будем слишком легкомысленными, помня о том, что за гибкость придется платить!
Предположим, нам требуется изменить значение даты покупки:
    ride purchased = new Date(2005,2,1);
Ничего страшного... если мы случайно не сделаем опечатку, например:
    ride purcahsed = new Date(2005,2,1);
Здесь нет компилятора, который предупредил бы вас о сделанной ошибке; новое свойство с именем purcahsed, с готовностью созданное по вашему требованию, позже заставит задуматься о том, почему вы не получили новую дату, обратившись к свойству с правильно указанным именем.
С большими возможностями приходит большая ответственность (вы уже слышали об этом?), так что печатайте внимательно!

Из этого примера мы узнали, что экземпляр Object в языке JavaScript, который мы далее будем называть просто объект, - это набор свойств, каждое из которых состоит из имени и значения. Имя свойства - это строка, а значение может быть любым объектом JavaScript, таким как Number, String, Date, Array, Object или любой другой тип JavaScript (в том числе и функция, как мы увидим ).
Таким образом, основная цель экземпляра Object заключается в том, чтобы служить контейнером для именованных наборов других объектов. Это может напомнить концепции других языков, например отображение (тар) в языке Java или словари и хеши в других языках.
Обращаясь к свойствам, мы можем по цепочке ссылаться на свойства объектов, выступающих в качестве свойств родительского объекта. Предположим, мы добавили к нашему объекту ride новое свойство, описывающее владельца транспортного средства. Это свойство - еще один объект JavaScript, который в свою очередь содержит такие свойства, как имя и профессия владельца:
    var owner = new Object();
    owner name = 'Spike Spiegel';
    owner occupation = 'bounty hunter';
    ride owner = owner;


Обращение к вложенным свойствам можно описать так:
    var ownerName = ride.owner.name
Здесь нет никаких ограничений на глубину вложенности (за исключением здравого смысла).
Между прочим, нет никакой необходимости создавать промежуточные переменные (такие как owner) - мы создали их в этих фрагментах программного кода исключительно для наглядности. Далее вы увидите более эффективные и компактные способы объявления объектов и их свойств.
До этого момента мы ссылались на свойства объекта с помощью оператора «точка» (символ точки), но, как выясняется, это синоним более общего оператора для выполнения ссылок на свойства.
Что если, к примеру, у нас есть свойство с именем color scheme? Заметили точку в середине имени? Это портит все дело, потому что интерпретатор JavaScript будет пытаться найти scheme как вложенное свойство color.
«Тогда просто не делайте так!» - скажете вы. Но как быть с пробельными символами? Как быть с другими символами, которые могут восприниматься как разделители, а не как часть имени? И, что самое главное, мы даже не знаем, чем является это свойство - значением другой переменной или результатом выражения.
Для всех этих случаев оператор «точка» не подходит, и для доступа к свойствам мы должны выбрать более общее обозначение. Вот более общий формат обращения к свойствам:
   object[propertyNameExpression]

propertyNaweExpression - выражение JavaScript, которое определяется как строка, формирующая имя свойства, к которому происходит обращение. Например, все три следующие ссылки эквивалентны:
    ride.make
    ride['make']
    ride['m'+ a'+'k +'e']
Также как:
    var p = 'make';
    ride[p];
Применение общего оператора ссылки - единственный способ обратиться к свойствам, имена которых не являются допустимыми идентификаторами JavaScript, например:
    ride["a property name that's rather odd'']
Такие имена содержат символы, недопустимые для идентификаторов JavaScript, или являются значениями других переменных.
Построение объектов путем создания новых экземпляров с помощью оператора new и присваивание значений каждому свойству с помощью отдельных операторов - утомительное занятие. В следующем разделе мы рассмотрим более компактные и удобочитаемые способы объявления объектов и их свойств.

1.3. Литералы объектов
в предыдущем разделе мы создали объект, моделирующий некоторые свойства мотоцикла, и присвоили его переменной с именем ride. Мы сделали это с помощью двух операторов new, промежуточной переменной с именем owner и тучи операторов присваивания. Это утомительно, кроме того, приходится вводить с клавиатуры много текста, что способствует появлению ошибок и делает затруднительным визуальное восприятие структуры объекта при просмотре кода.
К счастью, мы можем использовать более компактную и удобную для визуального восприятия запись.
Рассмотрим инструкцию:
var ride = {
make: 'Yamaha',
model: 'V-Star Silverado 1100',
year: 2005,
purchased: new Date(2005,3,12),
owner: {
    name:  'Spike Spiegel',
    occupation: 'bounty hunter'
    }
};

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

Большинство авторов страниц отдают предпочтение такой форме записи, называемой JSON (JavaScript Object Notation), когда при построении объекта приходится использовать множество операций присваивания. Структура этой формы записи очень проста - объект обозначается парой фигурных скобок, внутри которых через запятую перечислены свойства. Каждое свойство обозначается путем записи его имени и значения, разделенных символом двоеточия.
Как видно из объявления свойства owner, объявления объектов могут быть вложенными.
Кстати, точно так же в формате JSON можно описывать массивы, поместив список элементов, разделенных запятыми, в квадратные скобки:
    var someValues = [2,3,5,7,11,13,17,19,23,29,31,37];
Как показано в примерах этого раздела, ссылки на объекты часто хранятся в переменных или в свойствах других объектов. Рассмотрим особый случай последнего сценария.

1.4. Объекты как свойства объекта window
До этого момента мы видели два способа хранения ссылок на объекты JavaScript - в переменных и в свойствах. Эти два способа хранения ссылок записываются по-разному, как показано в следующем фрагменте:
    var aVanable = 'Before I teamed up with you, I led quite a normal life. ';,
    someObject.aProperty = 'You move that line as you see fit for yourself. ';
В этих двух инструкциях два экземпляра String (созданные с помощью литералов) присваиваются переменной и свойству объекта соответственно, с помощью операторов присваивания.

Но действительно ли эти инструкции выполняют разные операции? Как выясняется - нет!
Когда ключевое слово var используется на глобальном уровне, за пределами какой-либо функции, эта удобная для программиста форма записи является всего лишь ссылкой на свойство предопределенного объекта JavaScript window. Любая глобальная ссылка неявно превращается в свойство объекта window.
Это означает, что эквивалентны все следующие инструкции:
    var foo = bar;
и
    window.foo = bar,
и
   foo = bar;
Независимо от формы записи во всех трех случаях создается свойство объекта window с именем foo (если оно еще не существовало), и ему присваивается значение bar. Кроме того, поскольку идентификатор bar никак не квалифицирован, предполагается, что он представляет собой имя свойства объекта window.
Надеемся, вы не подумали, что глобальная область видимости - это область видимости объекта window, потому что любые неквалифицированные глобальные ссылки подразумевают ссылки на свойства объекта window. Правила области видимости еще усложнятся, когда мы углубимся в изучение тел функций, причем сильно усложнятся, и случится это достаточно скоро.
На этом обзор объекта Object в языке JavaScript можно считать практически завершенным. Здесь были рассмотрены следующие важные понятия:

  • Объект в языке JavaScript - это неупорядоченный набор свойств.
  • Свойство состоит из имени и значения.
  • Можно объявлять объекты посредством литералов объектов.
  • Глобальные переменные являются свойствами объекта window.

Теперь посмотрим, что имелось в виду под словами «функции в JavaScript - это обычные объекты».

2. Функции как обычные объекты
Во многих традиционных объектно-ориентированных языках объекты могут содержать данные и обладать методами, В этих языках данные и методы, как правило, - не одно и то же, но язык JavaScript пошел другим путем.

В языке JavaScript функции считаются объектами, подобно объектам любого другого типа, определенного в JavaScript, например String, Number или Date. Как и другие объекты, функции определяются конструктором JavaScript, в данном случае - Function, и могут:

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

Поскольку в некоторых случаях функции рассматриваются как объекты, мы говорим, что функции - это обычные объекты.
Вы могли бы подумать, что функции кардинально отличаются от объектов других типов, таких как String или Number, потому что они обладают не только значением (тело функции, если речь идет об экземпляре Function), но еще и именем.
Не спешите с выводами!

2.1. Что есть имя?
Большинство программистов JavaScript напрасно полагают, что функции являются именованными сущностями. Это не так. Если вы один из таких программистов, то вас сбили с толку искусно замаскированные уловки. Как и в случае с экземплярами других объектов, будь то String, Date или Number,- на функции ссылаются, только когда они присваиваются переменным, свойствам или параметрам.
Рассмотрим объекты типа Number. Мы часто описываем экземпляр Number буквенным обозначением, таким как 213. Инструкция
    213;
вполне правильна, но совершенно бесполезна. Экземпляр Number не всегда полезен, если не присваивается свойству или переменной или не связан с именем параметра. В противном случае, мы не сможем обратиться к такому экземпляру.
Это же относится и к экземплярам объектов Function.
«Стоп-стоп-стоп, - скажете вы. - А как насчет следующего фрагмента программного кода?»
    function doSomethingWonderful() {
        alert('does something wonderful');
    }
«Разве такое объявление не создает функцию с именем doSomethingWonderful
Нет, не создает. Несмотря на то что запись может показаться знакомой и часто используется для создания глобальных функций, это тот же самый синтаксический подсластитель, который использует ключевое var для создания свойств window. Ключевое слово function автоматически создает экземпляр Function и присваивает его свойству window, имя которого совпадает с «именем» функции (то, что мы ранее назвали искусно замаскированными уловками), как показано в следующем примере:

    doSomethingWonderful = function() {
       alert('does something wonderful');
    }
Если это объявление выглядит странным, взгляните на другую инструкцию, где используется точно такой же формат, за исключением того, что здесь участвует литерал Number:
     aWonderfulNumber = 213;
Здесь нет ничего странного, и инструкция присваивания функции глобальной переменной (свойству объекта window) ничем не отличается - литерал функции используется для создания экземпляра Function, а затем присваивается переменной doSomethingWonderful так же, как литерал 213 объекта Number был использован для присваивания экземпляра Number переменной aWonderfulNumber.
Если вы никогда раньше не встречались с синтаксисом литерала функции, это может показаться странным. Он состоит из ключевого слова function, за которым идет список параметров, заключенный в круглые скобки, и далее следует тело функции.
Когда мы объявляем глобальную именованную функцию, создается экземпляр Function и присваивается свойству объекта window, которое создается автоматически, на основе так называемого имени функции. Сам по себе экземпляр Function не имеет имени, как литерал Number или String.
Помните, что, когда в HTML-странице создается глобальная переменная, она создается как свойство объекта window. Поэтому все следующие инструкции эквивалентны:
    function hello(){ alert('Hi there!');  }

    hello = function(){ alert('Hi there!');   }
    window.hello = function(){ alert('Hi there!');  }
Хотя все это может показаться синтаксическими манипуляциями, очень важно понять, что экземпляры Function являются значениями, которые можно присвоить переменным, свойствам или параметрам, а также экземплярам объектов других типов. И подобно этим объектам других типов безымянные экземпляры нельзя использовать, если они не связаны с переменными, свойствами или параметрами, через которые на них можно сослаться.
Мы посмотрели примеры присваивания функций переменным и свойствам - а как насчет передачи функции в качестве параметра? Давайте посмотрим, зачем и как можно это сделать.

2.2. Функции обратного вызова
Глобальные функции хороши, когда наш программный код следует красивым и упорядоченным синхронным потоком, но характерной чертой HTML-страниц сразу после загрузки является асинхронность. Будь то обработка событий, установка таймеров или выполнение запросов Ajax, программный код веб-страницы по своей природе является асинхронным. И одно из самых распространенных понятий в асинхронном программировании - понятие функции обратного вызова.
Возьмем в качестве примера таймер. Мы можем заставить таймер сработать, например, через пять секунд, передав соответствующее значение длительности методу window.setTimeout(). Но каким образом этот метод сообщит нам о том, что время таймера истекло, чтобы мы могли выполнить необходимые действия по истечении времени ожидания? Делается это путем вызова функции, которую мы предоставим.
Рассмотрим следующий программный код:
    function hello() { alert('Hi there!'); }
    setTimeout( hello, 5000);

Мы объявляем функцию с именем hello и устанавливаем таймер на 5 секунд, заданных во втором параметре как 5000 миллисекунд. В первом параметре мы передаем методу setTimeout() ссылку на функцию. Передача функции в виде параметра ничем не отличается от передачи любого другого значения - точно так же мы передали во втором параметре значение типа Number.
Когда время таймера истечет, будет вызвана функция hello. Поскольку метод setTimeout() делает обратный вызов функции в нашем собственном программном коде, эту функцию называют функцией обратного вызова.
Этот пример программного кода покажется наивным большинству опытных программистов JavaScript, поскольку создание имени hello не является необходимостью. Если функцию не требуется вызвать где-нибудь в другом месте страницы, нет никакой необходимости создавать свойство hello в объекте window, чтобы на мгновение сохранить экземпляр Function и потом передать его в качестве параметра.
Вот более изящный способ записи этого фрагмента:
    setTimeout(function() { alert('Hi there' ); },5000);
Здесь мы определяем функцию непосредственно в списке параметров в виде литерала, и нет никакой нужды создавать имя. Это - идиома, которая часто будет нам встречаться в программном коде jQuery, когда нет необходимости присваивать экземпляр функции глобальному свойству.
Функции, которые мы до сих пор создавали в примерах, - это либо глобальные функции (которые, как мы знаем, являются свойствами объекта window), либо параметры других функций. Мы также можем присваивать экземпляры Function свойствам объектов, и эта возможность действительно представляет интерес.

2.3. К чему это все?
Объектно-ориентированные языки программирования автоматически предоставляют средства для получения ссылки на текущий экземпляр объекта внутри метода. В таких языках, как Java и C++, на текущий экземпляр указывает переменная с именем this. В JavaScript тоже есть подобное понятие и даже используется то же самое ключевое слово this, которое обеспечивает доступ к объекту, связанному с функцией. Но будьте внимательны! Реализация this в JavaScript отличается от аналогов других объектно-ориентированных языков едва различимым, но существенным образом.
В объектно-ориентированных языках, основанных на классах, указатель this, как правило, ссылается на экземпляр класса, в пределах которого был объявлен метод. В JavaScript, где функции являются обычными объектами, они не объявляются как часть чего-либо. Объект, на который ссылается this, называется контекстом функции и определяется не тем, как функция объявляется, а тем, как она вызывается.

Это означает, что одна и та же функция может иметь различный контекст в зависимости от того, как она вызывается. На первый взгляд, это кажется странным, но может оказаться весьма полезным.
По умолчанию контекст (this) функции - это объект, свойство которого содержит ссылку для вызова функции. Давайте вернемся к нашему примеру с мотоциклом и изменим создание объекта (дополнения выделены жирным шрифтом):
    var ride = {
     make: 'Yamaha',
     model: 'V-Star Silverado 1100',
     year: 2005,
     purchased: new Date(2005,3,12),
     owner: {name: 'Spike Spiegel', occupation: 'bounty hunter' },
     whatAnI: function() {
       return this.year + '  '+ this.make + '  ' + this.model;
    }
  };
К первоначальному коду примера мы добавили свойство с именем WhatAmI, которое ссылается на экземпляр Function.
Если функция вызывается через свойство
    var bike = ride.whatAml();

то в качестве контекста функции (ссылка this) устанавливается экземпляр объекта, на который указывает ride. В результате в переменную bike записывается строка 2005 Yamaha V-Star Silverado 1100, потому что функция выбирает с помощью this значения свойств объекта, посредством которого она была вызвана.
То же справедливо и для глобальных функций. Помните, что глобальные функции являются свойствами объекта window, поэтому контекстом таких функций при их вызове как глобальных функций является
объект window.
Хотя такое неявное поведение вполне обычно, JavaScript позволяет нам четко установить, что будет использоваться в качестве контекста функции. Мы можем передать в контексте функции все, что угодно, вызвав функцию с помощью метода са11() или apply() экземпляра Function.
Кроме того, будучи обычными объектами, даже функции имеют методы, определяемые конструктором Function.
Метод call() вызывает функцию, передавая в качестве первого параметра объект, который является контекстом функции, а в качестве остальных параметров передаются параметры вызываемой функции - вторым параметром метода са11() становится первый аргумент вызываемой функции и т. д. Метод арр1у() работает аналогично, за исключением того, что вторым параметром он ожидает получить массив объектов, которые становятся аргументами вызываемой функции.
Запутались? Пришло время для более обстоятельного примера. Рассмотрим следующую странцу:
 <html>
  <head>
   <title>Function Context Example</title>
   <script>
    var o1 = {handle   o1 };
    var o2 = {handle   o2 };
    var o3 = {handle   o3 };
    window.handle = 'window';

    function whoAml() {
      return this handle,;
   }

   o1.identifyMe = whoAmI;

   alert(whoAmI());
   alert(o1.identifyMe());
   alert(whoAmI.call(o2));
   alert(whoAmI apply(o3));
  </script>
 </head>
 <body>
 </body>
</html>

в данном примере мы определяем три простых объекта, у каждого из которых есть свойство handle, позволяющее легко идентифицировать объект по ссылке на него. Мы также добавляем свойство handle в экземпляр объекта window, чтобы его тоже можно было легко идентифицировать.
Затем мы определяем глобальную функцию, которая возвращает значение свойства handle для любого объекта, используемого в качестве контекста функции в, и присваиваем ту же самую функцию свойству identifyMe объекта о1. Можно сказать, что тем самым был создан метод объекта о1 с именем identifyMe, хотя важно отметить, что функция объявляется независимо от объекта.

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

  • Если функция вызывается как глобальная функция, контекстом функции является экземпляр объекта window.
  • Если функция вызывается как свойство объекта (о1 в данном случае), контекстом функции становится этот объект. Мы могли бы сказать, что функция действует как метод этого объекта - аналогично объектно-ориентированным языкам. Но не слишком радуйтесь этой аналогии. Вы можете сбиться с пути, если не будете осторожны, что показывает оставшаяся часть этого примера.
  • Использование метода са11() объекта Function приводит к тому, что контекстом функции становится любой объект, полученный методом са11() в качестве первого параметра, - в данном случае, о2. В этом примере функция действует как метод объекта о2 при том, что она никак не связана с объектом о2, даже как свойство.
  • Как и в случае с методом са11(), при использовании метода арр1у() контекстом функции становится любой объект, переданный в качестве первого параметра. Разница между этими двумя методами становится существенной, только когда параметры передаются функции (чего мы в этом примере не делали для простоты).

Этот пример страницы явно свидетельствует о том, что контекст функции определяется способом вызова и что одна и та же функция может быть вызвана с любым объектом, выступающим в качестве ее контекста. Поэтому, скорее всего, будет ошибкой сказать, что функция является методом объекта. Гораздо правильнее сказать так:
Функция f действует как метод объекта о, когда объект о выступает в качестве контекста функции при вызове функции f.
В качестве дополнительной иллюстрации данной концепции рассмотрим результат добавления к нашему примеру следующей инструкции:
    alert(o1.identifyMe.call(o3));
Даже при том, что мы ссылаемся на функцию как на свойство объекта о1, роль контекста функции в этом вызове играет объект оЗ. Далее подчеркнем еще раз, что дело не в том, как функция объявляется, а в том, как она вызывается, что и определяет контекст функции.
При использовании команд и функций jQuery применяются функции обратного вызова, что доказывает важность этой концепции.

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

2.4. Замыкания
Для авторов страниц, пришедших из традиционного объектно-ориентированного или процедурного программирования, замыкания часто являются странным понятием, тогда как для тех, кто знаком с функциональным программированием, замыкания являются знакомым и удобным понятием. Что же такое замыкания?

Сформулируем просто, насколько это возможно: замыкание (closure) -это экземпляр Function вместе с локальными переменными из его окружения, необходимыми для выполнения.
При объявлении функция может ссылаться на любые переменные, находящиеся в ее области видимости на момент объявления. Эти переменные достижимы для функции даже после того, как текущее положение в объявлении выйдет из области видимости, замыкая объявление.
Возможность для функций обратного вызова ссылаться на локальные переменные, действующие на момент объявления, - важный инструмент создания эффективного программного кода JavaScript. Воспользовавшись таймером еще раз, посмотрим наглядный пример: получение доступа к окружению функции,
существовавшему на момент объявления, через замыкания
<html>
 <head>
  <title>Closure Example</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
   <script> $(function(){
    var local = 1;
    window.setInterval(function(){
     $('#display').append('<div>At '+new Date()+' local='+local+'</div>');
     local++;
   },3000);
});
</script>
</head>
<body>
 <div id='display'></div>
</body>
</html>


в данном примере мы определяем обработчик события готовности документа, который запускается после загрузки дерева DOM. В этом обработчике мы объявляем локальную переменную с именем local и присваиваем ей числовое значение 1. Затем с помощью метода window.setlnterval() взводим таймер, который будет срабатывать каждые 3 секунды. В качестве функции обратного вызова для таймера мы определяем встроенную функцию, которая ссылается на переменную local и показывает текущее время и значение переменной local, добавляя элемент <div> в элемент с именем display, определенный в теле страницы. Кроме того, внутри функции обратного вызова значение переменной local увеличивается.
Если бы мы были незнакомы с замыканиями, то до запуска этого примера могли бы заметить в его коде некоторые проблемы. Мы могли бы предположить, что поскольку функция обратного вызова запустится через три секунды после загрузки страницы (что произойдет далеко не сразу после того, как обработчик события готовности документа закончит выполняться), то во время выполнения функции обратного вызова значение переменной local окажется неопределенным. В конце концов, блок, в котором объявляется переменная local, выходит из области видимости, когда обработчик события готовности документа заканчивает работу, правильно?
Но после загрузки страницы, позволив ей отработать, через короткий промежуток времени мы увидим изображение:
AtTue Oct 23 2007 14:00:11 GMT-0500 (CDT) local=1
AtTue Oct 23 2007 14:00:14 GMT-0500 (CDT) local=2
AtTue Oct 23 2007 14:00:17 GMT-0500 (CDT) local=3
At Tue Oct 23 2007 14:00:20 GMT-0500 (CDT) local=4
At Tue Oct 23 2007 14:00:23 GMT-0500 (CDT) local=5
At Tue Oct 23 2007 14:00:26 GMT-0500 (CDT) local=6
At Tue Oct 23 2007 14:00:29 GMT-0500 (CDT) local=7
At Tue Oct 23 2007 14:00:32 GMT-0500 (CDT) local=8
At Tue Oct 23 2007 14:00:35 GMT-0500 (CDT) local=9

Она работает! Но каким образом?
Несмотря на то что блок, в котором объявляется переменная local, действительно выходит из области видимости, когда обработчик события готовности документа завершит работу, замыкание, созданное в объявлении функции, включающее в себя переменную local, остается в области видимости функции на протяжении всего жизненного цикла.
Стоит отметить, что это замыкание, как и все замыкания в JavaScript было создано неявно, поскольку нет необходимости явного синтаксиса, как это требуется в некоторых других языках, поддерживающих замыкания. Это обоюдоострый меч - с его помощью легко создать замыкание (предполагаете вы это или нет!), но он может и усложнить их реализацию в программном коде.
Непредусмотренные замыкания могут вызвать непредвиденные последствия. Например, циклические ссылки могут привести к утечкам памяти. Классический пример этого - создание элементов DOM, которые ссылаются на переменные в замыканиях, препятствуя удалению этих переменных.
Еще одна важная особенность замыканий заключается в том, что контекст функции никогда не является частью замыкания. Например, следующий фрагмент кода не будет выполняться, как можно было бы ожидать:
this id =  'somelD';
$('*').each(function(){
  alert(this.id);
});
Помните, что у каждого вызова функции собственный контекст функции, поэтому в приведенном выше программном коде контекст функции внутри функции обратного вызова, передаваемый методом each(), является элементом обернутого набора jQuery, а не свойством внешней функции, установленным в значение 'somelD'. Каждый вызов функции обратного вызова в свою очередь отображает окно предупреждения, где выводится значение атрибута id каждого элемента обернутого набора.
Получить доступ к объекту, выступающему в качестве контекста внешней функции, можно с помощью общей идиомы создания копии этой ссылки в локальной переменной, которая будет включена в замыкание. Рассмотрим следующие изменения в нашем примере:
this id = 'somelD';
var outer = this; 
$('*').each(function(){
alert(outer.id),
});
Локальная переменная outer, которой присваивается ссылка на контекст внешней функции, становится частью замыкания и будет доступна внутри функции обратного вызова. Теперь измененный программный код будет выводить предупреждение со строкой 'somelD' столько раз, сколько элементов в обернутом в наборе.

Замыкания действительно незаменимы для создания элегантного программного кода с применением команд jQuery, использующих асинхронные обратные вызовы, что особенно актуально в случае применения Ajax-запросов и обработки событий.

Заключение
JavaScript - это язык, широко распространенный в Сети, но многие авторы страниц, создающие веб-приложения, зачастую используют далеко не все его возможности. В этом Приложении мы представили часть глубинных аспектов языка, которые необходимо понимать, чтобы эффективно применять jQuery на своих страницах.
Мы увидели, что объект Object в языке JavaScript в первую очередь нужен затем, чтобы служить контейнером для других объектов. Если вы знакомы с объектно-ориентированным программированием, понимание экземпляра объекта как неупорядоченного набора пар имя/значение может оказаться далеким от того, что вы представляете себе под термином объект, но эта концепция важна при создании программного кода JavaScript, даже умеренной сложности.
Функции в JavaScript являются обычными объектами, которые могут быть объявлены и описаны таким же образом, как и объекты других типов. Мы можем объявлять их как литералы, сохранять в переменных и в свойствах объектов и даже передавать их другим функциям в виде параметров для использования в качестве функций обратного вызова.
Термин контекст функции описывает объект, на который указывает ссылка this внутри функции. Хотя функция может играть роль метода объекта, для чего объект должен быть определен как контекст функции, тем не менее функции не объявляются методами какого-то отдельного объекта. Контекст функции определяется способом вызова (возможно, под явным управлением вызывающей программы).
Наконец, мы видели, как объявление функции и ее окружение образуют замыкание, позволяющее функции при последующих вызовах получить доступ к локальным переменным, которые становятся частью замыкания.
Твердо усвоив эти понятия, можно решать задачи, стоящие перед нами при создании в наших страницах эффективных сценариев JavaScript с применением jQuery.

Комментариев нет:

Отправить комментарий