Функции в html: Тег — Глоссарий | MDN

Содержание

Render-функции | Vue.js

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

Рассмотрим небольшой пример, где функция render() оказалась бы практичнее обычного подхода. Например, требуется сгенерировать заголовки с якорными ссылками:

<h2>
  <a name="hello-world" href="#hello-world">
    Hello world!
  </a>
</h2>

1
2
3
4
5

Такие заголовки будут использоваться часто, поэтому сразу стоит создать компонент:

<anchored-heading :level="1">Hello world!</anchored-heading>

1

Компонент должен генерировать заголовок, в зависимости от входного параметра level, что скорее всего приведёт к такому решению:

const { createApp } = Vue
const app = createApp({})
app.component('anchored-heading', {
  template: `
    <h2 v-if="level === 1">
      <slot></slot>
    </h2>
    <h3 v-else-if="level === 2">
      <slot></slot>
    </h3>
    <h4 v-else-if="level === 3">
      <slot></slot>
    </h4>
    <h5 v-else-if="level === 4">
      <slot></slot>
    </h5>
    <h5 v-else-if="level === 5">
      <slot></slot>
    </h5>
    <h6 v-else-if="level === 6">
      <slot></slot>
    </h6>
  `,
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

Шаблон такого компонента выглядит не очень. Он не только многословен, но и дублирует <slot></slot> для каждого уровня заголовка. А при добавлении нового элемента якоря, потребуется снова дублировать его в каждой ветке

v-if/v-else-if.

Хотя шаблоны отлично работают для большинства компонентов, очевидно, что данный случай не один из них. Давайте перепишем компонент с помощью функции render():

const { createApp, h } = Vue
const app = createApp({})
app.component('anchored-heading', {
  render() {
    return h(
      'h' + this.level, // имя тега
      {}, // входные параметры/атрибуты
      this.$slots.default() // массив дочерних элементов
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Реализация с функцией render() получилась гораздо проще, но требует больше знаний о свойствах экземпляра компонента. Для этого примера потребуется знать, что при передаче дочерних элементов в компонент без директивы

v-slot, например Hello world! внутрь anchored-heading, они будут доступны в экземпляре компонента через $slots.default(). Если это ещё непонятно, рекомендуем сначала прочитать раздел API свойств экземпляра перед углублением в render-функции.

DOM-дерево

Прежде чем погрузиться в изучение render-функций, важно немного подробнее разобраться в том, как работают браузеры. Возьмём, к примеру, этот HTML:

<div>
  <h2>My title</h2>
  Some text content
  <!-- TODO: Add tagline -->
</div>

1
2
3
4
5

Когда браузер читает этот код, он строит дерево «DOM узлов» (opens new window), чтобы помочь себе отслеживать всё.

Для HTML из примера выше дерево DOM-узлов получится таким:

Каждый элемент является узлом. Каждый текст является узлом. Каждый комментарий является узлом! Каждый узел может иметь дочерние элементы (т.е. каждый узел может содержать другие узлы).

Эффективно обновлять все эти узлы — непростая задача, но, к счастью, это не потребуется делать вручную. Требуется лишь сообщать Vue какой HTML нужен на странице в шаблоне:

<h2>{{ blogTitle }}</h2>

1

Или в render-функции:

render() {
  return h('h2', {}, this.blogTitle)
}

1
2
3

В обоих случаях Vue будет автоматически поддерживать страницу в обновлённом состоянии, даже при изменениях значения blogTitle.

Виртуальное DOM-дерево

Vue поддерживает страницу в обновлённом состоянии с помощью виртуального DOM. Он помогает определить изменения, которые необходимо внести в реальный DOM. Взглянем внимательнее на эту строку:

return h('h2', {}, this.blogTitle)

1

Что вернёт функция h()? Это не совсем настоящий DOM-элемент.

Возвращается обычный объект с информацией для Vue, какой узел должен отобразиться на странице, в том числе описание любых дочерних элементов. Это описание называют «виртуальным узлом» или «виртуальной нодой», обычно сокращая до VNode. «Виртуальным DOM» можно назвать всё дерево из VNode, созданных по дереву компонентов Vue.

Аргументы

h()

Функция h() — утилита для создания VNode. Возможно её стоило назвать createVNode() для точности, но она называется h() из-за частого использования и для краткости. Функция принимает три аргумента:

// @returns {VNode}
h(
  // {String | Object | Function} тег
  // Имя HTML-тега, компонента, асинхронного или функционального компонента.
  // Использование функции, возвращающей null, будет отрисовывать комментарий.
  //
  // Обязательный параметр
  'div',
  // {Object} входные параметры
  // Объект, соответствующий атрибутам, входным параметрам
  // и событиям, которые использовались бы в шаблоне.
// // Опциональный {}, // {String | Array | Object} дочерние элементы // Дочерние VNode, созданные с помощью `h()`, // или строки для получения 'текстовых VNode' или // объект со слотами. // // Опциональный [ 'Какой-то текст в начале.', h('h2', 'Заголовок'), h(MyComponent, { someProp: 'foobar' }) ] )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Если входных параметров нет, то дочерние элементы можно передать вторым аргументом. В случаях, если это может добавить путаницы, можно указывать null вторым аргументом, чтобы третьим явно передавать дочерние элементы.

Полный пример

С полученными знаниями можно теперь завершить начатый компонент:

const { createApp, h } = Vue
const app = createApp({})
/** Рекурсивно получаем текст из дочерних узлов */
function getChildrenTextContent(children) {
  return children
    .
-|-$)/g, '') // удаляем в начале и конце висящие тире return h('h' + this.level, [ h( 'a', { name: headingId, href: '#' + headingId }, this.$slots.default() ) ]) }, props: { level: { type: Number, required: true } } })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

Ограничения

VNode должны быть уникальными

В дереве компонентов все VNode должны быть уникальными. Это означает, что следующая render-функция некорректна:

render() {
  const myParagraphVNode = h('p', 'hi')
  return h('div', [
    // НЕПРАВИЛЬНО - одинаковые VNode!
    myParagraphVNode, myParagraphVNode
  ])
}

1
2
3
4
5
6
7

Если требуется многократно дублировать один и тот же элемент/компонент, то реализовать это можно с помощью функции-фабрики. Например, следующая render-функция будет абсолютно корректным способом для отрисовки 20 одинаковых параграфов:

render() {
  return h('div',
    Array.from({ length: 20 }).map(() => {
      return h('p', 'hi')
    })
  )
}

1
2
3
4
5
6
7

Создание VNode компонентов

При создании VNode для компонента первым аргументом h должен быть сам компонент:

render() {
  return h(ButtonCounter)
}

1
2
3

Если необходимо разрешить компонент по имени, можно использовать resolveComponent:

const { h, resolveComponent } = Vue
// ...
render() {
  const ButtonCounter = resolveComponent('ButtonCounter')
  return h(ButtonCounter)
}

1
2

3
4
5
6
7
8

Функцию resolveComponent используют шаблоны под капотом для разрешения компонентов по имени.

В функции render обычно приходится использовать resolveComponent для компонентов зарегистрированных глобально. При локальной регистрации компонентов обычно можно обойтись без неё. Рассмотрим следующий пример:

// Такой код можно упростить
components: {
  ButtonCounter
},
render() {
  return h(resolveComponent('ButtonCounter'))
}

1
2
3
4
5
6
7

Вместо регистрации компонента по имени, а затем поиска, можно использовать его сразу:

render() {
  return h(ButtonCounter)
}

1
2
3

Замена возможностей шаблона обычным JavaScript

v-if и v-for

Всё что используется можно легко реализовать на простом JavaScript, для render-функции Vue не создаёт никакой проприетарной альтернативы. Например, шаблон с v-if и v-for:

<ul v-if="items.length">
  <li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>Элементов не найдено.</p>

1
2
3
4

Можно переписать с использованием JavaScript if/else и map() в render-функции:

props: ['items'],
render() {
  if (this. items.length) {
    return h('ul', this.items.map((item) => {
      return h('li', item.name)
    }))
  } else {
    return h('p', 'Элементов не найдено.')
  }
}

1
2
3
4
5
6
7
8
9
10

В шаблоне иногда удобно использовать тег <template>, чтобы указать v-if или v-for. При миграции на использование render-функции тег <template> можно просто опустить.

v-model

На этапе компиляции шаблона директива v-model раскладывается на входные параметры modelValue

и onUpdate:modelValue — их потребуется указать самостоятельно:

props: ['modelValue'],
emits: ['update:modelValue'],
render() {
  return h(SomeComponent, {
    modelValue: this.modelValue,
    'onUpdate:modelValue': value => this.$emit('update:modelValue', value)
  })
}

1
2
3
4
5
6
7
8

v-on

Необходимо предоставить правильное имя входного параметра для обработчика события, например, для обработки событий click имя входного параметра должно быть onClick.

render() {
  return h('div', {
    onClick: $event => console.log('кликнули!', $event.target)
  })
}

1
2
3
4
5

Модификаторы событий

Модификаторы событий .passive, .capture и .once необходимо указывать после имени события в camelCase.

Например:

render() {
  return h('input', {
    onClickCapture: this.doThisInCapturingMode,
    onKeyupOnce: this.doThisOnce,
    onMouseoverOnceCapture: this.doThisOnceInCapturingMode
  })
}

1
2
3
4
5
6
7

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

Модификатор(ы)Эквивалент в обработчике
.stopevent.stopPropagation()
.preventevent.preventDefault()
. selfif (event.target !== event.currentTarget) return
Клавиши:
например, .enter
if (event.key !== 'Enter') return

Замените Enter на соответствующий key (opens new window)

Модификаторы клавиш:
.ctrl, .alt, .shift, .meta
if (!event.ctrlKey) return

Замените ctrlKey на altKey, shiftKey или metaKey

Пример обработчика со всеми этими модификаторами, используемыми вместе:

render() {
  return h('input', {
    onKeyUp: event => {
      // Отменяем обработку, если элемент вызвавший событие
      // не является элементом, к которому событие было привязано
      if (event.target !== event.currentTarget) return
      // Отменяем обработку, если код клавиши не соответствовал
      // enter и клавиша shift не была нажата в то же время
      if (!event. shiftKey || event.key !== 'Enter') return
      // Останавливаем всплытие события
      event.stopPropagation()
      // Останавливаем поведение по умолчанию для этого элемента
      event.preventDefault()
      // ...
    }
  })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Слоты

Доступ к содержимому слотов в виде массива VNode можно получить через this.$slots:

render() {
  // `<div><slot></slot></div>`
  return h('div', this.$slots.default())
}

1
2
3
4

props: ['message'],
render() {
  // `<div><slot :text="message"></slot></div>`
  return h('div', this.$slots.default({
    text: this.message
  }))
}

1
2
3
4
5
6
7

Для VNode компонента необходимо передать дочерние элементы в h в виде объекта, а не массива. Каждое свойство будет использовано для заполнения одноимённого слота:

render() {
  // `<div><child v-slot="props"><span>{{ props. text }}</span></child></div>`
  return h('div', [
    h(
      resolveComponent('child'),
      null,
      // передаём `slots` как дочерний объект в формате
      // { slotName: props => VNode | Array<VNode> }
      {
        default: (props) => h('span', props.text)
      }
    )
  ])
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

Слоты передаются как функции, что позволяет дочернему компоненту управлять созданием содержимого каждого слота. Любые реактивные данные должны быть доступны внутри функции слота, чтобы гарантировать, что они зарегистрированы как зависимость дочернего компонента, а не родительского. И наоборот, обращения к resolveComponent нужно делать вне функции слота, иначе они будут разрешаться относительно неправильного компонента:

// `<MyButton><MyIcon :name="icon" />{{ text }}</MyButton>`
render() {
  // Вызовы resolveComponent должны находиться вне функции слота
  const Button = resolveComponent('MyButton')
  const Icon = resolveComponent('MyIcon')
  return h(
    Button,
    null,
    {
      // Используем стрелочную функцию для сохранения значения `this`
      default: (props) => {
        // Реактивные свойства должны считываться внутри функции слота,
        // чтобы они стали зависимостями для отрисовки дочернего компонента
        return [
          h(Icon, { name: this. icon }),
          this.text
        ]
      }
    }
  )
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Если компонент получает слоты из родителя — их можно передать напрямую дочернему:

render() {
  return h(Panel, null, this.$slots)
}

1
2
3

Но можно также передавать их по-отдельности или оборачивать по необходимости:

render() {
  return h(
    Panel,
    null,
    {
      // Если хотим просто передать функцию слота
      header: this.$slots.header,
      // Если требуется как-то управлять слотом,
      // тогда нужно обернуть его в новую функцию
      default: (props) => {
        const children = this.$slots.default ? this.$slots.default(props) : []
        return children.concat(h('div', 'Extra child'))
      }
    }
  )
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<component> и is

Шаблоны для реализации атрибута is используют под капотом resolveDynamicComponent. Можно воспользоваться этой же функцией, если в создаваемой render-функции требуется вся гибкость, предоставляемая is:

const { h, resolveDynamicComponent } = Vue
// ...
// `<component :is="name"></component>`
render() {
  const Component = resolveDynamicComponent(this.name)
  return h(Component)
}

1
2
3
4
5
6
7
8
9

Аналогично is, resolveDynamicComponent поддерживает передачу имени компонента, имени HTML-элемента или объекта с опциями компонента.

Но обычно такой уровень гибкости не требуется. Поэтому resolveDynamicComponent часто можно заменить на более конкретную альтернативу.

К примеру, если нужно поддерживать только имена компонентов — можно использовать resolveComponent.

Если VNode всегда будет HTML-элементом — можно передавать имя напрямую в h:

// `<component :is="bold ? 'strong' : 'em'"></component>`
render() {
  return h(this. bold ? 'strong' : 'em')
}

1
2
3
4

Аналогично, если передаваемое в is значение будет объектом опций компонента, то не нужно ничего разрешать и можно сразу передать его первым аргументом в h.

Подобно тегу <template>, тег <component> нужен в шаблонах только в качестве синтаксического сахара и его следует опустить при миграции на render-функции.

Пользовательские директивы

Пользовательские директивы можно применить к VNode с помощью withDirectives:

const { h, resolveDirective, withDirectives } = Vue
// ...
// <div v-pin:top.animate="200"></div>
render () {
  const pin = resolveDirective('pin')
  return withDirectives(h('div'), [
    [pin, 200, 'top', { animate: true }]
  ])
}

1
2
3
4
5
6
7
8
9

Функция resolveDirective используется в шаблонах под капотом, чтобы разрешить директиву по имени. Это нужно лишь в случаях, когда нет прямого доступа к объекту с объявлением директивы.

Встроенные компоненты

Встроенные компоненты, такие как <keep-alive>, <transition>, <transition-group> и <teleport> по умолчанию не регистрируются глобально. Это позволяет системе сборки выполнять tree-shaking, чтобы добавлять эти компоненты в сборку только в случае, если они используются. Но это также означает, что не выйдет получить к ним доступ с помощью resolveComponent или resolveDynamicComponent.

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

const { h, KeepAlive, Teleport, Transition, TransitionGroup } = Vue
// ...
render () {
  return h(Transition, { mode: 'out-in' }, /* ... */)
}

1
2
3
4
5
6
7

Возвращаемые значения render-функций

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

Если вернуть строку, то будет создана текстовая VNode без какого-либо элемента-обёртки:

render() {
  return 'Привет мир!'
}

1
2
3

Также можно вернуть массив дочерних узлов, не оборачивая их в корневой узел. В таком случае будет создан фрагмент:

// Аналогично шаблону `Пример<br>мир!`
render() {
  return [
    'Привет',
    h('br'),
    'мир!'
  ]
}

1
2
3
4
5
6
7
8

Если компоненту не нужно ничего отображать (например, потому что ещё загружаются данные), то можно просто вернуть null. Тогда в DOM будет создан узел комментария.

JSX

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

h(
  resolveComponent('anchored-heading'),
  {
    level: 1
  },
  {
    default: () => [h('span', 'Привет'), ' мир!']
  }
)

1
2
3
4
5
6
7
8
9

Особенно, когда эквивалент в шаблоне выглядит очень лаконично:

<anchored-heading :level="1"> <span>Привет</span> мир! </anchored-heading>

1

Поэтому есть плагин для Babel (opens new window), чтобы использовать JSX во Vue и получить синтаксис, близкий к шаблонам:

import AnchoredHeading from '. /AnchoredHeading.vue'
const app = createApp({
  render() {
    return (
      <AnchoredHeading level={1}>
        <span>Привет</span> мир!
      </AnchoredHeading>
    )
  }
})
app.mount('#demo')

1
2
3
4
5
6
7
8
9
10
11
12
13

Подробнее о том, как JSX преобразуется в JavaScript смотрите в документации плагина (opens new window).

Функциональные компоненты

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

Для создания функционального компонента нужно использовать простую функцию, а не объект с опциями компонента. Она фактически является render-функцией компонента. И так как у функционального компонента нет this, то props будут передаваться первым аргументом:

const FunctionalComponent = (props, context) => {
  // . ..
}

1
2
3

Вторым аргументом передаётся context, содержащий три свойства: attrs, emit и slots. Они эквивалентны свойствам экземпляра $attrs, $emit и $slots.

В функциональных компонентах большинство обычных опций конфигурации компонентов недоступны. Но можно определять props и emits, добавив их в качестве свойств:

FunctionalComponent.props = ['value']
FunctionalComponent.emits = ['click']

1
2

Если не указана опция props, то передаваемый в функцию объект props будет содержать все атрибуты, как и attrs. Также имена входных параметров не будут нормализоваться в camelCase.

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

Компиляция шаблона

Интересный факт, все шаблоны во Vue компилируются в render-функции. Обычно нет нужды знать такие детали реализации, но может быть любопытно увидеть как же компилируются те или иные возможности шаблона. Небольшая демонстрация ниже показывает работу метода Vue.compile при компиляции строковых шаблонов на лету, попробуйте сами:

Лямбды — Kotlin

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

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

Функции высшего порядка

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

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

fun <T, R> Collection<T>.fold(
    initial: R,
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}

В приведённом выше коде параметр combine имеет функциональный тип (R, T) -> R, поэтому он принимает функцию, которая принимает два аргумента типа R и T и возвращает значение типа R. Он вызывается внутри цикла for и присваивает accumulator возвращаемое значение.

Чтобы вызвать fold, вы должны передать ему экземпляр функционального типа в качестве аргумента и лямбда-выражение (описание ниже). Лямбда-выражения часто используются в качестве параметра функции высшего порядка.

fun main() {
    val items = listOf(1, 2, 3, 4, 5)
    // Лямбда - это блок кода, заключенный в фигурные скобки.
    items.fold(0, {
        // Если у лямбды есть параметры, то они указываются перед знаком '->'
        acc: Int, i: Int ->
        print("acc = $acc, i = $i, ")
        val result = acc + i
        println("result = $result")
        // Последнее выражение в лямбде считается возвращаемым значением:
        result
    })
    // Типы параметров в лямбде необязательны, если они могут быть выведены:
    val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
    // Ссылки на функции также могут использоваться для вызовов функций высшего порядка:
    val product = items.fold(1, Int::times)
    println("joinedToString = $joinedToString")
    println("product = $product")
}

Функциональные типы

Kotlin использует семейство функциональных типов, таких как (Int) -> String, для объявлений, которые являются частью функций: val onClick: () -> Unit = . ...

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

  • У всех функциональных типов есть список с типами параметров, заключенный в скобки, и возвращаемый тип: (A, B) -> C обозначает тип, который предоставляет функции два принятых аргумента типа A и B, а также возвращает значение типа C. Список с типами параметров может быть пустым, как, например, в () -> A. Возвращаемый тип Unit не может быть опущен;
  • У функциональных типов может быть дополнительный тип — получатель (ориг.: receiver), который указывается в объявлении перед точкой: тип A.(B) -> C описывает функции, которые могут быть вызваны для объекта-получателя A с параметром B и возвращаемым значением C. Литералы функций с объектом-приёмником часто используются вместе с этими типами;
  • Останавливаемые функции (ориг. : suspending functions) принадлежат к особому виду функциональных типов, у которых в объявлении присутствует модификатор suspend, например, suspend () -> Unit или suspend A.(B) -> C.

Объявление функционального типа также может включать именованные параметры: (x: Int, y: Int) -> Point. Именованные параметры могут быть использованы для описания смысла каждого из параметров.

Чтобы указать, что функциональный тип может быть nullable, используйте круглые скобки: ((Int, Int) -> Int)?.

При помощи круглых скобок функциональные типы можно объединять: (Int) -> ((Int) -> Unit).

Стрелка в объявлении является правоассоциативной (ориг.: right-associative), т.е. объявление (Int) -> (Int) -> Unit эквивалентно объявлению из предыдущего примера, а не ((Int) -> (Int)) -> Unit.

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

typealias ClickHandler = (Button, ClickEvent) -> Unit

Создание функционального типа

Существует несколько способов получить экземпляр функционального типа:

  • Используя блок с кодом внутри функционального литерала в одной из форм:

    • лямбда-выражение: { a, b -> a + b },
    • анонимная функция: fun(s: String): Int { return s.toIntOrNull() ?: 0 }

    Литералы функций с объектом-приёмником могут использоваться как значения функциональных типов с получателем.

  • Используя вызываемую ссылку на существующее объявление:

    • функции верхнего уровня, локальной функции, функции-члена или функции-расширения: ::isOdd, String::toInt,
    • свойства верхнего уровня, члена или свойства-расширения: List<Int>::size,
    • конструктора: ::Regex

    К ним относятся привязанные вызываемые ссылки, которые указывают на член конкретного экземпляра: foo::toString.

  • Используя экземпляр пользовательского класса, который реализует функциональный тип в качестве интерфейса:
class IntTransformer: (Int) -> Int {
    override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()

При достаточной информации компилятор может самостоятельно вывести функциональный тип для переменной.

val a = { i: Int -> i + 1 } // Выведенный тип - (Int) -> Int

Небуквальные (ориг.: non-literal) значения функциональных типов с и без получателя являются взаимозаменяемыми, поэтому получатель может заменить первый параметр, и наоборот. Например, значение типа (A, B) -> C может быть передано или назначено там, где ожидается A.(B) -> C, и наоборот.

fun main() {
    val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
    val twoParameters: (String, Int) -> String = repeatFun // OK
    fun runTransformation(f: (String, Int) -> String): String {
        return f("hello", 3)
    }
    val result = runTransformation(repeatFun) // OK
    println("result = $result")
}

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

Вызов экземпляра функционального типа

Значение функционального типа может быть вызвано с помощью оператора invoke(...): f.invoke(x) или просто f(x).

Если значение имеет тип получателя, то объект-приёмник должен быть передан в качестве первого аргумента. Другой способ вызвать значение функционального типа с получателем — это добавить его к объекту-приёмнику, как если бы это была функция-расширение: 1.foo(2).

Пример:

fun main() {
    val stringPlus: (String, String) -> String = String::plus
    val intPlus: Int.(Int) -> Int = Int::plus
    println(stringPlus.invoke("<-", "->"))
    println(stringPlus("Hello, ", "world!"))
    println(intPlus.invoke(1, 1))
    println(intPlus(1, 2))
    println(2.intPlus(3)) // вызывается как функция-расширение
}

Встроенные функции

Иногда выгодно улучшить производительность функций высшего порядка, используя встроенные функции (ориг. : inline functions).

Лямбда-выражения и анонимные функции

Лямбда-выражения и анонимные функции — это «функциональный литерал», то есть необъявленная функция, которая немедленно используется в качестве выражения. Рассмотрим следующий пример:

max(strings, { a, b -> a.length < b.length })

Функция max является функцией высшего порядка, потому что она принимает функцию в качестве второго аргумента. Этот второй аргумент является выражением, которое в свою очередь есть функция, то есть функциональный литерал. Как функция он эквивалентен объявлению:

fun compare(a: String, b: String): Boolean = a.length < b.length

Синтаксис лямбда-выражений

Полная синтаксическая форма лямбда-выражений может быть представлена следующим образом:

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
  • Лямбда-выражение всегда заключено в скобки {...};
  • Объявление параметров при таком синтаксисе происходит внутри этих скобок и может включать в себя аннотации типов;
  • Тело функции начинается после знака ->;
  • Если тип возвращаемого значения не Unit, то в качестве возвращаемого типа принимается последнее (а возможно и единственное) выражение внутри тела лямбды.

Если вы вынесите все необязательные объявления, то, что останется, будет выглядеть следующим образом:

val sum = { x: Int, y: Int -> x + y }

Передача лямбды в качестве последнего параметра

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

val product = items.fold(1) { acc, e -> acc * e }

Такой синтаксис также известен как trailing lambda.

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

run { println("...") }

it: неявное имя единственного параметра

Очень часто лямбда-выражение имеет только один параметр.

Если компилятор способен самостоятельно определить сигнатуру, то объявление параметра можно опустить вместе с ->. Параметр будет неявно объявлен под именем it.

ints. filter { it > 0 } // этот литерал имеет тип '(it: Int) -> Boolean'

Возвращение значения из лямбда-выражения

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

Таким образом, два следующих фрагмента равнозначны:

ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}
ints.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter
}

Это соглашение, вместе с передачей лямбда-выражения вне скобок, позволяет писать код в стиле LINQ.

strings.filter { it.length == 5 }.sortedBy { it }.map { it.uppercase() }

Символ подчеркивания для неиспользуемых переменных

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

map.forEach { _, value -> println("$value!") }

Деструктуризация в лямбдах

Деструктуризация в лямбдах описана в Деструктурирующие объявления.

Анонимные функции

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

fun(x: Int, y: Int): Int = x + y

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

fun(x: Int, y: Int): Int {
    return x + y
}

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

ints.filter(fun(item) = item > 0)

Аналогично и с типом возвращаемого значения: он вычисляется автоматически для функций-выражений или же должен быть явно определён (если не является типом Unit) для анонимных функций с блоком в качестве тела.

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

Одним из отличий лямбда-выражений от анонимных функций является поведение оператора return (non-local returns). Слово return, не имеющее метки (@), всегда возвращается из функции, объявленной ключевым словом fun. Это означает, что return внутри лямбда-выражения возвратит выполнение к функции, включающей в себя это лямбда-выражение. Внутри анонимных функций оператор return, в свою очередь, выйдет, собственно, из анонимной функции.

Замыкания

Лямбда-выражение или анонимная функция (так же, как и локальная функция или анонимные объекты) имеет доступ к своему замыканию, то есть к переменным, объявленным вне этого выражения или функции. Переменные, захваченные в замыкании, могут быть изменены в лямбде.

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

Литералы функций с объектом-приёмником

Функциональные типы с получателем, такие как A.(B) -> C, могут быть вызваны с помощью особой формы – литералов функций с объектом-приёмником.

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

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

Это схоже с принципом работы функций-расширений, которые позволяют получить доступ к членам объекта-приёмника внутри тела функции.

Ниже приведён пример литерала с получателем вместе с его типом, где plus вызывается для объекта-приёмника:

val sum: Int. (Int) -> Int = { other -> plus(other) }

Синтаксис анонимной функции позволяет вам явно указать тип приёмника. Это может быть полезно в случае, если вам нужно объявить переменную типа нашей функции для использования в дальнейшем.

val sum = fun Int.(other: Int): Int = this + other

Лямбда-выражения могут быть использованы как литералы функций с приёмником, когда тип приёмника может быть выведен из контекста. Один из самых важных примеров их использования это типобезопасные строители (ориг.: type-safe builders).

class HTML {
    fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
    val html = HTML() // создание объекта-приёмника
    html.init()       // передача приёмника в лямбду
    return html
}
html {     // лямбда с приёмником начинается тут
    body() // вызов метода объекта-приёмника
}

Как вызвать функцию JavaScript в HTML

Джейми Джувилер

Обновлено:

Опубликовано:

Как только вы хорошо разберетесь в HTML и CSS, следующим шагом будет JavaScript, чтобы вывести ваши веб-проекты на новый уровень. JavaScript добавляет к веб-страницам интерактивные и динамические функции — это то, что делает их интересными и привлекательными, помимо статического контента.

Если вы хотите создать интерактивную страницу, одним из основных навыков, которые вам необходимо знать, является вызов функций JavaScript из HTML-документа. Другими словами, когда пользователь каким-то образом взаимодействует с элементом HTML (например, щелкает по нему), как вы можете вызвать в ответ функцию JavaScript?

К счастью, HTML и JavaScript упрощают эту задачу. В этом посте вы узнаете три метода вызова функции JavaScript с помощью HTML. Давайте углубимся.

Как вызвать функцию JavaScript в HTML

В этом разделе мы рассмотрим три способа вызова функции с помощью HTML

  • с тегами сценария
  • с внешним файлом JavaScript
  • с прослушивателями событий

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

Самый простой способ вызвать функцию JavaScript в документе HTML — определить функцию внутри пары тегов <тело> <дел>

А вот как выглядит код HTML и JavaScript в действии:

417541503" data-theme-id="39533" data-default-tab="html,result" data-slug-hash="MWBbBxR" data-editable="true" data-user="hubspot"> См. функцию вызова пера в js: external js от HubSpot (@hubspot) на CodePen.

 

Использование прослушивателей событий

Последний и наиболее эффективный способ вызова функций JavaScript с помощью HTML — прослушиватели событий. В JavaScript прослушиватель событий — это функция, ожидающая события (например, щелчка мыши) для элемента или набора элементов. Как только это событие происходит, прослушиватель событий вызывает функцию.

Чтобы создать прослушиватель событий в JavaScript, вы используете встроенный Функция addEventListener() . Эта функция добавляет прослушиватель событий к HTML-элементу, классу или идентификатору. Функция addEventListener() принимает два обязательных параметра:

  • событие: Это запускает прослушиватель, например click или mouseover .
  • Слушатель
  • : это функция JavaScript, которая вызывается при возникновении события. При желании вы можете добавить несколько слушателей.

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

См. функцию вызова пера в js: прослушиватель событий от HubSpot (@hubspot) на CodePen.

Прослушиватели событий идеально подходят для запуска функций JavaScript с помощью пользовательского ввода, поскольку они устраняют необходимость включения вызовов функций JavaScript (например, onclick ) из HTML-кода, что упрощает поддержку кода. Кроме того, вы можете вызывать несколько функций из одного события.

Вызов функций JavaScript с помощью HTML.

По своей сути HTML отвечает только за содержимое страницы. Без JavaScript пользователи не смогут активно взаимодействовать с вашей веб-страницей.

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

Темы: HTML

Не забудьте поделиться этим постом!

Связанные статьи

  • Span против Div: разница, объясненная в 1100 слов или меньше

    29 марта 2023 г.

  • hubspot.com/website/html">

    Полное руководство по HTML для начинающих: как его писать, изучать и использовать

    28 марта 2023 г.

  • Как выделить жирный шрифт, курсив и форматировать текст в HTML

    21 марта 2023 г.

  • hubspot.com/website/what-is-colspan-in-html">

    Что такое colspan в HTML?

    16 марта 2023 г.

  • HTML Mailto: как его использовать

    15 марта 2023 г.

  • HTML Strikethrough: 3 простых способа сделать это

    15 марта 2023 г.

  • Ваше руководство по типам ввода HTML

    14 марта 2023 г.

  • Как добавить CSS в HTML: понимание встроенного, внутреннего и внешнего CSS

    13 марта 2023 г.

  • Как использовать атрибут Rel в HTML

    27 февраля 2023 г.

  • Что нужно и что нельзя делать при добавлении разрыва строки в HTML

    17 января 2023 г.

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

.
secondToFormattedString(seconds) Строка возврата "чч:мм:сс"
exec(f, аргументы, контекст) Функция или код Exec с аргументами и контекстом
isOutsider(el) Проверить элемент за пределы поля зрения
в области просмотра (эл) Проверить элемент в окне просмотра
Длина объекта (эл) Возвращает длину объекта.
процентов(а, б, г) Возвращает процентное значение для b из a. Если r истинно, возвращаемое значение является целым числом, иначе - реальным
объектСдвиг(объект) Возврат смещенного объекта. Удалить элемент с первым ключом
objectDelete(объект, ключ) Удалить ключ из объекта и вернуть его
объектКлон (объект) Скопировать объект для клонирования и вернуть его
arrayDelete(arr, val) Удалить из массива по значению и вернуть его
arrayDeleteByKey(арр, ключ) Удалить ключ из массива и вернуть его
arrayDeleteByMultipleKeys(arr, keys_array) Удалить ключ из массива и вернуть новый массив
нвл(данные, прочее) Проверить данные на нулевое значение и вернуть другое, если нулевое
гитхаб (репо, обратный вызов) Получить информацию с github и поместить ее в обратный вызов
обнаружитьIE() Определить, является ли браузер Internet Explorer или Edge
обнаружитьChrome() Определить, является ли браузер Chrome
мд5(стр) Кодировать строку с помощью алгоритма md5
encodeUri(str) Исправление базовой функции encodeUri
страницаВысота() Вернуть реальную высоту страницы
cleanPreCode(селектор) Удалить пробелы из кода
координат(эл) Возвращает координаты элемента как объект {сверху, слева}
positionXY(событие, тип) Позиция возврата для типа: экран, страница, клиент
клиентXY(событие) Возврат позиции
страницаXY(событие) Возврат позиции
экранXY(событие) Возврат позиции
isRightMouse(событие) Проверить, нажимает ли пользователь правую кнопку мыши
hiddenElementSize(el, includeMargin) Возвращает размер элемента как объект {ширина, высота}
getStyle(эль, псевдо) Возвращает вычисляемые стили элемента
getStyleOne(el, свойство) Возвращает рассчитанные стили элемента для указанного свойства
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(данные) Зарегистрировать локаль метро во время выполнения
соотношение сторонH(ширина, соотношение) Высота возврата для определенного соотношения
соотношение сторон(высота, соотношение) Ширина возврата для определенного соотношения
значениеВОбъекте(объект, значение) Проверить, существует ли значение в объекте
keyInObject(объект, ключ) Проверить, существует ли ключ в объекте
новыйCssSheet(носитель) Создать объект листа css
addCssRule(лист, селектор, правила, индекс) Добавить правило в объект листа css
медиа (запрос) Проверить медиа-запрос
медиарежимы() Получить текущие точки мультимедиа
СМИСуществует(м) Возвращает true, если точка существует в текущем носителе
в СМИ(м) Проверить, является ли точка текущим носителем
isValue (значение) Вернуть true, если значение не в [undefined, null, ""]
Отрицательное (значение) Возвращает истину, если значение меньше 0
isPositive(val) Возвращает истину, если значение больше 0
isZero(val) Возвращает true, если val равно 0 (целое число или число с плавающей запятой)
между(вал, низ, верх, равно) Возвращает истину, если значение находится между нижним и верхним
parseMoney(val) Возвращает числовое значение из любого денежного формата.
Оставить комментарий

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

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

© 2019 Штирлиц Сеть печатных салонов в Перми

Цифровая печать, цветное и черно-белое копирование документов, сканирование документов, ризография в Перми.