Обычно
описание языка программирования начинают с
типов и
структур данных, операторов и
функций, а
заканчивают библиотеками стандартных
функций (главным
образом, ввода/вывода) и опциями компилятора.
В своем описании JavaScript мы двигались в
обратном порядке и рассказываем об этом в
конце нашего курса.
Многое из
того, чему посвящены страницы данного
раздела, так или иначе уже использовалось в
других частях. Здесь мы соберем все вместе.
Теперь основное внимание мы уделим:
-
типам и
структурам данных;
-
операторам языка;
-
функциям
пользователя;
-
особенностям размещения кода;
-
возможности исполнения программ в
фоновом режиме;
-
управлению фокусом;
-
вопросам безопасности.
Как видно
из этого перечня, первые три позиции
полностью посвящены формальному описанию
языка, в то время как следующие четыре
призваны показать наличие "подводных камней"
при использовании JavaScript.
Типы и
структуры данных
Как и
любой другой язык программирования,
JavaScript поддерживает встроенные
типы и структуры
данных. Все их многообразие
подразделяется на:
-
литералы и переменные;
-
массивы, функции
и объекты.
При этом
все они делятся на встроенные и определяемые
программистом. Функции
и объекты
рассматриваются в разделах "Функции"
и "Объекты".
Поэтому здесь мы остановимся на литералах,
переменных и массивах.
Литералы
Литералом
называют данные, которые используются в
программе непосредственно. При этом под
данными понимаются числа или строки текста.
Все они рассматриваются в JavaScript как
элементарные типы
данных. Приведем примеры литералов:
числовой литерал:
10
числовой литерал: 2.310
числовой литерал: 2.3e+2
строковый литерал: 'Это строковый
литерал'
строковый литерал: "Это строковый
литерал"
Литералы
используются в операциях присваивания
значений переменным или в операциях
сравнения:
var a=10;
var str = 'Строка';
if(x=='test') window.alert(x);
Два
варианта строковых литералов необходимы для
того, чтобы использовать вложенные строковые
литералы. Вообще говоря, есть подозрение,
что равноправие "..." и '...' мнимое. Если
внимательно посмотреть на реализацию страниц
программирования гипертекстовых ссылок (href.htm,
path.htm и
mouse.htm),
можно заметить, что вместо прямого
переназначения гипертекстовой ссылки
литералом типа '...' там используется
косвенное переназначение через
функцию
литералом "...":
...
function line(a)
{
...
window.document.main.document.links[4].href=
"javascript:data(0);void(0);";
...
}
...
<A HREF="javascript:line(0);void(0);">
<IMG SRC=image.gif BORDER=0>
</A>
вместо:
<A HREF="javascript:
window.document.main.document.links[4].href=
'javascript:data(0);void(0);';void(0);">
<IMG SRC=image.gif BORDER=0>
</A>
Это
связано с особенностями реализации Netscape.
Дело в том, что прямое переназначение
неправильно отображает кириллицу в win32, а
вот косвенное работает. Похоже, что "..."
разрешает анализ информации внутри
строкового литерала JavaScript-интерпретатором,
а '...' — нет.
Если быть
более точным, то следует сказать, что строка
— это объект. У
этого объекта
существует великое множество методов.
Строчный литерал и строчный
объект — далеко
не одно и то же. При применении к строчным
литералам методов строчных
объектов
происходит преобразование первых в последние.
Переменные
Переменные
в JavaScript могут быть определены
назначением или при помощи оператора
var:
i=10;
var i;
var i=10;
var id = window.open();
var a = new Array();
Как видно
из примеров, переменные могут принимать
самые разные значения, при этом тип
переменной определяется контекстом.
Переменная
является свойством окна. Например, мы можем
открыть окно, определить в нем новую
переменную и использовать ее:
wid = window.open("","test","width=200,height=100,statusbar");
wid.document.open();
wid.document.write("<HTML><HEAD>");
wid.document.write("<SCRIPT>var
t;</SCRIPT>");
wid.document.write("</HEAD><BODY>");
wid.document.write("<CENTER>Новое
окно<BR>");
wid.document.write("<FORM>");
wid.document.write("<INPUT TYPE=button
VALUE='Закрыть окно'
onClick=window.close();></FORM>");
wid.document.write("</CENTER></BODY></HTML>");
wid.document.close();
...
<A
HREF="javascript:wid.t=window.prompt("Новое
состояние:");wid.defaultStatus=t;wid.focus();void(0);>...</A>
Пример
18.1.
Существуют
ли в JavaScript различные типы переменных?
По всей видимости, да. При объявлении
переменной тип не указывается. Тип значения
определяется контекстом, поэтому можно было
бы предположить, что все переменные — одного
и того же типа. Однако очевидно, что
присваивание переменной значения
объекта окна (window.open())
или объекта
потока (setTimeout()),
вызывает создание в памяти совершенно разных
структур.
Поставим
вопрос несколько иначе. Может ли одна и та
же переменная принимать значения разных
типов? Для ответа на этот вопрос рассмотрим
следующий пример:
var flag=0;
var cid=null;
function clock()
{
flag=1;
d = new Date();
window.document.main.document.f0.fi1.value=
d.getHours()+":"+d.getMinutes()+":"+d.getSeconds();
cid = setTimeout("clock();",10000);
}
function stop()
{
if(cid!=null)
{
clearTimeout(cid);
cid=null;
flag=0;
}
}
function wo()
{
cid =
window.open("","test","width=400,height=100");
cid.document.open();
cid.document.write("<HTML><HEAD></HEAD><BODY><CENTER>");
cid.document.write("<FORM><INPUT
TYPE=button
onClick='window.close();' value='Закрыть
окно'></FORM></CENTER>");
cid.document.write("</BODY></HTML>");
cid.document.close();
cid.focus();
}
...
<FORM NAME=f0>
<INPUT NAME=fi1 SIZE=8 MAXLENGTH=8>
<INPUT TYPE=button
VALUE="Часы(start/stop)"
onClick="if(flag==0)clock();else
stop();">
<INPUT TYPE=button VALUE="Окно"
onClick="wo();">
</FORM>
Пример
18.2.
Можно в
любом порядке нажимать на кнопки формы, и
все будет работать правильно. При этом
переменная cid
используется и как идентификатор потока, и
как идентификатор окна. Это означает, что
JavaScript все-таки поддерживает
полиморфизм, т.е. существует два разных
объекта с
одинаковыми именами, и система в них не
путается.
Массивы
Массивы
делятся на встроенные (document.links[],
document.images[],...) и определяемые
пользователем (автором документа).
Встроенные массивы мы подробно обсуждаем в
разделах "Программируем картинки",
"Программируем формы" и "Программируем
гипертекстовые переходы". Поэтому подробно
остановимся на массивах, определяемых
пользователем. Для массивов задано несколько
методов:
-
join();
-
reverse();
-
sort();
и свойство
length,
которое позволяет получить число элементов
массива. Это свойство активно используется в
примерах данного раздела. В частности, при
обсуждении метода
join().
Для
определения массива пользователя существует
специальный конструктор:
a = new Array();
b = new Array(10);
c = new Array(10,"Это значение");
Пример
использования:
<SCRIPT>
c = new Array(30,"Это значение");
</SCRIPT>
<FORM><INPUT SIZE=& {c[0];};
value=& {c[1];};
onFocus="this.blur();">
</FORM>
Как видно
из этого примера, массив может состоять из
разнородных элементов. Массивы не могут быть
многомерными.
Для работы
с массивами в JavaScript применяются методы
join(),
reverse(),
sort(). Кроме того, массивы обладают
свойством длины,
length.
Метод
join()
Метод
join()
позволяет объединить элементы массива в одну
строку. Он является обратной
функцией методу
split(),
который применяется к
объектам типа
STRING. Рассмотрим пример
преобразования локального URL в URL схемы
http:
window.location:
http://intuit.ru/help/index.html
Выполнили:
b =
window.location.href.split('/');
Получили
массив b:
b[0]=http:
b[1]=
b[2]=intuit.ru
b[3]=help
b[4]=index.html
Заменили
схему и вставили "host:port":
for(i=0;i<b.length;i++)
{
if(b[i]=="file:") b[i]="http:/";
if(b[i]=="c%7C")
b[i]="remote.host.domain:80";
}
Получили
массив b:
b[0]=http:
b[1]=
b[2]=intuit.ru
b[3]=help
b[4]=index.html
Слили
элементы массива b:
l=b.join("/");
Получили в
результате:
http://intuit.ru/help/index.html
Другой
пример использования метода
join() —
замена символа в строке:
str =
"document.img1.src=
'http://images/imagе1.gif';"
document.write(str);
Исходная
строка:
document.img1.src='http://images/imagе1.gif';
Заменяем в
строке все единицы на двойки:
b =
str.split('1');
str = b.join('2');
Получаем
следующий результат:
document.img2.src='http://images/imagе2.gif';
Последний
пример показывает, что массив пользователя
можно получить и без явного применения
конструктора массива. Массив элементов
строки получается просто как результат
действия функции
split().
Метод
reverse()
Метод
reverse()
применяется для изменения на противоположный
порядка элементов массива внутри массива.
Предположим, массив натуральных чисел
упорядочен по возрастанию:
a = new
Array(1,2,3,4,5);
Упорядочим
его по убыванию:
a.reverse();
a[0]=5
a[1]=4
a[2]=3
a[3]=2
a[4]=1
Можно,
наверное, придумать и более внятный пример,
но, к сожалению, на практике встречаться с
этим методом мне приходилось нечасто.
Метод
sort()
Как
принято в современных интерпретируемых
языках, например в Perl, метод
sort()
позволяет отсортировать элементы массива в
соответствии с некоторой
функцией
сортировки, чье имя используется в качестве
аргумента метода:
a = new
Array(1,6,9,9,3,5);
function g(a,b)
{
if(a > b) return 1;
if(a < b) return -1;
if(a==b) return 0;
}
b = a.sort(g);
В
результате выполнения этого кода получим
массив следующего вида:
b[0]=1
b[1]=3
b[2]=5
b[3]=6
b[4]=9
b[5]=9
Возможность использования произвольной
функции
сортировки позволяет выполнять подробный
анализ строковых
объектов. Одним из таких примеров
может служить анализ строки атрибута
SRC контейнера
IMG, если
картинка подставляется скриптом, и
сортировка полей формы по значениям:
document.image.src
=
"http://www.intuit.ru:80/cgi-bin/
image?x=10&y=20&z=15";
Выделим
x и
y. Затем
отсортируем их:
str =
"http://www.intuit.ru:80/cgi-bin/
image?x=10&y=20&z=15";
s = str.split('?');
s1 = s[1].split('&');
s2 = s1.sort(g);
for(i=0;i<s2.length;i++)
document.write("s2["+i+"]=
'"+s2[i]+"'<br>");
s2[0]='x=10'
s2[1]='z=15'
s2[2]='y=20'
Аналогичные манипуляции можно проделать с
любым массивом. Если не указывать
функции в
аргументе метода сортировки, то элементы
массива сортируются в лексикографическом
порядке. Это значит, что они сначала
преобразуются в строки и только потом
сортируются.
Функции
Язык
программирования не может обойтись без
механизма многократного использования кода
программы. Такой механизм обеспечивается
процедурами или
функциями. В
JavaScript функция
выступает в качестве одного из основных
типов данных.
Одновременно с этим в JavaScript определен
объект
Function.
В общем
случае любой объект
JavaScript определяется через
функцию. Для
создания объекта
используется конструктор, который в свою
очередь вводится через
Function.
Таким образом, с
функциями в JavaScript связаны
следующие ключевые вопросы:
-
функция —
тип данных;
-
функция —
объект;
-
конструкторы
объектов.
Именно эти
вопросы мы и рассмотрим в данном разделе.
Функция —
тип данных
Определяют
функцию при
помощи ключевого слова
function:
function
f_name(arg1,arg2,...)
{
/* function body */
}
Здесь
следует обратить внимание на следующие
моменты. Во-первых,
function определяет переменную
f_name. Эта
переменная имеет тип "function":
document.write("Тип переменной f_name:"+
typeof(f_name));
Тип
переменной f_name:
function.
Во-вторых, этой переменной присваивается
значение:
document.write("Значение
i:"+i.valueOf());
document.write("Значение f_name:"+
f_name.valueOf());
Значение
переменной f_name:
10. Значение переменной
f_name:function
f_name(a) { if(a>=0) return true; else
return false; }. В данном случае
метод valueOf()
применяется как к числовой переменной
i, так и к
f_name. По
этой причине функции
можно назначить синоним путем присваивания
ее значения другой переменной:
function f_name(a)
{
if(a>=0) return true; else return false;
}
document.write("Значение переменной
f_name:"+
f_name(1)+"");
b = f_name;
document.write("Значение переменной b:"+
b(1)+"");
Значение переменной f_name:true
Значение переменной b:true
Очевидно,
что если функцию
можно присвоить переменной, то ее можно
передать и в качестве аргумента другой
функции. Все
это усиливается при использовании
функции
eval(),
которая позволяет реализовать отложенное
исполнение JavaScript-кода. Отложенное
исполнение — это возможность изменения
программы по ходу ее исполнения. Типичным
использованием eval()
является сокращение кода за счет генерации
однотипных строк:
for(i=0;i<5;i++)
{
eval("document.write('test"+i+"<br>')");
}
Результат
исполнения кода:
test0
test1
test2
test3
test4
При
непосредственном кодировании пришлось бы
написать пять строк кода. Данный подход
использовался в разделе "Изменение картинки"
для инициирования массивов картинок, имена
которых построены по принципу индексации
ячеек таблицы.
Функция —
объект
У любого
типа данных
JavaScript существует
объектовая "обертка" — Wrapper,
которая позволяет применять методы
типов данных к
переменным и литералам, а также получать
значения их свойств. Например, длина строки
символов определяется свойством
length.
Аналогичная "обертка" есть и у
функций —
объект
Function.
Например,
увидеть значение
функции можно не только при помощи
метода valueOf(),
но и используя метод
toString():
function
f_name(x,y)
{
return x-y;
}
document.write(f_name.toString()+"<br>");
Результат
распечатки:
function
f_name(x,y) { return x-y; }
Свойства
функции
доступны для программиста только тогда,
когда они вызываются внутри
функции. При
этом обычно программисты имеют дело с
массивом аргументов
функции (arguments[]),
его длиной (length),
именем функции,
вызвавшей данную
функцию (caller)и
прототипом (prototype).
Рассмотрим
пример использования списка аргументов
функции и его
длину:
function my_sort()
{
a = new Array(my_sort.arguments.length);
for(i=0;i<my_sort.arguments.length;i++)
a[i] = my_sort.arguments[i];
b = a.sort();
return b;
}
b = my_sort(9,5,7,3,2)
for(i=0;i<b.length;i++)
document.write("b["+i+"]="+b[i]+"<br>");
Результат
исполнения:
b[0]=2
b[1]=3
b[2]=5
b[3]=7
b[4]=9
Если
функция может
быть вызвана из других
функций, то в этом случае
используется свойство
caller:
function slave()
{
document.write(slave.caller+"");
return slave.caller;
}
function master1()
{
slave();
}
function master2()
{
slave();
}
...
master1();
master2();
Результат
исполнения двух последних строк:
function master1()
{ slave(); }
function master2() { slave(); }
Еще одним
свойством объекта
Function
является prototype,
но это общее свойство всех
объектов,
поэтому и обсуждать его мы будем в контексте
типа данных
Object.
Упомянем только о конструкторе
объекта
Function:
f = new
Function(arg_1,...,arg_n, body)
Здесь
f — это
объект класса
Function. Его
можно использовать и как обычную
функцию.
Конструктор используют для получения
безымянных функций,
которые назначают или переопределяют методы
объектов. Здесь
мы вплотную подошли к вопросу
конструирования
объектов. Дело в том, что переменные
внутри функции
можно рассматривать в качестве ее свойств, а
функции – в
качестве методов:
function
Rectangle(a,b,c,d)
{
this.x0 = a;
this.y0 = b;
this.x1 = c;
this.y1 = d;
this.area = new Function(
"return Math.abs(this.x0-this.x1)*
Math.abs(this.y0-this.y1)");
this.perimeter = new Function(
"return (Math.abs(this.x0-this.x1)+
Math.abs(this.y0-this.y1))*2");
}
c = new Rectangle(0,0,100,100);
document.write(c.area());
Результат
исполнения:
10000
Обратите
внимание еще на одну особенность — ключевое
слово this.
Оно позволяет сослаться на текущий
объект, в
рамках которого происходит исполнение
JavaScript-кода. В данном случае это
объект c класса
Rectangle.
Объекты
Объект —
это главный тип данных
JavaScript. Любой другой
тип данных
имеет объектовую
"обертку" — Wrapper. Это означает, что
прежде чем можно будет получить доступ к
значению переменной того или иного типа,
происходит конвертирование переменной в
объект, и
только после этого выполняются действия над
значением. Тип данных
Object сам
определяет объекты.
В данном
разделе мы остановимся на трех основных
моментах:
-
понятие объекта;
-
прототип
объекта;
-
методы объекта
Object.
Мы не
будем очень подробно вникать во все эти
моменты, так как при программировании на
стороне браузера чаще всего обходятся
встроенными средствами JavaScript. Но
поскольку все эти средства —
объекты, нам
нужно понимать, с чем мы имеем дело.
Понятие
объекта
Сначала
рассмотрим пример произвольного,
определенного пользователем
объекта, потом
выясним, что же это такое:
function
Rectangle(a,b,c,d)
{
this.x0 = a;
this.y0 = b;
this.x1 = c;
this.y1 = d;
this.area = new Function(
"return Math.abs(this.x0-this.x1)*
Math.abs(this.y0-this.y1)");
this.perimeter = new Function(
"return (Math.abs(this.x0-this.x1) +
Math.abs(this.y0-this.y1))*2");
}
c = new Rectangle(0,0,100,100);
document.write(c.area());
Результат
исполнения:
10000
Этот же
пример использовался в разделе "Функции"
для иллюстрации применения конструктора
функции. Здесь
мы рассмотрим его в более общем контексте.
Функция
rectangle() —
это конструктор
объекта класса
Rectangle, который определяется
пользователем. Конструктор позволяет создать
реальный объект
данного класса. Ведь
функция — это не более чем описание
некоторых действий. Для того чтобы эти
действия были выполнены, необходимо передать
функции
управление. В нашем примере это делается при
помощи оператора new
. Он вызывает функцию
и тем самым генерирует реальный
объект.
Создается
четыре переменных: x0,
y0,
x1,
y1 — это
свойства объекта
Rectangle. К
ним можно получить доступ только в контексте
объекта данного
класса, например:
up_left_x = c.x0;
up_left_y = c.y0;
Кроме
свойств мы определили внутри конструктора
два объекта
типа Function(),
применив встроенные конструкторы языка
JavaScript, — area
и perimeter.
Это методы объекта
данного класса. Вызвать эти
функции можно
только в контексте
объекта класса
Rectangle:
sq = c.area();
length = c.perimeter();
Таким
образом, объект
— это совокупность свойств и методов, доступ
к которым можно получить, только создав при
помощи конструктора
объект данного класса и использовав
его контекст.
На
практике довольно редко приходится иметь
дело с объектами,
созданными программистом. Дело в том, что
объект
создается функцией-конструктором,
которая определяется на конкретной странице
и, следовательно, все, что создается в
рамках данной страницы, не может быть
унаследовано другими страницами. Нужны очень
веские основания, чтобы автор Web-узла
занялся разработкой библиотеки классов
объектов
пользователя. Гораздо проще писать
функции для
каждой страницы.
Прототип
Обычно мы
имеем дело со встроенными
объектами
JavaScript. Собственно, все, что изложено в
других разделах курса — это обращение к
свойствам и методам встроенных
объектов. В
этом смысле интересно свойство
объектов,
которое носит название
prototype.
Прототип — это другое название конструктора
объекта
конкретного класса. Например, если мы хотим
добавить метод к
объекту класса
String:
String.prototype.out = new Function("a",
"a.write(this)");
...
"Привет!".out(document);
Результат
исполнения:
Привет!
Для
объявления нового метода для
объектов класса
String мы
применили конструктор
Function. Есть один существенный
нюанс: новыми методами и свойствами будут
обладать только те
объекты, которые порождаются после
изменения прототипа
объекта. Все встроенные
объекты
создаются до того, как JavaScript-программа
получит управление, что существенно
ограничивает применение свойства
prototype.
Тем не
менее покажем, как можно добавить метод к
встроенному в JavaScript классу
объектов.
Примером будет служить встроенный
поименованный Image.
Задача состоит в том, чтобы разобрать URL
картинки таким же образом, как и URL
объекта класса
Link:
function prot()
{
a = this.src.split(':');
protocol = a[0]+":";
return protocol;
}
function host()
{
a = this.src.split(':');
path = a[1].split('/');
return path[2];
}
function pathname()
{
a = this.src.split(':');
path = a[1].split('/');
b = new array();
for(i=3;i<path.length;i++)
b[i-3]="path[i];<br" c = b.join('/');
return "/"+c;
}
Image.prototype.protocol = prot;
Image.prototype.host = host;
Image.prototype.pathname = pathname;
...
document.write("<IMG NAME=i1
SRC='image1.gif'><BR>");
document.write(document.i1.src+"<BR>");
document.write(document.i1.protocol()+"<BR>");
document.write(document.i1.host()+"<BR>");
document.write(document.i1.pathname()+"<BR>");
Пример
18.3.
Основная
идея заключается в том, чтобы переопределить
конструктор раньше, чем он будет
использован. HTML-парсер разбирает HTML и
создает встроенные
объекты раньше, чем запускается
JavaScript-интерпретатор. Из этого следует,
что объект на
странице нужно создать через JavaScript-код.
В этом случае сначала происходит
переопределение
объекта
Image, а уже после этого создается
встроенный объект
данного класса. При работе с Internet
Explorer все иначе. Если на свойство
prototype у
строкового объекта
он не "ругается", то для
Image такое
свойство уже не определено.
Методы
объекта Object
Object — это
объект и,
следовательно, у него могут быть методы.
Таких методов мы рассмотрим три:
toString(),
valueOf() и
assign().
Метод
toString()
осуществляет преобразование
объекта в
строку символов. Он используется в
JavaScript-программах повсеместно, но
неявно. Например, при выводе числа или
строковых объектов.
Интересно применение
toString() к
функциям:
document.write(prot.toString()+"<BR>");
Результат
исполнения:
function prot() {
a = this.src.split(':');
protocol = a[0]+":";
return protocol; }
Здесь мы
используем функцию
prot() из
примера с прототипом. Если распечатать таким
же образом объект
Image, то
получим следующее:
картинка:[object]
Таким
образом, далеко не всегда метод
toString()
возвращает строковый эквивалент содержания
объекта. Он
может просто вернуть его тип. Internet
Ехplorer при этом возвращает "Object", в то
время как Netscape Navigator — "object
Image".
Аналогично
ведет себя и метод
valueOf(). Этот метод позволяет
получить значение
объекта. В большинстве случаев он
работает подобно методу
toString(),
особенно если нужно выводить значение на
страницу:
document.write(prot.valueOf()+"<BR>");
Результат
исполнения:
function prot() {
a = this.src.split(':');
protocol = a[0]+":";
return protocol; }
Как видим,
результат тот же, что и в методе
toString().
В отличие
от двух предыдущих методов,
assign()
позволяет не прочитать, а переназначить
свойства и методы
объекта. Данный метод используется в
контексте присваивания
объекту некоторого значения:
object = value;
<=> object.assign(value);
Рассмотрим
пример:
c = new Image();
c.src = "image1.gif";
b =new Image();
b.src = "image2.gif";
Image.prototype.assign =
new Function("a","this.src = a.src;");
...
<A
HREF='javascript:window.document.main.
document.i2.assign(c);
void(0);'>Покрасить картинку</A>
В данном
примере мы применяем метод
assign() не
внутри JavaScript-кода, а в обычной
HTML-разметке. При этом перед использованием
метода мы переопределили его своей
функцией.
Пользователей Internet Explorer следует
предупредить, что здесь мы их немножко
обманываем, так как переназначить прототип
Image в
Internet Explorer мы в данном случае не
можем.
Операторы
языка
В этом
разделе будут рассмотрены операторы
JavaScript. Основное внимание при этом мы
уделим операторам декларирования и
управления потоком вычислений. Без них не
может быть написана ни одна
JavaScript-программа.
Общий
перечень этих операторов выглядит следующим
образом:
-
var;
-
{...};
-
if;
-
while;
-
for;
-
for ... in;
-
break;
-
continue;
-
return.
Сразу
оговоримся, что этот список неполный.
var
Оператор
var служит для объявления переменной. При
этом переменная может принимать значения
любого из разрешенных типов данных. На
практике довольно часто обходятся без явного
использования var.
Переменная соответствующего типа создается
путем простого присваивания:
var a;
var a=10;
var a = new Array();
var a = new Image();
Все
перечисленные выше примеры использования var
верны и могут быть применены в
JavaScript-программе. Область действия
переменной определяется блоком (составным
оператором), в котором используется
переменная. Максимальная область действия
переменной — страница.
{...}
Фигурные
скобки определяют составной оператор
JavaScript — блок. Они одновременно
ограничивают область действия переменных,
которые определены внутри этих скобок. За
пределами блока переменные не видны:
{
var i=0;
}
Основное
назначение блока — определение тела цикла и
тела условного оператора.
if
Условный
оператор применяется для ветвления программы
по некоторому логическому условию. Общий
синтаксис:
if (логическое
выражение) оператор1;
[else оператор2;]
Логическое
выражение — это выражение, которое принимает
значение true
или false.
Если оно равно true,
то оператор 1 исполняется. В квадратных
скобках необязательная составляющая
оператора if —
альтернатива основной ветви вычислений:
if
(navigator.appName=="Netscape")
{
window.location.href=
"http://intuit.ru/netscape.htm";
}
else
{
window.location.href=
"http://intuit.ru/explorer.htm";
}
Примеры
использования условного оператора можно
найти, например, в разделе "Тип браузера".
while
Оператор
while
определяет цикл. Определяется он в общем
случае следующим образом:
While (логическое
выражение)
оператор;
Оператор,
в том числе и составной, — тело цикла. Тело
исполняется до тех пор, пока верно
логическое условие:
while (flag==0)
{
id=setTimeout ("test();",500);
}
Обычно
цикл этого типа применяют при выполнении
периодических действий до некоторого
события.
for
Оператор
for — это еще
один оператор цикла. В общем случае он имеет
вид:
for (инициализация
переменных цикла;
условие; модификация переменных цикла)
оператор;
Оператор в
теле цикла может быть блоком. Рассмотрим
типичный пример использования этого
оператора:
for(i=0;i<document.links.length;i++)
{
document.write(document.links[i].href+"<BR>");
}
http://intuit.ru/help/index.html
http://intuit.ru/help/shop.html#choice
http://intuit.ru/help/payment.html
Подобные
примеры разбросаны по всем разделам курса.
for ... in
Данный
оператор позволяет "пробежаться" по
свойствам объекта.
Рассмотрим пример:
for(v in
window.document)
{
document.write(v+"<BR>");
}
Все
свойства текущего
объекта "документ":
namespaces:[object]
lastModified:07/16/2002 21:22:53
onmousedown:null
URLUnencoded:http://intuit.ru/help/index.html
fileCreatedDate:07/16/2002
onbeforeeditfocus:null
bgColor:#ffffff
oncontextmenu:null
onrowexit:null
embeds:[object]
scripts:[object]
mimeType:HTML Document
alinkColor:#0000ff
onmousemove:null
onselectstart:null
oncontrolselect:null
body:[object]
protocol:HyperText Transfer Protocol
onkeypress:null
onrowenter:null
vlinkColor:#800080
URL:http://intuit.ru/help/index.html
onreadystatechange:null
applets:[object]
domain:intuit.ru
fileModifiedDate:07/16/2002
onmouseover:null
dir:
media:
defaultCharset:windows-1251
plugins:[object]
ondragstart:null
oncellchange:null
cookie:hotlog=1; hotlog=1; b=b
documentElement:[object]
ondatasetcomplete:null
nameProp:Web-engineering (Introduction
to the JavaScript. Operators.)
referrer:http://intuit.ru/help/index.html
onrowsdelete:null
onerrorupdate:null
onselectionchange:null
ondblclick:null
onkeyup:null
location:http://intuit.ru/help/index.html
forms:[object]
title:Web-engineering (Introduction to
the JavaScript. Operators.)
onrowsinserted:null
onmouseup:null
onkeydown:null
fgColor:#000080
ondatasetchanged:null
onmouseout:null
parentWindow:[object]
fileUpdatedDate:01/01/1601
onpropertychange:null
onstop:null
onhelp:null
linkColor:#0000ff
images:[object]
readyState:interactive
frames:[object]
all:[object]
onclick:null
childNodes:[object]
anchors:[object]
selection:[object]
onbeforeupdate:null
security:This type of document does not
have a security certificate.
fileSize:15911
ondataavailable:null
styleSheets:[object]
activeElement:null
links:[object]
onafterupdate:null
sea001:[object]onafterupdate:null
down:[object]onafterupdate:null
Пример
18.4.
Данный
документ состоит из нескольких разделов.
Обращение
"window.document" позволяет
обратиться к общему документу, а не к
отдельно взятому разделу. Поэтому
распечатанные свойства — это свойства всего
документа.
break
Оператор
break
позволяет досрочно покинуть тело цикла.
Распечатаем только
title документа:
for(v in
window.document)
if(v=="title")
{
document.write(v+":"+eval('document.'+v)+"
");
break;
}
Результат
исполнения:
title:Web-engineering
(Introduction to the JavaScript.
Operators.).
В пример
распечатки свойств
объекта
document мы вставили
break при
просмотре свойства
title и получили искомый результат.
continue
Того же
результата, что и при использовании
break, можно
было бы достичь при помощи оператора
continue:
for(v in
window.document)
{
if(v!="title") continue;
document.write(v+":"+eval('document.'+v));
break;
}
Результат
исполнения:
title:Web-engineering
(Introduction to the JavaScript.
Operators.)
Этот
оператор позволяет пропустить часть тела
цикла (от оператора до конца тела) и перейти
к новой итерации. Таким образом мы просто
пропускаем все свойства до
title и после
этого выходим из цикла.
return
Оператор
return
используют для возврата значения из
функции или
обработчика события (см. разделы "Поле
статуса", "Обмен
данными"). Рассмотрим пример:
<FORM>
<INPUT TYPE=submit VALUE=Submit
onClick="return false;">
</FORM>
В данном
примере return
используется для маскирования передачи
данных на сервер.
Управление
фокусом
Фокус —
это характеристика текущего окна, фрейма или
поля формы. В каждом из разделов,
описывающем программирование этих
объектов, мы,
так или иначе, касаемся вопроса фокуса. Под
фокусом понимают возможность активизации
свойств и методов
объекта. Например, окно в фокусе,
если оно является текущим, т.е. лежит поверх
всех других окон и исполняются его методы
или можно получить доступ к его свойствам.
В данном
разделе мы рассмотрим управление фокусом в
-
окнах;
-
фреймах;
- полях
формы.
Следует
сразу заметить, что фреймы — это тоже
объекты класса
Window, и
многие решения, разработанные для окон,
справедливы и для фреймов.
Управляем
фокусом в окнах
Для
управления фокусом у
объекта класса "окно" существует два
метода: focus()
и blur().
Первый передает фокус в окно, в то время как
второй фокус из окна убирает. Рассмотрим
простой пример:
function
hide_window()
{
wid=window.open("","test",
"width=400,height=200");
wid.opener.focus();
wid.document.open();
... wid.document.close();
}
В данном
примере новое окно открывается и сразу
теряет фокус; прячется за основным
окном-родителем. Если при первичном нажатии
на кнопку оно еще всплывает и только после
этого прячется, то при повторном нажатии
пользователь не видит появления нового окна,
так как оно уже открыто и меняется только
его содержимое.
Для того
чтобы этого не происходило, нужно после
открытия передавать фокус на новое окно:
function
visible_window()
{
wid=window.open("","test",
"width=400,height=200");
wid.focus();
wid.document.open();
... wid.document.close();
}
Если
теперь нажимать попеременно кнопки "Скрытое
окно" и "Видимое окно", окно будет то
появляться, то исчезать. При этом новых окон
не появляется, так как с одним и тем же
именем может быть открыто только одно окно.
Невидимое
окно может доставить пользователю
неприятности, из которых самая безобидная —
отсутствие реакции на его действия. Код
просто записывается в невидимое окно. Но
ведь в скрытом окне можно что-нибудь и
запустить. Для этого стоит только проверить,
существует ли данное окно или нет, и если
оно есть и не в фокусе, то активизировать в
нем какую-нибудь программу.
Для
реализации такого сценария достаточно
использовать метод окна
onblur(). Его
можно также задать в контейнере
BODY в
качестве обработчика события
onBlur, но в
этом случае он видим пользователю. Мы
воспользуемся этим методом "в лоб":
window.onblur =
new Function("window.defaultStatus =
'Background started...';");
window.onfocus =
new Function("window.defaultStatus =
'Document:Done';");
Обратите
внимание на поле статуса браузера. Оно
демонстрирует возможность выполнения
функции в
фоновом режиме. Кроме того,
onblur() в
этом виде не отрабатывает в Internet
Explorer. Причина кроется в прототипе
объекта и
возможности его переназначения
программистом.
Конечно,
когда разработчики создавали всю эту
конструкцию, думали не о том, как насолить
пользователю, а о том, как сократить
ресурсы, необходимые браузеру для
отображения нескольких окон. Ведь можно
выполнить все то же самое с точностью до
наоборот: запускать, например, часы в фокусе
и останавливать их в фоне. Но этот пример мы
рассмотрим в контексте фреймов.
Управление
фокусом во фреймах
Фрейм —
это такое же окно, как и само окно браузера.
Точнее — это объект
того же класса. К нему применимы те же
методы, что и к обычному
объекту "окно":
var flag=1;
function clock()
{
if(flag==0)
{
d=new Date();
s=d.getHours()+':'+d.getMinutes()+':'+
d.getSeconds();
window.document.forms[0].elements[0].value=s;
}
setTimeout('clock();',100);
}
window.onblur =
new Function('this.flag = 1;');
window.onfocus =
new Function('this.flag = 0;');
window.onload = clock;
Данный
фрагмент кода размещен в каждом из двух
фреймов, которые отображаются в примере. А
их именно два. Просто ширина границы набора
фреймов установлена в 0. Если окно примера
разделить мысленно пополам и "кликнуть"
мышью в одну из половин, то пойдут часы в
этой половине. Если теперь переместиться в
другой фрейм и "кликнуть" мышью в нем, то
часы пойдут в поле формы этого фрейма, а в
другом фрейме остановятся.
Фокус в
полях формы
Управление
фокусом в полях формы, кроме этого раздела,
описано еще и в разделе "Текст в полях
ввода". Здесь мы рассматриваем этот вопрос в
контексте общего применения методов
blur() и
focus(). Эти
методы определены для любого поля формы, а
не только для полей ввода. Рассмотрим
простой пример.
Попробуйте
изменить в этой форме значение любого из
полей. Вряд ли это вам удастся. Обработчик
события Focus (onFocus)
уводит фокус из поля на произвольное место
страницы.
Скрытая
передача данных из форм
Обмен данными в
Web-технологии подробно рассматривается в
другой главе — "CGI и формы".
Программирование элементов форм обсуждается
в разделе "Программируем формы". В этом
разделе мы рассмотрим вопрос о возможности
передачи скрытых от пользователя данных.
Рассмотрим
следующий пример. Нажмите на кнопку
"Подписка" и посмотрите на строку location
своего браузера. Вы обнаружите там два поля,
которых нет в заполняемой вами форме:
h1 и
h2. Это уже
неприятно, хотя сама информация в них отнюдь
не криминальная (location.href
и document.referer).
Это означает, что в тексте страницы есть
вызов функции
со строками типа:
<SCRIPT>
document.f.h1.value =
window.location.href;
document.f.h2.value =
window.document.referer;
</SCRIPT>
Теперь
посмотрим другой пример. Если начать вводить
данные в левом фрейме окна примера, то, как
только вы переходите от поля к полю, в
правом фрейме заполняются соответствующие
поля. Cкрипт из правого фрейма читает данные
из полей левого фрейма. В кодах это будет
выглядеть примерно так:
function ask()
{
document.forms[0].elements[0].value=
window.top.frames[0].document.forms[0
].elements[0].value;
document.forms[0].elements[1].value=
window.top.frames[0].document.forms[0
].elements[1].value;
document.forms[0].elements[2].value=
window.top.frames[0].document.forms[0
].elements[2].value;
document.forms[0].elements[3].value=
window.top.frames[0].document.forms[0
].elements[3].value;
setTimeout("ask();",100);
}
...
<BODY onLoad="ask();"
BGCOLOR=lightyellow TEXT=navy>
Это
означает, что данные из одного окна могут
быть считаны программой из другого окна.
Вопрос только в том, хотите ли вы, чтобы это
происходило. Как решаются эти вопросы,
рассказано в разделе "Модель
безопасности".
Еще один
пример — отправка данных по событию без
наличия какой-либо формы в документе вообще.
<FORM NAME=hf
ACTION="javascript:window.alert('Готово');
void(0);"
METHOD=post>
<INPUT NAME=hfi TYPE=hidden>
</FORM>
<SCRIPT>
document.hf.hfi.value = location.href;
</SCRIPT>
<A
HREF="javascript:window.alert('Внимание');
void(0);"
onClick="document.hf.submit();">
Нажми на ссылку
</A>
Результат
исполнения:
Нажми на ссылку
Согласно
примеру при нажатии на гипертекстовую ссылку
произойдет не только выдача сообщения,
которое в этой ссылке указано, но и событие
Submit для формы. В итоге вы получите два
окна предупреждения. Но второе окно вы не
заказывали!
Конечно,
бесконтрольной передачи данных на сервер
можно избежать, введя режим подтверждения
отправки. Но, во-первых, многие пользователи
его отключают, а во-вторых, можно
использовать не формы, а, например, графику.
И эту возможность мы рассматриваем в разделе
"Невидимый код".
Невидимый
код
Вопрос
доступности JavaScript-кода рассматривается
с двух точек зрения: идентификация, как
следствие — необходимость сокрытия кода, и
безопасность пользователя, следовательно —
доступность кода.
Приемы
программирования со скрытым кодом позволяют
решить еще несколько задач, которые не
связаны с безопасностью.
Мы будем
рассматривать возможность использования
скрытого кода без выдачи вердиктов о
преимуществе того или иного подхода.
Рассмотрим несколько вариантов:
-
невидимый фрейм;
- код
во внешнем файле;
-
обмен данными
посредством встроенной графики.
Строго
говоря, первый и последний варианты не
скрывают код полностью. Они рассчитаны либо
на неопытных пользователей, либо на
нелюбопытных. Так или иначе, не каждый же
раз вы будете смотреть исходный текст
страницы.
Невидимый
фрейм
Технология
программирования в невидимом фрейме основана
на том, что при описании фреймовой структуры
можно задать конфигурацию типа:
<FRAMESET
COLS="100%,*">
<FRAME NAME=left SRC=hcfl.htm>
<FRAME NAME=right SRC=hcfl.htm>
</FRAMESET>
При таком
размещении страниц по фреймам и фреймов в
рабочей области окна левый фрейм займет весь
объем рабочей области окна, а содержание
правого будет скрыто. Именно в этом
невидимом фрейме мы и разместим код
программы.
При
нажатии на кнопку "Пример невидимого фрейма"
откроется новое окно. Если присмотреться
внимательно, то кроме картинки с правой
стороны окна можно увидеть вертикальную
границу. Это граница фрейма. Ее можно
двигать. В правый невидимый фрейм мы
поместили функцию
подкачки картинок. Этот прием позволяет
загружать картинки с сервера тогда, когда
содержание левого фрейма уже отображено.
Если функцию
разместить в главном окне, то время
отображения будет зависеть от многих
факторов, например картинки, размещенные в
заголовке документа, браузер начнет
перекачивать раньше картинок в теле
документа. При последовательном обмене это
будет означать увеличение времени загрузки
отображаемой части страницы.
Код во
внешнем файле
Попав на
данную страницу, вы уже использовали
программу из внешнего файла. Чтобы убедиться
в этом, достаточно посмотреть на
HTML-разметку данной страницы:
<HTML>
<HEAD>
...
<SCRIPT LANGUAGE=JavaScript
SRC="../css/jsc.pgm">
</SCRIPT>
...
</HEAD>
<BODY onLoad="jump();">
...
</BODY>
</HTML>
Контейнер
SCRIPT определяет внешний файл размещения
скриптов. Функция
jump()
расположена именно в этом файле. Она
анализирует ссылку на данный документ, и
если в ней есть компонент
hash(#), то
она продергивает файл до якоря, указанного в
hash. Чтобы в
этом убедиться, перейдите по любой
внутренней ссылке, например, из меню
разбивки раздела на подразделы, а после
этого перезагрузите документ по Ctrl+R.
Сначала документ будет загружен, а потом
прокручен до указанного якоря.
Обмен
данными посредством встроенной графики
Данный
прием основан на двух идеях: возможности
подкачки графического образа без
перезагрузки страницы и возможности подкачки
этого графического образа не через указание
URL графического файла, а через CGI-скрипт,
который возвращает
Content-type: image/... или
осуществляет перенаправление.
При этом
следует учитывать, что использовать метод,
отличный от GET, можно только в формах, а мы
хотим просто менять значение свойства
src:
...
function change_image(x)
{
s =
"http://intuit.ru/cgi-bin/image_script?"+
document.cookie;
document.x.src= s;
...
<a HREF="javascript:change_image(i);
void(0);">
<IMG NAME=i SRC=image1.gif>
</A>
Эта
безобидная последовательность операторов
JavaScript позволит нам узнать получил ли
клиент cookie. "Волшебные ключики" могут не
поддерживаться по разным причинам. В данном
случае программа передает на сервер
выставленные им "ключики" в качестве
параметра скрипта под видом изменения
картинки.
Модель
безопасности
При
программировании на JavaScript потенциально
существует возможность доступа из программы
к персональной информации пользователя.
Такая проблема возникает всегда, когда нечто,
запускаемое на компьютере, имеет возможность
самостоятельно организовать
обмен данными
по сети с удаленным сервером.
От версии
к версии управление защитой таких данных
постоянно совершенствуется, но всегда нужно
иметь в виду, что множество "следопытов"
исследует эту проблему и постоянно открывает
все новые и новые возможности обхода
механизмов защиты.
Объясним
только основные моменты в принципах защиты
информации в JavaScript, а поиск
потенциально слабых мест оставим в качестве
домашнего задания для наиболее пытливых
читателей.
По
умолчанию к защищенным в JavaScript данным
относятся:
Объект |
Свойства |
Document |
cookie, domain,
forms[], lastModified, links[],
location, referer, title, URL |
Form |
action |
document.forms
[].elements[] |
checked,
defaultChecked, defaultValue,
name, selectedIndex, toString,
value |
History |
current, next,
previous, toString(), all array
elements |
Location, Link,
Area |
hash, host,
hostname, href, pathname, port,
protocol, search, toString() |
Option |
defaultSelected,
selected, text, value |
Window |
defaultStatus,
status |
Защищенными эти данные являются с той точки
зрения, что программа не может получить
значения соответствующих атрибутов. Главным
образом речь здесь идет о программе, которая
пытается получить доступ к данным, которые
определены на другой странице (не на той, в
рамках которой данная программа исполняется).
Например, к данным из другого окна.
В
настоящее время известны три модели защиты:
запрет на доступ (Navigator 2.0), taint
model (Navigator 3.0), защита через Java
(Navigator 4.0). Применение моделей и
соответствующие приемы программирования —
это отдельный сложный вопрос, требующий
знаний и навыков программирования на языке
Java, поэтому в рамках данного курса мы его
рассматривать не будем.
Отметим
только, что к большинству свойств
объектов
текущей страницы и окна программист имеет
доступ. Они становятся защищенными только в
том случае, если относятся к документу в
другом окне и загруженному из другого Web-узла.
Поэтому ограничения, накладываемые системой
безопасности JavaScript, достаточно гибкие и
не очень сильно мешают разработке страниц с
применением этого языка программирования.
|