Добавление и удаление элементов
Одной из задач, которая становится все более распространенной в современных приложениях JavaScript, является возможность добавления или удаления элементов страницы. Предположим, что имеется форма, которая позволяет послать кому-нибудь ссылку. Обычно используется одно поле ввода для адреса e-mail и второе - для имени получателя. Если требуется послать ссылку нескольким адресатам, то либо придется посылать форму несколько раз, либо можно было бы разместить на странице более одного набора полей имя/e-mail. Но в этом случае мы все еще ограничены заданным числом контактов. Если имеется пространство для 5 контактов и необходимо послать ссылку 20 людям, то форму придется заполнять 4 раза.
JavaScript позволяет обойти эту проблему, делая возможным динамическое дополнение и удаление содержимого страницы:
1 var inputs = 0; 2 function addContact(){ 3 var table = document.getElementById('contacts'); 4 5 var tr = document.createElement('TR'); 6 var td1 = document.createElement('TD'); 7 var td2 = document.createElement('TD'); 8 var td3 = document.createElement('TD'); 9 var inp1 = document.createElement('INPUT'); 10 var inp2 = document.createElement('INPUT'); 11 12 if(inputs>0){ 13 var img = document.createElement('IMG'); 14 img.setAttribute('src', 'delete.gif'); 15 img.onclick = function(){ 16 removeContact(tr); 17 } 18 td1.appendChild(img); 19 } 20 21 inp1.setAttribute("Name", "Name" +inputs); 22 inp2.setAttribute("Name", "Email"+inputs); 23 24 table.appendChild(tr); 25 tr.appendChild(td1); 26 tr.appendChild(td2); 27 tr.appendChild(td3); 28 td2.appendChild(inp1); 29 td3.appendChild(inp2); 39 31 inputs++; 32 } 33 function removeContact(tr){ 34 tr.parentNode.removeChild(tr); 35 } 36 <table> 37 <tbody id="contacts"> 38 <tr> 39 <td colspan="3"><a href="javascript:addContact();">Добавьте контакт</a></td> 40 </tr> 41 <tr> 42 <td></td> 43 <td>Name </td> 44 <td>Email</td> 45 </tr> 46 </tbody> 47 </table>
Демонстрация
Добавьте контакт
Name Email
Возможно вам не приходилось ранее использовать тег TBODY. Многие браузеры автоматически добавляют этот тег в DOM, не сообщая об этом. Если необходимо изменить содержимое таблицы, то в действительности необходимо изменить содержимое TBODY. Во избежание возможных недоразумений мы просто добавили тег TBODY, чтобы каждый мог бы его видеть. Все это может показаться достаточно сложным, но здесь очень мало нового материала.
Прежде всего здесь имеется новая функция: document.createElement. Функция createElement создает задаваемый аргументом элемент. Можно видеть, что в строках сценария с 5 по 10 создается несколько элементов. В действительности создается новая строка TR, которая вставляется в таблицу в строках 37-46. В результате новая строка TR будет выглядеть следующим образом:
<tr> <td> <img src="delete.gif"> </td> <td> <input name="Name1"> </td> <td>Если это <input name="Email1"> </td> </tr>
Другими словами, мы создали 7 элементов: 1 TR, 3 TD, 2 INPUT и 1 IMG. Тег IMG будет использоваться как изображение кнопки "Удалить ". Так как пользователь должен всегда видеть по крайней мере 1 строку ввода, то первую строку удалить невозможно. Поэтому в 12 строке сценария проверяется, что создается первая строка. Если строка не первая, то добавляется изображение.
После создания всех этих элементов остается в действительности поместить их в документ. Каждый элемент на странице имеет встроенную функцию " appendChild ", которую можно использовать для добавления к этому элементу потомка. Когда добавляется потомок, то он добавляется как последний элемент, поэтому если таблица уже имеет в качестве потомков 10 тегов TR и добавляется еще один, то он будет добавлен как 11-ый тег TR. Мы начинаем с получения ссылки на таблицу (строка 3). Затем мы добавляем TR к этой таблице (строка 24) и добавляем затем 3 TD (строки 25-27). Второй и третий TD содержат поле ввода, поэтому мы добавляем эти поля ввода (28-29).
Вот и все! Теперь у нас есть новый элемент TR, и он находится на странице. Осталось пояснить еще пару моментов. Чтобы форма была обработана правильно, все поля ввода должны иметь различные имена. Поэтому мы задаем имя двух полей ввода на основе счетчика (21-22), а затем увеличиваем счетчик (31). Это делается с помощью еще одной новой функции setAttribute, которая имеет два параметра: имя атрибута и значение атрибута. Для нее существует дополнительная функция getAttribute, которая имеет только один аргумент: имя атрибута, значение которого надо получить.
element.setAttribute("name", "elementName")
по сути то же самое, что
element.name="elementName"
Однако задание атрибута непосредственно, как в предыдущем примере, может иногда вызывать некоторые проблемы для различных браузеров или для некоторых специфических атрибутов. Поэтому хотя любой метод обычно будет работать, предпочтительным является первый метод, использующий setAttribute.
Необходимо также позаботиться о кнопке удаления. Мы уже знаем, что кнопка удаления для первой строки полей не создается, но необходимо заставить ее работать для всех остальных. Это делается в строках кода 15-16. Здесь к изображению добавлена функция onclick, которая вызывает функцию removeContact, передавая элемент TR в качестве единственного аргумента.
Взглянув на функцию removeContact, можно видеть, что прежде всего происходит обращение "tr.parentNode " к функции parentNode, которая является еще одной функцией для работы с DOM. Она просто возвращает порождающий элемент для текущего элемента. Если посмотреть на изображенное ранее дерево документа, то видно, что parentNode вернет элемент непосредственно над элементом, на котором он вызван. Поэтому, если вызвать parentNodeна одиночном элементе A в этом дереве, то будет получена ссылка на элемент TD над ним.
Поэтому tr.parentNodeвозвращает ссылку на элемент TABLE над TR. Затем вызывается функция removeChild на этом элементе TABLE, которая просто удаляет у предка указанного потомка.
Взглянув еще раз на строку 34, можно теперь увидеть, что она просто говорит: "Удалить элемент TR у его предка " или еще проще "Удалить элемент TR".
on_load_lecture()
« |
1
|
2
|
3
|
4
|
вопросы | »
учебники
|
для печати и PDA
Курсы | Учебные программы | Учебники | Новости | Форум | Помощь Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование |
Изображение: Блок-схема документа
Изображение может помочь лучше понять отношения предок-потомок в этом коде.
Необходимо также отметить атрибуты в некоторых из этих тегов. Например, тег TABLE (<table border="0" cellspacing="2" cellpadding="5">) имеет 3 заданых атрибута: border, cellspacing и cellpadding. При изменении DOM часто бывает необходимо изменить эти атрибуты. Можно, например, изменить атрибут SRC тега IMG, чтобы изменить выводимое изображение. Это часто делают, например, для создания эффекта изменения изображения, на которое направлен указатель (rollover).
Элементы потомки
Ко всем потомкам элемента можно обратиться с помощью атрибута childNodes, который возвращает массив, содержащий все узлы потомки текущего элемента. Можно также использовать атрибуты firstChild и lastChild на любом элементе, чтобы получить ссылки на первый или на последний элемент.
Чтобы увидеть, как это работает, давайте напишем сценарий для раскраски чередующихся строк TR в таблице:
function setColors(tbody, color1, color2){ var colors = [color1, color2]; var counter = 0; var tr = tbody.firstChild;
while(tr){ tr.style.backgroundColor = colors[counter++ % 2]; tr = tr.nextSibling; } }
Демонстрация
Row 1 Row 2 Row 3 Row 4 Row 5 Row 6
Color #1: Color #2: Раскрасьте таблицу
При рассмотрении этого небольшого фрагмента кода мало что нужно пояснять в том, как можно получить этот интересный небольшой эффект. Код начинается с получения ссылки на первый элемент TR в таблице с помощью метода firstChild. Затем каждый TR раскрашивается по очереди двумя разными цветами, используя tr.style. Цвет фона задается одним из двух цветов из массива colors. Если counter имеет четное значение, то цвет фона задается как color1. Иначе он задается как color2. Это реализуется с помощью оператора деления по модулю (%). Для тех, кто забыл, напомним, что операция вычисляет остаток при делении. 5/2 = 2 с остатком 1. Поэтому 5 % 2 (5 по модулю 2) = 1.
Здесь не будет обсуждаться в данный момент изменение стилей, но достаточно сказать, что element.style предоставляет доступ ко всему, что можно задать с помощью таблицы стилей. Если нужно, например, задать стиль элемента, то можно прочитать/записать весь стиль с помощью element.style.cssText.
После задания цвета фона берется следующий элемент TR в таблице. Это делается с помощью функции nextSibling, которая возвращает следующий элемент в DOM, с тем же предком, что и текущий элемент. Если посмотреть на тег TABLE, то все его потомки являются элементами TR, поэтому nextSibling будет в цикле перебирать все элементы TR. Если отыскивается элемент TR с потомками, состоящими из элементов TD, то nextSiblingбудет циклически перебирать все элементы TD. Когда элементов TR больше не останется, цикл автоматически закончится, так как TR будет неопределенным, что в JavaScript оценивается как false.
С целью рассмотрения оператора childNodes и функции getElementsByTagName перепишем приведенный пример немного по-другому:
function setColors(tbody, color1, color2){ var colors = [color1, color2];
for(var i=0; i<tbody.childNodes.length; i++){ tbody.childNodes[i].style.backgroundColor = colors[i % 2]; } }
function setColors(tbody, color1, color2){ var colors = [color1, color2]; var trs = tbody.getElementsByTagName('TR');
for(var i=0; i<trs.length; i++){ trs[i].style.backgroundColor = colors[i % 2]; } }
Обе эти функции делают то же самое, что и первая функция setColors, но написано это немного по-другому. Первая функция использует атрибут childNodes. Как ранее говорилось, childNodes содержит массив, элементами которого являются потомки. Поэтому мы циклически перебираем tbody.childNodes и изменяем цвет каждого потомка, которые все должны быть элементами TR.
Другая функция использует новую функцию getElementsByTagName, которая выдает массив всех элементов с указанным именем тега. Так как нам требуются все элементы TR, то мы просто передаем в функцию 'TR' и получаем список всех элементов TR в таблице. После этого код почти идентичен предыдущей функции.
Работа с текстом
Работа с текстом немного отличается от работы с другими элементами DOM. Первое: каждый фрагмент текста на странице помещен в невидимый узел #TEXT. Поэтому следующий код HTML
<div id="ourTest">this is <a href="link.html">a link</a> and an image: <img src="img.jpg"></div>
имеет четыре корневых элемента: текстовый узел со значением "this is ", элемент A, еще один текстовый узел со значением " and an image: " и, наконец, элемент IMG. Элемент A имеет конечный текстовый узел в качестве потомка со значением " a link ". Когда необходимо изменить текст, то прежде всего необходимо получить этот "невидимый" узел. Если мы хотим изменить текст " and an image: ", то необходимо написать:
document.getElementById('ourTest').childNodes[2].nodeValue = 'our new text';
document.getElementById('ourTest') дает нам тег div. childNodes[2] дает узел текста " and an image: " и наконец nodeValue изменяет значение этого узла текста.
Что, если требуется добавить к этому еще текст, но не в конце, а перед " a link "?
var newText = document.createTextNode('our new text'); var ourDiv = document.getElementsById('ourTest'); ourDiv.insertBefore(newText, ourDiv.childNodes[1]);
Первая строка показывает, как создать текст с помощью document.createTextNode. Это аналогично функции использованной ранее функции document.createElement. Третья строка содержит еще одну новую функцию insertBefore, которая аналогична appendChild, за исключением того, что имеет два аргумента: добавляемый элемент и существующий элемент, перед которым надо сделать вставку. Так как мы хотим добавить новый текст пред элементом A и знаем, что элемент A является вторым элементом в div, то мы используем ourDiv.childNodes[1] в качестве второго аргумента для insertBefore.
По большей части это все манипуляции с DOM. Если требуется создать, например, поле с изменяемым размером, то для изменения ширины и высоты поля будут использоваться те же функции мыши и функции getAttribute и setAttribute. Очень похожим образом, если изменять верхнюю и левую позицию стиля элемента, то можно перемещать элементы по странице, либо в ответ на ввод мыши (перетаскивание), либо по таймеру (анимация).
В качестве последнего замечания к этой лекции: одним из наиболее полезных средств при попытке протестировать или отладить код JavaScript, который изменяет DOM, является сценарий обхода дерева DOM. Проще говоря - это сценарий, который показывает каждый элемент и каждый атрибут объекта DOM. Описание этого кода выходит за рамки этой лекции, но он мог бы, например, показывать все атрибуты и объекты-потомки любого получаемого в качестве аргумента объекта.
Теперь можно включить свое воображение и экспериментировать, так как почти нет ничего такого, чего нельзя сделать со страницей HTML, когда вы знаете, как обращаться с DOM. В следующей лекции будут рассмотрены объекты окна и документа.
XML
Те, кто знает, что такое документ XML, могут пропустить этот раздел. Остальным необходимо его прочитать.
Будем надеяться, что читатель в какой-то степени знаком с HTML. Это то, из чего состоит (почти) каждая Web-страница. Каждое изображение, ссылка, таблица, поле формы и т.д. имеют свой собственный тег. Ниже приведен пример простой страницы HTML:
<HTML> <BODY> <table border="0" cellspacing="2" cellpadding="5"> <tr> <td colspan="2"><img src="Greetings.jpg" id="greetingImg" /></td> </tr> <tr> <td> Добро пожаловать на мою страницу HTML! <br /> <a href="somelink.html" id="myLink" >Щелкните здесь!</a> </td> <td><img src="hello.jpg" id="helloImg" /></td> </tr> </table> </BODY> </HTML>
Это просто обычная страница HTML. Возможно, вы не знаете о том, что это также пример документа XML. Здесь нас интересует то, что каждый элемент является потомком и/или предком другого элемента. Первое изображение находится внутри тега TD, который находится внутри тегов TR, TABLE, BODY и HTML. Двигаясь в другом направлении, можно видеть, что тег BODY имеет одного "потомка" - тег TABLE. Этот тег TABLE имеет в качестве потомков два тега TR и т.д. По сути именно так мы перемещаемся в документе XML или HTML DOM - двигаясь от потомка к предку или от предка к потомку.
Document.body и document.documentElement
document.body ссылается на тег <BODY>, где должен предположительно находится весь контент. Весь DOM сайта вложен в document.body. Кроме этого, необходимо использовать document.body для определения, что документ был прокручен, и для получения размера окна. К сожалению, это является одной из наиболее сложных вещей, применяемых сегодня в Web-браузерах.
Существует концепция, называемая "Тип документа", которая задает для Web-браузера определенный набор правил. Изменение типа документа заставляет некоторые свойства переместиться из document.body в document.documentElement, но только некоторые свойства и только для некоторых браузеров.
Проще говоря, это является полным беспорядком, поэтому две следующие функции (будем надеятся) выдадут позицию прокручивания и размеры окна независимо от браузера.
function getScrollPos(){ if (window.pageYOffset){ return {y:window.pageYOffset, x:window.pageXOffset}; } if(document.documentElement && document.documentElement.scrollTop){ return {y:document.documentElement.scrollTop, x:document.documentElement.scrollLeft}; } if(document.body){ return {y:document.body.scrollTop, x:document.body.scrollLeft}; } return {x:0, y:0}; }
function getWindowDims(){ if (window.innerWidth){ return {w:window.innerWidth, h:window.innerHeight}; } if (document.documentElement && document.documentElement.clientWidth){ return {w:document.documentElement.clientWidth, h:document.documentElement.cliendHeight}; } if (document.body){ return {w:document.body.clientWidth, h:document.body.clientHeight}; } return {w:0, h:0} }
Объект Document (window.document)
Одной из наиболее часто используемых функций в JavaScript является document.write. Можно сказать, что document.write получает строку и выводит ее на странице. Здесь необходимо только следить за одной вещью. Если страница полностью загрузилась и вызывается document.write, то вся страница будет очищена и на экране будет только результат работы document.write.
Мы уже видели различные свойства объекта document в действии. Например, document.forms возвращает массив всех форм на странице. Здесь также существует несколько свойств, подобных этому.
document.forms - массив, содержащий все формы на текущей странице; document.images - массив, содержащий все изображения на текущей странице; document.links - массив, содержащий все ссылки на текущей странице; document.anchors - массив, содержащий все анкеры на текущей странице; document.applets - массив, содержащий все апплеты на текущей странице; document.styleSheets - массив, содержащий все таблицы стилей на текущей странице; window.frames - массив, содержащий все фреймы на текущей странице.
Как мы видели в предыдущей лекции, почти все эти свойства можно продублировать с помощью document.getElementsByTagName. Чтобы получить все изображения на странице, можно воспользоваться, например, document.getElementsByTagName('IMG');. Существует три подобные функции:
document.getElementById - возвращает один элемент на основе его ID; document.getElementsByName - возвращает массив элементов, определенных по имени. В отличие от ID многие элементы могут иметь на странице одинаковые имена; document.getElementsByTagName - возвращает массив элементов, определенных по имени тега. Имя тега является просто именем тега HTML, т.е. 'DIV', 'IMG', 'TABLE', 'A 'и т.д.
Существует еще одно свойство, document.all, которое выдает массив всех элементов на странице. Однако document.all поддерживается не всеми браузерами, поэтому предполагается, что вместо этого используется функция document.getElementsByTagName('*'), которая также вернет все элементы на странице.
Объект Window
Прежде всего объект window предоставляет доступ к информации об окне:
window.location | возвращает текущий URL окна |
window.opener | Если окно было открыто другим окном (с помощью window.open), то возвращается ссылка на открывающее окно, иначе null |
MSIE: window.screenTop Другие: window.screenY | Возвращает верхнюю позицию окна. Отметим, что эти значения в MSIE существенно отличаются от других браузеров. MSIE возвращает верхнюю позицию области содержимого (ниже адресной строки, кнопок, и т.д.). Другие браузеры возвращают верхнюю позицию реального окна (выше кнопки закрытия) |
MSIE: window.screenLeft Другие: window.screenX | Возвращает левую позицию окна с такими же различиями, как и для screenTop и screenY |
Положение окна на экране пользователя редко бывает необходимо, но два других свойства, location и opener, будут очень полезны. Свойство window.location выполняет две функции. Если изменить его с помощью JavaScript, например, window.location='http://www.htmlgoodies.com', то браузер будет перенаправлен на эту страницу. Чтение window.location выдает адрес текущего документа. Зачем это нужно знать? Обычно адрес страницы не нужен, но может потребоваться строка запроса или анкер. Возьмем, например, следующий URL:
http://www.somesite.com/page.asp?action=browse&id=5#someAnchor. Этот URL можно разбить на три части:
URL: | http://www.somesite.com/page.asp |
Строка запроса: | action=browse&id=5 |
Анкер: | someAnchor |
Так как window.location содержит всю эту информацию, то можно написать функцию, которая будет возвращать переменную querystring (строку запроса). Это аналогично request.querystring в ASP или $_GET в PHP, если вы знакомы с каким-либо из этих языков:
function queryString(val){ var q = unescape(location.search.substr(1)).split('&');
for(var i=0; i<q.length; i++){ var t=q[i].split('='); if(t[0].toLowerCase()==val.toLowerCase()) return t[1]; } return ''; }
Для предыдущего URL функция queryString('action') вернет 'browse'. Мы видим здесь новую функцию "window.unescape ". Функция unescape, а также ее дополнительная функция escape, используются в соединении с window.location. При передаче данных в строке запроса она должна быть экранирована ("escaped "), чтобы она не влияла на саму строку запроса. Если, например, среди данных имеется знак амперсанд (&), то необходимо его экранировать, чтобы можно было различить этот знак в данных и тот &, который разделяет два различных значения. Функция escape подготавливает посылаемые данные для использования в качестве значения querystring, поэтому она используется при задании window.location. Например:
window.location='/page.asp?name='+escape(SomeInputBox.value);
Функция unescape делает обратное и позволяет получить "нормальный" текст из window.location.
Вернемся к свойствам window, где имеется свойство "opener ". Это свойство используется в соединении с обычно используемой функцией window.open, которая позволяет открывать новое окно браузера и, для некоторых свойств управлять его выводом. Блокировщики всплывающих окон очень часто будут препятствовать открытию окна с помощью window.open, если в этот процесс не вовлечен щелчок мышью. Поэтому, если в коде имеется вызов window.open и при этом пользователь не щелкает на ссылке или чем-то подобном, то скорее всего это не будет работать.
Функция window.open получает до 3 аргументов: URL окна, которое надо открыть, имя окна и свойства окна.
var newWindow=window.open('', 'TestWindow', 'width=200,height=200'); newWindow.document.write('Это окно будет закрыто через 2 секунды'); setTimeout(function(){ newWindow.close(); }, 2000);
Третий аргумент window.open получает строку аргументов. Обычно используются следующие:
width, height - задают размеры окна;left, top - задают положение окна на экране; location, menubar, toolbar, status, titlebar, scrollbars - эти параметры выводят/скрывают свои соответствующие "панели" на окне. Задайте "yes ", чтобы вывести соответствующую "панель";resizable - если задан как "yes ", то размер окна можно изменять.
Полное описание window.open можно увидеть в документации Mozilla.
Так как мы открываем пустое окно, то первый аргумент будет пустым. Для открытия страницы 'test.html' вызов выглядел бы следующим образом: window.open ('test.html', 'TestWindow', 'width=200,height=200').
В этом примере для объекта window, открываемого окна, задается переменная newWindow. В связи с этим для вывода содержимого в окне необходимо использовать "newWindow.document.write ".
Функция window.open также имеет свою противоположность, функцию window.close. Однако эта функция может успешно вызываться только на окнах, созданных JavaScript. Если попробовать закрыть окно, созданное не JavaScript, то возможны два варианта: либо появится сообщение, говорящее, что сценарий пытается закрыть окно, либо браузер просто это проигнорирует.
SetTimeout и setInterval
Можно видеть, что в этом примере используется еще одна новая функция setTimeout. Функции setTimeout и setInterval применяются для выполнения кода после указанного интервала времени и обе получают два аргумента: функцию или строку кода и период ожидания в мс. 1 мс = 1/1000 секунды, поэтому для задания выполнения кода через 5 секунд необходимо определить в этом случае для второго аргумента значение 5000.
setTimeout выполнит код один раз после завершения заданного интервала времени. setInterval будет продолжать выполнять код после завершения каждого интервала. При заданном интервале 5000 setInterval будет выполнять код каждые 5 секунд.
Существуют еще две другие функции: clearTimeout и clearInterval, которые отменяют выполнение, через заданные интервалы. Однако для этого необходимо иметь ссылку на вызов setTimeout или setInterval, например:
var myTimeout = setTimeout("alert('Hi!');", 500); clearTimeout(myTimeout);
Если не сохранить ссылку в переменной myTimeout, то не существует способа отменить заданное выполнение. Давайте посмотрим на пример этого в действии:
function createTimeout(text, time){ setTimeout("alert('"+text+"');", time); }
var intervals = []; function createInterval(text, time){ // сохраняет интервал в массиве intervals intervals.push(setInterval("alert('"+text+"');", time)); }
function tut5(){ if(intervals.length==0) return; // удаляет последний интервал выполнения в массиве intervals clearInterval(intervals.pop()); }
Демонстрация в действии
Текст для вывода:
Время ожидания(в мс):
setTimeout setInterval clearInterval
Существует также функция clearTimeout, которая идентична по синтаксису clearInterval.
Важно отметить, что во время ожидания выполнения заданного кода функциями setTimeout или setInterval весь остальной код JavaScript продолжает выполняться. Когда функция setTimeout или setInterval будет готова, она выполнит заданный код, но только после того, как другой код закончит выполнение. Другими словами, setTimeout и setInterval никогда не прерывают для выполнения другой код.
Сookie
Последняя тема этой лекции, переменная cookie, отличается от всего остального в JavaScript. cookie является строкой текста, которую можно сохранить с одной страницы на другой, если вы находитесь на одном и том же сервере. В отличие от других переменных в JavaScript, cookie не стирается при перезагрузке страницы. cookie стираются только через определенный период времени или когда все cookie удаляются в браузере.
cookie читают и записывают через document.cookie. В отличие от других свойств изменение document.cookie в действительности не перезаписывает, а добавляет к cookie. Поэтому, если требуется задать 5 cookie, то каждое из них задается с помощью document.cookie= "...";. Формат cookie имеет свои особенности, поэтому мы рассмотрим несколько функций для выполнения этой задачи:
function writeCookie(name, value, days){ if(days){ (time = new Date()).setTime(new Date().getTime()+days*24*60*60*1000); var exp = '; expires='+time.toGMTString(); }else{ var exp=''; } document.cookie=name+"="+value+exp+"; path=/"; }
function readCookie(name){ var cookies = document.cookie.split(';'); for(var i=0; i<cookies.length; i++){ var cookie=cookies[i].replace(/^\s+/, ''); if (cookie.indexOf(name+'=')==0) return cookie.substring(name.length+1); } return null; }
function eraseCookie(name){ writeCookie(name, "", -1); }
Три эти функции выполняют запись, чтение и стирание cookie на текущей странице. Их можно протестировать с помощью следующего кода:
function addToCounter(){ var counter = readCookie('myCounter'); if(counter){ counter = parseInt(counter); } else { counter = 0; }
writeCookie('myCounter', counter+1, 1); }
function showCounter(){ alert(readCookie('myCounter')); }
Если увеличить счетчик cookie несколько раз, обновить страницу, а затем проверить счетчик, то можно увидеть, что он остался таким же, как был до обновления страницы. Эти cookie будут сохранятся до тех пор, пока они не будут удалены из браузера или пока не пройдет 24 часа - cookie заданы на период одни сутки.
Это почти все об объектах окна и документа. В следующей лекции речь пойдет об объектно-ориентированном коде.
New Object и объектные литералы
Для начала необходимо объяснить, как объекты действуют в JavaScript. Объекты позволяют определить переменную и задать затем для этой переменной любое число свойств. Чтобы понять это, давайте посмотрим на простой пример:
var myObj = new Object; myObj.a = 5; myObj['b'] = 10; myObj.c = 20; myObj.getTotal = function(){ alert(this.a+this.b+this.c); });
// или
var myObj = {a:5, b:10, c:20, getTotal:function(){ alert(this.a+this.b+this.c); }};
Оба эти фрагмента кода создают одну и ту же переменную myObj. Первый пример использует синтаксис new Object(), а затем определяет все свойства одно за другим. Второй фрагмент кода является сокращенной нотацией, которая делает в точности то же самое. Мы имеем теперь переменную myObj. myObj содержит три переменные для целых чисел: a, b и c. Для доступа к любой из них мы просто используем myObj.a или myObj['a']. Можно видеть, что myObj имеет также в качестве свойства функцию getTotal. Мы обращаемся к getTotal таким же образом, как и к свойствам a, b и c: myObj.getTotal(). Функция getTotal() обращается к переменным в переменной myObj с помощью "this". При выполнении кода внутри функции в объекте "this" ссылается на объект. В данном случае "this" ссылается на сам объект myObj.
Как можно видеть, объекты в JavaScript являются чрезвычайно полезными. К сожалению, часто приходится объявлять слишком много информации каждый раз, когда мы хотим создать объект. Например, давайте создадим объект Animal:
var myAnimal = { name: 'felix', species: 'cat', talk: function(){ alert('Meow!'); }, callOver: function(){ alert(this.name+' ignores you'); }, pet: function(){ alert('Purr!'); } }
Мы имеем переменную myAnimal, которая представляет кота (cat) с именем Феликс (felix). Однако, если потребуется создать другого кота, нам понадобиться ввести все это снова. Здесь на помощь приходит объектно-ориентированный подход. Вместо перепечатки всякий раз всего объекта можно определить функцию, которая создает для нас аналогичный объект:
function Cat(name){ this.name = name;
this.species = 'Cat'; this.talk = function(){ alert('Meow!'); } this.callOver = function(){ alert(this.name+' ignores you'); }, this.pet = function(){ alert('Purr!'); } }
var felix = new Cat('Felix'); var sam = new Cat('Sam'); var patty = new Cat('Patty');
felix.pet(); // выводит 'Purr!' sam.callOver(); // выводит 'Sam ignores you'. Просто, как кот! alert(patty.species); // выводит 'cat'
В этом примере создана одна функция Cat, а затем создаются три новых кота с помощью этой функции: Felix, Sam и Patty. Каждый из этих котов имеет одинаковые функции: talk, callOver и pet, и каждая из них имеет свойство species, заданное как 'cat'. И подобно всем котам, они в равной степени своенравны.
Обычно говорят, что felix, sam и patty являются экземплярами одного объекта: Cat. Код самой функции Cat называется конструктором. Мы передаем ей переменную "name" и используем ее для задания this.name. К сожалению, при объявлении каждой функции Cat в конструкторе в действительности создается новая копия каждой функции всякий раз при создании нового Cat. Так как функции talk, callOver и pet являются идентичными, то нам в действительности требуется только одна копия каждой функции. Здесь на помощь приходит прототипирование.
Основы JavaScript
Почти каждый современный язык программирования имеет некоторый метод записи объектно-ориентированного (ОО) кода. Если вы знакомы с такими языками, как C, Java, VB, php или подобными, то должны быть знакомы и со структурой Class этих языков.
Классы в этих языках позволяют быстро создавать объекты с одинаковыми свойствами и методами, не требуя при этом заново определять все эти свойства и методы. В терминах структур мы имеем классы, а внутри этих классов - функции и переменные, а внутри каждой из этих функций - дополнительные переменные. Каждая функция в классе идеально работает с переменными и другими функциями, внутренними для этого класса.
Структура Class и объекты в целом хорошо работают потому, что они действуют как реальные объекты. Например, мы можем иметь класс Телевизор. Так как все телевизоры в целом работают одинаково, то нет необходимости переопределять множество атрибутов телевизора, чтобы сделать еще один новый. Этот класс будет определять методы и свойства телевизора. Мы будем иметь такие свойства, как марка, размер экрана, и т.д., и методы, такие, как включить/выключить, сменить канал, и т.д.
JavaScript слегка отличается от стандартной объектно-ориентированной структуры Class. Вместо использования классов для создания объектов, каждая функция в JavaScript может эффективно действовать как класс. Мы используем концепцию вложенных функций, называемую прототипированием (Prototyping), для выполнения таких же вещей, как и языки со структурами Class. Если вы знакомы с ОО-языком на основе классов, то прототипирование может показаться сначала непривычной концепцией, но она предлагает большие возможности и часто является значительно более гибкой с точки зрения программирования, чем другие методы.
Переменные Private, Public и Static
Способ определения переменных в объекте определяет, какие методы этого объекта можно использовать для доступа к этим переменным. В JavaScript при работе с объектно-ориентированным кодом используется пять уровней методов и свойств.
Скрытая (Private) - объявляется с помощью 'var variableName' или 'function functionName' внутри объекта. Могут быть доступны только другим скрытым или привилегированным функциям. Открытая (Public) - объявляется с помощью 'this.variableName' внутри объекта. Может изменяться любой функцией или методом. Привилегированная (Privileged) - объявляется с помощью 'this.functionName = function(){ ... }' внутри объекта. Доступна для любой функции или метода и может обращаться или изменять любую скрытую переменную. Прототипированная (Prototype) - объявляется с помощью 'Class.prototype.variableName' или 'Class.prototype.functionName'. Объявленные таким образом функции будут иметь доступ к любой открытой или прототипированной функции. Попытки изменить созданную таким образом переменную будут вместо этого создавать новую открытую переменную на объекте, а прототипированная переменная будет недоступна. Статическая (Static) - объявляется с помощью 'Class.variableName' или 'Class.functionName'. Может изменяться любой функцией или методом. Такой метод используется редко.
Чтобы понять различия между уровнями, давайте рассмотрим пример:
function Cat(name, color){ /* Конструктор: при создании объекта выполняется любой находящийся здесь код */ Cat.cats++;
/* Скрытые переменные и функции доступны только скрытым или привилегированным функциям. Отметим, что 'name' и 'color', переданные в Class, уже являются скрытыми переменными. */
var age = 0; var legs = 4; function growOlder(){ age++; }
/* Открытые переменные доступны открыто или скрыто */
this.weight = 1; this.length = 5;
/* Привилегированные функции доступны открыто или скрыто. Могут обращаться к скрытым переменным.
Невозможно изменить, можно только заменить открытой версией */
this.age = function(){ if(age==0) this.length+=20;
growOlder(); this.weight++; } }
/* Прототипированные функции доступны открыто */ Cat.prototype = { talk: function(){ alert('Meow!'); }, callOver: function(){ alert(this.name+' ignores you'); }, pet: function(){ alert('Pet!'); } }
/* Прототипированные переменные доступны открыто. Нельзя перезаписать, только заменить открытой версией */
Cat.prototype.species = 'Cat';
/* Статические переменные и функции доступны открыто */
Cat.cats = 0;
Мы видим, что существует несколько уровней доступа. Как было сказано ранее, все скрытые, привилегированные и открытые функции и переменные копируются всякий раз, когда создается новый экземпляр объекта. Обычно почти все, что нужно сделать, можно реализовать с помощью прототипированных и открытых переменных. В связи с этим обычно лучше избегать использования скрытых, привилегированных и статических переменных, если это не требуется специально.
Прототипирование
Можно переписать функцию Cat таким образом, чтобы каждая функция объявлялась только один раз:
function Cat(name){ this.name = name; } Cat.prototype.species = 'Cat'; Cat.prototype.talk = function(){ alert('Meow!'); }; Cat.prototype.callOver = function(){ alert(this.name+' ignores you'); }; Cat.prototype.pet = function(){ alert('Purr!'); };
Синтаксис этого метода немного отличается от предыдущего. Вместо объявления всех свойств и методов внутри функции Cat, они теперь объявляются с помощью Cat.prototype. Это может показаться более сложным, но предлагает много преимуществ. Предположим, например, что надо добавить новую функцию sleep для каждого имеющегося cat. Это можно сделать двумя способами. Первый: можно добавить функцию sleep в felix, sam и patty. Это, однако, не только трудоемко, но также и неэффективно. Если бы имелось 500 cat, то потребовалось бы сначала отследить всех этих 500 котов, а затем добавить функцию каждому cat.
Однако с помощью прототипов можно добавить функцию sleep всем cat одновременно:
Cat.prototype.sleep = function(){ alert(this.name+' falls asleep'); };
Этот способ не только быстрее, но к тому же нам больше не требуется отслеживать каждого cat, чтобы добавить функцию sleep.
Существует более быстрый способ добавления прототипов. С помощью объектного литерала можно одновременно задать любое количество прототипов свойств или методов.
function Cat(name){ this.name = name; } Cat.prototype = { species: 'Cat', talk: function(){ alert('Meow!'); }, callOver: function(){ alert(this.name+' ignores you'); }, pet: function(){ alert('Pet!'); } }
Важно отметить, что с помощью этого метода можно задать свойства функции только один раз. После этого необходимо использовать предыдущий метод.
Object.prototype.method = function(){ ... }.
Если попробовать теперь добавить в cat метод sleep с помощью этого метода:
Cat.prototype = { sleep: function(){ alert(this.name+' falls asleep'); } }
то предыдущие прототипы, species, talk, callOver и pet, будут все удалены. Единственным имеющимся прототипом для cat будет sleep.
Прототипы можно также использовать для расширения встроенных объектов JavaScript. Можно реализовать, например, функцию String.prototype.reverse, которая будет возвращать любую созданную строку в обратном порядке:
String.prototype.reverse = function(){ var out = ''; for(var i=this.length-1; i>=0; i--){ out+=this.substr(i, 1); } return out; } alert('asdf'.reverse());
Это может быть очень полезным инструментом при правильном использовании. Можно реализовать String.prototype.trim() для удаления из строки всех пробелов, Date.prototype.dayName - для получения названия дня недели из объекта Date и т.д. Однако настоятельно рекомендуется воздержаться от добавления каких-либо прототипов в Array или Object, так как в этом случае циклы "for-in " для двух этих типов данных будут работать неправильно. Рассмотрим следующий пример:
var myArray = [1, 2, 3]; for(n in myArray) alert(n); // выводит 0, 1 и 2 - индексы массива.
Array.prototype.something = function(){ };
for(n in myArray) alert(n); // выводит 'something', 0, 1 и 2.
Как можно видеть, здесь выполнено прототипирование Array и добавлена функция 'something'. Однако теперь эта функция 'something' видна как элемент массива, результат, который определенно не ожидался и не требовался. То же самое происходит с объектами и объектными литералами, если выполнить прототипирование Object. Если можно быть абсолютно уверенным, что цикл "for-in" никогда не будет использоваться и никакой другой разработчик не будет использовать этот код JavaScript, то можно применять прототипирование для Array или Object, но надо помнить о связанных с этим проблемах. Однако существуют другие методы для достижения тех же результатов. Например, для расширения Array можно задействовать следующий метод без прототипирования:
Array.find = function(ary, element){ for(var i=0; i<ary.length; i++){ if(ary[i] == element){ return i; } } return -1; }
alert(Array.find(['a', 'b', 'c', 'd', 'e'], 'b')); // выводит 1
Как можно видеть, теперь необходимо печатать type Array.find(ary, e) вместо ary.find(e), что пришлось бы делать, если прототипировать объект Array, но стоит напечатать эти несколько дополнительных символов, чтобы избежать потери существующей функциональности JavaScript.
Наследование
В предыдущей лекции была создана функция 'Cat' :
function Cat(name){ this.name = name; } Cat.prototype = { species: 'Cat', talk: function(){ alert('Meow!'); }, callOver: function(){ alert(this.name+' ignores you'); }, pet: function(){ alert('Pet!'); } }
Теперь можно создать любое количество котов, но как быть, если мы захотим создать объект другого типа, например, собаку? В этом случае понадобится создать совершенно новую функцию, со своими собственными прототипами. Если два объекта используют одни и те же функции (например, можно было бы добавить функции sleep (спать), eat (есть), и play (играть)), то в результате мы бы имели чрезмерное дублирование кода. Решением является концепция наследования.
По сути наследование позволяет определить объекты "предки" и "потомки". Потомок наследует все свойства своего предка. Можно было создать, например, функцию "Animal", "Pet" или "Mammal". Обе функции Cat и Dog обладали бы многими свойствами функции предка Animal, и нам пришлось бы писать этот код один раз.
Проблема в том, что JavaScript не имеет в действительности встроенного механизма наследования, поэтому эту функциональность необходимо создавать самостоятельно. Для этого существует несколько различных способов. Один из них состоит в использовании функции "call". Эта функция позволяет вызывать одну функцию из контекста другой, т.е. мы можем определить, как действует ключевое слово "this". С помощью "call" можно написать класс Animal (Животное), а затем вызвать его из класса Cat или Dog.
function Animal(name){ this.name = name;
this.species = 'Animal'; this.sleep = function(){ alert(this.name+' спит: Хрррр'); } } function Cat(name){ Animal.call(this, name);
this.talk = function(){ alert('Мяу!'); } } function Dog(name){ Animal.call(this, name);
this.talk = function(){ alert('Гав!'); } }
var sam = new Cat('Sam'); var joe = new Dog('Joe'); sam.sleep(); // Sam спит: Хрррр joe.sleep(); // Joe спит: Хрррр
sam.talk(); // Мяу! joe.talk(); // Гав!
Хотя это работает, мы немного ограничены в своих возможностях. Например, прототипирование не действует при использовании этого метода: все прототипы, заданные на Animal, не будут переноситься в функции Cat или Dog. Как мы знаем из предыдущей лекции, определенные внутренне с помощью "this." функции создают новый экземпляр всякий раз при создании новой копии предка. В этом случае всякий раз при создании функции Animal, Cat или Dog появляется новая копия функций species и sleep. Как можно догадаться, это не самый эффективный способ.
Лучшим подходом является прототипирование всего родительского класса на классе-потомке. Это предоставляет доступ ко всем свойствам и методам класса предка:
function Animal(name){ this.name = name; } Animal.prototype = { species: 'Animal', sleep : function(){ alert(this.name+' спит: Хрррр'); } }
function Cat(name){ Animal.apply(this, arguments); } Cat.prototype = new Animal; Cat.prototype.species = 'Cat'; Cat.prototype.talk = function(){ alert('Мяу!'); }
function Dog(name){ Animal.apply(this, arguments); } Dog.prototype = new Animal; Dog.prototype.talk = function(){ alert('Гав!'); }
var sam = new Cat('Sam'); var joe = new Dog('Joe');
sam.sleep(); // Sam спит : Хрррр joe.sleep(); // Joe спит: Хрррр
alert(sam.species); // Cat alert(joe.species); // Animal - для Dog функция species не определена
Можно продолжить это дальше и создать отдельные функции для различных пород собак или кошек и т.д.
Замыкание
Замыкание (сlosure) является одним из наиболее мощных средств JavaScript. Если воспользоваться простым объяснением, то замыкание связывает внутренние и внешние переменные в функции. Почему это так важно? Потому что замыкание позволяет эмулировать почти любое свойство любого языка программирования, даже если оно не существует в JavaScript. Это звучит немного непонятно, поэтому лучше начать с более простого примера:
function beginAdding(a){ a *= 5; return function finishAdding(b){ alert(a+b); } }
var add = beginAdding(10);
add(20); // 70
Можно видеть, что в приведенном коде переменной 'a' присваивается значение 10 и передается в функцию beginAdding, в то время как переменной 'b' присваивается значение 20 и передается в функцию finishAdding.
А что содержится в переменной 'add'? Она содержит функцию finishAdding с копией связанной с ней всей функции beginAdding. Копия переменной 'a' из beginAdding сохраняется в памяти для дальнейшего использования.
Теперь, имея представление о замыкании, прежде чем продолжать, необходимо обсудить проблему утечки памяти. Internet Explorer, в частности, подвержен достаточно неприятным утечкам памяти при использовании замыкания. Чтобы понять, почему необходимо знать о сборке мусора в Internet Explorer, посмотрим, как в этом браузере выполняется очистка памяти от ненужных объектов.
Internet Explorer имеет два отдельных сборщика мусора: один для JavaScript и другой для объектов DOM. При выгрузке страницы браузер просматривает и удаляет весь код JavaScript и все объекты DOM со страницы. Утечка происходит, когда имеются циклические ссылки из объекта DOM в JavaScript и снова на объект DOM или из JavaScript-->Dom-->Javascript. Internet Explorer запутывается и не удаляет объекты при циклической ссылке.
var someInput = document.getElementById('inputbox'); var someFunction = function(){ alert(someInput.value); }
someInput.onclick = someFunction;
Здесь представлен бесконечный цикл в терминах замыканий. Объект DOM someInput вовлечен в замыкание с функцией someFunction и наоборот. Браузер не может определить, что удалить в первую очередь, поэтому в результате имеем утечку памяти.
Замыкания создают, часто даже не осознавая этого. Возьмем, например, простую функцию:
function Animal(name){ this.sleep = function(){ alert(name+' спит: Хрррр'); } }
Можно не заметить этого, но переменная 'name' в функции sleep приходит из родительской функции Animal. Это создает замыкание.
Даже определение простейшей функции может создавать замыкание:
var x = 5; var n = function(){ y=10; return y; }
Здесь также создается замыкание, хотя на первый взгляд это не так. Почему? Когда мы создаем функцию, она получает доступ ко всем переменным в своей текущей области действия, поэтому мы создаем новую ссылку на переменную х.
Теперь мы подошли к понятию области действия. Каждая функция имеет свою область действия, в которой она выполняется. Все области действия кода сохраняются во внутреннем стеке памяти. Когда создается замыкание, оно получает доступ к одной из этих областей действия. Если создается несколько замыканий в одной и той же области действия, то каждое замыкание будет в действительности указывать на одинаковые копии каждой переменной области действия. Например:
var x = 5; var alertX1 = function(){ alert(x); }
x = 10; var alertX2 = function(){ alert(x); }
alertX1(); alertX2();
Обе функции в этом случае выведут 10. Почему? Потому что они обе указывают на одну и ту же копию х.
Если изменить x в любой точке, то обе функции отразят это. Как это исправить? Проще всего изменить область действия замыкания:
function makeClosure(x){ return function(){ alert(x); } }
var x = 5; var alertX1 = makeClosure(x);
x = 10; var alertX2 = makeClosure(x);
alertX1(); // 5 alertX2(); // 10
Это решает проблему. Если, однако, этот код создавал утечку памяти, то утечка будет существенно больше, чем в предыдущем примере; и также используется больший объем памяти. Оказывается, что в действительности имеется три различных области действия в этом простом примере:
Можно видеть, что переменная х копируется в каждую из двух областей действия. Это связано с тем, что x является строкой или числом. JavaScript всегда передает строки и числа по значению - то есть всегда делается копия переменной. С объектами все происходит иначе. Если х является функцией, массивом или базовым объектом, то в этом случае ссылка в двух наших функциях происходит на одну и ту же копию x и поэтому в результате выводимое сообщение будет одинаковым:
Пример со списком контактов
В действительности уже есть все, что нужно для создания приложений AJAX, но мы рассмотрим достаточно простой пример. Мы собираемся написать небольшую таблицу данных (data grid), которая извлекает данные из трех различных файлов JSON. Для простоты эти файлы уже были сгенерированы. На практике эти файлы будут скорее всего генерироваться оперативно с помощью серверного сценария.
Файл 1
{contacts:[ {"firstname":"Steve" ,"lastname":"Smith", "phone":"555-1212"}, {"firstname":"Joe" ,"lastname":"Stevens", "phone":"555-0193"}, {"firstname":"Sam" ,"lastname":"Smith", "phone":"555-5120"}, {"firstname":"Dave" ,"lastname":"Stevens", "phone":"555-0521"}, {"firstname":"Suzy" ,"lastname":"Smith", "phone":"555-9410"}, {"firstname":"Jessica" ,"lastname":"Stevens", "phone":"555-8521"}, {"firstname":"James" ,"lastname":"Smith", "phone":"555-4781"}, {"firstname":"Jacob" ,"lastname":"Stevens", "phone":"555-9281"}, {"firstname":"Alex" ,"lastname":"Smith", "phone":"555-7261"}, {"firstname":"Tam" ,"lastname":"Stevens", "phone":"555-1820"} ]}
Файл 2
{contacts:[ {"firstname":"Nancy" ,"lastname":"Smith", "phone":"555-9583"}, {"firstname":"Elane" ,"lastname":"Stevens", "phone":"555-7281"}, {"firstname":"Shawn" ,"lastname":"Smith", "phone":"555-5782"}, {"firstname":"Jessie" ,"lastname":"Stevens", "phone":"555-7312"}, {"firstname":"Matt" ,"lastname":"Smith", "phone":"555-4928"}, {"firstname":"Jason" ,"lastname":"Stevens", "phone":"555-3917"}, {"firstname":"Daniel" ,"lastname":"Smith", "phone":"555-8711"}, {"firstname":"Shannon" ,"lastname":"Stevens", "phone":"555-0912"}, {"firstname":"Diana" ,"lastname":"Smith", "phone":"555-6172"}, {"firstname":"Mark" ,"lastname":"Stevens", "phone":"555-8831"} ]}
Файл 3
{contacts:[ {"firstname":"Laura" ,"lastname":"Stevens", "phone":"555-3915"}, {"firstname":"Jeff" ,"lastname":"Smith", "phone":"555-8614"}, {"firstname":"Frank" ,"lastname":"Stevens", "phone":"555-0213"}, {"firstname":"Elizabeth" ,"lastname":"Smith", "phone":"555-7531"}, {"firstname":"Jim" ,"lastname":"Stevens", "phone":"555-3951"} ]}
Эти файлы будут обеспечивать все данные для нашего списка контактов на AJAX. Построение списка контактов является в действительности вполне простым: создается таблица TABLE для хранения всех контактов и функция для очищения и повторного заполнения этой таблицы. Вот и все.
<table cellspacing="1" cellpadding="3" bgcolor="#000000" style="font-family:tahoma;font-size:10px;"> <tbody id="contactListTable"> <tr style="background-color:#CCF;"> <th>First Name</th> <th>Last Name</th> <th>Phone #</th> </tr> </tbody> </table>
function loadContactListPage(n){ var oXML = getXMLHttpObj(); oXML.open('GET', '/img/10_json_file'+n+'.txt', true); oXML.onreadystatechange = function(){ doneLoading(oXML); } oXML.send(''); }
function doneLoading(oXML){ if(oXML.readyState!=4) return;
var json = eval('('+oXML.responseText+')'); var table = document.getElementById('contactListTable');
for(var i=table.childNodes.length-1; i>0; i--){ table.removeChild(table.childNodes[i]); }
for(var i=0; i<json.contacts.length; i++){ var tr = document.createElement('TR'); var td1 = document.createElement('TD'); var td2 = document.createElement('TD'); var td3 = document.createElement('TD');
tr.style.backgroundColor = i%2?'#FFF':'#E6E6E6';
Создание объекта XMLHttp
При создании запроса AJAX прежде всего необходимо создать объект XMLHTTP. Netscape/Firefox, Opera и другие браузеры имеют этот объект встроенным. Internet Explorer использует ActiveXObject. Поэтому мы создадим одну функцию для работы со всеми этими браузерами:
if(typeof(XMLHttpRequest)!='undefined'){ var getXMLHttpObj = function(){ return new XMLHttpRequest(); } } else { var getXMLHttpObj = function(){ var activeXObjects = ['Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'Msxml2.XMLHTTP.3.0', 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP']; for(var i=0; i<activeXObjects.length; i++){ try{ return new ActiveXObject(activeXObjects[i]); }catch(err){} } } }
Любой браузер, который поддерживает объект XMLHttpRequest, будет использовать этот встроенный объект. Internet Explorer 6 и более ранние версии будут использовать объект ActiveX XMLHttp компании Microsoft. Существует много различных версий этого объекта, поэтому мы попробуем их все, начиная с самого нового.
Теперь, когда мы имеем объект XMLHttp, можно разместить запрос на сервере:
var oXml = getXMLHttpObj(); oXml.open('GET', 'getData.php', true); oXml.onreadystatechange = processingFunction; oXml.send();
function processingFunction(){ if(oXml.readyState!=4) return; // запрос не выполнен
// Результаты обрабатываются здесь. Подробнее дальше! }
После создания объекта XMLHttp остается еще 4 дополнительных шага. Сначала определяются параметры соединения с помощью функции .open. Функция .open получает 3 аргумента: тип запроса, URL и флаг, который сообщает объекту, выполняться или нет асинхронно.
Первый аргумент, тип запроса, почти всегда будет GET или POST. Если извлекаются данные, то это будет обычно GET, а если отправляется форма с AJAX, то это часто будет POST.
Флаг асинхронности немного отличается почти от всего остального в JavaScript. Если этот флаг задан как false, то код продолжает выполняться, как и любой другой фрагмент кода, то есть он ожидает, пока объект XMLHttp завершит свою работу, прежде чем двигаться дальше. Однако, если этот флаг задан как true, то код продолжает делать то, что следует в коде после запроса. Когда состояние запроса изменяется, вызывается функция, которая определена в onreadystatechange.
В чем различие? Если флаг асинхронности задан как false, то браузер будет полностью заблокирован, пока обрабатывается запрос. Если запрошенные данные нужны в обязательном порядке для продолжения работы, то используйте это значение флага. Если флаг задан как true, то пользователь может продолжать использовать Web-страницу, но ничего не известно о том, когда вернется ответ на запрос. Может потребоваться 1/2 секунды или 1 минута. Все зависит от нагрузки на сервер и соединения конечного пользователя.
Когда все готово, посылается запрос. Затем мы ожидаем. Если запрос выполняется синхронно с функцией обработки, определенной с помощью onreadystatechange, то она будет вызываться несколько раз. На самом деле она вызывается всякий раз при изменении состояния запроса. Существуют различные состояния, такие, как отправка и загрузка данных, но нас интересует только завершение загрузки данных. Если readyState == 4, то запрос выполнен, поэтому в любом другом случае мы просто выходим из функции.
Теперь мы получили данные, но что это за данные?
XML, JSON или текст
Существует три возможных варианта получения данных: XML, JSON или обычный текст. При извлечении данных из базы данных, скорее всего, будет использоваться XML или JSON. Ни один из этих вариантов не имеет явных преимуществ. XML - широко распространенный стандарт, и поэтому существует много приложений, которые работают с файлами XML. JSON является более новой идеей, но быстро становится популярным. Его обычно легче прочитать (для человека), и он требует немного меньшую полосу пропускания для пересылки.
Предположим, что создается приложение для управления контактами. Cервер может возвращать информацию о людях. Одни и те же данные можно выразить в форме XML или JSON:
XML:
<xml> <contacts> <person firstname="Joe" lastname="Smith" phone="555-1212" /> <person firstname="Sam" lastname="Stevens" phone="123-4567" /> </contacts> </xml>
JSON:
{contacts:[ {"firstname":"Joe", "lastname":"Smith", "phone":"555-1212"}, {"firstname":"Sam", "lastname":"Stevens", "phone":"123-4567"} ]}
Можно видеть, что нотация XML выглядит очень похоже на HTML. По большей части это так и есть. HTML и XML оба являются основанными на тегах языками и могут даже анализироваться одинаковым образом (см. Лекция 6).
Нотация JSON выглядит очень похоже на простой JavaScript. JSON означает JavaScript Object Notation и поэтому действительно является обычным JavaScript.
Данные в любой нотации можно посылать с Web-сервера просто как обычный текст. Никакие пробелы, имеющиеся в этих примерах, не нужны, за исключением одиночных пробелов между именами атрибутов в тегах person (в версии XML).
Формат XML является совокупностью тегов, очень похожих на HTML. Можно иметь любое количество тегов, вложенных друг в друга, и каждый тег может иметь любое количество атрибутов: например, firstname, lastname и phone в примере выше. Однако имеется несколько вещей, за которыми необходимо следить.
Каждый тег должен иметь закрывающий тег. Например, <contacts> закрывается с помощью расположенного ниже </contacts>. Теги person являются замкнутыми. /> в конце действует по сути как дополнительный закрывающий тег. Можно было так же легко написать один из них как <person ... ></person>.XML имеет ограниченный набор символов, которые можно использовать. В частности следующие символы являются недопустимыми в узлах и атрибутах и должны заменяться специальными комбинациями:
& | --> | & |
< | --> | < |
> | --> | > |
" | --> | " |
' | --> | ' |
Приходящие с сервера данные должны посылаться с content-type, заданным как text/xml. Если извлекается файл с расширением .xml, то это должно происходить автоматически. Если данные извлекаются из сценария, необходимо задавать это вручную.
Для PHP добавьте следующее:
<?php header('Content-type: text/xml'); ?>
Для ASP добавьте:
<% response.contentType = "text/xml" %>
Для всех других языков добавьте эквивалентный заголовок content-type.
Если этот заголовок не задан, свойство responseXML объекта XMLHttp будет пустым (это свойство будет описано далее).
JSON имеет аналогичный набор правил, и всю документацию по способам записи можно увидеть на json.org. Однако упрощенно можно сказать, что:
объекты начинаются и заканчиваются с помощью символов { и } соответственно;массивы начинаются и заканчиваются с помощью символов [ и ] соответственно;все строки заключаются в двойные кавычки ";символы " в строке должны экранироваться: \".
Проще говоря, строка JSON должна представлять допустимый объект JavaScript.
Теперь посмотрим на то, как можно выполнить синтаксический разбор этих данных. В данный момент мы создадим просто сценарий, который сообщит, сколько имеется контактов, и выведет о них информацию. Начнем с версии XML, возвращаясь к предыдущему незаконченному фрагменту кода:
function processingFunction(){ if(oXml.readyState!=4) return; // запрос не выполнен
// Результаты обрабатываются здесь. Подробнее дальше! }
Когда скрипт попадает в тело функции, запрос XMLHttp будет выполнен. Объект XMLHttp имеет два метода для возврата данных: responseXML и responseText. Так как в данный момент мы работаем с файлом XML, то будем использовать responseXML:
function processingFunction(){ if(oXml.readyState!=4) return;
var xmlDoc = oXml.responseXML; var contacts = xmlDoc.selectNodes('/xml/contacts/person');
alert('There are '+contacts.length+' contacts!');
for(var i=0; i<contacts.length; i++){ alert('Contact #'+(i+1)+':\n\n'+ 'First Name: '+contacts[i].getAttribute('firstname')+'\n'+ 'Last Name: '+contacts[i].getAttribute('lastname') +'\n'+ 'Phone #: '+contacts[i].getAttribute('phone') +'\n'); } }
Здесь имеется 3 функции вывода (alert). Одна сообщает, что имеется два контакта, а еще две выводят контактную информацию для каждого человека.
Посмотрим на тот же сценарий, использующий текст JSON:
function processingFunction(){ if(oXml.readyState!=4) return;
var json = eval('('+oXml.responseText+')');
alert('There are '+json.contacts.length+' contacts!');
for(var i=0; i<json.contacts.length; i++){ alert('Contact #'+(i+1)+':\n\n'+ 'First Name: '+json.contacts[i].firstname+'\n'+ 'Last Name: '+json.contacts[i].lastname +'\n'+ 'Phone #: '+json.contacts[i].phone +'\n'); } }
Как можно видеть, строки JSON можно преобразовать в JavaScript, используя просто встроенную в JavaScript команду eval(). Однако это можно делать, только если вы полностью доверяете источнику данных. Если это не так (если данные поступают из не вполне известного источника данных), то необходимо пропустить их в целях безопасности через JSON Parser (Анализатор JSON).
Наконец можно выбрать получение данных в любом другом простом текстовом формате вместо XML или JSON. Однако в этом случае необходимо решить, как выполнить синтаксический анализ данных. Если имеется только один фрагмент данных, такой, как комментарий, то это может быть практичным решением.
Что использовать: XML или JSON? Здесь нет больших различий. XML имеет более широко распространенный формат и переносим почти на любую систему. Если создаваемый проект будет работать с внешними источниками, то, вероятно, лучше использовать XML. Однако JSON немного легче для понимания и в общем он быстрее для разработки кода, чем XML. Если эта технология применяется для персонального проекта или в начале нового проекта, которому не нужно взаимодействовать с другими приложениям, то JSON определенно заслуживает рассмотрения.
Обработка ошибок в AJAX
Запросы XMLHttp, рассмотренные в предыдущей лекции, могут иметь совершенно другой тип ошибки: данные просто не проходят. Это можно проверить через статус объекта XMLHttp:
function processingFunction(){ if(oXml.readyState!=4) return; // запрос не выполнен
switch(oXml.status){ case 0: case 200: // запрос выполнен break; case 408: case 504: // запрос превысил время ожидания // код break; default: // ошибка запроса // код return; // возможно, вы захотите выйти break; }
// продолжение обработки запроса }
oXml в этом примере является объектом XMLHttp, а функция processingFunction была присоединена к свойству onreadystatechange этого объекта.
Проверяя код статуса, мы узнаем, был ли запрос обработан успешно. Код 200 является в HTTP стандартным кодом статуса "Все прошло нормально" . Код 0 возникает при загрузке файлов из локальной файловой системы (если для этого есть соответствующие полномочия). Статус код 0 часто возникает при локальном тестировании приложения.
Коды статуса 408 и 504 представляют ситуацию с превышением времени ожидания. Очень часто это указывает на сетевые проблемы, и простое повторение запроса может разрешить проблему. Однако отметим, что эти коды представляют также слишком длительную работу сервера над ответом. Например, если существует ошибка сценария на сервере, которая приводит к бесконечному циклу, то может возникнуть код ошибки 408 или 504. В этом случае повторная попытка будет вредоносной, поэтому надо быть осторожным. Самым безопасным является уведомление пользователя и выход из функции, но это не очень корректно по отношению к пользователю.
Все другие коды ошибок имеют свои собственные значения, но в данной ситуации это не важно. Нас интересует только то, что мы не получили нужные данные. Поэтому если код попадает в область "default", то мы имеем проблему. Наверно в этой ситуации лучше всего сообщить пользователю о проблеме и выйти из функции.
Это почти все об обработке ошибок в JavaScript. Имеет смысл включать в функции обработку ошибок, но, возможно, что это не требуется для каждой функции или каждого фрагмента кода. В большинстве ситуаций достаточно проверки ввода пользователей. Для реализации проверки пользователя наиболее полезным средством является использование блоков Try/Catch/Throw.
В следующей лекции будет рассмотрена рекурсия:
"Чтобы понять рекурсию, сначала необходимо понять рекурсию".
Ошибки времени выполнения
Перейдем к ошибкам времени выполнения. После запуска кода на исполнение начинают появляться ошибки времени выполнения. Эти ошибки могут возникать в связи с множеством причин. Каждый из следующих далее блоков кода будет порождать ошибку:
alert(x); // 'x' не определено
var x;
x[5] = 'test'; // 'x' будет null или не является объектом
window.frames = 5; // Не реализовано
var for; // ожидается идентификатор
document.doesNotExist(5); // объект не поддерживает это свойство или метод
alert(parseInt('5')); // ожидается объект
Многие из этих проблем вызываются более общими ошибками, которые приходится разыскивать.
Неправильное использование прописных букв
Все встроенные функции JavaScript используют специальную форму записи имен функций, предполагающую, что имя функции начинается со строчной буквы, а в начале каждого следующего слова будет использоваться прописная буква: parseInt, getElementById, createElement, appendChild, и т.д.
Так как JavaScript учитывает регистр символов, то неправильный ввод имени одной из этих функций часто будет приводить к ошибке во время выполнения.
Ссылка на несуществующий код, функции или объекты DOM
Эта проблема возникает обычно в отношении объектов DOM. Предположим, что имеется код, который изменяет некоторые элементы формы на странице. Если делается попытка выполнить этот код до появления элементов формы, например, если поместить его в тег <HEAD>, то будет получена ошибка JavaScript.
Обычно эта проблема легко решается. Лучшим решением будет выполнение кода по событию onload, например:
<BODY onload="loadFunction();">
или еще лучше, присоединение события к загрузке тела.
Использование зарезервированного слова
Существует длинный список зарезервированных ключевых слов JavaScript. Если делается попытка использовать многие из них вне их специального контекста, как, например, запись
var for = 5;
то будет возникать ошибка.
Использование пропущенного параметра
При определении функции обычно используется некоторое количество аргументов. Если некоторые из этих аргументов пропущены и делается попытка их использовать, то возникнут ошибки.
Большинство из этих проблем попадают в категорию опечаток и просто обычных ошибок, которые можно исправить, но необходимо о них знать, чтобы случайно не сделать.
Однако последний тип ошибки из этого списка с пропущенными параметрами можно проверить достаточно легко:
function myFunction(a, b, c){ if(a){ // выполняется работа с a } if(b && c){ // выполняется работа с b и c } }
Если функция вызывается только с одной переменной, то проблемы не возникает. Однако надо помнить об одной вещи: если входящая по значению переменная может быть определена как false (0 или false), то код не будет работать. В связи с этим лучше проверять, что переменная не была определена:
function myFunction(a, b, c){ if(typeof(a)!='undefined'){ // выполнение кода с a } if((typeof(b)!='undefined') && (typeof(c)!='undefined')){ // выполнение кода с b и c } }
В этом случае, даже если одна из переменных будет передана как 0, false или null, код все равно будет работать.
Сейчас мы перейдем к изучению механизмов обработок ошибок - с помощью операторов Try/Catch и функции window.onerror.
Синтаксические ошибки
JavaScript имеет два основных уровня обработки ошибок: синтаксические ошибки и ошибки времени выполнения. Синтаксические ошибки возникают до выполнения кода JavaScript, означая в основном, что код невозможно компилировать. Возьмем, например, следующий код:
for(var i=0; i<10; i++) // рабочий код здесь }
Обратите внимание, что здесь пропущена открывающая скобка {. Если попробовать выполнить этот код, то немедленно появится сообщение об ошибке, даже если код находится в функции, которая не выполняется сразу. Такие ошибки почти всегда легко находятся и исправляются. В этом случае будет получено сообщение, говорящее что-нибудь подобное " Ожидалась ')', строка 10, позиция 18". Если перейти к строке 10, то там обычно будет находиться достаточно очевидная ошибка, такая, как пропущенная ), дополнительный знак <или какая-то другая опечатка. С такими ошибками ничего нельзя сделать, кроме как просто исправить и двигаться дальше. Ниже представлен список некоторых наиболее распространенных синтаксических ошибок:
Пропущенные или непарные фигурные, круглые или квадратные скобки
Каждая фигурная {, круглая (, или квадратная [ скобка должна иметь свою закрывающую парную скобку. Если имеются вложенные скобки, то внутренние должны быть закрыты прежде, чем внешние. Например, набор скобок {[}] является недопустимым.
Условия операторов if, for и while должны помещаться в круглые скобки. Выражнение "if x=5{" является недопустимым, так как "x=5" должно быть заключено в круглые скобки. Если с этим возникнут проблемы, то существуют редакторы, такие, как EditPlus, которые могут выделять соответствующие пары скобок, и т.д.
Пропущенные или непарные кавычки
Это очень распространенная проблема. Строки в JavaScript начинаются символом 'или " и должны заканчиваться таким же символом. Если этот символ существует в строке, то он должен быть экранирован. Например, код
var x = 'It's a beautiful day';
является недопустимым, потому что ' в It's не экранировано. Этот код должен выглядеть следующим образом:
var x = 'It\'s a beautiful day';
// или
var x = "It's a beautiful day";
Еще одной достаточно распространенной ошибкой является завершение строки другим символом, т.е.:
var x = "It's a beautiful day';
Эта строка начинается с символа ", поэтому должна закончиться также символом ".
Пропущенная точка с запятой
Хотя точки с запятой обычно не нужны в JavaScript, но лучше все же их использовать. Например, если нужно сократить файл JavaScript, то обычно удаляют все переносы строк. Возьмем следующий код:
var x=5 var y=10
Если удалить переносы строк, то получим код
var x=5 var y=10
который вызовет ошибку. Если бы использовались точки с запятой, то проблемы не было бы.
Try/Catch/Finally и Throw
Операторы Try/Catch являются несомненно наиболее распространенным и обычно лучшим способом реализовать обработку ошибок в JavaScript. Но не только это - операторы Try/Catch могут иногда быть единственным способом реализовать некоторые задачи, такие, как обнаружение объекта. Возьмем, например, простую функцию для создания в Internet Explorer объекта XMLHttp:
var activeXObjects = ['Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'Msxml2.XMLHTTP.3.0', 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP']; for(var i=0; i<activeXObjects.length; i++){ try{ return new ActiveXObject(activeXObjects[i]); }catch(err){} }
Заранее неизвестно, какие объекты установил пользователь, и, к сожалению, браузер не предоставляет никакого механизма это определить. Поэтому остается создавать каждый из 6 возможных объектов, пока один из них (будем надеяться) не заработает.
Операторы Try/Catch можно использовать для перехвата ошибок двух типов: ошибок времени выполнения и ошибок пользователя. Ошибки времени выполнения, как говорилось ранее, возникают, когда у компилятора JavaScript существует проблема с созданным кодом. Ошибки пользователя, с другой стороны, будут технически проходить без проблем, но возникают в связи с контекстом приложения. Если имеется поле, в которое пользователь, например, должен ввести свой возраст, и пользователь вводит -2, то это приводит к появлению ошибки.
Блок Try/Catch имеет достаточно простой синтаксис:
try{ // код }catch(err){ // код обработки ошибки }
Если код в блоке try приводит к ошибке, то сценарий немедленно переходит в блок catch. Объект ошибки " err" в JavaScript имеет ряд полезных свойств - описание, сообщение, имя и номер, которые можно использовать для вывода информации о том, что произошло:
try{ var x; x[5] = 5; }catch(err){ alert('An error occured: '+err.description); }
Если в операторе catch окажется ошибка, то JavaScript сможет обратиться в дальнейшем к ее описанию.
Такой блок кода Try/Catch можно применять в любом месте. Однако, обычно, код должен быть написан таким образом, чтобы это не нужно было использовать, - в частности, весь ввод должен проверяться.
Блок Try/Catch можно применять также для создания своих собственных ошибок:
function setAge(x){ if(typeof(x)=='undefined') throw('Вы должны ввести возраст'); if(typeof(x)!='number') throw('Возраст должен быть числом'); if(x<0) throw(' Возраст не может быть меньше 0'); if(x>120) throw('Возраст не может быть больше 120');
var myAge = x;
// еще код }
try{ setAge(userInput); }catch(err){ alert(err); }
В этом случае выполняется проверка того, что пользователь вводит возраст. Если он вводит недопустимые данные, сценарий немедленно завершается, а пользователь получает сообщение об ошибке.
Блок try/catch имеет еще одну часть, оператор "finally":
try{ // код }catch(err){ // код }finally{ // код }
Код в "завершающем блоке" будет выполняться независимо от того, что происходит с операторами Try/Catch. В чем же разница между завершающим блоком и простым размещением кода после блока try/catch? В большинстве случаев никакой разницы не будет. Однако, если блок try/catch находится в функции и происходит выход из функции в блоке try или catch, то возникнет существенное различие:
function myFunction(){ try{ return someValue; }catch(err){ return defaultValue; }finally{ alert('finally!'); }
alert('End!'); }
В этом случае оба блока try и catch возвращают значение. Мы получим сообщение "finally!", но не получим сообщение "End!", потому что произойдет выход из функции до сообщения alert('End!'). То же самое остается справедливым для операторов Try/Catch, которые осуществляют выход из тела цикла for или while, например:
for(var i=0; i<10; i++){ try{ if(i==5) continue; }catch(err){ // обработка ошибки }finally{ // код }
// еще код }
Window.onerror
Откровенно говоря, функция window.onerror имеет небольшую практическую пользу. Ее чаще всего используют для полного отключения всех сообщений об ошибках во время выполнения:
window.onerror = function(){ return true; }
С этим кодом сообщение об ошибке никогда не будет выводиться. Однако в связи с этим приложение может не работать. Например, если бы переменная была null и с ней была выполнена какая-то операция, то в обычной ситуации должно появиться сообщение об ошибке. При использовании этого кода функция или сценарий в случае возникновения ошибки будет просто молча останавливаться.
Функцию window.onerror можно также использовать для вывода пользователям несколько более дружественных сообщений об ошибках. Можно просто вывести, например, сообщение 'Произошла ошибка, свяжитесь, пожалуйста, с Web-мастером', вместо вывода пользователю всех технических деталей ошибки (что большинство браузеров делает по умолчанию).
Еще одно использование window.onerror состоит в отправке разработчику списка всех ошибок, произошедших на сайте. Можно использовать AJAX для отправки сообщений об ошибках в форме, чтобы можно было позже их исправить. Все это возможно сделать неявно, без взаимодействия с пользователем.
Создание собственного стека
Чтобы создать свой собственный стек, необходимо удалить рекурсивную часть нашей функции. Мы все равно собираемся многократно вызывать функцию, но собираемся вызывать ее из другой функции, а не из себя самой.
var Stack = [];
function floodFill(x, y){ fillPixel(x, y);
while(Stack.length>0){ toFill = Stack.pop(); fillPixel(toFill[0], toFill[1]); } }
function fillPixel(x, y){ if(!alreadyFilled(x, y)) fill(x, y);
if(!alreadyFilled(x, y-1)) Stack.push([x, y-1]); if(!alreadyFilled(x+1, y )) Stack.push([x+1, y ]); if(!alreadyFilled(x, y+1)) Stack.push([x, y+1]); if(!alreadyFilled(x-1, y )) Stack.push([x-1, y ]); }
function fill(x, y){ // эта функция будет фактически изменять цвет поля }
function alreadyFilled(x, y){ // эта функция проверяет, что квадрат еще не был закрашен }
Как можно видеть, процесс не намного сложнее, но требует немного больше микроуправления, чем написанный ранее рекурсивный код. Вместо рекурсивного вызова функции floodFill создается переменная Stack, содержащая список квадратов, которые необходимо закрасить. Затем функция fillPixel заполняет этот массив, а функция floodFill извлекает последний элемент и закрашивает его. Когда все будет сделано, результат будет таким же, как и у первой функции, которая была написана, с одним исключением: эта функция не имеет никаких ограничений в отношении величины сетки.
Отметим, что существует очень немного рекурсивных функций, которые будут реально переполнять стек JavaScript. В действительности это не совсем верно. Почти любая рекурсивная функция может переполнить стек, но чаще всего данные, которые функция получает от пользователя, делают это крайне маловероятным событием. Когда, например, вы встретите документ XML с глубиной вложения узлов, равной 1000? Почти наверняка - никогда.
Стек
При вызове функции копия множества переменных, которые нужны ей для работы, сохраняется в памяти. Если функция вызывается рекурсивно, то другая копия всех этих переменных сохраняется в памяти, затем еще одна и т.д. Эти копии переменных сохраняются в так называемом стеке. Первая копия находится внизу, следующая поверх нее, и т.д. К сожалению, существует ограничение на размер стека. В большинстве браузеров этот предел определен где-то в районе 1000. Это означает, что для функции заливки сетка могла бы содержать до 1000 квадратов. Некоторые браузеры имееют меньший размер стека. Web-браузер Safari, например, имеет максимальный размер стека, равный примерно 100, поэтому даже небольшая сетка 10 х 10исчерпает возможности браузера.
Таким образом мы имеем ограничение в самом браузере, которое определяет, что при использовании рекурсии можно углубиться только на 1000 уровней. К сожалению, наш алгоритм floodFill (заливки) будет в худшем случае углубляться на 1000 уровней на сетке, содержащей только 1000 полей. Если имеется что-то более сложное, то существует вероятность, что произойдет переполнение стека и просто остановка выполнения кода. Очевидно, что существуют ситуации, когда необходимо закрасить область, содержащую более 1000 пикселей. Что делать в таком случае? Попробуем изменить подход.
Прежде всего попробуем изменить алгоритм. Существует много различных алгоритмов закраски - мы использовали один из самых простых. Он проверяет все направления и затем закрашивает каждый просмотренный квадрат, если это будет необходимо. С помощью небольшого изменения можно существенно улучшить этот алгоритм, с точки зрения необходимого пространства стека. Вместо просмотра влево и вправо на 1 квадрат, можно просматривать влево и вправо на максимально возможное количество позиций и закрашивать каждый пиксель в этом направлении. Это называется алгоритмом закрашивания со сканированием по линиям, который оказывается одним из наиболее эффективным существующих алгоритмов закрашивания. К сожалению, он все еще имеет свои ограничения, и для достаточно большой области закрашивания он также может переполнять стек.
Если это произойдет, то остается единственный вариант: не использовать рекурсию. Вместо использования стека JavaScript можно создать свой собственный с помощью простого массива JavaScript. Так как массив не имеет ограничений на количество элементов массива, то можно получить "бесконечно" большой стек, которым мы управляем самостоятельно.
AJAX = Асинхронный JavaScript и XML
AJAX является сокращением от "Asynchronous JavaScript And XML" (Асинхронный JavaScript и XML).
AJAX является не новым языком программирования, а просто новым способом создания Web-приложений, которые будут лучше, быстрее, и более интерактивными.
AJAX использует JavaScript для отправки и получения данных при взаимодействии Web-браузера и Web-сервера.
Технология AJAX делает Web-страницы более гибкими и быстро реагирующими, осуществляя обмен данными с Web-сервером в фоновом режиме, а не перезагружая всю Web-страницу всякий раз, когда пользователь делает изменение.
AJAX использует XML и запросы HTTP
Традиционное Web-приложение посылает введенные данные на Web-сервер (используя форму HTML). После обработки данных Web-сервер возвращает пользователю совершенно новую Web-страницу.
Так как сервер возвращает новую Web-страницу всякий раз, когда пользователь посылает данные на сервер, то традиционное Web-приложение часто выполняется медленно и оказывается менее удобным для пользователя.
С помощью AJAX Web-приложения могут посылать и получать данные без перезагрузки всей Web-страницы. Это делается с помощью запросов HTTP, посылаемых на сервер (в фоновом режиме), и модификации только отдельных частей Web-страницы с помощью JavaScript, когда сервер возвращает данные.
XML используется обычно в качестве формата для получения данных сервера, хотя можно использовать любой формат, включая обычный текст.
Как это делается, будет показано далее в этом кратком руководстве.
AJAX является технологией браузера
AJAX является технологией, которая выполняется в браузере клиента. Она использует асинхронную передачу данных (запросы HTTP) между браузером и Web-сервером, позволяя Web-страницам запрашивать с сервера небольшие объемы данных вместо целых страниц.
Эта технология позволяет уменьшить объем приложений Интернет, сделать их более быстрыми и более удобными для пользователей.
AJAX является технологией Web-браузера, которая не зависит от программного обеспечения Web-сервера.
AJAX можно использовать прямо сейчас
Нет ничего нового, что требует изучения.
Технология AJAX основывается на открытых стандартах. Эти стандарты использовались множеством разработчиков многие годы.
Большинство существующих Web-приложений можно легко переписать с помощью технологии AJAX вместо традиционных форм HTML.
AJAX основан на открытых стандартах
AJAX использует следующие открытые стандарты:
JavaScript
XML
HTML
CSS
Используемые в AJAX открытые стандарты строго определены и поддерживаются всеми основными браузерами. Приложения AJAX не зависят от используемых браузеров и платформ.
Браузеры AJAX
Приложения AJAX могут выполняться только в Web-браузерах с поддержкой XML.
Еще пример?
Некоторые программисты предпочтут использовать самую новую и быструю версию объекта XMLHttpRequest.
Следующий пример пытается использовать самую последнюю версию объекта "Msxml2.XMLHTTP" компании Microsoft, доступную в Internet Explorer 6, прежде чем обратиться к объекту "Microsoft.XMLHTTP", доступному в Internet Explorer 5.5 и выше.
var XMLHttp=null try { XMLHttp=new ActiveXObject("Msxml2.XMLHTTP") } catch(e) { try { XMLHttp=new ActiveXObject("Microsoft.XMLHTTP") } }
if (XMLHttp==null) { XMLHttp=new XMLHttpRequest() }
Форма HTML
Пусть на Web-странице имеется форма HTML со следующим кодом:
<form> Имя: <input type="text" id="txt1" onkeyup="showHint(this.value)"> </form> <p>Советуем: <span id="txtHint"></span></p>
Как можно видеть, это простая форма HTML с полем ввода с именем "txt1".
Атрибут события этого поля ввода определяет функцию, которая будет запускаться при возникновении события onkeyup.
Параграф ниже формы содержит тег span с именем "txtHint". Тег span используется в качестве поля для подстановки данных, получаемых с Web-сервера.
Когда пользователь вводит данные, выполняется функция с именем "showHint()". Выполнение функции запускается событием "onkeyup". Другими словами, всякий раз, когда пользователь убирает свой палец с клавиатуры внутри поля ввода (отпускает нажатую клавишу), вызывается функция showHint.
Функция showHint()
Функция showHint() является очень простой функцией JavaScript, помещенной в раздел заголовка <head> страницы HTML.
Функция имеет следующий код:
function showHint(str) { if (str.length==0) { document.getElementById("txtHint").innerHTML="" return } xmlHttp=GetXmlHttpObject() if (xmlHttp==null) { alert ("Браузер не поддерживает запросы HTTP") return } var url="gethint.asp" url=url+"?q="+str url=url+"&sid="+Math.random() xmlHttp.onreadystatechange=stateChanged xmlHttp.open("GET",url,true) xmlHttp.send(null) }
Функция выполняется всякий раз, когда в поле ввода вводится символ.
Если имеется какой-то ввод в текстовое поле (str.length > 0), то функция выполняет следующее:
Определяет url (имя файла) для отправки на сервер Добавляет к url параметр (q) с содержимым поля ввода Добавляет случайное число, чтобы сервер не использовал кэшированный файлСоздает объект XMLHTTP, и приказывает объекту выполнить функцию с именем stateChanged, когда произойдет изменение. Открывает объект XMLHTTP с заданным url.Посылает запрос HTTP на сервер
Если поле ввода будет пустым, то функция просто очищает содержимое поля для подстановки txtHint.
Исходный код приложения AJAX
Далее показан полный исходный код рассмотренного выше примера AJAX.
Код JavaScript приложения AJAX
Это код JavaScript, который находится в файле "clienthint.js":
var xmlHttp
function showHint(str) { if (str.length==0) { document.getElementById("txtHint").innerHTML="" return } xmlHttp=GetXmlHttpObject() if (xmlHttp==null) { alert ("Браузер не поддерживает запросы HTTP") return } var url="gethint.asp" url=url+"?q="+str url=url+"&sid="+Math.random() xmlHttp.onreadystatechange=stateChanged xmlHttp.open("GET",url,true) xmlHttp.send(null) }
function stateChanged() { if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete") { document.getElementById("txtHint").innerHTML=xmlHttp.responseText } }
function GetXmlHttpObject() { var objXMLHttp=null if (window.XMLHttpRequest) { objXMLHttp=new XMLHttpRequest() } else if (window.ActiveXObject) { objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP") } return objXMLHttp }
Далее речь пойдет о серверной странице AJAX.
Следующий далее код JavaScript находится в файле "selectcd.js":
var xmlHttp
function showCD(str) { xmlHttp=GetXmlHttpObject() if (xmlHttp==null) { alert ("Браузер не поддерживает запросы HTTP") return } var url="getcd.asp" url=url+"?q="+str url=url+"&sid="+Math.random() xmlHttp.onreadystatechange=stateChanged xmlHttp.open("GET",url,true) xmlHttp.send(null) }
function stateChanged() { if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete") { document.getElementById("txtHint").innerHTML=xmlHttp.responseText } }
function GetXmlHttpObject() { var objXMLHttp=null if (window.XMLHttpRequest) { objXMLHttp=new XMLHttpRequest() } else if (window.ActiveXObject) { objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP") } return objXMLHttp }
Это код JavaScript, который находится в файле "clienthint.js":
var xmlHttp
function showHint(str) { if (str.length==0) { document.getElementById("txtHint").innerHTML="" return } xmlHttp=GetXmlHttpObject() if (xmlHttp==null) { alert ("Браузер не поддерживает запросы HTTP") return } var url="gethint.asp" url=url+"?q="+str url=url+"&sid="+Math.random() xmlHttp.onreadystatechange=stateChanged xmlHttp.open("GET",url,true) xmlHttp.send(null) }
function stateChanged() { if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete") { document.getElementById("txtHint").innerHTML=xmlHttp.responseText } }
function GetXmlHttpObject() { var objXMLHttp=null if (window.XMLHttpRequest) { objXMLHttp=new XMLHttpRequest() } else if (window.ActiveXObject) { objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP") } return objXMLHttp }
Далее речь пойдет о серверной странице AJAX.
Методы объекта XMLHttpRequest
Метод open()
Метод open() создает запрос для Web-сервера.
Метод send()
Метод send() посылает запрос серверу.
Метод abort()
Метод abort() отменяет текущий запрос к серверу.
Объект AJAX XMLHttpRequest
Объект XMLHttpRequest делает возможной технологию AJAX.
Объект XMLHttpRequest
Чтобы создавать Web-приложения AJAX, необходимо хорошо знать объект JavaScript, называемый XMLHttpRequest.
Объект XMLHttpRequest является ключевым понятием технологии AJAX. Он был доступен с момента появления в июле 2000 браузера Internet Explorer 5.5, но не был полностью понят до 2005 года, когда начались разговоры об AJAX и Web 2.0.
Ниже представлены некоторые методы и свойства этого объекта, с которыми необходимо быть знакомым.
Описание примера
На странице выводится поле ввода с предложением ввести имя. Когда пользователь начинает вводить в этом поле имя, ниже появляются возможные варианты имен.
На Web-странице выводится список выбора с именами исполнителей музыкальных произведений. При выборе исполнителя на странице появляется информация с описанием компакт-диска с записями этого музыканта.
На странице выводится поле ввода с предложением ввести имя. Когда пользователь начинает вводить в этом поле имя, ниже появляются возможные варианты имен.
Поддержка AJAX браузерами
Приложения AJAX могут выполняться только в Web-браузерах с полной поддержкой XML, т.е. всеми основными современными браузерами.
Предыдущий пример вызывает функцию с именем GetXmlHttpObject.
Эта функция предназначена для решения проблемы создания различных объектов XMLHTTP для различных браузеров.
Функция имеет следующий код:
function GetXmlHttpObject(handler) { var objXMLHttp=null if (window.XMLHttpRequest) { objXMLHttp=new XMLHttpRequest() } else if (window.ActiveXObject) { objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP") } return objXMLHttp }
Пример AJAX
В следующем примере приложения AJAX показано, как Web-страница может оперативно общаться с Web-сервером, когда пользователь вводит данные в Web-форму.
Пример AJAX c базой данных
AJAX можно использовать для интерактивного взаимодействия с базой данных.
Пример AJAX на ASP
Код на странице "gethint.asp" написан на VBScript для Информационного сервера Интернет (IIS). Он просто проверяет массив имен и возвращает клиенту подходящие имена:
<% dim a(30) 'Заполнение массива именами a(1)="Anna" a(2)="Brittany" a(3)="Cinderella" a(4)="Diana" a(5)="Eva" a(6)="Fiona" a(7)="Gunda" a(8)="Hege" a(9)="Inga" a(10)="Johanna" a(11)="Kitty" a(12)="Linda" a(13)="Nina" a(14)="Ophelia" a(15)="Petunia" a(16)="Amanda" a(17)="Raquel" a(18)="Cindy" a(19)="Doris" a(20)="Eve" a(21)="Evita" a(22)="Sunniva" a(23)="Tove" a(24)="Unni" a(25)="Violet" a(26)="Liza" a(27)="Elizabeth" a(28)="Ellen" a(29)="Wenche" a(30)="Vicky" 'извлечение параметра q из URL q=ucase(request.querystring("q")) 'поиск всех рекомендаций из массива, если длина q>0 if len(q)>0 then hint="" for i=1 to 30 if q=ucase(mid(a(i),1,len(q))) then if hint="" then hint=a(i) else hint=hint & " , " & a(i) end if end if next end if 'Вывод "нет предложений" если рекомендуемого имени не найдено 'или вывод подходящих значений if hint="" then response.write("нет предложений") else response.write(hint) end if %>
Пример AJAX на PHP
Приведенный выше код переписан на PHP.
Примечание: Чтобы выполнить весь пример на PHP, не забудьте изменить значение переменной url в "clienthint.js" с "gethint.asp" на "gethint.php".
Пример AJAX с базой данных
В следующем примере приложения AJAX будет показано, как Web-страница может извлекать информацию из базы данных с помощью технологии AJAX.
Пример использования AJAX
Технологию AJAX можно использовать для создания приложений с большими интерактивными возможностями.
Пример использования AJAX c XML
AJAX можно использовать для интерактивного взаимодействия с файлом XML.
AJAX можно использовать для интерактивного взаимодействия с файлом XML.
Разбор примера AJAX с базой данных
Описанный выше пример страницы содержит простую форму HTML и ссылку на сценарий JavaScript:
<html> <head> <script src="selectcustomer.js"></script> </head> <body> <form> Выберите заказчика: <select name="customers" onchange="showCustomer(this.value)"> <option value="ALFKI">Alfreds Futterkiste <option value="NORTS ">North/South <option value="WOLZA">Wolski Zajazd </select> </form> <p> <div id="txtHint"><b>Информация о заказчике будет выводиться здесь.</b></div> </p> </body> </html>
Как можно видеть, это просто форма HTML с раскрывающимся списком с названием "customers".
Параграф ниже формы содержит тег div с именем "txtHint". Тег div используется в качестве поля для заполнения получаемой с Web-сервера информацией.
Когда пользователь выбирает данные (имя заказчика), выполняется функция "showCustomer()". Выполнение функции запускается событием "onchange". Другими словами, каждый раз, когда пользователь изменяет значение в поле раскрывающегося списка, вызывается функция showCustomer.
Код JavaScript показан далее.
Разбор примера AJAX с использованием XML
Рассматриваемый пример содержит простую форму HTML и ссылку на код JavaScript:
<html> <head> <script src="selectcd.js"></script> </head> <body> <form> Выберите компакт-диск: <select name="cds" onchange="showCD(this.value)"> <option value="Bob Dylan">Bob Dylan</option> <option value="Bonnie Tyler">Bonnie Tyler</option> <option value="Dolly Parton">Dolly Parton</option> </select> </form> <p> <div id="txtHint"><b>Здесь выводится информация о компакт-диске.</b></div> </p> </body> </html>
Как можно видеть, это простая форма HTML с простым раскрывающимся списком выбора с именем "cds".
Параграф ниже формы содержит тег div с именем "txtHint". Тег div используется в качестве поля для заполнения информацией, получаемой с Web-сервера.
Когда пользователь делает свой выбор, выполняется функция с именем "showCD". Выполнение функции запускается событием "onchange". Другими словами, каждый раз, когда пользователь изменяет значение в поле раскрывающегося списка, вызывается функция showCD.
Код JavaScript показан далее.
Разбор приведенного примера
Сначала создается переменная XMLHttp для использования в качестве объекта XMLHttpRequest. Ее значение задается как null.
Затем проверяется, доступен ли объект window.XMLHttpRequest. Этот объект доступен в более новых версиях браузеров, таких как Firefox, Mozilla, и Opera.
Если объект доступен, то он используется для создания нового объекта.
XMLHttp=new XMLHttpRequest().
Если он не доступен, то проверяется, доступен ли объект window.ActiveXObject. Этот объект будет доступен в браузере Internet Explorer версии 5.5 и выше.
Если этот объект доступен, то он используется для создания нового объекта:
XMLHttp=new ActiveXObject().
Сначала создается переменная XMLHttp для использования в качестве объекта XMLHttpRequest. Ее значение задается как null.
Затем проверяется, доступен ли объект window.XMLHttpRequest. Этот объект доступен в более новых версиях браузеров, таких как Firefox, Mozilla, и Opera.
Если объект доступен, то он используется для создания нового объекта.
XMLHttp=new XMLHttpRequest().
Если он не доступен, то проверяется, доступен ли объект window.ActiveXObject. Этот объект будет доступен в браузере Internet Explorer версии 5.5 и выше.
Если этот объект доступен, то он используется для создания нового объекта:
XMLHttp=new ActiveXObject().
Серверная страница AJAX - ASP и PHP
Серверная страница, вызываемая сценарием JavaScript в рассматриваемом примере, является простым файлом ASP с именем "gethint.asp".
Ниже представлены два примера кода серверной страницы, один, написанный на ASP, а другой – на PHP.
Серверная страница приложения AJAX
Серверная страница, вызываемая сценарием JavaScript, является просто файлом ASP file с именем "getcustomer.asp".
Страница написана на VBScript для Информационного сервера Интернет (IIS). Ее можно легко переписать на PHP, или любой другой серверный язык.
Код выполняет команды SQL на базе данных и возвращает результат в виде таблицы HTML:
sql="SELECT * FROM CUSTOMERS WHERE CUSTOMERID=" sql=sql & request.querystring("q")
set conn=Server.CreateObject("ADODB.Connection") conn.Provider="Microsoft.Jet.OLEDB.4.0" conn.Open(Server.Mappath("/db/northwind.mdb")) set rs = Server.CreateObject("ADODB.recordset") rs.Open sql, conn
response.write("<table>") do until rs.EOF for each x in rs.Fields response.write("<tr><td><b>" & x.name & "</b></td>") response.write("<td>" & x.value & "</td></tr>") next rs.MoveNext loop
response.write("</table>")
Серверные страницы AJAX для ASP и PHP
Сервера AJAX не существует.
Страницы AJAX может обрабатывать любой сервер Интернет.
Создание объекта XMLHttpRequest
Различные браузеры используют для создания объекта XMLHttpRequest различные методы.
Internet Explorer использует ActiveXObject.
Другие браузеры используют встроенный в JavaScript объект, называемый XMLHttpRequest.
Вот простейший код, который можно использовать, чтобы обойти эту проблему.
var XMLHttp=null
if (window.XMLHttpRequest) { XMLHttp=new XMLHttpRequest() } else if (window.ActiveXObject) { XMLHttp=new ActiveXObject("Microsoft.XMLHTTP") }
Страница HTML для примера AJAX
Эта страница HTML содержит простую форму HTML и ссылку на файл JavaScript.
<html> <head> <script src="clienthint.js"></script> </head> <body> <form> Имя: <input type="text" id="txt1" onkeyup="showHint(this.value)"> </form> <p>Советуем: <span id="txtHint"></span></p> </body> </html>
Свойство readyState объекта XMLHttpRequest
Свойство readyState определяет текущее состояние объекта XMLHttpRequest.
В таблице показаны возможные значения свойства readyState:
0 | Запрос не инициализирован |
1 | Запрос создан |
2 | Запрос послан |
3 | Запрос обрабатывается |
4 | Запрос завершен |
readyState=0 после создания объекта XMLHttpRequest, но до вызова метода open().
readyState=1 после вызова метода open(), но до вызова метода send().
readyState=2 после вызова метода send().
readyState=3 после того, как браузер соединится с сервером, но до завершения сервером ответа.
readyState=4 после завершения запроса и полного получения всех данных ответа с сервера.
Различные браузеры используют свойство состояния готовности по-разному. Не стоит рассчитывать, что все браузеры будут сообщать обо всех состояниях. Некоторые не сообщают о состоянии 0 и 1.
Для приложений AJAX представляет интерес фактически только состояние 4. Те есть, когда запрос завершен, и можно использовать полученные данные.
Технология AJAX улучшает приложения Интернет
Web-приложения имеют множество преимуществ при сравнении с традиционными приложениями настольных компьютеров. Они имеют более обширную аудиторию, их легче устанавливать и поддерживать, и их легче разрабатывать.
Однако приложения Интернет не всегда бывают достаточно "богаты" свойствами и удобны для использования по сравнению с традиционными приложениями.
С помощью AJAX приложения Интернет можно сделать богаче (меньше, быстрее, и легче в использовании).