接着上一章
基于原型的繼承
Qt Script原型對象的目的是定義其他Qt Script對象集應該共享的行為。我們說,共享相同原型對象的對象屬于同一類(同樣,在技術方面,這不應該與C++和Java等語言的類結構混淆,ECMAScript沒有這樣的構造)。
基本的基于原型的繼承機制工作如下:每個QT腳本對象都有另一個對象的内部連結,即它的原型。當在對象中查找屬性時,當在對象中查找屬性時,對象本身不具有該屬性,而是在原型對象中查找該屬性;如果原型具有該屬性,則傳回該屬性。否則,在原型對象的原型中查找屬性,等等;該對象鍊構成了原型鍊。遵循原型對象鍊,直到找到屬性或到達鍊的末尾。
例如,當通過表達式new Object()建立新對象時,結果對象将具有标準Object原型Object.prototype作為其原型;通過該原型關系,新對象繼承一組屬性,包括hasOwnProperty()函數和toString()函數:
var o = new Object();
o.foo = 123;
print(o.hasOwnProperty('foo')); // true
print(o.hasOwnProperty('bar')); // false
print(o); // calls o.toString(), which returns "[object Object]"
toString()函數本身沒有在o中定義(因為我們沒有向o.toString配置設定任何内容),是以調用标準Object原型中的toString()函數,它傳回o(“[object Object]”)的高度通用的字元串表示。
注意,原型對象的屬性不複制到新對象;隻維護從新對象到原型對象的連結。這意味着對原型對象所做的更改将立即反映在具有修改對象作為原型的所有對象的行為中。
定義基于原型的類
在Qt腳本中,沒有明确定義一個類;沒有類關鍵字。相反,您可以用兩個步驟定義一個新類:
- 定義一個初始化新對象的構造函數
- 建立定義類接口的prototype對象,并将此對象配置設定給構造函數中的公共prototype屬性。
通過這種安排,構造函數的公共prototype屬性将自動設定為通過将new運算符應用到構造函數中建立的對象的原型;例如,由new Foo()建立的對象的原型将是Foo.prototype的值。
不對this對象進行操作的函數(“靜态”方法)通常作為構造函數屬性存儲,而不是作為原型對象的屬性存儲。常數也一樣,例如枚舉值。
下面的代碼為一個名為“Person”的類定義了一個簡單的構造函數。
function Person(name)
{
this.name = name;
}
接下來,您需要将Person.prototype設定為原型對象;即,定義應該對所有Person對象都通用的接口。Qt Script自動為每個腳本函數建立一個預設的原型對象(通過表達式new Object());您可以向該對象添加屬性,或者您可以配置設定自己的自定義對象。(一般來說,任何Qt腳本對象都可以作為任何其他對象的原型。)
下面是一個示例,說明如何重寫Person.prototype從Object.prototype繼承的toString()函數,以便為您的Person對象提供更合适的字元串表示:
Person.prototype.toString = function() { return "Person(name: " + this.name + ")"; }
這類似于在C++中重新實作虛拟函數的過程。此後,當在Person對象中查找名為toString的屬性時,它将在Person.prototype中解析,而不是像以前那樣在Object.prototype中解析:
var p1 = new Person("John Doe");
var p2 = new Person("G.I. Jane");
print(p1); // "Person(name: John Doe)"
print(p2); // "Person(name: G.I. Jane)"
也有一些其他有趣的事情我們可以了解Person對象:
print(p1.hasOwnProperty('name')); // 'name' is an instance variable, so this returns true
print(p1.hasOwnProperty('toString')); // returns false; inherited from prototype
print(p1 instanceof Person); // true
print(p1 instanceof Object); // true
hasOwnProperty()函數不是從Person.prototype繼承的,而是從Object.prototype繼承的,Object.prototype是Person.prototype本身的原型;即,Person對象的原型鍊是Person.prototype後跟Object.prototype。該原型鍊建立類層次結構,如應用instanceof操作符所示範的;instanceof通過跟蹤左側對象的原型鍊來檢查右側構造函數公共原型屬性的值是否達到。
定義子類時,有一個通用的模式可以使用。下面的示例示範如何建立一個名為“Employee”的子類:
function Employee(name, salary)
{
Person.call(this, name); // call base constructor
this.salary = salary;
}
// set the prototype to be an instance of the base class
Employee.prototype = new Person();
// initialize prototype
Employee.prototype.toString = function() {
// ...
}
同樣,可以使用執行個體驗證Employee和Person之間的類關系已經正确建立:
var e = new Employee("Johnny Bravo", 5000000);
print(e instanceof Employee); // true
print(e instanceof Person); // true
print(e instanceof Object); // true
print(e instanceof Array); // false
這表明Employee對象的原型鍊與Person對象的原型鍊相同,但是将Employee.prototype添加到了鍊的前面。
基于Qt腳本C++原型的程式設計
可以使用QScriptEngine::newFunction()來封裝本地函數。在實作構造函數時,将原型對象作為參數傳遞給QScriptEngine::newFunction()。您可以調用QScriptValue::construct()來調用構造函數函數,如果需要調用基類構造函數,則可以在本機構造函數中使用QScriptValue::call()。
QScript類提供了一種友善的方法來實作原型對象的C++插槽和屬性。檢視預設原型示例,看看這是如何完成的。或者,原型功能可以通過使用QScriptEngine::newFunction()包裝的獨立本地函數實作,并通過調用QScriptValue::setProperty()設定為原型對象的屬性。
在實作原型函數時,您使用QScriptable::thisObject()(或 QScriptContext::thisObject())來獲得對正在運作的QScriptValue的引用;然後調用qscriptvalue_cast() 将其轉換為C++類型,并使用通常的C++ API來執行相關操作。
通過調用QScriptEngine::setDefaultPrototype()将原型對象與C++類型相關聯。一旦建立了映射,當一個類型的值被包裝在QScriptValue中時,Qt腳本将自動配置設定正确的原型;當您顯式調用Engine::toScriptValue()時,或者當這種類型的值從C++時隙傳回并由引擎内部傳遞回腳本代碼時。這意味着,如果使用這種方法,就不必實作包裝類。
作為示例,讓我們考慮如何根據Qt Script API實作前一節中的Person類。我們從本地構造函數開始:
QScriptValue Person_ctor(QScriptContext *context, QScriptEngine *engine)
{
QString name = context->argument(0).toString();
context->thisObject().setProperty("name", name);
return engine->undefinedValue();
}
這裡是我們以前見過的Person.prototype.toString 函數的本地等價物:
QScriptValue Person_prototype_toString(QScriptContext *context, QScriptEngine *engine)
{
QString name = context->thisObject().property("name").toString();
QString result = QString::fromLatin1("Person(name: %0)").arg(name);
return result;
}
然後可以初始化如下類:
QScriptEngine engine;
QScriptValue ctor = engine.newFunction(Person_ctor);
ctor.property("prototype").setProperty("toString", engine.newFunction(Person_prototype_toString));
QScriptValue global = engine.globalObject();
global.setProperty("Person", ctor);
Employee子類的實作類似。我們使用QScriptValue::call()調用超級類(Person)構造函數:
QScriptValue Employee_ctor(QScriptContext *context, QScriptEngine *engine)
{
QScriptValue super = context->callee().property("prototype").property("constructor");
super.call(context->thisObject(), QScriptValueList() << context->argument(0));
context->thisObject().setProperty("salary", context->argument(1));
return engine->undefinedValue();
}
然後可以初始化Employee 類如下:
QScriptValue empCtor = engine.newFunction(Employee_ctor);
empCtor.setProperty("prototype", global.property("Person").construct());
global.setProperty("Employee", empCtor);
在實作類的原型對象時,您可能希望使用QScriptable類,因為它允許您根據Qt屬性、信号和槽定義腳本類的API,并自動處理Qt腳本和C++側之間的值轉換。