Dojo class中跟变量相关的几个问题
2011 年 11 月 09 日 | dojo针对 Java 开发人员的 Dojo 概念是学习dojo不得不读的一篇文章。本文对该文中没有详尽讲述的的跟变量相关的几个问题做一些阐述。
引文中在“复杂的类属性”一节,举例如下:
dojo.declare( "myClass", null, { globalComplexArg : { val : "foo" }, localComplexArg : null, constructor : function() { this.localComplexArg = { val:"bar" }; } } ); // Create instances of myClass A and B... var A = new myClass(); var B = new myClass(); // Output A's attributes... console.log("A's global val: " + A.globalComplexArg.val); console.log("A's local val: " + A.localComplexArg.val); // Update both of A's attributes... A.globalComplexArg.val = "updatedFoo"; A.localComplexArg.val = "updatedBar"; // Update B's attributes... console.log("A's global val: " + B.globalComplexArg.val); console.log("A's local val: " + B.localComplexArg.val);
引文中谈到,“类属性可以在声明时进行初始化,但是如果使用复杂对象类型(例如 hash 或数组)初始化属性,该属性将类似于 Java 类中的公共静态变量。这意味着任何实例无论在何时更新它,修改将反映到所有其他实例中。为了避免这个问题,应当在构造函数中初始化复杂属性;然而,对于字符串、布尔值等简单属性则不需要这样做。 ”这里需要特别强调的是,上述示例中的localComplexArg是类的属性之一,但当一个“类”实例化以后,这个属性的取值是各个实例自己拥有一份,不会共享。globalComplexArg只是类似于Java类的公共静态变量,但从其调用方法来看,仍然必须通过实例化的对象来调用,所以dojo的本意并非要将其模拟为一个类的静态变量。它的存在,更象是一个不得已的例外。
- 在dojo类中真正要使用类的静态变量,推荐使用下面的语法:
myClass.staticVar = “xxx”;// A static property is declared. You don’t need to instantiate myClass to use this property, instead, use it directly, via the class name
- 上述globalComplexArg并非在实例间共享,而是通过原型链共享。即便不存在任何实例,该globalComplexArg的取值仍然存在。如果从未通过类myClass实例化过任何类,则在上例中,始终拥有{val : “foo”}这个对象;如果实例化后又改变过其取值,则始终保存改变后的值,即使是后来所有的实例都消亡,这个值也不会被析构。请看下面的例子:
dojo.declare("my.GrandPa", null, { print:function(msg){ console.log("this is grandpa:", msg); } }); dojo.declare("my.Parent", [my.GrandPa], { a : 0, b : [], constructor:function(){ this.c = 110; this.b.push('Parent()'); }, print:function(msg){ console.log("my.Parent.print:a=%s, b = [%s], c=%s ", this.a, this.b, this.c, msg); } }); dojo.declare("my.Son", [my.Parent], { a : 10, constructor:function(){ this.b.push('Son()'); }, print:function(msg){ console.log("in my.Son ", msg); this.inherited(arguments); } }); var g1 = new my.GrandPa(); g1.print("from g1"); var f1 = new my.Parent(); f1.print("from f1"); f1.a = 100; f1.b.push(125); f1 = null; var f2 = new my.Parent(); f2.print("from f2"); f2.a = 99; f2.print("from f2, after a changed"); var s1 = new my.Son(); s1.print("from s1"); g1.print("from g1 again");
运行上例,得到输出如下:
this is grandpa: from g1 my.Parent.print:a=0, b = [Parent()], c=110 from f1 my.Parent.print:a=0, b = [Parent(),125,Parent()], c=110 from f2 my.Parent.print:a=99, b = [Parent(),125,Parent()], c=110 from f2, after a changed in my.Son from s1 my.Parent.print:a=10, b = [Parent(),125,Parent(),Parent(),Son()], c=110 from s1 dojo.xd.js (第 3306 行) this is grandpa: from g1 again
上述代码首先定义了一个GrandPa类,没有任何属性,只有一个方法。然后定义了Parent类,它有简单属性a和一个数组b,并在构造函数中,将构造函数的名字保存至数组b中。代码第31行比较有趣。此时f1是Parent类的惟一一个实例,通过f1= null我们将其析构。此时f1.a不再可以访问,但数组b仍然保存了我们刚刚push进去的值125,并且在我们实例化对象s1时,可以再次访问这个值。
结论:从上例我们可以发现,
- 在子类中声明的属性并不会成为父类的属性(my.Parent),dojo在这一点上实现了传统语言的语法。
- 在类声明中(与constructor对应)声明的复杂类型变量,其值保存在原型声明中,从而实际上被所有的实例(从本类实例化的对象,以及从子类实例化的对象)共享(my.Parent, my.Son)。这一点上,dojo的实现与传统语言并不一致。
- 子类的方法和属性可以覆盖父类(my.Son, 第20行)。
- dojo class并没有私有成员。这句话有两层意思,其一,可以通过实例变量直接访问任何变量(比如f1.a),其二,所有在父类中声明/定义的属性(无论是在声明中初始化:my.Parent第7行,a: 0,还是在构造函数中初始化my.Parent第10行, this.c = 110),都将成为子类的一个属性(见输出部分第6行)。
- 基于上面的发现,应该始终通过myClass.staticVar = “xx”来为类引入静态变量;始终将所有的类属性声明放入到构造器constructor中。这些属性仍然可以被子类继承,但不会有被错误共享的担忧。
上例中没有揭示当子类覆盖了父类的属性和方法后,是否还可以访问父类的属性和方法?当然,子类覆盖父类的属性,实质是使用不同的值进行初始化,保存父类属性的初始化值并没有意义。而要访问父类的方法,可以使用this.inherited(arguments)。注意,这是个固定的写法,不可以有任何改变。即参数一定是arguments,而且一定要带上。此外,dojo还做了一个有意义的扩展,即调用链。传统语言只在构造函数上有调用链,即当子类实例化时,会优先调用父类的构造函数,然后才是子类的构造函数。但dojo通过声明”-chains-”属性,实现了对其它方法的链式调用,并且可以指明基类函数和派生类函数的先后调用次序。