aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/doc/src/cppintegration/integrating-with-js-values-from-cpp.qdoc
blob: aac9c080d8d3ea5a372c2c60e112c70f770b7e52 (plain)
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright (C) 2012 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\page qtqml-integrating-with-js-values-from-cpp.html
\title Integrating with JavaScript values from C++
\brief Description of how to load and access JavaScript from C++ code.

The following classes can be used to load and access JavaSript from C++ code:

\list

    \li \l QJSValue, which acts as a container for Qt/JavaScript data types.
    \li \l QJSManagedValue, which represents a value on the JavaScript heap
            belonging to a \l QJSEngine.
    \li \l QJSPrimitiveValue, which operates on primitive types in JavaScript semantics.
\endlist

Use QJSValue to transfer values to and from the engine, and use QJSManagedValue
to interact with JavaScript values. Only use QJSPrimitiveValues if you have to
emulate the semantics of JS primitive values in C++.

\table
    \header
        \li QJSValue
        \li QJSManagedValue
        \li QJSPrimitiveValue
    \row
        \li Persistently store values
        \li Short lived
        \li Short lived
    \row
        \li Transport values to/from engine
        \li Access properties
        \li Only Primitives
    \row
        \li
        \li Call methods
        \li Basic arithmetic and comparison
\endtable

\section1 QJSValue as a Container Type

\l QJSValue stores the Qt/JavaScript data types supported in ECMAScript including
function, array and arbitrary object types as well as anything supported by
QVariant. As a container, it can be used to pass values to and receive values
from a QJSEngine.

\snippet qtjavascript/integratingjswithcpp/exampleqjsascontainer.cpp qjs-as-container

In case of a cache miss, \c undefined is returned. Otherwise, the cached value is
returned. Note that implicit conversions (from QString and QJSValue::SpecialValue respectively)
occur when the value is returned.

QJSValue also has an API to interact with the contained value, but using
QJSManagedValue is recommended.

\section1 Primitive and Managed Values

QJSValue and QJSManagedValue store values that can be either managed or primitive.
In QML’s JS engine, a managed value can be thought of as a pointer to some data
structure on the heap, whose memory is managed by the engine’s garbage collector.
The actual content of primitive values is stored directly, using a technique
called NaN-boxing that enables you to represent a NaN-value in multiple ways, even
though only two are actually needed; one for signalling and one for quiet NaN-value.

\table
\header
    \li Primitive Values
    \li Managed Values
\row
    \li int
    \li Function
\row
    \li double
    \li Array
\row
    \li undefined
    \li QVariant
\row
    \li null
    \li string object
\row
    \li QString
    \li
\endtable

A pointer to the engine can be obtained from a managed value, but not from a
primitive one. When using QJSValue for its JavaScript API, you need access
to the engine to evaluate JavaScript. For example, to run the \c call(args) function,
you have to interpret it in the engine. This works, as the function is a managed
value, and you can obtain the engine from it.

Similarly, where the engine is needed when you call a function or
access a property on a primitive number or string. Whenever you call a method on
a primitive, an instance of its corresponding non-primitive objects is created.
This is referred as boxing. When you write \c (42).constructor, that is equivalent
to \c (new Number(42)).constructor, and it returns the constructor method of the
global number object. Accordingly, if you write \c QJSValue(42).property("constructor"),
you would expect to obtain a QJSValue containing that function. However, what you
get is instead a QJSValue containing \c undefined.

The QJSValue that you constructed contains only a primitive value, and thus you have
no way to access the engine. You also can’t simply hardcode the property lookup
for primitive values in QJSEngine, as in one engine you might set
\e {Number.prototype.constructor.additionalProperty = "the Spanish Inquisition"}
whereas in another \e {Number.prototype.constructor.additionalProperty = 42}.
The end result would then clearly be unexpected.

To ensure that property accesses always work, you would need to always store boxed
values in QJSValue or store an additional pointer to the engine.

However, this would be incompatible with how QJSValue is currently used, lead to
pointless JS heap allocations when passing around primitives, and increase the
size needed to store a QJSValue. Therefore, you should use \l QJSValue only for
storage and \l QJSManagedValue to obtain the engine.

\section1 QJSManagedValue

QJSManagedValue is similar to QJSValue, with a few differences:

\list
\li The constructors (except for the default and move constructor2) require
    passing a QJSEngine pointer.
\li Methods like  \l {QJSManagedValue::}{deleteProperty} and
    \l {QJSManagedValue::}{isSymbol} are added.
\li If QJSManagedValue methods encounter an exception, they leave it intact.
\endlist

To obtain the engine in code, either you are in a scripting context where you’ve
already got access to an engine to create new objects with \c QJSEngine::newObject
and to evaluate expressions with \c QJSEngine::evaluate, or you want to evaluate
some JavaScript in a QObject that has been registered with the engine. In the
latter case, you can use \c qjsEngine(this) to obtain the currently active
QJSEngine.

QJSManagedValue also provides a few methods that have no equivalent in QJSEngine.

In the example below, QJSManagedValue methods encounter an exception, and
QJSEngine::catchError is used to handle the exception.

\snippet qtjavascript/integratingjswithcpp/exampleqjsengine.cpp qjs-engine-example

However, inside a method of a registered object, you might want to instead let
the exception bubble up the call stack.

QJSManagedValue should be temporarily created on the stack,
and discarded once you don’t need to work any longer on the contained value.
Since QJSValue can store primitive values in a more efficient way, QJSManagedValue
should also not be used as an interface type which is the return or parameter type of
functions, and the type of properties, as the engine does not treat it in a
special way, and will not convert values to it (in contrast to QJSValue).

\section1 QJSPrimitiveValue

\l QJSPrimitiveValue can store any of the primitive types, and supports arithmetic
operations and comparisons according to the ECMA-262 standard. It allows for
low-overhead operations on primitives in contrast to QJSManagedValue, which always goes
through the engine, while still yielding results that are indistinguishable
from what the engine would return. As QJSPrimitiveValue is comparatively large, it
is not recommended to store values.

*/