Что должен знать о JavaScript каждый ASP.NET разработчик

Опубликовано 19.06.2007 6:09:00
Перевод (оригинал: Скотт Аллен 8 мая 2007 г.) Некоторое время назад JavaScript не пользовался популярностью, но постепенно вернул утраченные позиции. Несовместимостьь браузеров и, в то же время, повсеместное распространение, привели к тому, что многие им пытаются пренебречь, но мало кто сомневается в его способностях. И уж коли Вы собираетесь создать интерактивное приложение сегодня, Вам понадобится немного JavaScript-кода на Вашем сайте. Эта статья рассматривает JavaScript с позиций ASP.NET разработчика, привыкшего к моделям разработки, принятым в языках C# или Visual Basic. Эта статья не рассматривает само по себе использование JavaScript в ASP.NET. Статья рассказывает, почему JavaScript так отличается от двух языков, которые мы обычно используем с .NET CLR. Статья предусматривает, что Вы уже в курсе, что JavaScript - это слабо типизированный язык (т.к. Вам не нужно объявлять тип данных, которые Вы храните в переменной), и его синтаксис похож на синтаксис C-подобных языков (с очаровательными фигурными скобками и потрясающими точками-с-запятой).

Что плохого в JavaScript

Во вступлении JavaScript не очень-то был обласкан. Однако, правда заключается в том, что это хороший язык. Самые большие проблемы при его использовании вызваны не самим языком. В основном, неприятности исходят от:
  • Инструменты;
  • Реализации;
  • Недостаток навыка

Инструменты

Большинство инструментов, которые мы используем в Visual Studio, заточены под CLR. Если Вы программируете на C# или Visual Basic, Вам будет помогать Intellisence, обзоры и диаграммы классов, code snippets, анализатор кода и первоклассный отладчик. В меню будут включены команды рефакторинга, а тестирование достигается несколькими кликами. Контраст при программировании на JavaScript разительный. Нет Intellisence (который уже есть в Visual Studio Orcas - прим. перев.). От отладчика мало толку (можно попробовать использовать FIreBug - прим. перев.), а большинство прочих вышеперечисленных инструментов отсутствуют вовсе. Конечно, подавляющее большинство кода, который мы пишем в Visual Studio - это не JavaScript, но, коли уж реальность требует от нас больше скриптов, мы начинаем нуждаться в более качественных инструментах. Недостаток инструментов совсем не упрощает нам работу с JavaScript.

Плохие реализации

JavaScript - язык доступный. Мы не ограничены какими-то специальными инструментами или компиляторами. Мы можем посмотреть исходный код любой страницы в интернете и скопировать скрипт для собственных нужд - многие так и поступают. Конечно, не все, кто использует JavaScript - это разработчик, внимательный к качеству кода. Насколько различны люди, использующие JavaScript, настолько различны и степени отвратительности кода, наполняющего Сеть. Даже разработчики (включая автора) приобрели манеру писать на JavaScript быстро и "грязно". В конце-концов, это всего лишь скрипт, и мы шлепаем код в текстовом редакторе, с одной целью - достичь желаемого результата. До тех пор, пока не наступает необходимость разобраться в этом беспорядке, и мы понимаем, что более системный подход сэкономил бы нам кучу времени. Итак, со всеми этими проблемами, почему мы все еще хотим подвергать себя подобным пыткам?

Что хорошего в JavaScript?

В течении последних нескольких лет количество JavaScript-кода в типичном web-приложении сильно увеличилось. Тому причиной два факта:
  • JavaScript повсеместно распространен,
  • JavaScript достиг своей "зрелости".

Распространенность

Если Вам нужно написать приложение, которое будет доступно максимальному количеству пользователей, Вы будете писать web-приложение. И оно станет доступно пользователям Windows, Macintosh, Linux и сотен других платформ на больших и маленьких устройствах. Как сделать web-приложение интерактивным? Использовать JavaScript. Пользователи не будут вынуждены устанавливать среду исполнения, интерпретатор или ActiveX. Они установят браузер с поддержкой JavaScript, как многие делают, и будут спокойно использовать Ваше приложение. JavaScript - самый вездесущий язык программирования на планете.

Зрелость

Как только спрос на JavaScript стал расти, стали появляться библиотеки и фреймворки качественного и оттестированного кода. Многие из этих фреймворков скрывают особенности и различия браузеров, ранее упомянутые, и могут сильно уменьшить количество времени, требуемого на написание и отладку межплатформенного JavaScript-кода. Вот некоторые из наиболее популярных фреймворков:
  • asp.net ajax
  • Prototype
  • Script.aculo.us
  • The Dojo Toolkit
  • Yahoo! UI Library
Также мы видим появление подходов и методик, доказавших свою оптимальность. Эти подходы делают удобным использование объектно-ориентированных свойств JavaScript. Что, не знали что JavaScript объектно-ориентирован? Мы могли не применять ООП-подходы к JavaScript-коду, но возможности к этому есть.

Объектно-ориентированный JavaScript

JavaScript не имеет типа данных для объекта, но эти объекты могут вести себя иначе, чем объекты, создаваемые в C#- или VB-коде. В C#- или VB мы создаем объекты, указывая среде исполнения, какого класса эти объекты. Класс - это шаблон для создания объекта. Класс фиксирует, какие свойства и методы будет иметь наш объект. Во время исполнения мы не можем добавить или убрать свойства и методы у нашего объекта. JavaScript не имеет классов, таким образом, мы не имеем шаблонов для создания объекта. Так как свойства и методы становятся частью объекта? Первый подход - это динамически создавать свойства и методы после создания объекта. Чтобы создать свойство, все что от нас требуется - это присвоить ему новое значение. Следующий код создает объект и добавляет ему свойства x и y. В конце скрипт выводит значения свойств в некую область HTML-документа.
var p = new Object();
p.x = 3;
p.y = 5;
message.innerHTML = p.x + "," + p.y;
Объекты JavaScript совершенно не похожи на объекты C# или VB, потому что они (объекты JavaScript) фактически представляют собой коллекцию пар имя/значение. Мы можем получить доступ к значениям элементов через оператор-точку ".", после которого нужно указать имя элемента. Значение не обязательно должно быть просто целым числом, как в примере. Это может быть массив, функция или другой объект. Если Вы подумали, что объект JavaScript похож на словарь (Dictionary) из библиотеки классов .NET, то концептуально Вы двигаетесь в правильном направлении.

Объекты JavaScript - это словари

Объект JavaScript похож на конструкцию Dictionary в том смысле, что мы можем ассоциировать со строкой произвольные данные. Более того, существует альтернативный синтаксис доступа к значениям свойств объекта, который полностью скрывает разницу объекта JavaScript и словаря. Вместо использования оператора-точки, мы можем использовать оператор [] - типичный оператор при работе с коллекциями. Давайте перепишем первый пример с использованием []-оператора.
var p = new Object();
p["x"] = 3;
p["y"] = 5;
message.innerHTML = p["x"] + "," + p.y;
Данный фрагмент кода приводит к тому же результату, что и наш первый скрипт. Он создает объект, добавляет к нему свойства x и y, и выводит их значения. Кстати, если мы запросим значение свойства, которое у данного объекта отсутствует, мы получим значение "undefined" (неопределено). Например, строчка "alert(p.z);" выведет диалоговое окно с надписью "undefined".

Создание методов объекта

Также в качестве значений объекта можно добавлять функции. Такие функции, ассоциированные с объектом, становятся его методами. Следующий пример показывает создание и использование метода с именем print.
var p = new Object();
p["x"] = 3;
p.y = 5;
p["print"] = function()
{
message.innerHTML = p.x + "," + p.y;
}
p.print();
Обратите внимание на чередующееся использование операторов . и []. По большей части, для создания и доступа к свойствам и методам объекта, эти операторы взаимозаменяемы. Иногда эти операторы приводят к путанице, так как не ясно, добавляет ли данный кусок кода новые свойства к объекту, или присваивает новые значения уже существующим. К счастью, существует третий синтаксис, который может обозначить наши намерения предельно четко.

Объектные литералы

Литеральный синтаксис (object literal syntax) позволяет указать и инициализировать свойства объекта прямо при его создании. Этот синтаксис использует список пар "имя":"значение", разделенный запятой, где в самих парах имя и значение разделены двоеточием. Давайте перепишем наш пример с использованием этого синтаксиса.
var p =
{
x : 5,
y : 3,
print : function() { message.innerHTML = p.x + ',' + p.y; }
}
p.print();
В данном коде становится ясно, где начинается и заканчивается инициализация объекта. Интересно и то, что допускаются вложенные объектные литералы, а также то, что значения свойств внутри них не обязаны быть константами - мы можем использовать любое корректное выражение языка. Следующий код содержит вложенный объект (address), и присваивает свойству createdDate текущую дату.
var person =
{
name: "Scott Allen",     createdDate: new Date()
website: "OdeToCode.com",
address: { state: "MD", postalCode: "21044" },
};
alert(person.address.state);
alert(person.createdDate);
Подобный синтаксис популярен благодаря своей компактности и ясности. Если заглянуть в исходный код многих популярных ныне фреймворков, можно обнаружить использование этого синтаксиса. Однако, его применение отнюдь не ограничивается фреймворками. Объектные литералы и JSON JavaScript Object Notation (JSON - объектная нотация JavaScript) - это облегченный формат обмена данными, основанный на подмножестве синтаксиса объектных литералов. Например, строковые литералы должны быть заключены в двойные кавычки - одинарные кавычки недопустимы. JSON позволяет JavaScript обмениваться данными по сети (обычно с помощью объекта XmlHttpRequest) и взаимодействовать с другими приложениями. Многие веб-сервисы предлагают JSON в качестве формата для сериализации или как альтернативу XML. Когда наш JavaScript запрашивает такой веб-сервис, этот веб-сервис возвращает данные в формате JSON. Нет необходимости манипулировать данными с помощью XML API, за исключением случаев, когда нужно использовать выражение eval для конвертации JSON в объектный граф:
var jsonString = "({ x : 3, y: 5 })";
var p = eval(jsonString);
alert(p.x + ',' + p.y);
JSON становится популярным в сети. Он легко читается и требует минимум ресурсов. Сетевой обмен данными в этом формате обычно уменьшает нагрузку на канал по сравнению с XML. Для использования JSON на стороне сервера в управляемом коде, ASP.NET AJAX имеет встроенный класс JavaScriptSerializer.

Так что там с объектно-ориентированным JavaScript?

Закончив с отступлением на тему JSON, давайте вернемся к теме объектно-ориентированного JavaScript. На данном этапе мы имеем следующее:
  1. Каждый объект JavaScript - это словарь.
Конечно, полезная информация для создания объектов, но это только начало. Чтобы достичь следующей ступени абстракции, нам потребуется другой постулат:
  1. Каждая функция JavaScript - это объект.
Это мы и обсудим в следующем разделе.

Функции JavaScript

Помимо того, что функция это участок кода, это еще и полноценный объект. Это фундаментальное ее отличие от функции C# или Visual Basic. В этих языках мы можем запустить функцию на выполнение, но мы не можем рассматривать эти методы как типы данных (хотя, C# c его делегатами и lamda expressions, в этом плане не так категоричен). В JavaScript мы можем манипулировать функциями в других участках кода, присваивать функции переменным, хранить их в массивах, создавать вложенные функции, и передавать функции в качестве параметров других функций. Это может звучать необычно, поэтому давайте рассмотрим простой пример.
function add(point1, point2)
{
var result = {
x : point1.x + point2.x,
y : point1.y + point2.y
}
return result;
}
Этот код объявляет функцию с именем add. Функция принимает два параметра, и подразумевает, что оба этих параметра имеют свойства x и y, чтобы сложить их между собой. В качестве результата функцифя возвращает другой объект (созданный в объектной нотации) со свойствами x и y. Мы можем использовать вызов этой функции таким образом, как это показано в следующем примере:
var p1 = { x: 1, y: 1 };
var p2 = { x: 1, y: 1 };
// use our add function
var p3 = add(p1, p2);
alert(p3.x + "," + p3.y);
Результатом станет диалоговое окно с надписью "2,2". Технически, мы создали объект функции, и присвоили этот объект (тип) переменной с именем add. Мы могли бы взять этот же объект и назначить его как тип другим переменным, а потом выполнить функцию через эти переменные.
function add(point1, point2)
{
var result = {
x : point1.x + point2.x,
y : point1.y + point2.y
}
return result;
}
var foo = add
var bar = add
var p1 = { x: 1, y:1 };
var p2 = { x: 1, y:1 };
// invoke add through foo variable
// p3 should be 2,2
var p3 = foo(p1, p2);
// invoke add again through bar variable
// 2,2 + 1,1 = 3,3
p3 = foo(p3, p1);
alert(p3.x + "," + p3.y);
Результатом станет диалог с надписью "3,3".

Функции-методы

Мы можем присвоить функцию свойству объекта. Как уже было сказано, это придает функции статус "метода".
var point1 =
{
x: 3,
y: 5,
add: function(otherPoint)
{
this.x = this.x + otherPoint.x;
this.y = this.y + otherPoint.y;
}
};
var point2 =
{
x: 1,
y: 1
};
// add 3,5 to 1,1
point1.add(point2);
// выводит "4,6"
alert(point1.x + "," + point1.y);
В первой части примера создается объект со свойствами x, y и add, где последнее - это функция, в которой мы впервые упомянули ключевое слово this. Как и любой метод объекта в C# имеет неявный параметр this (параметр Me в Visual Basic), так любой метод JavaScript имеет неявный параметр this, указывающий на объект, через который метод был вызван. "this.x" указывает на свойство x объекта point1, т.к. метод add был вызван через этот объект. Проблема состоит в том, что у нас два объекта "point", один из которых имеет метод add, а другой - нет. Помните, мы не определяем классы, как в C# или VB, мы просто создаем объекты и добавляем свойства и методы на лету. Если нам понадобится такой же метод в объекте point2, мы можем сочинить нечто подобное:
function addPoints(otherPoint)
{
this.x = this.x + otherPoint.x;
this.y = this.y + otherPoint.y;
}
var point1 =
{
x: 3,
y: 5,
add: addPoints
};
var point2 =
{
x: 1,
y: 1,
add: addPoints
};
// add 3,5 to 1,1
point1.add(point2);
// выводит "4,6"
alert(point1.x + "," + point1.y);
Мы определили объект-функцию, и присвоили этот объект переменной addPoints каждого объекта. Мы используем addPoints в обоих объектах. Будет ли работать ссылка на this в этом случае? Будет, потому что она так же будет ссылаться на объект, через который метод вызван. Вообще, как мы позже увидим, "this" в JavaScript - не совсем тривиальная вещь. Но в данном случае мы добились своего - мы можем вызывать метод add у обоих объектов. Такой синтаксис немного сбивает с толку. Похоже на попытку определить класс Point, и описать его методы и свойства. Но JavaScript классы не поддерживает, так что это останется только мечтой, не так ли? На самом деле, кое-что мы можем.

Конструкторы

В JavaScript функция-конструктор работает в связке с оператором new и инициализирует объекты. Конструктор может помочь нам оптимизировать код из предыдущего примера, чтобы с ее помощью инициализировать все объекты, которые нам нужны в качестве экземпляров Point.
function Point(x,y)
{
this.x = x;
this.y = y;
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
// выводит "3,5"
alert(p1.x, p1.y);
Конструкторы ничем не отличаются от обычных функций. Мы сами определили ее таким образом, чтобы ее можно было использовать с оператором new. Соглашение рекомендует именовать конструкторы с заглавной буквы, чтобы отделить их от остальных функций и обозначить их роль. Когда мы используем функцию Point с оператором new, этот оператор сначала создаст новый объект. Затем он выполнит функцию Point и передаст ссылку на новый объект в неявный параметр this.

Конструкторы и методы объектов

Также внутри конструкторов мы можем определять методы объектов.
function Point(x,y)
{
this.x = x;
this.y = y;
this.add = function(point2)
{
this.x += point2.x;
this.y += point2.y;
}
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
p1.add(p2);
// shows 7,11
alert(p1.x + ',' + p1.y);
Этот подход вполне работоспособен, но существует альтернативный, более распространенный на сегодня. Чтобы его понять, нам потребуется ввести третий постулат. Вспомним первые два:
  1. Каждый объект JavaScript - это словарь
  2. Каждая функция JavaScript - это объект
теперь третий: Каждый объект JavaScript ссылается на объект-прототип

Прототипы

Прототипы - отличительная черта JavaScript. C#, Visual Basic, C++ и Java - это примеры языков программирования, основанных на классах. Чтобы создать объект, мы должны определить класс, и описать в нем поля, методы, свойства и события. Когда мы создаем сам объект, мы создаем экземпляр этого класса. В JavaScript нет классов. Это язык программирования, основанный на прототипах. Каждый объект имеет свойство, которое ссылается на прототип этого объекта. Любые свойства и методы прототипа будут присутствовать и у самого объекта. Вспомним: любая функция является объектом и любой объект ссылается на свой прототип. Это становится полезным при совместном использовании с оператором new, из-за действий, которые этот оператор вызывает:
  1. Создает пустой объект.
  2. Присваивает значение свойства-прототипа конструктора свойству-прототипу нового объекта.
  3. Выполняет конструктор, передавая ссылку на новый объект в неявное свойство this.
Из всего этого следует, что все объекты, созданные конструктором, будут иметь один прототип - прототип конструктора. Если мы изменим прототип конструктора - мы изменим все объекты, уже созданные и еще не созданные этим конструктором. Если Вы будете считать, что каждый объект наследует от своего прототипа - Вы будете недалеки от истины, так как в нем будут доступны все свойства и методы прототипа. Я сказал "недалеки от истины", потому что существуют нюансы, которые нужно учитывать.

Использование прототипов

Наш постулат №1 гласит, что все объекты - словари, и объект-прототип не исключение. Мы можем модифицировать прототип объекта, просто используя свойство-прототип (нетрудно догадаться, что имеется ввиду свойство с именем prototype - прим. перев.), и добавлять свойства и методы к этому объекту. Давайте перепишем наш "класс" Point еще раз.
function Point(x,y)
{
this.x = x;
this.y = y;
}
Point.prototype.add = function(point2)
{
this.x += point2.x;
this.y += point2.y;
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
p1.add(p2);
// выводит "7,11"
alert(p1.x + ',' + p1.y);
Свойства и методы, добавленные в прототип (Point.prototype), будут разделяемы (shared) со всеми объектами, созданных конструктором Point. При добавлении методов в самом конструкторе, каждый объект получит свой экземпляр свойства, ссылающийся на объект-функцию, поэтому подход с прототипами более эффективный и немного более удобный при изучении кода. Прототипы используются во многих современных JavaScript-фреймворках.

Собираем все вместе

Темы, рассмотренные нами к текущему моменту, вплотную подводят нас к имитации классов в JavaScript. Осталось затронуть всего пару вопросов, перед тем как делать выводы. Первый из них - инкапсуляция. В JavaScript каждая пара имя/значение, добавленные в объект, становятся общедоступными свойствами (public property). Язык не содержит директив, позволяющих задать область видимости, как internal, protected и private в C#. Однако, закрытые члены мы сможем имитировать. Дуглас Крокфорд (Douglas Crockford) опубликовал статью "Private Members In JavaScript", которая иллюстрирует добавление в объект закрытых членов. Закрытие информации - важная часть в объектно-ориентированном программировании, и многие инструментарии JavaScript используют этот подход при работе с закрытыми членами. Закрытые члены должны быть созданы в конструкторе объекта. Локальные переменные и параметры могут стать закрытыми членами с помощь "сокрытия". Сокрытие в JavaScript - это внутренняя функция, котрая возвращает ссылку на внутреннюю переменную или параметр во внешнюю функцию. Эти локальные переменные/параметры, которые обычно уходят из области видимости по завершении работы внешней функции, теперь "скрыты" внутренней функцией, которая имеет возможность на них ссылаться и изменять. Перепишем наш пример еще раз, создав для свойств "интерфейсы" get и set.
function Point(x, y)
{
this.get_x = function() { return x; }
this.set_x = function(value) { x = value; }
this.get_y = function() { return y; }
this.set_y = function(value) { y = value; }
}
Point.prototype.print = function()
{
return this.get_x() + ',' + this.get_y();
}
var p = new Point(2,2);
p.set_x(4);
alert(p.print());
Теперь клиентский код не имеет прямого доступа к значениям x и y объекта, а вынужден использовать интерфейсы "set_" и "get_". От переводчика: что касается сокрытия, то блог команды Internet Explorer содержит рекомендации, в которых говорится о губительном воздействии функций сокрытия на быстродействие JavaScript-кода под управением этого браузера.

Пространства имен

Пространства имен (namespaces) необходимы для избежания конфликтов имен, которые могут стать серьезной проблемой в JavaScript. В отличие от компилируемых языков как C# или VB, в которых конфликт имен приведет к ошибке компиляции, в JavaScript конфликт имен может быть замечен слишком поздно, например когда проект уже сдан и эксплуатируется заказчиком. JavaScript с радостью перепишет одно значение на другое. Теперь, когда мы часто используем код со стороны, использование пространств имен более чем оправдано. Есть всего одна маленькая проблемка. JavaScript не поддерживает пространства имен. Это нормально, т.к. мы можем имитировать пространства имен... все теми же объектами. Давайте заключим наш класс Point в пространство имен Geometry.
var Geometry = {}
Geometry.Point = function(x,y)
{
this.x = x;
this.y = y;
}
Geometry.Point.prototype =
{
print: function()
{
return this.x + ',' + this.y;
}
}
var p1 = new Geometry.Point(5,2);
alert(p1.print());
По существу, мы добавляем наш конструктор Point в объект Geometry. Добавляя другие функции-конструкторы (Square, Rectangle и т.д), мы сохраним все свои типы внутри Geometry и не затронем глобальное пространство имен. Естественно, большинство фреймворков использует эту технику.
От переводчика: я мог позабыть правильные переводы некоторых терминов на русский язык. Пожалуйста, если у Вас есть уточнения и комментарии, не стесняйтесь их оставлять.

Метки: ,

Подблог:

Комментарии

21.12.2007 21:18:10

pingback

Pingback from glad.westra.ru

Поток сознания  » Blog Archive   » JavaScript

glad.westra.ru

Добавить комментарий


(Отображает Gravatar)

biuquote
Loading