八、JavaScript面向对象的支持
~~~~~~~~~~~~~~~~~~
(续)
4). 需要用户维护的另一个属性:constructor
------
回顾前面的内容,我们提到过:
- (如果正常地实现继承模型,)对象实例的constructor属性指向构造器
- obj.constructor.prototype指向该对象的原型
- 通过Object.constructor属性,可以检测obj2与obj1是否是相同类型的实例
与原型链要通过用户代码来维护prototype属性一样,实例的构造器属性constructor也需要用户代码维护。
对于JavaScript的内置对象来说,constructor属性指向内置的构造器函数。如:
//---------------------------------------------------------
// 内置对象实例的constructor属性
//---------------------------------------------------------
var _object_types = {
@#function@# : Function,
@#boolean@# : Boolean,
@#regexp@# : RegExp,
// @#math@# : Math,
// @#debug@# : Debug,
// @#image@# : Image;
// @#undef@# : undefined,
// @#dom@# : undefined,
// @#activex@# : undefined,
@#vbarray@# : VBArray,
@#array@# : Array,
@#string@# : String,
@#date@# : Date,
@#error@# : Error,
@#enumerator@#: Enumerator,
@#number@# : Number,
@#object@# : Object
}
function objectTypes(obj) {
if (typeof obj !== @#object@#) return typeof obj;
if (obj === null) return @#null@#;
for (var i in _object_types) {
if (obj.constructor===_object_types[i]) return i;
}
return @#unknow@#;
}
// 测试数据和相关代码
function MyObject() {
}
function MyObject2() {
}
MyObject2.prototype = new MyObject();
window.execScript(@#@#+
@#Function CreateVBArray()@# +
@# Dim a(2, 2)@# +
@# CreateVBArray = a@# +
@#End Function@#, @#VBScript@#);
document.writeln(@#<div id=dom style="display:none">dom<@#, @#/div>@#);
// 测试代码
var ax = new ActiveXObject("Microsoft.XMLHTTP");
var dom = document.getElementById(@#dom@#);
var vba = new VBArray(CreateVBArray());
var obj = new MyObject();
var obj2 = new MyObject2();
document.writeln(objectTypes(vba), @#<br>@#);
document.writeln(objectTypes(ax), @#<br>@#);
document.writeln(objectTypes(obj), @#<br>@#);
document.writeln(objectTypes(obj2), @#<br>@#);
document.writeln(objectTypes(dom), @#<br>@#);
在这个例子中,我们发现constructor属性被实现得并不完整。对于DOM对象、ActiveX对象来说这个属性都没有正确的返回。
确切的说,DOM(包括Image)对象与ActiveX对象都不是标准JavaScript的对象体系中的,因此它们也可能会具有自己的constructor属性,并有着与JavaScript不同的解释。因此,JavaScript中不维护它们的constructor属性,是具有一定的合理性的。
另外的一些单体对象(而非构造器),也不具有constructor属性,例如“Math”和“Debug”、“Global”和“RegExp对象”。他们是JavaScript内部构造的,不应该公开构造的细节。
我们也发现实例obj的constructor指向function MyObject()。这说明JavaScript维护了对象的constructor属性。——这与一些人想象的不一样。
然而再接下来,我们发现MyObject2()的实例obj2的constructor仍然指向function MyObject()。尽管这很说不通,然而现实的确如此。——这到底是为什么呢?
事实上,仅下面的代码:
--------
function MyObject2() {
}
obj2 = new MyObject2();
document.writeln(MyObject2.prototype.constructor === MyObject2);
--------
构造的obj2.constructor将正确的指向function MyObject2()。事实上,我们也会注意到这种情况下,MyObject2的原型属性的constructor也正确的指向该函数。然而,由于JavaScript要求指定prototype对象来构造原型链:
--------
function MyObject2() {
}
MyObject2.prototype = new MyObject();
obj2 = new MyObject2();
--------
这时,再访问obj2,将会得到新的原型(也就是MyObject2.prototype)的constructor属性。因此,一切很明了:原型的属性影响到构造过程对对象的constructor的初始设定。
作为一种补充的解决问题的手段,JavaScript开发规范中说“need to remember to reset the constructor property@#,要求用户自行设定该属性。
所以你会看到更规范的JavaScript代码要求这样书写:
//---------------------------------------------------------
// 维护constructor属性的规范代码
//---------------------------------------------------------
function MyObject2() {
}
MyObject2.prototype = new MyObject();
MyObject2.prototype.constructor = MyObject2;
obj2 = new MyObject2();
更外一种解决问题的方法,是在function MyObject()中去重置该值。当然,这样会使得执行效率稍低一点点:
//---------------------------------------------------------
// 维护constructor属性的第二种方式
//---------------------------------------------------------
function MyObject2() {
this.constructor = arguments.callee;
// or, this.constructor = MyObject2;
// ...
}
MyObject2.prototype = new MyObject();
obj2 = new MyObject2();
[1]