Функции в html: Функции — Переиспользуемые блоки кода — Изучение веб-разработки

Функции

Update: Более новый материал по этой теме находится по адресу https://learn.javascript.ru/function-basics.

  1. Создание функций
  2. Функции — объекты
  3. Области видимости
  4. Параметры функции
    1. Работа с неопределенным числом параметров
  5. Пример передачи функции по ссылке
  6. Сворачивание параметров в объект

В этой статье описаны функции Javascript на уровне языка: создание, параметры, приемы работы, замыкания и многое другое.

Существует 3 способа создать функцию. Основное отличие в результате их работы — в том, что именованная функция видна везде, а анонимная — только после объявления:

Именованные
(FunctionDeclaration)
Анонимные (FunctionExpression)
function имя(параметры) {
. ..
}
var имя = function(параметры) {

}

var имя = new Function(параметры, ‘…’)
Именованные функции доступны везде в области видимостиАнонимные — доступны только с момента объявления. Синтаксис new Function используется редко, в основном для получения функции из текста, например, динамически загруженного с сервера в процессе выполнения скриптов.
/* функция sum 
определена ниже 
*/
var a = sum(2,2)
function sum(x,y) {
	return x+y
}
/* будет ошибка, 
т.к sum еще не существует
*/
var a = sum(2,2)
var sum = function(x,y) {
	return x+y
}

В javascript функции являются полноценными объектами встроенного класса Function. Именно поэтому их можно присваивать переменным, передавать и, конечно, у них есть свойства:

function f() {
	...
}
f.test = 6
...
alert(f.test) // 6

Свойства функции доступны и внутри функции, так что их можно использовать как статические переменные.

Например,

function func() {
	var funcObj = arguments.callee
	funcObj.test++
	alert(funcObj.test)
}
func.test = 1
func()
func()

В начале работы каждая функция создает внутри себя переменную arguments и присваивает arguments.callee ссылку на себя. Так что arguments.callee.test — свойство func.test, т.е статическая переменная test.

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

var test = arguments.callee.test
test++

так как при этом операция ++ сработала бы на локальной переменной test, а не на свойстве test объекта функции.

Объект arguments

также содержит все аргументы и может быть преобразован в массив (хотя им не является), об этом — ниже, в разделе про параметры.

Каждая функция, точнее даже каждый запуск функции задает свою индивидуальную область видимости.

Переменные можно объявлять в любом месте. Ключевое слово var задает переменную в текущей области видимости. Если его забыть, то переменная попадет в глобальный объект window. Возможны неожиданные пересечения с другими переменными окна, конфликты и глюки.

В отличие от ряда языков, блоки не задают отдельную область видимости. Без разницы — определена переменная внутри блока или вне его. Так что эти два фрагмента совершенно эквивалентны:

Заданная через var переменная видна везде в области видимости, даже до оператора var. Для примера сделаем функцию, которая будет менять переменную,

var для которой находится ниже.

Например:

function a() {
	z = 5 // поменяет z локально.. 
	// .. т.к z объявлена через var
	var z
}
// тест
delete z // очистим на всякий случай глобальную z
a()
alert(window.z)  // => undefined, т.к z была изменена локально

Функции можно запускать с любым числом параметров.

Если функции передано меньше параметров, чем есть в определении, то отсутствующие считаются undefined.

Следующая функция возвращает время time, необходимое на преодоление дистанции distance с равномерной скоростью speed.

При первом запуске функция работает с аргументами distance=10, speed=undefined. Обычно такая ситуация, если она поддерживается функцией, предусматривает значение по умолчанию:

// если speed - ложное значение(undefined, 0, false...) - подставить 10
speed = speed || 10

Оператор || в яваскрипт возвращает не true/false, а само значение (первое, которое приводится к true).

Поэтому его используют для задания значений по умолчанию. В нашем вызове speed будет вычислено как undefined || 10 = 10.

Поэтому результат будет 10/10 = 1.

Второй запуск — стандартный.

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

Ну и в последнем случае аргументов вообще нет, поэтому distance = undefined, и имеем результат деления undefined/10 = NaN (Not-A-Number, произошла ошибка).

Непосредственно перед входом в тело функции, автоматически создается объект

arguments, который содержит

  1. Аргументы вызова, начиная от нуля
  2. Длину в свойстве length
  3. Ссылку на саму функцию в свойстве callee

Например,

function func() {
    for(var i=0;i<arguments.length;i++) {
        alert("arguments["+i+"] = "+arguments[i])
    }
}
func('a','b',true)
// выведет
// arguments[0] = a
// arguments[1] = b
// arguments[2] = true

Свойство arguments похоже на массив, т.к у него есть длина и числовые индексы. На самом деле arguments не принадлежит классу Array и не содержит его методов, таких как push, pop и других.

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

var args = Array.
prototype.slice.call(arguments) // .. теперь args - настоящий массив аргументов .. args.shift() ...

Вызвать функцию для массива аргументов можно при помощи apply:

var func = function(a,b) { alert(a+b) }
var arr = [1,2]
func.apply(null, arr)  // => alert(3)

Функцию легко можно передавать в качестве аргумента другой функции.

Например, map берет функцию func, применяет ее к каждому элементу массива arr и возвращает получившийся массив:

var map = function(func, arr) {
    var result = [ ]
    for(var i=0; i<arr.length; i++) {
        result[i] = func(arr[i])
    }
    return result
}

Пример использования:

map(run, [10, 20, 30])  // = [1,2,3]

Или можно создать анонимную функцию непосредственно в вызове map:

// анонимная функция утраивает числа
map( function (a) { return a*3 } ,  [1,2,3])  // = [3,6,9]

Бывают функции, аргументы которых сильно варьируются.

Например:

// можно указать только часть аргументов
// не указанные - вычисляются или берутся по умолчанию
function resize(toWidth, toHeight,  saveProportions, animate)  {
	// значения по умолчанию
	saveProportions = saveProportions || true
	animate = animate || true
	toHeight = toHeight || ...
}

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

resize(100, null, null, true)

Чтобы избежать лишних null и сделать код более понятным, используют нечто вроде «keyword arguments», существующих в Python и Ruby. Для этого много параметров пакуют в единый объект:

function resize(setup)  {
	// значения по умолчанию
	var saveProportions = setup.saveProportions || true
	var animate = setup.animate || true
	var toHeight = setup.toHeight || ...
}

Вызов теперь делается гораздо проще:

var setup = {toWidth: 100, animate: true}
resize(setup)
// или
resize({toWidth: 100, animate: true})

Так — куда понятнее. А если параметров больше 5, то вообще — единственный нормальный способ.

Кроме того, с объектом можно удобнее делать последовательности вызовов вроде:

var setup = {toWidth: 100, animate: true, saveProportions: false}
resize(setup)
setup.toWidth = 200
resize(setup)

Куда вставлять Javasсript

Чтобы код Javascript сработал в браузере пользователя, его необходимо вставить между тегами <script> и </script>.

<html>
<head>
	<title>Пример JavaScript</title>
</head>
<body>
<script>
	document.getElementById("demo").innerHTML = "Первый сценарий на JavaScript";
</script>
<noscript>
	Ваш браузер не поддерживает JavaScript или поддержка отключена в настройках.
</noscript>
<div></div>
</body>
</html>

В данном примере, как только HTML страница будет загружена, браузер запустит команду document. getElementById(«demo»).innerHTML = «Первый сценарий на JavaScript», которая ищет элемент с идентификатором «demo» и, найдя его, помещает в него строку «Первый сценарий на JavaScript».

В действительности полная запись тега <script> имеет следующий вид: <script type=»text/javascript»>. В атрибуте type указывается используемый язык скриптов. Однако в настоящее время существует не так уж много таких языков, и в HTML язык Javascript установлен как язык скриптов по умолчанию. Поэтому атрибут type использовать не нужно.

Также, обратите внимание на теги

<noscript> и </noscript>. Этот тег срабатывает, когда по той или иной причине, например, выполнение сценариев отключено в настройках браузера, невозможно выполнить сценарий Javasсript.

Функции и события JavaScript

Функция JavaScript — это блок кода, который выполняется по «вызову».

Например, функция может вызываться при возникновении какого-нибудь события, вроде нажатия пользователем на кнопку мыши.

Подробнее о функциях и событиях вы узнаете позже в соответствующих главах.

JavaScript в теге <head> и <body>

В HTML документ можно вставлять любое число скриптов.

На HTML странице скрипты можно размещать внутри секции <body> или <head>, либо в обоих сразу.

В следующем примере функция JavaScript размещена в секции <head>. Эта функция вызывается при нажатии пользователем на кнопку:

<!DOCTYPE html>
<html>
<head>
<script>
function myFunction() {
    document.getElementById("demo").innerHTML = "Параграф изменен.";
}
</script>
</head>
<body>
<h2>Веб-страница</h2>
<p>Параграф</p>
<button type="button">Изменить</button>
</body>
</html> 

В следующем примере функция JavaScript размещена в секции <body>:

<!DOCTYPE html>
<html>
<body>
<h2>Веб-страница</h2>
<p>Параграф</p>
<button type="button">Изменить</button>
<script>
function myFunction() {
    document. getElementById("demo").innerHTML = "Параграф изменен.";
}
</script>
</body>
</html> 

Размещение скриптов в нижней части элемента <body> увеличивает скорость отображения HTML документа, так как компиляция скриптов замедляет рендеринг веб-страницы.

Внешний JavaScript

Скрипты также можно размещать во внешних файлах:

Внешний файл: myScript.js

function myFunction() {
   document.getElementById("demo").innerHTML = "Параграф изменен.";
} 

Внешние скрипты оправданы тогда, когда нужно один и тот же сценарий использовать в нескольких HTML документах.

Обычно у файлов JavaScript расширение .js.

Чтобы использовать внешний скрипт, нужно поместить имя файла Javascript в атрибут src тега <script>:

<script src="//msiter.ru/myScript.js"></script>

Подключать внешние скрипты можно внутри тега <head> или <body>.

Скрипт будет вести себя так же, как если бы он был вставлен непосредственно внутри тега <script>.

Внимание! Внешние скрипты не могут содержать теги <script>.

Преимущества внешних JavaScript

У размещения скриптов во внешних файлах есть ряд преимуществ:

  • Разделяется HTML и Javascript код
  • Становится проще читать и обслуживать HTML и JavaScript
  • Благодаря кешированию файлов JavaScript увеличивается скорость загрузки веб-страницы

Чтобы добавить несколько файлов скриптов на HTML страницу, достаточно вставить нужное число тегов <script>:

<script src="//msiter.ru/myScript1.js"></script>
<script src="//msiter.ru/myScript2.js"></script>

Внешние ссылки

В качестве ссылок на внешние скрипты может использоваться либо абсолютный URL, либо относительный путь к текущей веб-странице.

В следующем примере для подключения внешнего скрипта используется полный URL:

<script src="//msiter.ru/js/myScript1.js"></script>

В следующем примере скрипт расположен в подкаталоге текущего веб-сайта:

<script src="//msiter. ru/js/myScript1.js"></script>

В следующем примере скрипт расположен в том же подкаталоге что и текущая страница:

<script src="//msiter.ru/myScript1.js"></script>

Устаревшие и нестандартные браузеры

Для обеспечения совместимости со старыми браузерами, которые не умеют работать со скриптами Javascript, код скрипта размещают внутри тегов комментариев HTML <!— и —>. Если пренебречь этой предосторожностью, то браузер, не знающий Javascript, просто выведет на экран код скрипта в виде обычного текста. К счастью таких нестандартных браузеров в настоящее время практически не осталось. Однако подобный код все еще встречается, и стоит знать для чего он используется.

<html>
<head>
	<title>Пример JavaScript</title>
</head>
<body>
<script type="text/javascript">
<!--
	document.write("Привет, Мир!")
// -->
</script>
</body>
</html>

В приведенном выше примере сразу же после тега <script type=»text/javascript»> вставлен открывающий тег HTML комментария <!—, а перед тегом </script> вставлен закрывающий тег комментария —>. При этом перед закрывающим тегом HTML комментария поставлен двойной прямой слэш //, который является уже комментарием JavaScript, показывающий, что строка после него является комментарием с точки зрения JavaScript. Назначения этого комментария JavaScript — указать браузерам, умеющим работат с JavaScript, что символы → следует проигнорировать.

Metro UI :: Популярная библиотека HTML, CSS и JS

..
secondToFormattedString(seconds) Строка возврата «чч:мм:сс»
exec(f, аргументы, контекст) Функция или код Exec с аргументами и контекстом
isOutsider(el) Проверить элемент за пределы поля зрения
в окне просмотра (эл) Проверить элемент в окне просмотра
длина объекта(el) Возвращает длину объекта.
процентов(а, б, г) Возвращает процентное значение для b из a. Если r истинно, возвращаемое значение является целым числом, иначе — реальным
objectShift(obj) Вернуть смещенный объект. Удалить элемент с первым ключом
objectDelete(объект, ключ) Удалить ключ из объекта и вернуть его
объектКлон(объект) Скопировать объект для клонирования и вернуть его
arrayDelete(arr, val) Удалить из массива по значению и вернуть его
arrayDeleteByKey(арр, ключ) Удалить ключ из массива и вернуть его
arrayDeleteByMultipleKeys(arr, keys_array) Удалить ключ из массива и вернуть новый массив
nvl(данные, прочее) Проверить данные на нулевое значение и вернуть другое, если нулевое
github (репо, обратный вызов) Получить информацию с github и добавить ее в обратный вызов
обнаружитьIE() Определить, является ли браузер Internet Explorer или Edge
обнаружитьChrome() Определить, является ли браузер Chrome
md5(str) Кодировать строку с помощью алгоритма md5
encodeUri(str) Исправление базовой функции encodeUri
pageHeight() Вернуть реальную высоту страницы
cleanPreCode(селектор) Удалить пробелы из кода
координат(эл) Возвращает координаты элемента как объект {сверху, слева}
positionXY(событие, тип) Позиция возврата для типа: экран, страница, клиент
клиентXY(событие) Возврат позиции
страницаXY(событие) Возврат позиции
экранXY(событие) Возврат позиции
isRightMouse(событие) Проверить, нажимает ли пользователь правую кнопку мыши
hiddenElementSize(el, includeMargin) Возвращает размер элемента как объект {ширина, высота}
getStyle(эль, псевдо) Возвращает рассчитанные стили элемента
getStyleOne(el, property) Возвращает рассчитанные стили элемента для указанного свойства
getTransformMatrix(el) Матрица преобразования возвращаемого элемента
вычисленныйRgbToHex(rgb) Возвращает шестнадцатеричное значение для вычисляемого цвета элемента.
вычисленныйRgbToHex(rgb) Возвращает шестнадцатеричное значение для строки rgb ‘rgb(x, x, x)’ => #xxxxxx
вычисленныйRgbToRgba(rgb,альфа) Возвращает строку rgba для строки rgb ‘rgb(x, x, x)’ => ‘rgba(x, x, x, a)’
вычисленныйRgbToArray(rgb) Возвращаемый массив для строки ‘rgb(x, x, x)’ => [x, x, x]
hexColorToArray(c) Преобразование шестнадцатеричного значения цвета в массив
hexColorToRgbA(c, a) Преобразовать шестнадцатеричное значение цвета в строку rgba: ‘#xxxxxx’ => ‘rgba(x, x, x, a)’
getInlineStyles(el) Возврат встроенных стилей элемента в виде массива
updateURIParameter(uri, key, val) Параметр обновления в Uri
getURIParameter(uri, ключ) получить параметр от Uri
getLocales() Получить зарегистрированные метрополитены
addLocale(данные) Зарегистрировать локаль метро во время выполнения
aspectRatioH(ширина, соотношение) Высота возврата для определенного соотношения
соотношение сторон(высота, соотношение) Ширина возврата для определенного соотношения
значениеВОбъекте(объект, значение) Проверить, существует ли значение в объекте
keyInObject(объект, ключ) Проверить, существует ли ключ в объекте
новыйCssSheet(носитель) Создать объект листа css
addCssRule(лист, селектор, правила, индекс) Добавить правило в объект листа css
медиа(запрос) Проверить медиа-запрос
медиарежимы() Получить текущие медиа-баллы
mediaExist(m) Возвращает true, если точка существует в текущем носителе
в Медиа(м) Проверить, является ли точка текущим носителем
isValue(val) Вернуть true, если значение не в [undefined, null, «»]
Отрицательное (значение) Возвращает истину, если значение меньше 0
isPositive(val) Вернуть true, если значение больше 0
isZero(val) Возвращает true, если val равно 0 (целое число или число с плавающей запятой)
между(значение, низ, верх, равно) Возвращает true, если значение находится между нижним и верхним
parseMoney(val) Возвращает числовое значение из любого денежного формата. Пример: 5 640,63 долл. США -> 5 640,63
функция(строка) Функциональный объект возврата
ближайший (значение, точность, уменьшение) поиск ближайшего целого числа, кратного искомому. Пример: Metro.utils.nearest(37, 5, false) -> 40, Metro.utils.nearest(37, 5, true) -> 35
копия(эл) Копировать элемент в буфер обмена
isLocalhost() Проверить, является ли текущее местоположение локальным хостом
getCursorPosition(элемент, событие) Возвращает позицию мыши или указателя как x, y
getCursorPositionX(элемент, событие) Возврат позиции мыши или указателя x
getCursorPositionY(элемент, событие) Вернуть позицию мыши или указателя y

Использование функций внутри шаблонов Go

В этом руководстве мы расскажем, как использовать функции шаблонов, такие как и , eq и index , чтобы добавить некоторую базовую логику в наши шаблоны. Как только у нас будет достаточно хорошее понимание того, как использовать эти функции, мы рассмотрим, как добавить некоторые пользовательские функции в наши шаблоны и использовать их.

Эта статья является частью серии

Это третья часть из четырех статей, посвященных html/template text/template ) пакетов в Go. Если вы еще этого не сделали, я предлагаю вам ознакомиться с остальной частью серии здесь: Введение в шаблоны в Go. Их не обязательно читать, но я думаю, они вам понравятся.

Если вам нравится эта серия, подпишитесь на мою рассылку, чтобы получать уведомления, когда я выпускаю новые подобные статьи. Обещаю не спамить.

Функция

и

По умолчанию , если 9Действие 0006 в шаблонах будет оценивать, является ли аргумент пустым, но что происходит, когда вы хотите оценить несколько аргументов? Вы можете написать вложенные блоки if/else , но это быстро станет некрасивым.

Вместо этого пакет html/template предоставляет функции и . Его использование похоже на то, как вы использовали бы функции и в Лиспе (другом языке программирования). Это легче показать, чем объяснить, поэтому давайте просто перейдем к коду. открыть main.go и добавьте следующее:

 пакет main
импорт (
  "html/шаблон"
  "сеть/http"
)
var testTemplate *template.Template
тип Пользовательская структура {
  Бул администратора
}
введите структуру ViewData {
  *Пользователь
}
основная функция () {
  вар ошибка ошибка
  testTemplate, ошибка = template.ParseFiles("hello.gohtml")
  если ошибка != ноль {
    паника (ошибка)
  }
  http.HandleFunc("/", обработчик)
  http.ListenAndServe(":3000", ноль)
}
func handler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Тип контента", "текст/html")
  vd := ViewData{&User{true}}
  ошибка: = testTemplate.Execute(w, vd)
  если ошибка != ноль {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
}
 

Затем откройте hello.gohtml и добавьте в свой шаблон следующее.

 {{if и .User .User.Admin}}
  Вы пользователь с правами администратора!
{{еще}}
  В доступе отказано!
{{конец}}
 

Если вы запустите этот код, вы должны увидеть вывод Вы являетесь пользователем с правами администратора! . Если вы обновите main.go , чтобы либо не включать объект *User , либо установить для Admin значение false, или даже если вы предоставите nil методу testTemplate.Execute() , вместо этого вы увидите Доступ запрещен! .

Функция и принимает два аргумента, позволяет называть их a и b , а затем выполняет логику, примерно эквивалентную if a then b else a . Самое странное, что и — это действительно функция, а не то, что вы помещаете между двумя переменными. Просто помните, что это функция, а не логическая операция, и все должно быть в порядке.

Аналогично, пакет шаблонов также содержит или , которая работает так же, как и , за исключением того, что при истинном значении происходит короткое замыкание. Т.е. логика для или a b примерно эквивалентна , если a, то a else b , поэтому b никогда не будет оцениваться, если a не пусто.

Функции сравнения (равно, меньше и т. д.)

До сих пор мы имели дело с относительно простой логикой, вращающейся вокруг того, является ли что-то пустым или нет, но что происходит, когда нам нужно выполнить некоторые сравнения? Например, что, если мы хотим настроить класс объекта в зависимости от того, приближается ли пользователь к превышению лимита использования?

Пакет html/template предоставляет нам несколько классов для сравнения. Это

  • eq — возвращает логическую истину arg1 == arg2
  • ne - Возвращает логическую истину arg1 != arg2
  • lt — возвращает логическую истину arg1 < arg2
  • le — возвращает логическую истину arg1 <= arg2
  • гт — возвращает логическое значение arg1 > arg2
  • ge — возвращает логическую истину arg1 >= arg2

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

 {{if (ge .Usage .Limit)}}
  

Вы достигли предела использования API. Пожалуйста, обновите или обратитесь в службу поддержки для получения дополнительной помощи.

{{else if (gt .Usage .Warning)}}

Вы использовали {{.Usage}} из {{.Limit}} вызовов API и приближаетесь к своему лимиту. Вы рассматривали возможность обновления?

{{else if (eq .Usage 0)}}

Вы еще не использовали API! Чего ты ждешь?

{{еще}}

Вы использовали {{.Usage}} из {{.Limit}} вызовов API.

{{конец}}

if...else if...else

Если вы следили за этой серией, также стоит отметить, что этот код также демонстрирует, как создать блок if...elseif...else , которые мы еще не рассмотрели. Они работают почти так же, как блок if...else , но они позволяют вам иметь несколько разных условных предложений.

Использование переменных функций

До сих пор мы в основном имели дело со структурами данных внутри наших шаблонов, но что произойдет, если мы захотим вызывать наши собственные функции из шаблона? Например, давайте представим, что у нас есть Тип пользователя , и нам нужно выяснить, есть ли у текущего пользователя разрешение на доступ к нашей корпоративной функции при создании пользовательского интерфейса. Мы могли бы создать структуру клиента для представления и добавить поле для разрешения.

 тип ViewData struct {
  Карта разрешений[string]bool
}
// или же
введите структуру ViewData {
  Структура разрешений {
    FeatureA bool
    логическое значение FeatureB
  }
}
 

Проблема с этим подходом заключается в том, что нам всегда нужно знать каждую функцию, которая используется в текущем представлении, или если мы вместо этого использовали map[string]bool нам нужно заполнить его значением для каждой возможной функции. Было бы намного проще, если бы мы могли просто вызывать функцию, когда хотели узнать, есть ли у пользователя доступ к какой-либо функции. Есть несколько способов сделать это в Go, поэтому я собираюсь рассказать о нескольких возможных способах сделать это.

1. Создайте метод для типа

User

Первый самый простой — скажем, у нас есть тип User , который мы уже предоставили представлению, мы можем просто добавить HasPermission() для объекта, а затем используйте его. Чтобы увидеть это в действии, добавьте следующее к hello.gohtml .

 {{if .User.HasPermission "feature-a"}}
  <дел>
     

Функция А

Еще кое-что здесь...

{{еще}} <дел>

Функция А

Чтобы включить функцию A, обновите свой план

{{конец}} {{if .User.HasPermission "feature-b"}} <дел>

Функция Б

Еще кое-что здесь...

{{еще}} <дел>

Функция Б

Чтобы включить функцию B, обновите свой план

{{конец}} <стиль> . особенность { граница: 1px сплошная #eee; отступ: 10 пикселей; поле: 5px; ширина: 45%; отображение: встроенный блок; } .инвалид { цвет: #ccc; }

Затем добавьте следующее в main.go в том же каталоге.

 пакет основной
импорт (
  "html/шаблон"
  "сеть/http"
)
var testTemplate *template.Template
введите структуру ViewData {
  Пользователь Пользователь
}
тип Пользовательская структура {
  ID внутр.
  Строка электронной почты
}
func (u User) HasPermission (строка функций) bool {
  если функция == "функция-a" {
    вернуть истину
  } еще {
    вернуть ложь
  }
}
основная функция () {
  вар ошибка ошибка
  testTemplate, ошибка = template.ParseFiles("hello.gohtml")
  если ошибка != ноль {
    паника (ошибка)
  }
  http.HandleFunc("/", обработчик)
  http.ListenAndServe(":3000", ноль)
}
func handler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Тип контента", "текст/html")
  vd := ViewData{
    Пользователь: Пользователь{1, "jon@calhoun. io"},
  }
  ошибка: = testTemplate.Execute(w, vd)
  если ошибка != ноль {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
}
 

После того, как вы запустите свой код, вы должны увидеть что-то вроде этого в своем браузере:

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

Теперь, когда мы увидели, как вызывать методы, давайте рассмотрим более динамичный способ вызова функций внутри шаблона с помощью вызов функции .

2. Вызов переменных и полей функции

Давайте представим, что по какой-то причине вы не можете использовать описанный выше подход, потому что ваш метод определения логики должен время от времени меняться. В этом случае имеет смысл создать атрибут HasPermission func(string) bool для типа User , а затем назначить ему функцию. Откройте main.go и измените свой код, чтобы отразить следующее.

 пакет основной
импорт (
  "html/шаблон"
  "сеть/http"
)
var testTemplate *template.Template
введите структуру ViewData {
  Пользователь Пользователь
}
тип Пользовательская структура {
  ID внутр.
  Строка электронной почты
  Функция HasPermission (строка) bool
}
основная функция () {
  вар ошибка ошибка
  testTemplate, ошибка = template.ParseFiles("hello.gohtml")
  если ошибка != ноль {
    паника (ошибка)
  }
  http.HandleFunc("/", обработчик)
  http.ListenAndServe(":3000", ноль)
}
func handler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Тип контента", "текст/html")
  vd := ViewData{
    Пользователь: Пользователь{
      ID: 1,
      Электронная почта: "[email protected]",
      HasPermission: func (строка функций) bool {
        если функция == "функция-b" {
          вернуть истину
        }
        вернуть ложь
      },
    },
  }
  ошибка: = testTemplate.Execute(w, vd)
  если ошибка != ноль {
    http. Error(w, err.Error(), http.StatusInternalServerError)
  }
}
 

Все выглядит хорошо, но если вы посетите localhost:3000 в своем браузере после запуска сервера, вы заметите, что мы получаем ошибку, например

 template: hello.gohtml:1:10: выполнение "hello.gohtml" в < .User.HasPermission>: HasPermission имеет аргументы, но не может быть вызван как функция
 

Когда мы назначаем функции переменным, нам нужно сообщить пакету html/template , что мы хотим вызвать функцию. Откройте файл hello.gohtml и добавьте слово 9.0005 позвоните по номеру сразу после ваших утверждений if , вот так.

 {{if (вызов .User.HasPermission "feature-a")}}
...
{{if (вызвать .User.HasPermission "feature-b")}}
...
 

Скобки можно использовать в шаблонах

Хотя скобки обычно не требуются в шаблонах Go, они могут быть невероятно полезны для того, чтобы прояснить, какие аргументы должны быть переданы в какие функции, и указать четкий порядок операций. Помните о них при использовании шаблонов!

Перезагрузите сервер и снова проверьте локальный хост. Вы должны увидеть ту же страницу, что и раньше, но на этот раз вместо функции A включена функция B. .User.HasPermission в нашем случае), используя остальные аргументы в качестве аргументов для вызова функции.

3.Создание пользовательских функций с помощью шаблона

.FuncMap

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

Для начала перейдите к документации для template.FuncMap . Первое, на что следует обратить внимание, это то, что этот тип выглядит просто как map[string]interface{} , но ниже есть примечание о том, что каждый интерфейс должен быть функцией с одним возвращаемым значением или функцией с двумя возвращаемыми значениями, где первое — это данные, к которым нужно получить доступ в шаблоне, а второе — ошибка, которая будет прекратить выполнение шаблона, если он не равен нулю.

Поначалу это может сбивать с толку, поэтому давайте сразу перейдем к примеру. Снова откройте main.go и обновите его, чтобы он соответствовал приведенному ниже коду.

 пакет основной
импорт (
  "html/шаблон"
  "сеть/http"
)
var testTemplate *template.Template
введите структуру ViewData {
  Пользователь Пользователь
}
тип Пользовательская структура {
  ID внутр.
  Строка электронной почты
}
основная функция () {
  вар ошибка ошибка
  testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{
    «hasPermission»: func (пользователь-пользователь, функциональная строка) bool {
      если user.ID == 1 && feature == "feature-a" {
        вернуть истину
      }
      вернуть ложь
    },
  }).ParseFiles("hello.gohtml")
  если ошибка != ноль {
    паника (ошибка)
  }
  http.HandleFunc("/", обработчик)
  http.ListenAndServe(":3000", ноль)
}
func handler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Тип контента", "текст/html")
  пользователь := пользователь{
    ID: 1,
    Электронная почта: "jon@calhoun. io",
  }
  vd := ViewData{пользователь}
  ошибка: = testTemplate.Execute(w, vd)
  если ошибка != ноль {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
}
 

И еще раз откройте hello.gohtml и обновите каждый оператор if, чтобы использовать новую функцию следующим образом.

 {{if hasPermission .User "feature-a"}}
...
{{if hasPermission .User "feature-b"}}
...
 

Функция hasPermission теперь должна управлять вашей логикой, которая определяет, включена функция или нет. В main.go мы определили template.FuncMap , который сопоставил имя метода ( "hasPermission" ) с функцией, которая принимает два аргумента ().0005 Пользователь и строка функции), а затем возвращает значение true или false. Затем мы вызвали функцию template.New() для создания нового шаблона, вызвали метод Funcs() в этом новом шаблоне для определения наших пользовательских функций, а затем, наконец, мы проанализировали наш файл hello. gohtml как источник для нашего шаблона.

Определить функции перед анализом шаблонов

В предыдущих примерах мы создавали наш шаблон, вызывая template.ParseFiles , предоставляемая пакетом html/template . Это функция уровня пакета, которая возвращает шаблон после разбора файлов. Теперь мы вызываем метод ParseFiles для типа template.Template , который имеет те же возвращаемые значения, но применяет изменения к существующему шаблону (а не совершенно новому), а затем возвращает результат.

В этой ситуации нам нужно использовать этот метод, потому что нам нужно сначала определить любые пользовательские функции, которые мы планируем использовать в наших шаблонах, и как только мы сделаем это с пакетом шаблона, он вернет *шаблон.Шаблон . После определения этих пользовательских функций мы можем перейти к анализу шаблонов, которые используют эти функции. Если бы мы сначала проанализировали шаблоны, вы бы увидели ошибку, связанную с вызовом неопределенной функции в вашем шаблоне.

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

Делаем наши функции полезными во всем мире

имеет разрешение 9Функция 0006, которую мы определили в предыдущем разделе, великолепна, но одна проблема заключается в том, что мы можем использовать ее только тогда, когда у нас есть доступ к объекту User . Поначалу это может быть не так уж плохо, но по мере роста приложения у него будет много шаблонов, и довольно легко забыть передать объект User в шаблон или пропустить его во вложенном шаблоне.

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

Первое, что нам нужно сделать, это создать функцию на случай отсутствия пользователя . Мы установим это в шаблоне .FuncMap перед синтаксическим анализом нашего шаблона, чтобы мы не получили ошибок синтаксического анализа и чтобы убедиться, что у нас есть какая-то логика на случай, если пользователь недоступен.

Откройте main.go и обновите функцию main() , чтобы она соответствовала приведенному ниже коду.

 основная функция () {
  вар ошибка ошибка
  testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{
    "hasPermission": функция (строка функции) bool {
      вернуть ложь
    },
  }).ParseFiles("hello.gohtml")
  если ошибка != ноль {
    паника (ошибка)
  }
  http.HandleFunc("/", обработчик)
  http.ListenAndServe(":3000", ноль)
}
 

Далее нам нужно определить нашу функцию, которая использует замыкание. По сути, это причудливый способ сказать, что мы собираемся определить динамическую функцию, которая имеет доступ к переменным, которые не обязательно передаются в нее, но становятся доступными, когда мы определяем функцию. В нашем случае этой переменной будет объект User . Обновите функцию handler() внутри main.go следующим кодом.

 func handler(w http. ResponseWriter, r *http.Request) {
  w.Header().Set("Тип контента", "текст/html")
  пользователь := пользователь{
    ID: 1,
    Электронная почта: "[email protected]",
  }
  vd := ViewData{пользователь}
  // Нам нужно клонировать шаблон перед установкой пользовательского
  // FuncMap, чтобы избежать возможных условий гонки.
  err := template.Must(testTemplate.Clone()).Funcs(template.FuncMap{
    "hasPermission": функция (строка функции) bool {
      если user.ID == 1 && feature == "feature-a" {
        вернуть истину
      }
      вернуть ложь
    },
  }).Выполнить(ш, вд)
  если ошибка != ноль {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
}
 

Возможные условия гонки!

Здесь следует отметить, что если вы не клонируете шаблон до того, как вызовет Funcs , вы потенциально можете столкнуться с состоянием гонки, когда несколько веб-запросов пытаются установить разные FuncMaps для шаблона. Конечным результатом может быть то, что пользователь получает доступ к чему-то, к чему у него не должно быть доступа. Это возможно по двум причинам:

  1. Веб-запросы по умолчанию обрабатываются горутинами, поэтому ваш сервер будет автоматически обрабатывать несколько запросов одновременно. 906:20
  2. Мы добавляем FuncMap с замыканием, которое использует переменную пользователя . В предыдущих примерах мы передавали пользователя в функцию, поэтому такое состояние гонки было невозможно.

Это довольно легко исправить с помощью Clone , но, возможно, стоит отметить в коде, что не следует удалять вызов Clone .

Хотите увидеть больше примеров закрытия?

Если вам интересно узнать немного больше о замыканиях, включая несколько примеров их действия, я предлагаю ознакомиться со статьей по теме (нажмите кнопку ниже). В статье я объясняю, что такое анонимные функции и замыкания, привожу примеры, и есть даже дополнительная статья с общим использованием замыканий в Go.

Связанная статья Что такое замыкание?

Несмотря на то, что мы определили функцию hasPermission в нашей функции main() , мы перезаписываем ее внутри нашего обработчика, когда у нас есть доступ к объекту User , но до того, как мы выполним шаблон. Это действительно мощно, потому что теперь мы можем использовать функцию hasPermission в любом шаблоне, не беспокоясь о том, что User объект был передан в шаблон или нет.

В разделе «Введение в шаблоны в Go — контекстное кодирование» я упомянул, что если вам нужно предотвратить удаление определенных HTML-комментариев из шаблонов, это возможно, но в то время мы не рассказывали, как это сделать. В этом разделе мы расскажем не только о том, как это сделать, но и о том, как заставить любую строку пропустить процесс кодирования по умолчанию, который происходит при выполнении html/template .

Чтобы освежить вашу память, представьте, что в вашем макете есть некоторый HTML-код, который нуждается в комментарии для совместимости с IE, например.

 
 

К сожалению, пакет html/template по умолчанию удалит эти комментарии, поэтому нам нужно придумать способ сделать комментарии безопасными для HTML. В частности, нам нужно создать функцию, которая предоставляет нам объект template.HTML с содержимым .

Откройте main.go и замените его содержимое следующим.

 пакет основной
импорт (
  "html/шаблон"
  "сеть/http"
)
var testTemplate *template.Template
основная функция () {
  вар ошибка ошибка
  testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{
    "ifIE": func() template.HTML {
      вернуть шаблон.HTML("")
    },
  }).ParseFiles("hello.gohtml")
  если ошибка != ноль {
    паника (ошибка)
  }
  http.HandleFunc("/", обработчик)
  http.ListenAndServe(":3000", ноль)
}
func handler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Тип контента", "текст/html")
  ошибка: = testTemplate.Execute(w, ноль)
  если ошибка != ноль {
    http. Error(w, err.Error(), http.StatusInternalServerError)
  }
}
 

В основной функции мы реализуем функции, которые я описал ранее, и назовем их ifIE и endif . Это позволяет нам обновить наш шаблон ( hello.gohtml ) вот так.

 {{ifIE}}

{{конец}}
 

И тогда, если вы перезапустите сервер, перезагрузите страницу, а затем просмотрите исходный код страницы, вы должны увидеть в нем следующее:

 
 

Это прекрасно работает, но создание функции для каждого отдельного комментария, который мы когда-либо захотим использовать в нашем приложении, очень быстро станет утомительным. Для действительно распространенных комментариев (таких как endif выше) создание собственной функции имеет смысл, но нам нужен способ передать любой HTML-комментарий и гарантировать, что он не будет закодирован. Для этого нам нужно определить функцию, которая принимает строку и преобразует ее в шаблон .HTML . Снова откройте main.go и обновите свой template.FuncMap , чтобы соответствовать приведенному ниже.

 основная функция () {
  // ...
  testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{
    "ifIE": func() template.HTML {
      вернуть шаблон.HTML("")
    },
    "htmlSafe": функция (строка html) template.HTML {
      вернуть шаблон.HTML(html)
    },
  }).ParseFiles("hello.gohtml")
  //...
}
 

С нашей новой htmlSafe мы можем добавлять пользовательские комментарии по мере необходимости, например оператор if специально для IE6.

 {{htmlSafe ""}}
 

Последней строкой в ​​этом примере также может быть {{endif}} , так как эта функция все еще определена, но я решил использовать htmlSafe для согласованности.

Наша функция htmlSafe может использоваться даже в сочетании с другими методами (например, {{htmlSafe .User.Widget}} ), если бы мы хотели, но, вообще говоря, если вы хотите, чтобы эти методы возвращали безопасные строки HTML, вам, вероятно, следует изменить их тип возвращаемого значения на template.HTML , чтобы ваши намерения уточнение для будущих разработчиков.

Подведение итогов

После выполнения всех примеров вы должны хорошо понимать, как использовать функции в шаблонах, а также как определять свои собственные функции и делать их доступными внутри ваших шаблонов.

В последней статье этой серии — Создание V в MVC — я рассказываю, как объединить все, что мы узнали в этой серии, чтобы создать многоразовый слой представления для веб-приложения. Мы даже начнем улучшать внешний вид наших страниц с помощью Bootstrap, популярного фреймворка HTML, CSS и JS, чтобы проиллюстрировать, как это совсем не влияет на сложность остального кода; вместо этого вся логика представления изолирована от нашего вновь созданного типа представления.

Эта статья является частью серии «Введение в шаблоны в Go».

Хотите увидеть, как работают шаблоны в целом?

В моем курсе «Веб-разработка с Go» мы используем пакет html/template для построения всего уровня представления для реалистичного приложения. Если вы когда-нибудь задумывались, как все эти части сочетаются друг с другом в рамках полного веб-приложения, я предлагаю вам ознакомиться с курсом.

Если вы подпишитесь на мою рассылку (внизу ↓вон там →), я пришлю вам БЕСПЛАТНЫЙ образец, чтобы вы могли убедиться, что он для вас. Образец включает в себя более 2,5 часов скринкастов и первых нескольких глав из книги.

Вы также будете получать уведомления, когда я публикую новые статьи, новости о предстоящих курсах (включая БЕСПЛАТНЫЕ), и я сообщу вам, когда мои платные курсы поступят в продажу.

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

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *