`
dyllove98
  • 浏览: 1384148 次
  • 性别: Icon_minigender_1
  • 来自: 济南
博客专栏
73a48ce3-d397-3b94-9f5d-49eb2ab017ab
Eclipse Rcp/R...
浏览量:38379
4322ac12-0ba9-3ac3-a3cf-b2f587fdfd3f
项目管理checkList...
浏览量:78739
4fb6ad91-52a6-307a-9e4f-816b4a7ce416
哲理故事与管理之道
浏览量:131910
社区版块
存档分类
最新评论

定义类

 
阅读更多

dojo中使用dojo.declare来定义类,它有如下三个参数:

className: 表示类名(即构造函数)的字符串,通常会在全局空间创建这个类,也可以将类名用点分割,如: "myNamespace.Shape",那么Shape就会最为对象myNamespace的属性,如果myNamespace不存在,那么myNamespace.Shapde就会作为全局空间的属性。

superclass: 可选项(null, 函数或者函数数组),指明这个类的父类或者要聚合的类。

props: 一个包含了属性的对象自变量,用来初始化类的原型。如果这个对象自变量中含有一个叫constructor的函数,那么这个函数用来初始化对象实例,叫做初始化函数。

下面用它来创建一个Shape类:

dojo.declare("Shape", null, {color: 0, setColor: function(color) {this.color = color;}});

创建一个Shape对象: var s = new Shpae();

Shape类包含下面属性:

superclass:指向父类原型,如果没有就为undefined。

mixin: 指向聚合类的原型,如果没有就为undefiened。

extend: 一个高级应用,下面要讲。

_constructor: 指向类的初始化函数,这个函数负责构造所有的实例属性,当创建新对象时,有构造函数自动调用。

declaredClass: 就是传给dojo.declare的第一个参数。

inherited: 指向一个用于调用父类方法的函数

使用单继承定义类

为了是子类继承父类,只要将dojo.declare的第二个参数设置为父类的构造函数就可以了:

/*Circle继承自Shape*/
dojo.declare("Circle", Shape, {
    radius: 0;
    /*名为constructor的函数来初始化新实例,再调用构造函数的时候被自动调用*/
    constructor: function(radius) {
        this.radius = radius || 0;
    },
    setRadius: function(radius) {
        this.radius = radius;
    }
});

当创建一个Circle对象时,dojo会做两件事:

1 调用父类构造函数

2 调用子类自身的初始化函数

var circle = new Circle(5);

/*调用父类构造函数*/
Shape.apply(this, arguments);
/*调用子类初始化函数*/
this._constructor.apply(this, arguments);

Circle类的原型会指向Shape的原型。

重载父类的方法

子类通常会覆写父类的方法,而且还会在覆写的方法中调用父类的方法,可以通过inherited方法实现这个操作。

/*Circle继承自Shape*/
dojo.declare("Circle", Shape, {
    radius: 0;
    /*名为constructor的函数来初始化新实例,再调用构造函数的时候被自动调用*/
    constructor: function(radius) {
        this.radius = radius || 0;
    },
    setRadius: function(radius) {
        this.radius = radius;
    },
    /*覆写父类中的setColor方法*/
    setColor: function(color) {
        if (color > 100) {
            this.inherited(arguments); //调用父类中的版本
        }
    }
});

inherited会找到父类中的方法,然后通过下面的形式调用:

1.function inherited(args, a, func){   
2.    // crack arguments   
3.    if(typeof args == "string"){   
4.        name = args;   
5.        args = a;   
6.        a = func;   
7.    }   
8.   // find method f   
9.    ……   
10.   if(f){   
11.    return a === true ? f : f.apply(this, a || args);   
12.   }   
13.}  

inherited接受三个参数:

methodName: 要调用的方法名,可选。

args: 即arguments字面量,必选

a: 额外的参数数组,若有这个参数,父类中的方法就不接受arguments字面量。可选

向父类添加方法

向由dojio.declare创建的类的原型中添加方法,应该调用类自带的extend方法,而向其他非dojo.declare创建的构造函数原型中添加方法,调用dojo.extend.

聚合与多继承

dojo.declare可以模拟多继承,这是以聚合的方式实现的。比如现在有一个Circle类,有一个Position类,我们要创建一个新类继承Circle类,聚合Position类:

dojo.declare("CirclePosition", [Circle, Position], {
    constructor: function(radius, x, y) {
        this.setPosition(x, y);
    }
});

这里,dojo.declare的第二个参数为一个构造函数数组,其中的第一个构造函数会被继承,后面的构造函数会被聚合。

而当我们实例化一个CirclePosition类时,会执行下列操作:

var circlePosition = new CriclePosition(5, 4, 3);

/*父类构造函数被调用*/
Circle.apply(this, arguments);

/*父类的构造函数被调用*/
Shape.apply(this, arguments);

/*聚合类构造函数被调用*/
Position.apply(this, arguments);

/*类本身的初始化函数被调用*/
CirclePosition._constructor.apply(this, arguments);

下面是CirclePosition对象空间

 

通过这个对象空间可以发现,CirclePosition的原型对象(6)并没有直接指向Circle的原型对象(7),而是指向了一个中间构造函数的原型对象(4),这个原型对象才指向了Circle的原型对象,同时,Position原型对象中的属性和方法都被复制到了这个原型对象中(其中基本类型是值复制,而方法以及对象是引用复制,并且是所有的属性,包括Position继承来的)。

从这里还要注意,Position的原型对象不在CirclePosition的原型链中,并且聚合的属性很可能覆盖掉更低级别的原型对象中的同名属性。

预处理构造函数参数

如果类自身,其父类,其聚合的类的构造函数的参数签名不一样,那么,在初始化一个类实例的时候很可能出问题。如:CirclePosition继承自Circle类,Circle类继承自Shape类,同时CriclePosition类聚合Position类。其中CirclePosition的构造函数签名(radius, x, y), Circle构造函数的签名(radius), Shape构造函数的签名(), Position构造函数的签名(x, y)。那么当实例化一个CirclePosition时:

var circlePosition = new CirclePosition(5, 4, 3);

/*下面的函数调用将依次执行*/
Circle.apply(this, [5, 4, 3]);

Shape.apply(this, [5, 4, 3]);

Position.apply(this, [5, 4, 3]);

CirclePosition.prototype._constructor.apply(this, [5, 4, 3]);

由于Circle只接受radius参数,因此能够被正常初始化,Shape类不接受任何参数,因此也能够正常初始化,而Position接受x,y作参数,因此,它会将radius当成x,把x当成y,不能够正常初始化。但是我们可以将Circle的初始化函数定义成:

function constructor(raduis, x, y) {
    this.setPosition(x, y);//setPosition是Position类中的方法,用来设置x, y的值
}

这样,尽管Position构造函数初始化不正确,但是CirclePosition的初始化函数还是可以正确初始化。使用这种方法大多数情况下不会出现问题,但有时候也会跑出异常。有两种方法可以解决这个问题:

1 接受预处理器

dojo.declare接受一个预处理器(即一个函数),这个预处理器定义在传递给它的但三个参数里面,这个预处理器被定义为一个叫preamble的属性。这个预处理器会在参数传递个父类或者聚合类的构造函数时,先对参数进行格式化,以产生适合父类或者构造函数的参数。当时单继承或者没有聚合类,或者聚合类和父类的参数都一样的时候,这中方法可以工作的很好。但是当聚合类和父类的构造函数参数不一致的时候,就会出问题,因为这个预处理器格式化的参数要么只适合父类,要么只适合聚合类。为了解决这个问题,我们可以创建一个夹层类(Shim Class),以上面的CriclePosition为例,首先创建一个夹层类,它集成自Position类:

 

dojo.declare("PositionShimClass", Position,  {
    preamble: funciton(raduis, x, y) { //预处理器
        return [x || null, y || null];
    }
}

里面的预处理器将为Position格式化参数。

然后用这个PositionShimClass来聚合CirclePosition:

dojo.declare("CirclePosition", [Circle, PositionShimClass], {});

当此时在创建CirclePosition的实例时:

var circlePosition = new CirclePosition(5, 4, 3);

/*下面的函数调用将依次执行*/
Circle.apply(this, [5, 4, 3]);

Shape.apply(this, [5, 4, 3]);

PositionShimClass.apply(this, PositionShimClass.prototype.preamble.apply(this, [5, 4, 3]); //预处理后的参数将传递给Position构造函数


if (CirclePosition.prototype._constructor) {//如果初始化函数存在
    CirclePosition.prototype._constructor.apply(this, [5, 4, 3]);
}

这样就能正确初始化。也就是说,子类中的参数顺序负责向父类构造函数提供正确的参数,而夹层类负责聚合类提供正确的参数。

2 用对象字面量来传递参数

 

var circlePosition = new CirclePosition({
    radius: 5,
    x: 4, 
    y: 3
});

/*Circle类中的初始化函数*/
function constructor(args) {
    if (args && args.radius) {
        this.radius = args.radius;
    }
}

/*Position类中的初始化函数*/
function constructor(args) {
    if (args && args.x && args.y) {
        this.x = args.x;
        this.y = args.y;
    }
}

使用这种方式,可以不用遵循参数的顺序,而且总是安全的。这种方法适合有多个参数的情况。

解决名字冲突:

聚合类中的所有属性被添加到了新类的原型链中,而这些属性在原型链中的位置位于父类之上,因此当出现同名属性时,很可能会覆盖掉父类中的属性。我们仍以CirclePosition,Circle, Shape, Position为例。CirclePosition继承自Cricle, Circle继承自Shape,同时CriclePosition聚合Position。Circle中有setRadius方法,Shape中有setColor方法,Position中有setPosition方法。如果以这种方式命名,他们不会产生冲突,但是如果他们都命名为set,那么就会产生冲突。当CirclePosition的实例调用set方法时,只有Position中的set方法被调用。解决这种问题的方法是给Position的set方法重命名:

CirclePosition.extend({
    setPosition: Position.prototype.set
});
delete CirclePosition.superclass.set;

那么接下来如何消除Cricle.set和Shape.set的歧义呢?这其实是设计上的错误,应该再设计中就为他们叫一个合理的名字。
两阶段构造

假设有这样的一个几类Base:

dojo.declare("Base", null, {
    constructor: function() {
        this.args = {baseArags: 
        dosomething(this);
    }
}

 

这个基类中的初始化函数要求实例完全构造完成之后调用才行,如果从这个基类派生出一个子类subclass,由于父类的初始化函数在子类初始化函数完成之前被调用,因此这个父类初始化函数将不能正常工作。因此我们需要保证子类已经被完全构造,再来调用这个方法,而dojo.declare的第三个对象字面量参数提供postscript属性来完成这个操作。它保证这个属性定义的方法会在所有方法被调用之后(包括父类构造器方法,聚合类构造器方法,子类自身的初始化方法),这个方法才在新实例上执行。这就是两阶段构造。

使用postscript必须注意,它仅仅被最终派生出来的类调用,如果父类定义了这个方法,而子类没有定义,那么调用的就是父类的方法;如果子类覆写了这个方法,那么可以通过this.inherited(arguments)实现,并且这个方法接受的参数是传递给构造器的参数。

下面我们来看一个例子,父类和子类将会把它们接受的字符存到args变量中,然后再将他们全部输出来。首先不适用postscript:

/*父类*/
dojo.declare("Base", null, {
    constructor: function() {
        this.args = {baseArgs: dojo._toArray(arguments)};
        console.log(dojo.toJson(this.args, true));
});
/*子类*/
dojo.declare("Subclass", Base, {
    constructor: function() {
        this.args.subArags = dojo._toArray(arguments);
    },
    preamble: function() {
        return dojo._toArray(arguments).slice(0, 3); //父类只得到其中的三个参数
    }
});
var subClass = new Subclass(1, 2, 3, 4, 5, 6);

这导致父类输出时,将漏掉传递给子类的参数。

现在修改如下:

ojo.declare("Base", null, {
    constructor: function() {
        this.args = {baseArgs: dojo._toArray(arguments)};
    },
    postscript: function() {
        console.log(dojo.toJson(this.args, true));
    }
);
dojo.declare("Subclass", Base, {
    constructor: function() {
        this.args.subArgs = dojo._toArray(arguments);
    },
    postscript: function() {
        console.log("in subclass");
        this.inherited(arguments);
    },
    preamble: function() {
        return dojo._toArray(arguments).slice(0, 3);
    }
});


不适用构造函数产生自定义对象:

dojo.delegate(obj, propt): 创建并返回一个新对象,并利用dojo.mixin将propt合并到新对象中:

var Base = {
    greet: function() {
        return "Hello, my Name is " + this.name;
    }
};

var newObject = dojo.delegate(Base, {name: "Jone"});

当调用newObject.greet()时产生"Hello, my name is Jone".

0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics