---
title: "Выполнение JavaScript: Evaluate"
description: "chromedp evaluate исполняет JS в контексте страницы и возвращает значения в Go: массивы, сырые байты, RemoteObject, EvaluateAsDevTools и границы — с примерами."
course: "Chromedp в Go: автоматизация Chrome без Selenium"
url: "https://golangmaster.ru/courses/chromedp/lessons/10-js-evaluation/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 20
---

# Выполнение JavaScript: Evaluate

*Курс «Chromedp в Go: автоматизация Chrome без Selenium», урок 10 из 25. ~20 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/chromedp/lessons/10-js-evaluation/*

Двести карточек на странице по пять значений в каждой — это тысяча обменов с браузером, если читать по одному действию. chromedp evaluate решает задачу одним вызовом: JavaScript исполняется в контексте страницы, а результат возвращается сразу в Go-переменную. Разберём js в браузере go-программой: механику возврата значений, undefined и сырые байты, контекст консоли DevTools и границы уместности.

У чтения из шестого урока есть экономический предел: каждое действие — отдельный обмен с браузером, и на больших страницах счёт идёт на сотни. Но у страницы под рукой есть исполнитель, который видит весь DOM целиком, — JavaScript. Один цикл Array.map по селектору собирает все заголовки за один проход, и именно это делает chromedp evaluate: отправляет выражение в контекст страницы, где доступны document, переменные скриптов сайта и всё состояние, которое фреймворк уже посчитал, и привозит результат в Go. Это не обход библиотеки, а её нижний этаж: сам протокол DevTools исполняет выражения той же командой Runtime.evaluate, и chromedp лишь даёт ей удобную обёртку. Отсюда и место действия в курсе: селекторы отвечают за поиск, чтение — за узлы, а Evaluate — за всё, что проще посчитать на месте, чем привозить по кусочкам. Скролл страницы из FAQ седьмого урока, агрегаты по таблицам, чтение данных SPA до того, как они попали в DOM, — всё это один и тот же механизм с разными выражениями. В этом уроке — как устроен возврат значений, какие ошибки он приносит и где заканчивается здравый смысл писать скрапер на JS-строках.

### chromedp evaluate: выражение и указатель

Сигнатура короткая: chromedp.Evaluate(expression, res). Первый аргумент — строка с выражением JavaScript, второй — указатель на приёмник результата; передадите nil, и результат будет проигнорирован, действие только исполнит код — так выглядит прокрутка window.scrollTo из седьмого урока. Механика возврата для обычных приёмников такая: результат выражения кодируется в JSON по значению, и стандартный json.Unmarshal раскладывает его в вашу переменную. Число ложится в int или float64, строка — в string, массив — в срез, объект — в карту или структуру с json-тегами. Это значит, что тип приёмника — ваша ответственность: выражение вернёт массив, а приёмник объявлен строкой — получите ошибку распаковки, и она будет честной ошибкой Go с описанием. Отсюда привычка подбирать пару «выражение плюс тип»: соберёте на JS массив строк — берите []string, посчитаете сумму — берите float64 или int, разберёте объект — карту map[string]any или структуру. Evaluate не дожидается ничего: он исполняет выражение в текущем состоянии страницы, поэтому перед ним стоят те же ожидания готовности, что и перед чтением узлов. Ошибка внутри выражения — обращение к несуществующему свойству, ошибка синтаксиса — возвращается как ошибка действия, и Run останавливается: тихого прохождения мимо не бывает.

### Все заголовки карточек одним обменом

```go
var titles []string
err := chromedp.Run(ctx,
        chromedp.WaitVisible(".cards"),
        chromedp.Evaluate(
                `Array.from(document.querySelectorAll('.card h3'))
                        .map(e => e.textContent)`,
                &titles,
        ),
)
if err != nil {
        panic(err)
}
fmt.Println("карточек:", len(titles))
```

Выражение собирает все узлы по селектору и возвращает массив текстов; результат кодируется в JSON и распаковывается в срез строк. WaitVisible дожидается контейнера — Evaluate сам ничего не ждёт. Один обмен с браузером вместо сотни действий Text: на больших страницах разница видна в секундах, а не миллисекундах.

### Возврат значений: undefined, null и сырые байты

Три особые формы приёмника закрывают края случая, и о них стоит знать заранее. Первая — ошибка undefined: если выражение вернуло undefined или null, а приёмник не умеет быть nil — строка, число, структура, — Evaluate вернёт ошибку: encountered an undefined value или encountered a null value. Приёмники, которые nil умеют, — указатели, срезы, карты — примут undefined молча: срез останется nil, и это штатный путь выразить «данных нет». Вторая особая форма — сырые байты: приёмник *[]byte получает JSON-кодировку результата как есть, без распаковки в тип; это способ забрать значение сложной формы и разобрать его у себя, когда писать JS-разбор не хочется, а промежуточная структура не нужна. Третья — низкоуровневый приёмник **runtime.RemoteObject: вы получаете объект протокола без попытки конвертации — класс объекта, идентификатор, тип; живые объекты браузер держит у себя, пока страница не закрыта или не переезжает, и освобождать их можно командами runtime.ReleaseObject, если сессия длинная и объектов много. Для повседневных задач хватает первого пути; RemoteObject нужен, когда результат — функция, узел DOM или большой объект, который вы хотите исследовать по частям, а не тащить целиком в Go.

### js в браузере go: границы уместности

Сила Evaluate провоцирует: раз можно всё — почему не перенести в JS-строку половину парсера? Здесь полезна граница, которую стоит провести заранее. Уместное: агрегаты одним обменом, прокрутка, чтение состояния, которое не попало в DOM — переменные скрипта, объекты аналитики, данные в памяти фреймворка; проверка сложной готовности, которую селектором не выразить. Неуместное: валидация, ветвления и маршрутизация парсера внутри JS-строк — код без типов, без отладчика Go, без тестов, и каждая правка — редактирование строки в кавычках. Спорное мнение с условием: логика должна жить в Go, а JS — только читать и трогать страницу; исключение, при котором мнение можно перевернуть, одно — когда страница сама держит данные в недоступном селекторам виде, и короткий JS-запрос к её состоянию экономит десятки действий. Хороший маркер уместности: если выражение умещается в одну-две строки и не содержит ветвлений, это работа для Evaluate; если третье — сигнал вернуться к действиям библиотеки. Второй практический предел — аргументы: у Evaluate нет параметра для значений, аргументы подставляются в строку выражения. Подставляйте через JSON-кодирование и экранирование, а не конкатенацией: сырая склейка строк с данными с сайта — прямой путь к сломанному выражению, а в худшем случае к исполнению чужого кода в вашей сессии. Для предикатов с аргументами есть PollFunction с WithPollingArgs из девятого урока — там подстановка безопасна и встроена.

### Пять способов забрать значение

| Способ | Что возвращает | Когда брать |
| --- | --- | --- |
| Text, AttributeValue, Value | одно значение одного узла | точечное чтение по селектору |
| Evaluate с []string или структурой | агрегат по значению: JSON в Go-тип | массивы, суммы, объекты одним обменом |
| Evaluate с *[]byte | сырую JSON-кодировку результата | сложная форма, разбор на своей стороне |
| Evaluate с **runtime.RemoteObject | живой объект протокола: класс, ObjectID | функции, узлы, большие объекты |
| EvaluateAsDevTools | то же плюс Command Line API консоли | скрипты с $ и $$, работа как в DevTools |

### EvaluateAsDevTools: контекст консоли

Вторая обёртка над той же командой протокола — chromedp.EvaluateAsDevTools: выражение исполняется так, как его исполняет консоль панели разработчика. Разница в двух деталях: выражение живёт в контексте объекта группы console, и ему доступен Command Line API — те самые сокращения консоли, включая выбор элементов dollar-скриптами $ и $$. Для задач «сделай, как я сделал в DevTools» это прямой путь: выражение, проверенное в консоли, переносится в код без переписывания dollar-сокращений на чистый querySelector. У библиотеки на этом механизме стоят привычные действия: Title и Location из третьего урока — обёртки над EvaluateAsDevTools с выражениями document.title и document.location.toString(), так что вы уже пользовались этой обёрткой, не зная о ней. Цена за удобство обозначена в исходниках честно: не используйте EvaluateAsDevTools с недоверенным JavaScript — Command Line API расширяет возможности исполняемого кода, и запускать в этой песочнице стоит только то, что вы написали сами или проверили. Рядом живут и опции нижнего уровня: EvalIgnoreExceptions заставляет оценку не падать на исключениях, EvalAsValue принудительно кодирует результат по значению — те же рычаги, которыми настроена обычная обёртка.

### Живой объект протокола

```go
var res *runtime.RemoteObject
err := chromedp.Run(ctx,
        chromedp.Evaluate(`window`, &res),
)
if err != nil {
        panic(err)
}
fmt.Println(res.ClassName)
```

Приёмник **runtime.RemoteObject получает объект протокола без конвертации: здесь это глобальное окно страницы с ClassName Window. У объекта есть идентификатор ObjectID, по которому к нему можно обращаться следующими командами протокола; живые объекты браузер держит до закрытия или навигации страницы, при длинной сессии их освобождают ReleaseObject.

> **Живое место: строка, которую собрали руками.** Типичная авария: в выражение конкатенацией подставили данные — часть пути, номер страницы, слово из запроса. На тестовых данных работало, на реальном товаре с кавычкой в названии выражение сломалось, а на злонамеренной строке исполнило бы что угодно в контексте страницы. Сломался не Evaluate, а склейка строк там, где нужна кодировка значений. Лечение: значения в выражение — через JSON-кодирование с экранированием, аргументные предикаты — через PollFunction с WithPollingArgs, а всё, что можно выразить селектором, — селектором. Спорное мнение с условием: держать логику в Go, JS — только чтение; переворачивается оно, когда данные живут в памяти фреймворка и не видны селекторам — тогда короткий JS-запрос к состоянию честнее двадцати действий.

### Аргументы, исключения и порядок работы

Соберём дисциплину оценки в правила, которые проверены на живых страницах. Выражение сначала живёт в консоли DevTools: написали, исполнили, увидели результат — и только потом переехало в Go; между консолью и Evaluate почти нет разницы, а цикл отладки в консоли в десять раз короче. Приёмник подбирается под форму результата и объявляется рядом с выражением: массив текстов — []string, сумма — число, объект — карта или структура; расхождение формы и типа даёт ошибку распаковки с понятным текстом. Undefined и null обрабатываются сознательно: если «нет данных» — норма, берите приёмники, умеющие nil, и проверяйте на nil, а не на ошибку. Для страницы, где результат появляется позже, Evaluate окружается ожиданиями: WaitVisible на узле или Poll с предикатом из девятого урока — оценка сама ждать не умеет. Крупные вычисления по сотням узлов делаются одним выражением с map и reduce: на стороне браузера это микросекунды, а расписывать ту же работу десятками действий — тратить обмены и время обхода. И держите выражения короткими: трёхэтажная JS-программа в строке — сигнал, что пора выносить её в отдельный файл или возвращаться к действиям библиотеки.

### Практики безопасной оценки

- отлаживайте выражение в консоли DevTools, потом переносите в Go без изменений
- тип приёмника объявляйте под форму результата: срез для массива, число для суммы
- undefined и null принимайте приёмниками, умеющими nil, и проверяйте на nil
- значения в выражение подставляйте через JSON-кодирование, не конкатенацией
- аргументные предикаты собирайте на PollFunction с WithPollingArgs
- перед оценкой дожидайтесь готовности: WaitVisible или Poll с предикатом
- EvaluateAsDevTools — только для своих и проверенных скриптов

> **Выражения живут в коде, а не в строках вызовов.** JavaScript-выражение — часть кодовой базы: держите его в именованной константе рядом с селекторами, а не в строке внутри Run. Это даёт три выгоды: выражение видно при ревью, его можно переиспользовать в нескольких местах и легко подменить в тесте. Рядом же комментируйте форму результата — с каким приёмником выражение согласовано, чтобы правки не расходились.

Что дальше: оценка JavaScript — последний инструмент низкого уровня перед большой темой данных. Одиннадцатый урок применит всё это к [SPA и динамическому контенту](/courses/chromedp/lessons/11-dinamicheskij-kontent/), где разметку собирает фреймворк, а состояние живёт в его памяти; четвёртый урок про [селекторы](/courses/chromedp/lessons/04-selektory/) вспомнится в связи с ByJSPath — путём элемента, который тоже исполняется оценкой. Практика — локально: понадобится Go и Chrome, песочница сайта исполняет чистый Go без браузера, поэтому настоящие JSON-обмены вы увидите только в своём терминале. Первый честный замер для домашней работы: страница с сотней узлов, время цикла Text против одного Evaluate — цифры убедят надёжнее слов.

### Проверка: undefined в приёмник

**Вопрос:** Выражение вернуло undefined, приёмник — строка var s string. Что произойдёт?

- s останется пустой строкой без ошибки
- ✅ Evaluate вернёт ошибку encountered an undefined value
- s получит строку undefined
- Run упадёт с паникой

Строка не умеет быть nil, поэтому undefined превращается в ошибку encountered an undefined value, а null — в encountered a null value. Молча принять undefined могут приёмники, которые умеют nil: указатели, срезы, карты. Это штатный способ выразить отсутствие данных без ошибки.

### Проверка: nil вместо приёмника

**Вопрос:** Зачем в Evaluate передают nil вместо указателя на результат?

- Чтобы результат записался в глобальную переменную
- ✅ Чтобы проигнорировать результат: действие только исполняет код
- Так включается контекст консоли DevTools
- nil отключает проверку ошибок выражения

При nil результат игнорируется: выражение исполняется ради побочного эффекта — прокрутки scrollTo, вызова функции страницы, установки флага. Ошибки при этом проверяются штатно: исключение в скрипте вернётся как ошибка действия. Контекст консоли включается EvaluateAsDevTools, а не nil.

### Проверка: контекст консоли

**Вопрос:** Чем EvaluateAsDevTools отличается от Evaluate?

- Исполняет код без ошибок и исключений
- Работает только в headless-режиме
- ✅ Даёт Command Line API консоли: сокращения вроде $ и $$
- Возвращает результат всегда строкой

EvaluateAsDevTools исполняет выражение в контексте группы console с доступом к Command Line API — тем же, что у панели F12, включая выбор элементов dollar-скриптами. На ней стоят действия Title и Location. В исходниках честная оговорка: недоверенный JavaScript в неё передавать не стоит.

### Что окажется в sum

```go
var sum int
// err := chromedp.Run(ctx,
//   chromedp.Evaluate(`1+2`, &sum),
// )
```

**Что выведет программа?**

- ✅ 3 — число распаковалось в int
- Строка 3: результаты всегда строки
- Ошибка: JS number не совместим с int
- 0 — результат игнорируется

Результат кодируется в JSON по значению и распаковывается стандартным json.Unmarshal: число 3 ложится в int без потерь. Игнорирование результата — только при явном nil вместо приёмника, а несовместимость типов дала бы ошибку распаковки, а не ноль.

### Соберите пару чтений

Восстановите приёмник агрегата и обёртку с контекстом консоли.

```go
var titles []string
var title string
err := chromedp.Run(ctx,
        chromedp.Evaluate(expr, &___),
        chromedp.___(`document.title`, &title),
)
```

Подсказка: Агрегат раскладывается в срез, а выражение с контекстом консоли исполняет EvaluateAsDevTools — им же читают document.title действие Title.

Правильные ответы в пропусках: `titles`, `EvaluateAsDevTools`

### Расставьте агрегат по порядку

Соберите по порядку: приёмник, Run с ожиданием и оценкой, обработка ошибки.

Правильный порядок строк:

1. `var titles []string`
2. `err := chromedp.Run(ctx,`
3. `  chromedp.WaitVisible(".cards"),`
4. `  chromedp.Evaluate(`
5. `    "Array.from(document.querySelectorAll('.card h3')).map(e => e.textContent)",`
6. `    &titles,`
7. `  ),`
8. `)`
9. `if err != nil {`
10. `  panic(err)`
11. `}`

### Карточки: выполнение JavaScript

- **chromedp.Evaluate** — Исполняет выражение в контексте страницы. Результат — JSON по значению в приёмник; nil означает «исполнить и игнорировать». Ошибки скрипта возвращаются как ошибки действия.
- **ErrJSUndefined и ErrJSNull** — Ошибки при undefined и null в приёмнике, который не умеет nil: строка, число, структура. Указатели, срезы и карты принимают отсутствие данных молча.
- **Приёмник *[]byte** — Получает сырую JSON-кодировку результата без распаковки — способ забрать сложную форму и разобрать её в Go самому.
- ****runtime.RemoteObject** — Низкоуровневый объект протокола: ClassName, ObjectID, тип. Живёт в браузере до навигации или закрытия страницы; освобождается ReleaseObject.
- **EvaluateAsDevTools** — Оценка в контексте консоли с Command Line API: $, $$ и другие сокращения F12. Не для недоверенного JavaScript. На ней стоят Title и Location.
- **Аргументы выражения** — У Evaluate параметра для аргументов нет: значения подставляются в строку через JSON-кодирование. Для предикатов — PollFunction с WithPollingArgs.

## Коротко: что запомнить

- Evaluate исполняет выражение в контексте страницы: доступны document, состояние скриптов и всё, что видит консоль.
- Результат возвращается по значению: JSON-кодирование и json.Unmarshal в приёмник; тип подбирается под форму результата.
- nil вместо приёмника — исполнить код ради побочного эффекта: прокрутка, вызов функции, флаг.
- undefined и null в приёмнике без nil — ошибки ErrJSUndefined и ErrJSNull; срезы, карты и указатели принимают их молча.
- *[]byte даёт сырой JSON, **runtime.RemoteObject — живой объект протокола с ClassName и ObjectID.
- EvaluateAsDevTools — контекст консоли и Command Line API: Title и Location из третьего урока — обёртки над ним.
- Аргументы подставляются в строку через JSON-кодирование; для предикатов — PollFunction с WithPollingArgs.
- Граница уместности: JS читает и считает, решает Go; выражение сначала проверяется в консоли DevTools.

## Домашнее задание

Возьмите страницу с сотней однотипных узлов и сделайте два чтения одного и того же: цикл Text по селектору и один Evaluate с Array.from и map. Замерьте время обоих подходов и запишите разницу. Затем верните сумму числового поля одним выражением с reduce в float64, а объект window прочитайте через **runtime.RemoteObject и напечатайте ClassName. Финал — сломайте приёмник: прочитайте массив в строку и изучите текст ошибки распаковки.

## Вопросы и ответы по уроку

### Evaluate вернул encountered an undefined value — что это значит?

Выражение вернуло undefined или null, а приёмник не умеет быть nil: строка, число, структура. Проверьте выражение в консоли и решите, откуда отсутствие данных: если это норма, возьмите приёмник, умеющий nil — указатель, срез или карту, — и проверяйте на nil. Если отсутствие неожиданно, ищите причину на странице: неверный селектор или данные, которые ещё не приехали.

### Можно ли передать аргументы в Evaluate?

Отдельного параметра нет: значения подставляются в строку выражения. Делайте это через JSON-кодирование с экранированием — конкатенация ломает выражение на кавычках и открывает дорогу инъекции. Для предикатов с аргументами есть готовый путь: PollFunction из девятого урока принимает значения через WithPollingArgs и передаёт их функции безопасно.

### Зачем EvaluateAsDevTools, если есть Evaluate?

Когда скрипт написан и проверен в консоли DevTools с dollar-сокращениями: EvaluateAsDevTools даёт тот же Command Line API, и выражение переезжает в код без переписывания. Сюда же относятся задачи, где удобно работать как в консоли. Для недоверенного кода обёртка не предназначена: API расширяет возможности исполняемого скрипта, поэтому её область — ваши собственные и проверенные выражения.
