QML

JavaScript в QML: функции, выражения, ограничения движка

Введение

Привязки (статья 481) и обработчики сигналов (статья 482) используют JavaScript-выражения — в этой статье разберём JavaScript в контексте QML отдельно: где он используется, какие практические ограничения накладывает движок QML по сравнению с полноценным JavaScript в браузере или Node.js, и как организовать более сложную логику.

Концепция

QML использует движок JavaScript (в современных версиях Qt — V4 либо аналогичный высокопроизводительный движок) для вычисления выражений привязок, выполнения кода обработчиков сигналов, и произвольных пользовательских функций, объявляемых непосредственно в QML-документах через ключевое слово function. В отличие от полноценной среды JavaScript браузера, движок QML не предоставляет доступ к DOM, window, и большинству Web API — вместо этого доступны QML-специфичные глобальные объекты (Qt, дающий вспомогательные функции работы с датами/цветами) и, что особенно важно, прямой доступ к объектам и свойствам QML-сцены (через id, статья 480) как к обычным JavaScript-объектам.

Пример кода: JavaScript в QML

import QtQuick

Item {
    id: root

    // Пользовательская JavaScript-функция, объявленная прямо в QML-документе
    function formatCurrency(amount) {
        return "₽ " + amount.toFixed(2).replace(".", ",")
    }

    Text {
        text: root.formatCurrency(1234.5) // "₽ 1234,50"
    }

    // Более сложная логика — обработка массива объектов
    property var items: [{name: "Товар A", price: 100}, {name: "Товар B", price: 250}]

    function totalPrice() {
        let total = 0
        for (let i = 0; i < items.length; i++) {
            total += items[i].price
        }
        return total
    }

    Text {
        text: "Итого: " + root.formatCurrency(root.totalPrice())
    }
}
// Использование встроенного объекта Qt для вспомогательных операций
Text {
    property date currentDate: new Date()
    text: Qt.formatDate(currentDate, "dd.MM.yyyy")
}

Rectangle {
    color: Qt.rgba(0.2, 0.4, 0.8, 1.0) // создание цвета через JavaScript-функцию Qt
}
// Внешний JavaScript-файл для более существенной логики — отделение от самого QML-документа
// utils.js:
// function calculateDiscount(price, percent) {
//     return price * (1 - percent / 100)
// }

import "utils.js" as Utils

Text {
    text: "Цена со скидкой: " + Utils.calculateDiscount(1000, 15)
}

Пояснения к коду

formatCurrency/totalPrice показывают типичные пользовательские JavaScript-функции, объявленные непосредственно в QML-документе — они доступны для вызова из выражений привязки (как в text: root.formatCurrency(...)) точно так же, как и обычные свойства, что даёт удобный способ инкапсулировать небольшую, повторяющуюся логику форматирования или вычисления без необходимости выносить её в C++. Объект Qt с функциями Qt.formatDate/Qt.rgba демонстрирует QML-специфичные глобальные вспомогательные функции, заменяющие отсутствующие в этом контексте Web API браузера — эти функции покрывают типичные потребности форматирования и работы с QML-специфичными типами данных (датами, цветами), не предоставляемые стандартным JavaScript напрямую. Импорт внешнего файла utils.js через import "utils.js" as Utils показывает практичный способ организации более существенной логики — вынос JavaScript-кода в отдельные файлы облегчает повторное использование той же логики в нескольких разных QML-документах и улучшает читаемость отдельных файлов, не загромождённых длинным, встроенным JavaScript-кодом.

Подводные камни

  • Попытка использовать Web API браузера (DOM-манипуляции, fetch, localStorage) в QML-коде, ошибочно предполагая полноценную совместимость с окружением браузера — движок QML предоставляет лишь подмножество стандартного JavaScript плюс QML-специфичные расширения, и попытка использовать недоступные в этом контексте API приводит к ошибке времени выполнения («undefined is not a function» или аналогичной), часто неочевидной для разработчика, не до конца понимающего границы этой среды выполнения.
  • Размещение существенной, сложной бизнес-логики в JavaScript-коде QML вместо выноса в C++ (статья 490) — хотя JavaScript в QML удобен для небольших вспомогательных функций (форматирование, простые вычисления), движок QML JavaScript в целом не предназначен для тяжёлых, вычислительно интенсивных задач, и такая логика, реализованная в JavaScript, как правило, заметно медленнее эквивалентного, оптимизированного C++ кода, особенно для операций, выполняемых часто или над большими объёмами данных.
  • Игнорирование различий в поведении некоторых JavaScript-конструкций между движком QML и стандартным JavaScript других сред — хотя движок QML стремится к соответствию стандарту ECMAScript, отдельные тонкие детали поведения (особенно связанные с конкретными версиями поддерживаемого синтаксиса языка или специфичными для среды выполнения аспектами) могут не всегда совпадать с поведением, привычным разработчику по опыту с Node.js или браузером, и при сомнениях стоит явно проверить конкретное поведение именно в среде QML, а не полагаться исключительно на общие знания JavaScript из другого контекста.
  • Отсутствие надлежащей организации существенного объёма JavaScript-кода, разбросанного по множеству встроенных функций прямо внутри различных QML-файлов без выделения в отдельные, переиспользуемые .js-модули — это усложняет поддержку и тестирование такого кода по сравнению с явным выделением логики в отдельные, именованные JavaScript-файлы (как показано с utils.js), которые легче тестировать в изоляции и переиспользовать в разных частях приложения.