Начало работы с интеграцией V8
Этот документ вводит в ключевые концепции V8 и предоставляет пример "hello world", чтобы вы могли начать работать с кодом V8.
Аудитория
Этот документ предназначен для программистов C++, которые хотят встроить движок JavaScript V8 в приложение на C++. Он помогает сделать объекты и методы C++ доступными для JavaScript, а объекты и функции JavaScript доступными для вашего приложения на C++.
Hello world
Рассмотрим пример Hello World, который принимает выражение JavaScript в виде строкового аргумента, выполняет его как код JavaScript и выводит результат в стандартный вывод.
Сначала рассмотрим ключевые концепции:
- Изолят (isolate) — это экземпляр виртуальной машины со своей собственной кучей.
- Локальный хендл (local handle) — это указатель на объект. Все объекты V8 обращаются через хендлы. Они необходимы из-за особенностей работы сборщика мусора V8.
- Область хендлов (handle scope) может рассматриваться как контейнер для любого количества хендлов. Когда вы завершаете работу с хендлами, вместо удаления каждого по отдельности можно просто удалить их область.
- Контекст (context) — это среда выполнения, которая позволяет запускать независимый JavaScript код в одном экземпляре V8. Вы должны явно указать контекст, в котором будет выполняться JavaScript.
Эти концепции рассматриваются более подробно в руководстве для продвинутых пользователей.
Выполнение примера
Следуйте приведенным ниже шагам, чтобы выполнить пример самостоятельно:
-
Загрузите исходный код V8, следуя инструкциям Git.
-
Инструкции для этого примера были протестированы с V8 v13.1. Вы можете получить эту ветку с помощью команды
git checkout branch-heads/13.1 -b sample -t
-
Создайте конфигурацию сборки, используя вспомогательный скрипт:
tools/dev/v8gen.py x64.release.sample
Вы можете просмотреть и вручную изменить конфигурацию сборки, запустив:
gn args out.gn/x64.release.sample
-
Постройте статическую библиотеку в системе Linux 64:
ninja -C out.gn/x64.release.sample v8_monolith
-
Скомпилируйте
hello-world.cc
, слинковав его со статической библиотекой, созданной в процессе сборки. Например, на 64-битной Linux с использованием GNU-компилятора и компоновщика LLD:g++ -I. -Iinclude samples/hello-world.cc -o hello_world -fno-rtti -fuse-ld=lld -lv8_monolith -lv8_libbase -lv8_libplatform -ldl -Lout.gn/x64.release.sample/obj/ -pthread -std=c++20 -DV8_COMPRESS_POINTERS -DV8_ENABLE_SANDBOX
-
Для более сложного кода V8 не работает без файла данных ICU. Скопируйте этот файл туда, где находится ваш бинарный файл:
cp out.gn/x64.release.sample/icudtl.dat .
-
Запустите исполняемый файл
hello_world
в командной строке. Например, в Linux в каталоге V8 выполните:./hello_world
-
На экране появится
Hello, World!
. Ура!
Примечание: начиная с ноября 2024 года, возможны сбои в начале запуска процесса. Расследование продолжается. Если вы столкнетесь с этой проблемой и сможете выяснить, в чем дело, пожалуйста, оставьте комментарий в issue 377222400 или представьте патч.
Если вам нужен пример, синхронизированный с основной веткой, обратите внимание на файл hello-world.cc
. Это очень простой пример, и вы, вероятно, захотите делать больше, чем просто выполнять скрипты в виде строк. Руководство для продвинутых пользователей ниже содержит больше информации для интеграторов V8.
Дополнительные примеры кода
Следующие примеры предоставляются в рамках загрузки исходного кода.
process.cc
Этот пример предоставляет код, необходимый для расширения гипотетического приложения обработки HTTP-запросов — которое может быть частью, например, веб-сервера — чтобы сделать его скриптируемым. Код принимает скрипт JavaScript в качестве аргумента, который должен предоставлять функцию под названием Process
. Функция JavaScript Process
может использоваться, например, для сбора информации, такой как количество обращений к каждой странице, обслуживаемой вымышленным веб-сервером.
shell.cc
Этот пример принимает названия файлов в качестве аргументов, затем читает и выполняет их содержимое. Включает командную строку, в которой вы можете вводить фрагменты кода JavaScript, которые затем будут выполнены. В этом примере дополнительные функции, такие как print
, также добавляются в JavaScript с использованием шаблонов объектов и функций.
Руководство для продвинутых пользователей
Теперь, когда вы знакомы с использованием V8 как автономной виртуальной машины, а также с некоторыми ключевыми концепциями V8, такими как дескрипторы, области и контексты, давайте более подробно обсудим эти концепции и представим несколько других, важных для интеграции V8 в ваше собственное C++ приложение.
API V8 предоставляет функции для компиляции и выполнения скриптов, доступа к методам и структурам данных C++, обработки ошибок и проверки безопасности. Ваше приложение может использовать V8 так же, как любую другую библиотеку на C++. Ваш код на C++ получает доступ к V8 через API V8, включая заголовок include/v8.h
.
Дескрипторы и сборка мусора
Дескриптор предоставляет ссылку на расположение объекта JavaScript в куче. Сборщик мусора V8 освобождает память, используемую объектами, которые больше недоступны. Во время процесса сборки мусора сборщик часто перемещает объекты в другие места в куче. Когда сборщик мусора перемещает объект, он также обновляет все дескрипторы, ссылающиеся на этот объект, указав новое местоположение объекта.
Объект считается мусором, если он недоступен из JavaScript и никакие дескрипторы на него не ссылаются. Иногда сборщик мусора удаляет все объекты, которые считаются мусором. Механизм сборки мусора V8 является ключевым фактором производительности V8.
Существует несколько типов дескрипторов:
-
Локальные дескрипторы хранятся в стеке и удаляются при вызове соответствующего деструктора. Время жизни этих дескрипторов определяется областью дескриптора, которая часто создается в начале вызова функции. Когда область дескриптора удаляется, сборщик мусора может освободить память для объектов, на которые ранее ссылались эти дескрипторы, если они больше недоступны из JavaScript или других дескрипторов. Этот тип дескриптора используется в приведенном выше примере Hello World.
Локальные дескрипторы имеют класс
Local<SomeType>
.Примечание: Стек дескрипторов не является частью стека вызовов C++, но области дескрипторов встроены в стек C++. Области дескрипторов могут быть только размещены на стеке, их нельзя выделить с помощью
new
. -
Постоянные дескрипторы предоставляют ссылку на объект JavaScript, размещенный в куче, так же, как локальные дескрипторы. Существуют два варианта, которые отличаются управлением временем жизни ссылки, которую они обрабатывают. Используйте постоянный дескриптор, когда вам нужно сохранить ссылку на объект более чем на один вызов функции или когда время жизни дескрипторов не соответствует областям C++. Например, Google Chrome использует постоянные дескрипторы для ссылок на узлы документа (DOM). Постоянный дескриптор можно сделать слабым, используя
PersistentBase::SetWeak
, чтобы вызвать обратный вызов сборщика мусора, когда единственные ссылки на объект идут от слабых постоянных дескрипторов.- Дескриптор
UniquePersistent<SomeType>
использует конструкторы и деструкторы C++ для управления временем жизни базового объекта. - Дескриптор
Persistent<SomeType>
можно создать с помощью его конструктора, но он должен быть явно очищен с помощьюPersistent::Reset
.
- Дескриптор
-
Существуют другие типы дескрипторов, которые редко используются, и здесь мы упоминем их лишь кратко:
Eternal
— это постоянный дескриптор JavaScript объектов, которые предполагаются никогда не удаляемыми. Его использование дешевле, так как оно освобождает сборщик мусора от определения активности этого объекта.- Дескрипторы
Persistent
иUniquePersistent
нельзя копировать, что делает их неподходящими в качестве значений с контейнерами стандартной библиотеки, предшествующими C++11.PersistentValueMap
иPersistentValueVector
предоставляют контейнерные классы для постоянных значений с семантикой карты и вектора. Встраиватели C++11 не требуют этого, так как семантика перемещения C++11 решает основную проблему.
Конечно, создание локального дескриптора каждый раз при создании объекта может привести к большому количеству дескрипторов! Здесь очень полезны области дескрипторов. Вы можете думать об области дескрипторов как о контейнере, который содержит много дескрипторов. Когда вызывается деструктор области дескрипторов, все дескрипторы, созданные внутри этой области, удаляются из стека. Как и ожидалось, это приводит к тому, что объекты, на которые указывали дескрипторы, становятся доступными для удаления из кучи сборщиком мусора.
Возвращаясь к нашему очень простому примеру Hello World, на следующей диаграмме вы можете видеть стек дескрипторов и объекты, размещенные в куче. Обратите внимание, что Context::New()
возвращает локальный дескриптор, и мы создаем новый постоянный дескриптор на его основе, чтобы продемонстрировать использование постоянных дескрипторов.
Когда вызывается деструктор HandleScope::~HandleScope
, область действия хэндлов удаляется. Объекты, на которые ссылаются хэндлы в удаленной области действия, становятся доступны для удаления в следующем сборщике мусора, если на них больше нет других ссылок. Сборщик мусора также может удалить объекты source_obj
и script_obj
из кучи, так как на них больше нет ссылок через хэндлы и их нельзя каким-либо образом достичь из JavaScript. Поскольку хэндл контекста является постоянным хэндлом, он не удаляется при выходе из области действия хэндла. Единственный способ удалить хэндл контекста — явно вызвать для него метод Reset
.
Примечание: На протяжении всего этого документа термин «хэндл» относится к локальному хэндлу. Когда обсуждается постоянный хэндл, используется полный термин.
Важно осознавать одну распространённую ошибку при использовании этой модели: вы не можете вернуть локальный хэндл напрямую из функции, которая объявляет область действия хэндлов. Если вы это сделаете, локальный хэндл, который вы пытаетесь вернуть, будет удалён деструктором области действия хэндлов сразу перед возвратом из функции. Правильный способ вернуть локальный хэндл — это создать EscapableHandleScope
вместо HandleScope
и вызвать метод Escape
для области действия, передав в него хэндл, значение которого вы хотите вернуть. Вот пример того, как это работает на практике:
// Эта функция возвращает новый массив с тремя элементами: x, y и z.
Local<Array> NewPointArray(int x, int y, int z) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
// Мы будем создавать временные хэндлы, поэтому используем область действия хэндлов.
v8::EscapableHandleScope handle_scope(isolate);
// Создаём новый пустой массив.
v8::Local<v8::Array> array = v8::Array::New(isolate, 3);
// Возвращаем пустой результат, если произошла ошибка при создании массива.
if (array.IsEmpty())
return v8::Local<v8::Array>();
// Заполняем значения.
array->Set(0, Integer::New(isolate, x));
array->Set(1, Integer::New(isolate, y));
array->Set(2, Integer::New(isolate, z));
// Возвращаем значение через Escape.
return handle_scope.Escape(array);
}
Метод Escape
копирует значение своего аргумента в охватывающую область действия, удаляет все ее локальные хэндлы, а затем возвращает новую копию хэндла, которую можно безопасно вернуть.
Контексты
В V8 контекст — это среда выполнения, позволяющая независимым JavaScript-приложениям работать в одном экземпляре V8. Вы должны явно указать контекст, в котором вы хотите выполнить JavaScript-код.
Почему это необходимо? Потому что JavaScript предоставляет набор встроенных утилит и объектов, которые могут быть изменены кодом JavaScript. Например, если два полностью независимых JavaScript-функции изменят глобальный объект одинаковым образом, то, скорее всего, возникнут непредвиденные результаты.
С точки зрения времени на CPU и памяти, создание нового контекста выполнения может показаться дорогой операцией, учитывая количество встроенных объектов, которые нужно создать. Однако обширное кеширование в V8 гарантирует, что, хотя создание первого контекста довольно затратное, последующие контексты создаются гораздо быстрее. Это объясняется тем, что первый контекст должен создать встроенные объекты и распарсить встроенный JavaScript-код, в то время как последующие контексты только создают встроенные объекты для своего контекста. С включённой функцией «snapshot» в V8 (активируется с параметром сборки snapshot=yes
, который включён по умолчанию), время, затрачиваемое на создание первого контекста, сильно оптимизировано, так как снапшот включает сериализованную кучу, содержащую уже скомпилированный код для встроенного JavaScript. Вместе со сборкой мусора, обширное кеширование в V8 является ключом к высокой производительности V8.
После создания контекста вы можете войти в него и выйти из него любое количество раз. Пока вы находитесь в контексте A, вы также можете войти в другой контекст, например, B, заменив A текущим контекстом B. Когда вы выходите из контекста B, контекст A восстанавливается как текущий. Это показано ниже:
Обратите внимание, что встроенные утилиты и объекты для каждого контекста остаются изолированными. Вы можете опционально задать токен безопасности при создании контекста. Подробности смотрите в разделе Модель безопасности.
Использование контекстов в V8 мотивировано тем, чтобы каждое окно и iframe в браузере имели свою собственную свежую JavaScript-среду.
Шаблоны
Шаблон — это чертёж для JavaScript-функций и объектов в контексте. Вы можете использовать шаблон для обёртки C++ функций и структур данных в JavaScript-объекты, чтобы они могли быть манипулированы JavaScript-скриптами. Например, Google Chrome использует шаблоны для обёртывания C++ DOM-узлов как JavaScript-объекты и установки функций в глобальное пространство имён. Вы можете создать набор шаблонов и затем использовать их для каждого нового создаваемого контекста. Вы можете иметь столько шаблонов, сколько вам нужно. Однако в любом данном контексте может существовать только один экземпляр каждого шаблона.
В JavaScript существует сильная двойственность между функциями и объектами. Чтобы создать новый тип объекта в Java или C++, вы обычно определяете новый класс. В JavaScript вместо этого вы создаёте новую функцию и создаёте экземпляры, используя эту функцию как конструктор. Макет и функциональность JavaScript-объекта тесно связаны с функцией, которая его создала. Это отражается в том, как работают шаблоны в V8. Существуют два типа шаблонов:
-
Шаблоны функций
Шаблон функции является чертежом для одной функции. Вы создаете экземпляр шаблона JavaScript, вызывая метод
GetFunction
шаблона в контексте, в котором хотите инстанцировать функцию JavaScript. Вы также можете связать обратный вызов C++ с шаблоном функции, который вызывается при обращении к экземпляру функции JavaScript. -
Шаблоны объектов
Каждый шаблон функции имеет ассоциированный шаблон объекта. Он используется для настройки объектов, создаваемых с использованием данной функции в качестве своего конструктора. Вы можете связать два типа обратных вызовов C++ с шаблонами объектов:
- обратные вызовы для доступа вызываются при доступе скрипта к определенному свойству объекта
- обратные вызовы для перехватчика вызываются при доступе скрипта к любому свойству объекта
Доступы и перехватчики обсуждаются далее в этом документе.
Следующий код демонстрирует пример создания шаблона для глобального объекта и настройки встроенных глобальных функций.
// Создайте шаблон для глобального объекта и настройте
// встроенные глобальные функции.
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
global->Set(v8::String::NewFromUtf8(isolate, "log"),
v8::FunctionTemplate::New(isolate, LogCallback));
// Каждый процессор получает собственный контекст, чтобы различные процессоры
// не влияли друг на друга.
v8::Persistent<v8::Context> context =
v8::Context::New(isolate, nullptr, global);
Этот пример кода взят из JsHttpProcessor::Initializer
в примере process.cc
.
Доступы
Доступ — это обратный вызов C++, который вычисляет и возвращает значение, когда свойство объекта запрашивается скриптом JavaScript. Доступы настраиваются через шаблон объекта, используя метод SetAccessor
. Этот метод принимает имя ассоциированного свойства и два обратных вызова для запуска, когда скрипт пытается прочитать или записать свойство.
Сложность доступа зависит от типа данных, с которыми вы работаете:
Доступ к статическим глобальным переменным
Предположим, что существуют две C++ переменные целого типа, x
и y
, которые должны быть доступны в JavaScript как глобальные переменные в контексте. Для этого необходимо вызывать функции доступа C++, когда скрипт читает или записывает эти переменные. Эти функции доступа преобразуют целое число C++ в целое число JavaScript используя Integer::New
, и преобразуют целое число JavaScript в целое число C++ используя Int32Value
. Пример предоставлен ниже:
void XGetter(v8::Local<v8::String> property,
const v8::PropertyCallbackInfo<Value>& info) {
info.GetReturnValue().Set(x);
}
void XSetter(v8::Local<v8::String> property, v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
x = value->Int32Value();
}
// YGetter/YSetter настолько похожи, что их опускают для краткости
v8::Local<v8::ObjectTemplate> global_templ = v8::ObjectTemplate::New(isolate);
global_templ->SetAccessor(v8::String::NewFromUtf8(isolate, "x"),
XGetter, XSetter);
global_templ->SetAccessor(v8::String::NewFromUtf8(isolate, "y"),
YGetter, YSetter);
v8::Persistent<v8::Context> context =
v8::Context::New(isolate, nullptr, global_templ);
Обратите внимание, что шаблон объекта в приведенном выше коде создается одновременно с контекстом. Шаблон мог быть создан заранее и затем использован для любого количества контекстов.
Доступ к динамическим переменным
В предыдущем примере переменные были статическими и глобальными. Что, если данные, с которыми работаем, являются динамическими, как это имеет место для дерева DOM в браузере? Предположим, x
и y
являются полями объекта в классе C++ Point
:
class Point {
public:
Point(int x, int y) : x_(x), y_(y) { }
int x_, y_;
}
Чтобы сделать любое количество экземпляров C++ point
доступными в JavaScript, нам нужно создать один объект JavaScript для каждого экземпляра C++ point
и установить связь между объектом JavaScript и экземпляром C++. Это делается с помощью внешних значений и внутренних полей объекта.
Сначала создайте шаблон объекта для объекта-обертки point
:
v8::Local<v8::ObjectTemplate> point_templ = v8::ObjectTemplate::New(isolate);
Каждый объект JavaScript point
сохраняет ссылку на объект C++, для которого он является оберткой, с внутренним полем. Эти поля так называются, потому что к ним нельзя получить доступ из JavaScript, их можно использовать только из кода C++. Объект может иметь любое количество внутренних полей, количество внутренних полей задается в шаблоне объекта следующим образом:
point_templ->SetInternalFieldCount(1);
Здесь количество внутренних полей устанавливается в 1
, что означает, что объект имеет одно внутреннее поле с индексом 0
, которое указывает на объект C++.
Добавьте доступы x
и y
в шаблон:
point_templ->SetAccessor(v8::String::NewFromUtf8(isolate, "x"),
GetPointX, SetPointX);
point_templ->SetAccessor(v8::String::NewFromUtf8(isolate, "y"),
GetPointY, SetPointY);
Далее оберните точку C++ путем создания нового экземпляра шаблона, а затем установите внутреннее поле 0
во внешний оберток вокруг точки p
.
Point* p = ...;
v8::Local<v8::Object> obj = point_templ->NewInstance();
obj->SetInternalField(0, v8::External::New(isolate, p));
Внешний объект просто является оберткой вокруг void*
. Внешние объекты можно использовать только для хранения ссылочных значений во внутренних полях. JavaScript-объекты не могут иметь ссылки непосредственно на объекты C++, поэтому внешнее значение используется как "мост" для перехода от JavaScript в C++. В этом смысле внешние значения являются противоположностью дескрипторов, поскольку дескрипторы позволяют C++ ссылаться на JavaScript-объекты.
Вот определение методов-доступников get
и set
для x
, определения доступников для y
идентичны, за исключением того, что y
заменяет x
:
void GetPointX(Local<String> property,
const PropertyCallbackInfo<Value>& info) {
v8::Local<v8::Object> self = info.Holder();
v8::Local<v8::External> wrap =
v8::Local<v8::External>::Cast(self->GetInternalField(0));
void* ptr = wrap->Value();
int value = static_cast<Point*>(ptr)->x_;
info.GetReturnValue().Set(value);
}
void SetPointX(v8::Local<v8::String> property, v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
v8::Local<v8::Object> self = info.Holder();
v8::Local<v8::External> wrap =
v8::Local<v8::External>::Cast(self->GetInternalField(0));
void* ptr = wrap->Value();
static_cast<Point*>(ptr)->x_ = value->Int32Value();
}
Доступники извлекают ссылку на объект point
, который был обернут JavaScript-объектом, а затем читают и записывают соответствующее поле. Таким образом, эти универсальные доступники можно использовать для любого количества обернутых объектов точек.
Перехватчики
Вы также можете указать обратный вызов для случаев, когда сценарий обращается к любому свойству объекта. Такие обратные вызовы называются перехватчиками. Для оптимизации существуют два типа перехватчиков:
- перехватчики именованных свойств - вызываются при доступе к свойствам с именами в формате строки. Примером в браузерной среде может быть
document.theFormName.elementName
. - перехватчики индексированных свойств - вызываются при доступе к индексированным свойствам. Примером в браузерной среде может быть
document.forms.elements[0]
.
Пример process.cc
, предоставленный с исходным кодом V8, содержит пример использования перехватчиков. В следующем фрагменте кода SetNamedPropertyHandler
указывает перехватчики MapGet
и MapSet
:
v8::Local<v8::ObjectTemplate> result = v8::ObjectTemplate::New(isolate);
result->SetNamedPropertyHandler(MapGet, MapSet);
Перехватчик MapGet
приведен ниже:
void JsHttpRequestProcessor::MapGet(v8::Local<v8::String> name,
const v8::PropertyCallbackInfo<Value>& info) {
// Извлечь карту, обернутую этим объектом.
map<string, string> *obj = UnwrapMap(info.Holder());
// Преобразовать строку JavaScript в std::string.
string key = ObjectToString(name);
// Использовать стандартный метод STL для поиска значения, если оно существует.
map<string, string>::iterator iter = obj->find(key);
// Если ключ отсутствует, вернуть пустой дескриптор в качестве сигнала.
if (iter == obj->end()) return;
// В противном случае получить значение и обернуть его в строку JavaScript.
const string &value = (*iter).second;
info.GetReturnValue().Set(v8::String::NewFromUtf8(
value.c_str(), v8::String::kNormalString, value.length()));
}
Как и доступники, указанные обратные вызовы вызываются каждый раз, когда происходит доступ к свойству. Отличие между доступниками и перехватчиками состоит в том, что перехватчики обрабатывают все свойства, тогда как доступники ассоциированы с одним конкретным свойством.
Модель безопасности
Политика "одного источника" (впервые введенная в Netscape Navigator 2.0) предотвращает получение или установку свойств документа или скрипта, загруженного из одного "источника", документом из другого "источника". Термин "источник" здесь определяется как комбинация имени домена (например, www.example.com
), протокола (например, https
) и порта. Например, www.example.com:81
не является таким же источником что и www.example.com
. Для того, чтобы две веб-страницы считались имеющими один источник, все три значения должны совпадать. Без этой защиты вредоносная веб-страница могла бы компрометировать целостность другой веб-страницы.
В V8 "источник" определяется как контекст. Доступ к любому контексту, отличному от того, из которого происходит вызов, по умолчанию не разрешен. Чтобы получить доступ к другому контексту, вам необходимо использовать токены безопасности или обратные вызовы безопасности. Токен безопасности может быть любым значением, но обычно это символ, каноническая строка, которая нигде больше не существует. Вы можете указать токен безопасности с помощью SetSecurityToken
при настройке контекста. Если вы не укажете токен безопасности, V8 автоматически сгенерирует его для создаваемого контекста.
При попытке доступа к глобальной переменной система безопасности V8 сначала проверяет токен безопасности глобального объекта, к которому осуществляется доступ, на соответствие токену безопасности кода, пытающегося получить доступ к глобальному объекту. Если токены совпадают, доступ предоставляется. Если токены не совпадают, V8 выполняет обратный вызов, чтобы проверить, следует ли разрешить доступ. Вы можете указать, следует ли разрешать доступ к объекту, установив на объекте обратный вызов безопасности, используя метод SetAccessCheckCallbacks
для шаблонов объектов. Система безопасности V8 может затем получить обратный вызов безопасности объекта, к которому осуществляется доступ, и вызвать его, чтобы проверить, можно ли другому контексту получить к нему доступ. Этот обратный вызов получает объект, к которому осуществляется доступ, имя свойства, к которому осуществляется доступ, тип доступа (например, чтение, запись или удаление), и возвращает, следует ли разрешить доступ.
Этот механизм реализован в Google Chrome таким образом, что если токены безопасности не совпадают, используется специальный обратный вызов, который разрешает доступ только к следующим действиям: window.focus()
, window.blur()
, window.close()
, window.location
, window.open()
, history.forward()
, history.back()
, и history.go()
.
Исключения
V8 выбрасывает исключение, если возникает ошибка — например, когда скрипт или функция пытается прочитать свойство, которого не существует, или если вызывается функция, которая не является функцией.
V8 возвращает пустой дескриптор, если операция не удалась. Поэтому важно, чтобы ваш код проверял, что возвращаемое значение не является пустым дескриптором, прежде чем продолжить выполнение. Проверьте пустой дескриптор с помощью открытого метода IsEmpty()
класса Local
.
Вы можете обрабатывать исключения с помощью TryCatch
, например:
v8::TryCatch trycatch(isolate);
v8::Local<v8::Value> v = script->Run();
if (v.IsEmpty()) {
v8::Local<v8::Value> exception = trycatch.Exception();
v8::String::Utf8Value exception_str(exception);
printf("Exception: %s\n", *exception_str);
// ...
}
Если возвращаемое значение является пустым дескриптором, и у вас нет установленного TryCatch
, ваш код должен завершить выполнение. Если у вас есть TryCatch
, исключение перехватывается, и ваш код может продолжить обработку.
Наследование
JavaScript является бесклассовым языком, ориентированным на объекты, и поэтому использует прототипное наследование вместо классического наследования. Это может быть непонятно программистам, обученным работе с традиционными объектно-ориентированными языками, такими как C++ и Java.
Объектно-ориентированные языки на основе классов, такие как Java и C++, основываются на концепции двух различных сущностей: классы и экземпляры. JavaScript является языком на основе прототипов и поэтому не делает этого различия: у него просто есть объекты. JavaScript не поддерживает нативное объявление иерархий классов; однако механизм прототипов в JavaScript упрощает процесс добавления пользовательских свойств и методов ко всем экземплярам объекта. В JavaScript вы можете добавлять пользовательские свойства объектам. Например:
// Создайте объект с именем `bicycle`.
function bicycle() {}
// Создайте экземпляр `bicycle` с именем `roadbike`.
var roadbike = new bicycle();
// Определите пользовательское свойство `wheels` для `roadbike`.
roadbike.wheels = 2;
Пользовательское свойство, добавленное таким образом, существует только для этого экземпляра объекта. Если мы создадим другой экземпляр bicycle()
, например, с именем mountainbike
, значение mountainbike.wheels
вернет undefined
, если свойство wheels
явно не добавлено.
Иногда это именно то, что требуется, а иногда полезно добавить пользовательское свойство ко всем экземплярам объекта — ведь у всех велосипедов есть колеса. Здесь прототипный объект JavaScript оказывается очень полезным. Чтобы использовать прототипный объект, обратитесь к ключевому слову prototype
на объекте перед добавлением пользовательского свойства, как показано ниже:
// Сначала создайте объект "bicycle"
function bicycle() {}
// Присвойте свойство wheels прототипу объекта
bicycle.prototype.wheels = 2;
Теперь все экземпляры bicycle()
будут иметь свойство wheels
, встроенное в них.
Тот же подход используется в V8 с шаблонами. Каждый FunctionTemplate
имеет метод PrototypeTemplate
, который предоставляет шаблон для прототипа функции. Вы можете задать свойства и связать функции C++ с этими свойствами на PrototypeTemplate
, которые затем будут присутствовать во всех экземплярах соответствующего FunctionTemplate
. Например:
v8::Local<v8::FunctionTemplate> biketemplate = v8::FunctionTemplate::New(isolate);
biketemplate->PrototypeTemplate().Set(
v8::String::NewFromUtf8(isolate, "wheels"),
v8::FunctionTemplate::New(isolate, MyWheelsMethodCallback)->GetFunction()
);
Это приводит к тому, что все экземпляры biketemplate
имеют метод wheels
в своей прототипной цепочке, который при вызове вызывает функцию C++ MyWheelsMethodCallback
.
Класс V8 FunctionTemplate
предоставляет открытый метод Inherit()
, который вы можете вызвать, если хотите, чтобы шаблон функции наследовал от другого шаблона функции, следующим образом:
void Inherit(v8::Local<v8::FunctionTemplate> parent);