# JavaScript-интерпретатор
Sales Ninja умеет вычислять JavaScript прямо внутри контента ваших модификаций. Это позволяет реагировать на данные посетителя — город, устройство, UTM-метки, погоду, время суток — и подставлять разный текст без отдельных вариантов под каждый случай.
Код пишется в двойных фигурных скобках: {{ ... }}. Система находит такие блоки, выполняет их на сервере и подставляет результат до отправки контента в браузер посетителя.
Это серверный интерпретатор. Он не имеет доступа к странице (
document,window,fetch). Если вам нужно работать с DOM уже в браузере — это Пользовательский JavaScript, отдельная возможность.
# Где работает
Один и тот же интерпретатор используется во всех продуктах сайтового стека:
- Персонализации — в вариантах
- A/B-тесты — в вариантах
- Действия по правилам
И внутри любого текстового поля модификации, а не только в поле «Контент»:
- HTML-контент (заменить/вставить HTML)
- текстовые значения («Установить значение»)
- значения атрибутов (
src,href,alt,data-*и т.д.) - инлайн-стили и CSS-контент
- значения cookie
- значения URL-параметров
- параметры, передаваемые в интеграции (JivoSite, CarrotQuest, Mindbox, ROIStat, Calltouch, Яндекс.Метрика)
Везде, где можно ввести текст, можно использовать {{ ... }}.
# Синтаксис
# Простое выражение
Сумма 2 + 2 = {{2+2}}
Результат: Сумма 2 + 2 = 4
Для простых выражений без условий можно опустить return. Если код содержит несколько строк или условия — используйте return явно.
# Условный оператор
10 больше двух? - {{
if (10 > 2) {
return "Да";
} else {
return "Нет";
}
}}
Результат: 10 больше двух? - Да
# Тернарный оператор
Для коротких условий удобен тернарный оператор:
Скидка: {{data.session.number > 1 ? "10% для постоянных клиентов" : "5% на первый заказ"}}
# Вложенные функции
Сумма 99 и 1 равна {{
function sum(arg1, arg2) {
return arg1 + arg2;
}
return sum(99, 1);
}}
Результат: Сумма 99 и 1 равна 100
Если используете вложенные функции — не забывайте явно возвращать результат через
return.
# Несколько блоков в одном поле
В одном поле может быть сколько угодно блоков в двойных фигурных скобках. Каждый блок вычисляется независимо:
{{5 + 5}} больше {{4 - 2}}? - {{return "Да";}}
Результат: 10 больше 2? - Да
Будьте аккуратны: большое количество блоков замедляет вычисление контента.
# Контекст: переменная data
Внутри кода доступна переменная data — объект с информацией о текущем посетителе. Данные собираются автоматически при каждом визите: по IP определяется город (и по нему — погода), по User-Agent — устройство, по URL — UTM-метки, со страницы — Schema.org-разметка.
Все поля data доступны только для чтения. Любое поле может быть null (например, город не определился по IP) — всегда проверяйте значения.
# Геолокация: город
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.geo.city.nativeName | Название на родном языке | string | "Москва", "Paris" |
data.geo.city.englishName | Название на английском | string | "Moscow" |
data.geo.city.russianName | Название на русском | string | "Москва", "Париж" |
data.geo.city.isRegionCapital | Столица региона | bool | true |
data.geo.city.isCountryCapital | Столица страны | bool | true |
data.geo.city.population | Население | number | 12500000 |
data.geo.city.agglomerationPopulation | Население агломерации | number | 17100000 |
data.geo.city.averageSalaryUSD | Средняя зарплата (USD) | number | 99.9 |
data.geo.city.economicLocalIndex | Экономический индекс (0–100) | number | 75.3 |
# Геолокация: регион
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.geo.region.nativeName | Название на родном языке | string | "Московская область" |
data.geo.region.englishName | Название на английском | string | "Moscow Oblast" |
data.geo.region.russianName | Название на русском | string | "Московская область" |
data.geo.region.population | Население региона | number | 7500000 |
# Геолокация: страна
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.geo.country.nativeName | Название на родном языке | string | "Россия" |
data.geo.country.englishName | Название на английском | string | "Russia" |
data.geo.country.russianName | Название на русском | string | "Россия" |
data.geo.country.population | Население страны | number | 146000000 |
# Данные страницы
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.page.pageClearedUrl | URL страницы (без трекинг-параметров) | string | "/catalog/phones" |
data.page.pageTitle | Заголовок страницы | string | "Каталог телефонов" |
# Данные страницы: Schema.org
Автоматически извлекаются из HTML Microdata и JSON-LD разметки на странице.
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.page.schemaOrg.brandName | Бренд | string | "Apple" |
data.page.schemaOrg.offer.price | Цена | number | 99990 |
data.page.schemaOrg.offer.availability | Наличие | string | "InStock" |
data.page.schemaOrg.aggregateRating.ratingValue | Средний рейтинг | number | 4.5 |
data.page.schemaOrg.aggregateRating.reviewCount | Количество отзывов | number | 120 |
# Погода
Определяется по городу посетителя. Если город не определён — все значения null.
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.weather.temp | Температура (°C) | number | 22.5 |
data.weather.tempFeelsLike | Ощущаемая температура (°C) | number | 20.0 |
data.weather.humidity | Влажность (%) | number | 65 |
# Устройство
| Параметр | Описание | Тип | Возможные значения |
|---|---|---|---|
data.device.browser | Браузер | string | "Chrome", "Safari", "Edge", "Opera", "Firefox", "YandexBrowser", "InternetExplorer", "Other" |
data.device.deviceType | Тип устройства | string | "Desktop", "Smartphone", "Tablet", "Other" |
data.device.os | Операционная система | string | "Windows", "MacOs", "Linux", "iOS", "Android", "Other" |
# UTM-метки
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.utm.source | Источник | string | "google" |
data.utm.medium | Канал | string | "cpc" |
data.utm.campaign | Кампания | string | "summer_sale" |
data.utm.content | Контент | string | "banner_1" |
data.utm.term | Ключевое слово | string | "best_deals" |
data.utm.snCampaign | Метка кампании Sales Ninja | string | "sn_promo" |
# Дата и время
Время определяется по часовому поясу посетителя.
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.dateAndTime.clientLocalHour | Локальный час (0–23) | number | 14 |
data.dateAndTime.dayOfWeekNumber | День недели (1–7, понедельник = 1) | number | 3 |
data.dateAndTime.dayOfWeekNumberUsa | День недели (0–6, воскресенье = 0) | number | 3 |
data.dateAndTime.weekNumberInMonth | Неделя в месяце (1–5) | number | 2 |
data.dateAndTime.isNight | Ночь (час < 7 или > 22, ≈ 23:00–07:00) | bool | false |
data.dateAndTime.isMorning | Утро (час 8–10) | bool | true |
data.dateAndTime.isWeekend | Выходной | bool | false |
# Сессия
| Параметр | Описание | Тип | Пример |
|---|---|---|---|
data.session.number | Номер визита пользователя (начиная с 1) | number | 3 |
data.session.durationSec | Длительность текущей сессии (секунды) | number | 245.5 |
data.session.pagesOpenedInThisSession | Страниц открыто в текущей сессии | number | 5 |
# Шаблонные функции
Помимо стандартного JavaScript доступны встроенные функции-помощники.
# simulateCityClients(min, max)
Возвращает правдоподобное количество клиентов в городе посетителя. Учитывает население города, размер агломерации и экономический индекс. Результат всегда находится в диапазоне от min до max.
Количество выполненных заявок в вашем городе — {{ simulateCityClients(500, 2000) }}
Результат для Москвы: Количество выполненных заявок в вашем городе — 1847
Результат для небольшого города: Количество выполненных заявок в вашем городе — 612
Если город не определён, возвращается среднее значение (min + max) / 2.
Пример с контекстом:
Уже {{simulateCityClients(100, 800)}} клиентов в г. {{data.geo.city.nativeName || "вашем регионе"}} выбрали нас
# log(message)
Записывает сообщение в серверный лог для отладки. Не влияет на результат и не виден посетителю.
{{
log("City: " + data.geo.city.nativeName);
log("Session: " + data.session.number);
return "Привет!";
}}
# Доступные возможности и ограничения
# Что доступно
- Стандартный JavaScript:
Math,String,Number,Array,JSON, условные операторы, циклы, функции. - Переменная
data(см. выше) и шаблонные функцииsimulateCityClients,log.
Math.log,Math.log2,Math.log10дополнительно защищены: для нуля и отрицательных аргументов возвращают0вместоNaN/Infinity.
# Песочница
Код выполняется в изолированной среде на сервере. Недоступно:
- DOM,
window,document,fetchи любые браузерные API - сеть, файловая система
- доступ к .NET-рантайму (рефлексия, CLR)
# Лимиты
Чтобы один тяжёлый шаблон не замедлял отдачу контента, на каждый блок действуют ограничения:
| Ограничение | Значение |
|---|---|
| Время выполнения | 200 мс |
| Память | 8 МБ |
| Глубина рекурсии | 64 |
| Количество операторов | 100 000 |
Каждый блок {{ ... }} выполняется в свежем изолированном движке — состояние между блоками не передаётся.
# Обработка null и ошибок
Любое поле data может быть null — например, если город не определён по IP или на странице нет Schema.org-разметки. Всегда проверяйте значения перед использованием:
// ❌ Плохо — упадёт если город не определён
Доставка в {{data.geo.city.nativeName}}
// ✅ Хорошо — с проверкой
Доставка в {{data.geo.city.nativeName || "ваш город"}}
// ✅ Хорошо — развёрнутая проверка
{{
let city = data.geo.city.nativeName;
if (city) {
return "Доставка в " + city;
}
return "Доставка по России";
}}
Если блок выбросит ошибку, превысит лимит времени/памяти или вернёт null, undefined, NaN или Infinity — подстановка не произойдёт: посетитель увидит контент без изменений, страница не сломается.
# Практические примеры
# Город и регион посетителя
Доставка в г. {{data.geo.city.nativeName}} за 1 день
Результат для москвича: Доставка в г. Москва за 1 день
{{
let city = data.geo.city.nativeName;
if (!city) {
return "Доставка по всей России";
}
return "Доставка в " + city + " и " + data.geo.region.nativeName;
}}
# Столица или нет
{{
if (data.geo.city.isCountryCapital) {
return "Бесплатная доставка по Москве";
} else if (data.geo.city.isRegionCapital) {
return "Бесплатная доставка по областному центру";
} else {
return "Доставка от 300 ₽";
}
}}
# Приветствие по времени суток
{{
if (data.dateAndTime.isMorning) {
return "Доброе утро! ☀️";
} else if (data.dateAndTime.isNight) {
return "Не спится? У нас скидка на ночные заказы 🌙";
} else if (data.dateAndTime.isWeekend) {
return "Отличных выходных! 🎉";
} else {
return "Добрый день!";
}
}}
# Адаптация под устройство
{{
if (data.device.deviceType === "Smartphone") {
return "Скачайте наше приложение — скидка 20%";
} else if (data.device.deviceType === "Tablet") {
return "Удобный каталог для планшета";
} else {
return "Полный каталог на десктопе";
}
}}
# Контент по UTM-меткам
{{
let source = data.utm.source;
if (source === "google") {
return "Нашли нас в Google? Вот промокод: GOOGLE10";
} else if (source === "yandex") {
return "Пришли из Яндекса? Промокод: YANDEX10";
} else {
return "Промокод для новых клиентов: WELCOME10";
}
}}
# Погода
{{
let temp = data.weather.temp;
if (temp === null) return "Бесплатная доставка";
if (temp < 0) {
return "На улице " + temp + "°C — закажите горячий чай с доставкой ☕";
} else if (temp > 30) {
return "Жара " + temp + "°C! Прохладительные напитки со скидкой 🧊";
} else {
return "Доставка при любой погоде";
}
}}
# Повторные визиты
{{
let sessionNum = data.session.number;
if (sessionNum === 1) {
return "Добро пожаловать! Скидка 5% на первый заказ";
} else if (sessionNum <= 3) {
return "Рады видеть вас снова! Бесплатная доставка на второй заказ";
} else {
return "Для постоянных клиентов — персональная скидка 15%";
}
}}
# Цена товара со страницы (Schema.org)
{{
let price = data.page.schemaOrg.offer.price;
if (price && price > 5000) {
return "При заказе от 5 000 ₽ — бесплатная доставка!";
} else {
return "Доставка от 300 ₽";
}
}}
# Комбинация нескольких параметров
{{
let city = data.geo.city.nativeName || "вашем городе";
let device = data.device.deviceType;
let isWeekend = data.dateAndTime.isWeekend;
let text = "Доставка в " + city;
if (isWeekend) {
text += " работает и в выходные!";
}
if (device === "Smartphone") {
text += " Закажите через приложение — скидка 5%.";
}
return text;
}}
# Подстановка в атрибут
Интерпретатор работает не только в тексте. Например, можно подставить картинку под устройство в атрибут src:
{{ data.device.deviceType === "Smartphone" ? "/img/banner-mobile.jpg" : "/img/banner-desktop.jpg" }}
# Это не «Пользовательский JavaScript»
Не путайте серверный интерпретатор с Пользовательским JavaScript в варианте:
JavaScript-интерпретатор ({{ }}) | Пользовательский JavaScript | |
|---|---|---|
| Где выполняется | На сервере, до отдачи контента | В браузере, после применения всех модификаций |
| Назначение | Подставить значение в текст/атрибут/контент | Произвольная логика на странице |
Доступ к data | Да | Нет |
Доступ к DOM, window, ninja() | Нет | Да |
Подробнее о пользовательском JS — в создании вариантов.
# Отладка
- Используйте отладочный режим, чтобы увидеть результат шаблонов на реальной странице.
- Записывайте промежуточные значения через
log(). - Начинайте с простого: подстановка города в заголовок уже даёт персонализированный контент.
- Самая частая ошибка — обращение к полю
data, которое не определено. Всегда проверяйте наnull.