Миграция с QSA на Qt Script
Цель данного документа - отобразить различия между Qt Script for Applications (QSA) и Qt Script, совместимым с ECMAScript механизмом, поставляемым с Qt 4.3. Назначение этого документа не предполагает выполнять функцию руководства по портированию, но охватить наиболее очевидные аспекты.
Прежде всего важно понять, что Qt Script - это только интерпретатор, он не предоставляет редактор, дополнение кода или управление проектами сценариев, как это делает QSA. Тем не менее, Qt Script обеспечивает почти полное соответствие стандарту ECMAScript и исполняется значительно лучше, чем механизм сценариев, предоставляемый QSA.
Язык сценариев
Язык сценариев, используемый в QSA, начиная с этого места упоминаемый как QSA, происходит от ECMAScript 3.0 и 4.0 и является гибридом этих стандартов. Большая часть логики времени выполнения, например, классы и правила областей видимости, базируется на рекомендации ECMAScript 4.0, в то время как реализация библиотеки базируется на стандарте ECMAScript 3.0. С другой стороны, Qt Script базируется исключительно на стандарте ECMAScript 3.0. Хотя на первый взгляд языки выглядят одинаковыми, имеется несколько различий, которые мы раскроем в разделах ниже.
Классы против объектов и свойств
QSA реализует классы и наследование способом, во многом знакомым пользователям других объектно-ориентированных языков программирования, например, C++ и Java. Тем не менее, стандарт ECMAScript 3.0 определяет, что всё является объектом, и объекты могут иметь именованные свойства. Например, чтобы создать объект точки со свойствами x и y, напишите следующий код Qt Script:
point = new Object();
point.x = 12;
point.y = 35;
Объект point в этом случае создаётся как обычный объект и ему присваиваются два свойства, x и y, со значениями 12 и 35. Объект point присваивается "Глобальному объекту" (Global Object) как именованное свойство point. Глобальный объект может учитывать глобальное пространство имён механизма сценариев. Аналогично, глобальные функции являются именованными свойствами глобального объекта; например:
function manhattanLength(point) {
return point.x + point.y;
}
Эквивалентной конструкцией, которая иллюстрирует то, что функция является свойством глобального объекта, является следующее присваивание:
manhattanLength = function(point) {
return point.x + point.y;
}
Так как функции являются объектами, они могут быть присвоены объектам как свойства, становясь функциями-членами:
point.manhattanLength = function() {
return this.x + this.y;
}
print(point.manhattanLength()); // печатает 47
В вышеприведённом коде мы видим первую, едва различимую разницу между QSA и Qt Script. В QSA класс точки будет записан примерно так:
class Point() {
var x;
var y;
function manhattanLength() { return x + y; }
}
где в функции manhattanLength() мы получаем непосредственный доступ к x и y, поскольку когда функция вызывается, объект this является неявной частью текущей области видимости, как в C++. В Qt Script, однако, это не совсем так, и нам нужен явный доступ к значениям x и y через this.
Весь вышеприведённый код работает с QSA, за исключением присваивания функции point.manhattanLength, которое мы повторим здесь для ясности:
point.manhattanLength = function() {
return this.x + this.y;
}
print(point.manhattanLength()); // печатает 47
Это связано с тем, что в QSA значение this определяется на основе расположения объявления функции, в которой он используется. В вышеприведённом коде, функция присваивается объекту, но она объявлена в глобальной области видимости, поэтому this не содержит правильного значения. В Qt Script, значение this определяется во время выполнения, поэтому вы можете присваивать функцию manhattanLength() любому объекту, который имеет значения x и y.
Конструкторы
В вышеприведённом коде мы используем слегка неуклюжий метод конструирования объектов, сначала создавая их экземпляры, а затем вручную присваивая свойства. В QSA правильный способ решения этого заключается в реализации конструктора класса:
class Car {
var regNumber;
function Car(regnr) {
regNumber = regnr;
}
}
var car = new Car("ABC 123");
Эквивалентом в Qt Script является создание функции конструктора:
function Car(regnr) {
this.regNumber = regnr;
}
var car = new Car("ABC 123");
Как мы можем увидеть, конструктор является обычной функцией. Что является особым - то как мы его вызываем, а именно добавляем спереди ключевое слово new. Этим будет создан новый объект и вызвана функция Car() с вновь созданным объектов в качестве указателя this. Поэтому, до некоторой степени, это эквивалентно:
var car = new Object();
car.constructor = function(regnr) { ... }
car.constructor();
Это похоже на пример с manhattenLength() выше. Кроме того, главное отличие между QSA и Qt Script заключается в том, что один явно использует ключевое слово this для получения доступа к членам и что вместо объявления переменной, regNumber, мы просто расширили объект this свойством.
Функции-члены и прототипы
Как мы увидели выше, один из способов создания функций-членов объекта в Qt Script - это присваивание функции-члена объекту в качестве свойства и использование объекта this внутри функций. Поэтому, если мы добавим функцию toString в класс Car
class Car {
var regNumber;
function Car(regnr) {
regNumber = regnr;
}
function toString() {
return regNumber;
}
}
можно переписать её в Qt Script как:
function Car(regnr) {
this.regNumber = regnr;
this.toString = function() { return this.regNumber; }
}
В QSA функции-члены были частью объявления класса, и по этой причине разделялись между всеми экземплярами данного класса. В Qt Script каждый экземпляр имеет экземпляр члена для каждой функции. Это означает, что используется больше памяти когда используется множественное наследование. Qt Script использует прототипы для исправления этого.
Базовый механизм наследования, основанный на прототипах, работает как изложено ниже. Каждый объект Qt Script имеет внутреннюю ссылку на другой объект, его прототип. Когда свойство ищется в объекте, а сам объект не имеет свойства, вместо этого интерпретатор ищет свойство в объекте-прототипе; если прототип имеет свойство, тогда возвращается это свойство. Если объект-прототип не имеет свойства, интерпретатор ищет свойство в прототипе объекта-прототипа и так далее.
Цепочка объектов составляет цепочку прототипов. Последовательность объектов-прототипов прослеживается до тех пор, пока свойство не найдётся или не будет достигнут конец цепочки.
Чтобы сделать функцию toString() частью прототипа, напишем примерно такой код:
function Car(regnr) {
this.regNumber = regnr;
}
Car.prototype.toString = function() { return this.regNumber; }
Здесь мы делаем функцию toString() частью прототипа для того, чтобы когда мы вызовем car.toString() она была разрешена через внутренний объект-прототип объекта car. Обратите однако внимание на то, что объект this остаётся исходным объектом, который вызывался функцией, а именно car.
Наследование
Теперь, когда мы увидели как использовать прототип для создания членов "класса" в Qt Script, давайте посмотрим, как мы можем использовать прототипы для создания полиморфизма. В QSA вы напишите
class GasolineCar extends Car {
function GasolineCar(regnr) {
Car(regnr);
}
function toString() {
return "GasolineCar(" + regNumber + ")";
}
}
С помощью Qt Script мы добъёмся такого же эффекта создав цепочку прототипов. Прототип по умолчанию объекта - обычный Object без каких-либо специальных членов, но этот объект можно заменить другим объектом-прототипом.
function GasolineCar(regnr) {
Car(regnr);
}
GasolineCar.prototype = new Car();
GasolineCar.prototype.toString = function() {
return "GasolineCar(" + this.regNumber + ")";
}
В вышеприведённом коде у нас есть конструктор, GasolineCar, который вызывает реализацию конструктора "базового класса" для инициализации объекта this с помощью свойства regNumber, основанного на значениях, переданных в конструктор. В данном случае интересна строка после конструктора, где мы изменяем прототип по умолчанию для GasolineCar на экземпляр типа Car. Это означает, что все члены, доступные в объекте Car, теперь доступны во всех объектах GasolineCar. В последней строке мы заменяем функцию toString() прототипа на свою, перегружая таким образом toString() для всех экземпляров GasolineCar.
Статические члены
QSA позволяет пользователям объявлять в классах статические члены, а получить доступ к ним можно как через экземпляры класса, так и через сам класс. Например, получаем доступ к следующей переменной через классCar:
class Car {
static var globalCount = 0;
}
print(Car.globalCount);
Эквивалент в Qt Script - присвоить переменные, которые будут выступать в качестве статических членов, как свойства функции конструктора. Например:
Car.globalCount = 0;
print(Car.globalCount);
Обратите внимание на то, что в QSA переменные-статические члены были доступны также в экземплярах данного класса. В Qt Script, с проиллюстрированным выше подходом, переменная является членом только объекта конструктора, и таким образом доступна только через Car.globalCount.
Встроенные функции и библиотека
Встроенные функции в QSA базируются на объявленных в стандарте ECMAScript 3.0, этот же стандарт используется для Qt Script, но QSA добавляет к ним несколько расширений, специально для типов String и RegExp. В QSA также отсутствуют некоторые функции из стандарта, наиболее заметен тип Date. Ниже мы перечислили все отличия. Все внесённые в Qt Script изменения увеличивают соответствие ECMAScript 3.0.
Функция QSA | Замечания об эквивалентных функциях Qt Script |
eval() | Функция eval в QSA открывая новую область видимости для кода, который выполняется в функции eval, поэтому локально определённые переменные не доступны извне. В Qt Script функция eval() разделяет текущую область видимости, делая локально определённые переменные доступными извне вызова eval(). |
debug() | Эта функция недоступна в Qt Script. Используйте взамен print(). |
connect() | QSA имеет замыкания, означающие что ссылка на функцию-член неявно содержит его объект this. Qt Script этого не поддерживает. Подробности об использовании функции connect смотрите в документации по Qt Script. |
String.arg() | Эта функция недоступна в Qt Script. Используйте взамен replace() или concat(). |
String.argDec() | Эта функция недоступна в Qt Script. Используйте взамен replace() или concat(). |
String.argInt() | Эта функция недоступна в Qt Script. Используйте взамен replace() или concat(). |
String.argStr() | Эта функция недоступна в Qt Script. Используйте взамен replace() или concat(). |
String.endsWith() | Эта функция недоступна в Qt Script. Используйте взамен lastIndexOf(). |
String.find() | Эта функция недоступна в Qt Script. Используйте взамен indexOf(). |
String.findRev() | Эта функция недоступна в Qt Script. Используйте взамен lastIndexOf() и length. |
String.isEmpty() | Эта функция недоступна в Qt Script. Используйте взамен length == 0. |
String.left() | Эта функция недоступна в Qt Script. Используйте взамен substring(). |
String.lower() | Эта функция недоступна в Qt Script. Используйте взамен toLowerCase(). |
String.mid() | Эта функция недоступна в Qt Script. Используйте взамен substring(). |
String.right() | Эта функция недоступна в Qt Script. Используйте взамен substring(). |
String.searchRev() | Эта функция недоступна в Qt Script. Используйте взамен search() / match(). |
String.startsWith() | Эта функция недоступна в Qt Script. Используйте взамен indexOf() == 0. |
String.upper() | Эта функция недоступна в Qt Script. Используйте взамен toUpperCase(). |
RegExp.valid | Это свойство недоступно в Qt Script поскольку оно не нужно; исключение SyntaxError возбуждается для плохих объектов RegExp. |
RegExp.empty | Это свойство недоступно в Qt Script. Используйте взамен toString().length == 0. |
RegExp.matchedLength | Это свойство недоступно в Qt Script. RegExp.exec() возвращает массив, чей размер имеет совпадающую длину. |
RegExp.capturedTexts | Это свойство недоступно в Qt Script. RegExp.exec() возвращает массив захваченных текстов. |
RegExp.search() | Эта функция недоступна в Qt Script. Используйте взамен RegExp.exec(). |
RegExp.searchRev() | Эта функция недоступна в Qt Script. Используйте взамен RegExp.exec() или String.search()/match(). |
RegExp.exactMatch() | Эта функция недоступна в Qt Script. Используйте взамен RegExp.exec(). |
RegExp.pos() | Эта функция недоступна в Qt Script. Используйте взамен String.match(). |
RegExp.cap() | Эта функция недоступна в Qt Script. RegExp.exec() возвращает массив захваченных текстов. |
QSA также определяет несколько внутренних Qt API, которых нет в Qt Script. Предоставляемые QSA типы, которые не предоставляются в Qt Script:
- Rect
- Point
- Size
- Color
- Palette
- ColorGroup
- Font
- Растровое изображение
- ByteArray
C++ API QSA против Qt Script
QSA - это больше, чем просто механизм выполнения сценариев. Он предоставляет управление проектами, редактор с завершением кода и минимальную IDE для редактирования проектов сценариев. С другой стороны, Qt Script - это просто механизм выполнения сценариев. Это означает, что эквивалента классам QSEditor, QSScript, QSProject и QSWorkbench в Qt Script нет. QSA также предоставляет те же расширенные API посредством QSUtilFactory и QSInputDialogFactory. Также нет эквивалента для этих классов в Qt Script API.
Делаем объекты QObject доступными из сценариев
Имеется два разных способа сделать объекты QObject доступными из сценариев в QSA. Первый метод - через функции QSInterpreter::addTransientObject() и QSProject::addObject(). В этом случае объекты добавляются в глобальное пространство имён интерпретатора, используя их имена объектов в качестве имён переменных.
QPushButton *button = new QPushButton();
button->setObjectName("button");
interpreter->addTransientObject(button);
Код выше добавляет кнопку в глобальное пространство имён под именем "button". Единственное очевидное ограничение здесь - это то, что имеется возможность для любых неименованных объектов QObject или объектов, чьи имена конфликтуют. Qt Script предоставляет более гибкий способ добавления объектов QObject в окружение сценария.
QPushButton *button = new QPushButton();
QScriptValue scriptButton = engine.newQObject(button);
engine.globalObject().setProperty("button", scriptButton);
В вышеприведённом коде мы создаём QPushButton и обёртываем его в значение сценария, используя функцию QScriptEngine::newQObject(). Это даёт нам значение сценария, которое мы помещаем в глобальный объект используя имя "button". Концепция объектов и свойств, обсуждаемая выше, также полностью видимо здесь в открытом C++ API. Зависимости от имени объекта нет и мы может также разрешать конфликты имён более изящно. Здесь мы работаем непосредственно с объектами QScriptValue. Это реальный объект, который был передан в механизм сценариев, поэтому мы действительно имеем низкоуровневый доступ к внутренним структурам данных сценария, далеко за пределами возможного в QSA. Свойства, сигналы и слоты QObject доступны для автора сценария в Qt Script, как в QSA.
Другой способ сделать видимыми объекты QObject в QSA - создать QSObjectFactory, которая сделает возможным создание экземпляров объектов QObject из сценариев.
Ниже приведён некоторый код для примера фильтра в пакете QSA.
ModuleFactory::ModuleFactory()
{
registerClass( "ImageSource", &ImgSource::staticMetaObject);
...
}
QObject *ModuleFactory::create( const QString &type,
const QVariantList &,
QObject * )
{
if ( type == "ImageSource" )
return new ImgSource();
...
}
...
interpreter.addObjectFactory(new ModuleFactory());
Эквивалент в Qt Script написан аналогично конструкторам, написанным в сценариях. Мы регистрируем функцию обратного вызова C++ под именем "ImageSource" в глобальном пространстве имён и возвращаем из этой функции объект QObject:
QScriptValue construct_QPushButton(QScriptContext *, QScriptEngine *engine) {
return engine->newQObject(new QPushButton());
}
...
QScriptValue constructor = engine.newFunction(construct_QPushButton);
QScriptValue value =
engine.newQMetaObject(&QPushButton::staticMetaObject,
constructor);
engine.globalObject().setProperty("QPushButton", value);
В случае Qt Script мы используем подход, аналогичный используемому для показа QObject, а именно через QScriptEngine::newQObject(). Эта функция также имеет преимущество в том, что можно задать, сделает ли QObject видимыми свойства и слоты своего базового класса. Также возможно задать пользовательские права владения.
Читатель может задаться вопросом - почему мы не добавили функцию конструктора сразу в пространство имён, но создали ему в дополнение значение мета-объектного сценария. Конечно, обычная функция будет достаточно хороша, но создав основанный на QMetaObject конструктор мы без затрат получили перечисления QPushButton в объекте функции QPushButton. Для сравнения - сделать видимыми перечисления в QSA весьма тяжело.
Если мы хотим в Qt Script добавить к типу QPushButton больше "статических" данных, мы вольны добавить свойства, аналогично тому как мы сделали это для сценария. Также можно добавить пользовательские функции к экземпляру QPushButton Qt Script, установив на неё больше свойств, например, сделав доступной функцию C++ setText(). Этого можно также добиться установкой пользовательского прототипа, эффективно использующего память, как обсуждалось в примере сценария выше.
Получение доступа к объектам, не унаследованным от QObject
В QSA можно сделать видимыми для QSA объекты, не унаследованные от QObject, обернув их в QObject и используя для того, чтобы сделать их видимыми, либо QSWrapperFactory, либо QSObjectFactory. Решить, когда использовать каждый из этих классов, может смутить, так как один использовался для конструирования на основе сценариев, а другой - для обёртывания параметров функции и возвращения значений, но по существу они делают одно и то же.
В Qt Script, предоставление доступа к объектам QObject и к объектам, не унаследованным от QObject, делается таким же способом, что показан выше, создавая функцию конструктора и добавляя свойства или пользовательский прототип к сконструированному объекту.
Отображение данных
QSA поддерживал жёстко запрограммированный набор отображений типов, которые охватывают большинство типов QVariant, объектов QObject и примитивов. Для более сложных сигнатур типов, таких как инструментальные классы на основе шаблонов, он имеет довольно ограниченную поддержку. Qt Script значительно лучше в отображении типов и конвертируется списки шаблонных типов в массивы соответствующих типов, при том что все типы объявлены в мета-объектной системе.
Copyright © 2009 Nokia Corporation and/or its subsidiary(-ies) |
Торговые марки |
Qt 4.5.3 |
|