Статьи

Поддержание состояния в анонимных функциях

В выходные я немного повеселился с JavaScript в своем личном блоге, собрав воедино несколько тем, которые я освещал здесь и в статьях на SitePoint. Задача состояла в том, чтобы предоставить способ ссылки на любой абзац в записи блога. Решение, которое я придумал, закончилось использованием ненавязчивого скрипта JavaScript, букмарклета и некоторых CSS. Вы можете прочитать все кровавые подробности в записи, но я хотел бы проанализировать букмарклет здесь.

Цель этого букмарклета (под названием «добавить идентификаторы plink») состоит в том, чтобы найти все текстовые области на текущей странице, которые, по-видимому, содержат HTML, а затем добавить атрибуты идентификатора для любых тегов абзаца в текстовых областях, у которых их еще нет. Например, следующий HTML:

 
<p>This is a paragraph.</p> <p>This is another paragraph.</p> 

Станет:

<p id="p-0">This is a paragraph.</p> <p id="p-1">This is another paragraph.</p>
<p id="p-0">This is a paragraph.</p> <p id="p-1">This is another paragraph.</p> 

Вот полный букмарклет, который я использовал, для удобства чтения:

 
javascript:(function() { var tas = document.getElementsByTagName('textarea'); for (var i = 0; i < tas.length; i++) { var ta = tas[ i ]; var text = ta.value.replace('<p>', function() { if (typeof arguments.callee.counter == 'undefined') { arguments.callee.counter = 0; } return '<p id="p-'+arguments.callee.counter++ +'">'; }); ta.value = text; } })(); 

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

var text = ta.value.replace('<p>', function() { if (typeof arguments.callee.counter == 'undefined') { arguments.callee.counter = 0; } return '</p><p id="p-'+arguments.callee.counter++ +'">'; });
var text = ta.value.replace('<p>', function() { if (typeof arguments.callee.counter == 'undefined') { arguments.callee.counter = 0; } return '</p><p id="p-'+arguments.callee.counter++ +'">'; }); 

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

Второй трюк — это аргумент arguments.callee.counter. Объект «аргумент» — это встроенный объект JavaScript, который доступен только внутри функций. Он представляет аргументы, которые были переданы функции, и обычно ведет себя как массив JavaScript. Однако он также предоставляет свойство ‘callee’, которое ссылается на сам объект-функцию (в JavaScript даже функции являются объектами). Эта функция действительно полезна только в анонимных функциях, где нет имени функции, по которой можно ссылаться на функцию.

Поскольку функции JavaScript являются объектами, они могут иметь свойства. Оператор if в начале анонимной функции проверяет, определено ли еще свойство callee функции: если оно не определено, оно инициализируется равным 0. Затем в операторе return для этого свойства вызывается оператор ++ (postincrement). — возвращает текущее значение свойства, но увеличивает его так, чтобы в следующий раз оно было на единицу выше.

Конечным результатом является то, что анонимная функция вызывается один раз для каждого тега абзаца, найденного в текстовой области, каждый раз возвращая тег paragrah с новым атрибутом ID. Свойство ‘counter’ просто используется для поддержания состояния между вызовами функций. Фактически, это может быть достигнуто также с использованием глобальной переменной, но лучше сохранить свойство «внутри» функции, которая использует его, чтобы избежать ненужного загрязнения пространства имен.