google closure 笔记-类和继承相关

closure中使用的类是构造函数加原型的组合继承方式,在构造函数中定义属性,在原型上定义方法。因为属性是私有的,方法是公用的。

1,非引用类型实例属性的默认值:

一般来讲,当在构造函数中没有给某一属性赋值的时候,会使用默认值。此处有一个技巧:

给定一个域实例属性同名的类属性并取默认值,如果在构造函数中没有给实例属性赋值,则根本不用创建此实例属性。默认使用共享的类属性。(注意是非引用类型才可以)。

看如下例子:

House = function(numberOfBathrooms) { 
if (goog.isDef(numberOfBathrooms)) {
this.numberOfBathrooms_ = numberOfBathrooms; }
House.prototype.numberOfBathrooms_ = 1;


注意其中的numberOfBathrooms_,这是一个实例属性而不是一个类属性,但是如果在构造函数中没有给定numberOfBathromms的值,则不会创建实例属性,只会有一个共享的类属性。这么做可以节省内存,减少构造函数的执内存读写。

举例说明这样做是不存在问题的:

var h1 = new House();

var h2 = new House(1);

var h3 = new House();

此时h1.numberOfBathroom_ == h2.numberOfBathroom_ == h3.numberOfBathroom_ 1。这是显然的。但是h1.numberOfBathroom_和h3.numberOfBathroom_其实是一个原型链上的共享属性,而h2.numberOfBathroom_是真正的实例属性。

此时就有疑问了:h1和h3共享一个变量,只读是没有问题的,但是写操作岂不是会互相影响?

答案是不会,没有任何影响。

h1.numberOfBathroom_ = 2。此时再读取 h3.numberOfBathroom_仍然是1,因为h1.numberOfBathroom_ = 2实际上是创建了一个h1的实例属性并赋值为2,此实例属性隐藏了同名的原型链上的类属性,所以并没有改变h1.__proto__.numberOfBathroom_的值,当然不会影响到h3的值。


为什么引用类型不可以?

举例说明:

House.prototype.room = ["a"];

此时h1.room = h2.room =["a"];

然后执行 h2.room.push("b"),则 h1.room == h2.room == ["a", "b"];

因为是引用类型,所以相互影响了。

但是如果这样:h2.room = ["a", "b"]则不会影响到h1,因为已经创建了一个h2的实例属性room,原型链上的room没有变。


2,静态方法的实现

静态方法就是在类中定义的方法,可以通过类名直接访问。

如下例:

/**
* This would be referred to as an instance method of House because it is * defined on House's prototype.
* @param {example.House} house2
* @return {number}
*/
example.House.prototype.calculateDistance = function(house2) {
return goog.math.Coordinate.distance(this.getLatLng(), house2.getLatLng()); };
/**
* This would be referred to as a static method of House because it is * defined on House's constructor function.
* @param {example.House} house1
* @param {example.House} house2
* @return {number}
*/
example.House.calculateDistance = function(house1, house2) {
return goog.math.Coordinate.distance(house1.getLatLng(), house2.getLatLng()); };


第一种是一般用的定义类的函数的方法,所谓“实例方法”,因为可以在每一个实例中访问。

第二种是静态方法,因为其定义在类上,这个方法无法在实例中直接访问,但是可以通过instantance.constructor.calculateDistance()来访问。

和java的区别:

在java中,静态方法既可以在类总共访问,也可以在属性中直接访问。但是在js中不可以,因为静态方法存在于构造函数上而不是原型上,所以实例中无法通过原型链来访问此属性,只能通过constructor取得构造函数后再访问。


3,继承机制的实现

goog 继承机制的实现-构造函数+原型的组合继承方式

function(childCtor, parentCtor){
1:         function tempCtor() {};
2:        tempCtor.prototype = parentCtor.prototype;
3:         childCtor.superClass_ = parentCtor.prototype;        
4:         childCtor.prototype = new tempCtor();
5:         childCtor.prototype.constructor = childCtor;
 } 



1,2,4:新建一个tempCtor 使其prototype引用父类的prototype,然后将子类的prototype指向tempCtor,使子类继承父类的方法。这是引用继承,多个子类的实例__proto__引用的是同一个tempCtor。

这个和直接new一个parentCtor的区别就是 它没有父类中的属性而只有方法。

为什么不直接new一个parentCtor?因为子类的__proto__是共享的,直接new出来的,其属性就会被所有子类共享,这是不合理的。

3:将子类添加一个superClass_属性指向父类。

5:将子类的constructor指向自己,否则其指向的是父类的constructor,这样就无法用construtor来判断构造函数。

可以通过在子类构造函数中调用父类构造函数call方法来继承父类的属性。父类属性被直接添加到子类的属性上,而不是添加到子类的__proto__上,因为__proto__是一个被所有子类实例共享的引用。


4,goog.base详解

goog.base用以在子类中方便的访问父类中的方法。因为其通过superClass_属性可以不断向上寻找父类。

有两种用法,一个是在构造函数中调用父类构造函数,一个是在方法中调用父类方法(这个需要指定方法名,但是却必须和当前方法名相同,原因在下面会讲到)。

如果不用goog.base来调用父类方法,也可以用ClassName.call(this, args)来调用,但是对于多层次的继承,有时候不太清楚此方法究竟是在哪个父类中定义的。

因为用superClass_来查找父类,所以这个方法必须和goog.inherits一起用,否则没有作用。


源码解析:

1455 goog.base = function(me, opt_methodName, var_args) {

1456   var caller = arguments.callee.caller;

1457   if (caller.superClass_) {

1458     // This is a constructor. Call the superclass constructor.

1459     return caller.superClass_.constructor.apply(

1460         me, Array.prototype.slice.call(arguments, 1));

1461   }

1462

1463   var args = Array.prototype.slice.call(arguments, 2);

1464   var foundCaller = false;

1465   for (var ctor = me.constructor;

1466        ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {

1467     if (ctor.prototype[opt_methodName] === caller) {

1468       foundCaller = true;

1469     } else if (foundCaller) {

1470       return ctor.prototype[opt_methodName].apply(me, args);

1471     }

1472   }

1473

1474   // If we did not find the caller in the prototype chain,

1475   // then one of two things happened:

1476   // 1) The caller is an instance method.

1477   // 2) This method was not called by the right caller.

1478   if (me[opt_methodName] === caller) {

1479     return me.constructor.prototype[opt_methodName].apply(me, args);

1480   } else {

1481     throw Error(

1482         'goog.base called from a method of one name ' +

1483         'to a method of a different name');

1484   }

1485 };


分析:

其中的caller就是调用goog.base的那个函数。

首先,如果claller.superClass_存在,说明caller是一个构造函数,其目的是调用父类构造函数,直接调用superClass_即可。

然后,从me.constructor开始向上一级父类查找,如果cons[methodName] === caller(methodName必须和caller的名字是一样的,因为继承的原则要求子类中重载父类方法就应该是同名的,如果想调用父类中非同名的方法,直接方法名调用就行了),找到了caller,再向上一级没有找到,说明存在于cons的原型链上,因此可以直接调用。(注:这里可能会出现一种错误见后面注释)。

如果还没找到,则可能是在实例的方法上调用此实例的原型链上的方法(例子如下),因此检测实例上caller是否在实例上,如果是,则可以调用其原型链上的方法。(因为继承来的方法只可能在原型链上,同理也可能会出现错误)。

 13 Human = function(){

 14 }

 15 Human.prototype.say = function(m){

 16     console.log("say:" + m);

 17 }

 18

 19 Boy = function(){

 20     Human.call(this);

 21 }

 22 goog.inherits(Boy, Human);

 23 Boy.prototype.sa = function(m){

 24     console.log(1);

 25 }

 26

 27 var b1 = new Boy();

 28 //var b2 = new Boy();

 29 b1.sa = function(m){

 30     goog.base(this, 'sa', m)

 31 }

 32 b1.sa("aa");

上例中,b1.sa调用的是b1.__proto__.sa。

最后,还没有找到,说明出错了,要么是在a方法中调用b方法(这样的话cons[methodName]===caller永远不成立),要么是类的设计就有问题。


注释:可能出现一种错误

因为在当前cons中根据methodName找到了caller,上一级又找不到,就认为methodName必定存在于cons的原型链上,

没有检查是否真的存在就直接调用,可能会导致错误。如下例:

 16 Human = function(){

 17     Animal.call(this);

 18 }

 19 goog.inherits(Human, Animal);

 20 Human.prototype.say = function(m){

 21     console.log("say:" + m);

 22 }

 23

 24 Boy = function(){

 25     Human.call(this);

 26 }

 27 goog.inherits(Boy, Human);

 28 Boy.prototype.sa = function(m){

 29     goog.base(this, 'sa', m);

 30 }

 31

 32 var b1 = new Boy();

 33 b1.sa("aa");


sa函数中调用goog.base。goog.base发现在Boy的原型链上确实能找到sa,而且在Human中确实没有找到sa,因此直接调用Human的sa函数,实际上human中压根就没定义sa,因此出错。当然,一般情况下是不会出现此错误的,出现这种错误也没有意义,因为原因是没有按照类的继承规范来做。


5,Disposable:可销毁的对象

Disposable定义了一个可销毁对象的基本方法,如果一个对象中包含其他引用:dom,com等,那么就应该继承此类,或者实现IDisposable接口。

实现disposeInternal的步骤:


1. Call the superclass’s disposeInternal method.

2. Dispose of all Disposable objects owned by the class. 

3. Remove listeners added by the class.

4. Remove references to DOM nodes.

5. Remove references to COM objects.

参见一个例子:

example.AutoSave.prototype.disposeInternal = function() {

// (1) Call the superclass's disposeInternal() method. 

example.AutoSave.superClass_.disposeInternal.call(this);


// (2) Dispose of all Disposable objects owned by 

this class. goog.dispose(this.failureDialog);


// (3) Remove listeners added by this class. 

this.container.removeEventListener('mouseover', this.eventListener, false);


// (4) Remove references to COM objects. 

this.xhr = null;

// (5) Remove references to DOM nodes, which are COM objects in IE. delete this.container;
this.label = null;

};

源码解析:

在idisposable.js中定义了IDisposable接口,其只有两个方法:dispose和isDisposed。

在disposable.js中定义了Disposable类。

Disposable类分为两部分,一部分是Disposable实例的属性,一部分是Disposable类的静态属性和方法(暂时把它当做一个类,其实还是一个实例)。

1,Disposable类的静态属性: 管理了一个instances_对象(当做一个map),其中保存了所有的未被释放的disposable实例的引用。并提供了三个方法来管理这个map:

在构造函数Disposable中会自动把new出来的实例添加到instantces_数组中。

getUndisposedObject:取得未被销毁的所有实例,此函数简单的把instances_转换成了array类型并返回。

clearUndisposedObject:清空instances_。

但是,只有当Disposable.ENABLE_MONITORING == true,时,才instances_才可用,否则,instances_始终为空,因为在构造函数中会检查ENABLE_MONITORING,只有true时才将new出来的对象添加到instances_中。默认ENABLE_MONITORING是false的。这个只是给测试用的,因为会消耗性能。

2,Disposable实例:通过dependentDisposables_数组来保存当前实例中包含的所有引用实例。并提供了如下方法来管理:

isDisposed:当前实例是否已经被销毁。

dispose:销毁当前实例中包含的所有引用类型。通过调用disposeInternal来实现,而disposeInternal需要在子类中重写。其默认实现会销毁所有保存在dependentDisposables_中的对象,通过调用其中每一个对象的dispose方法来实现。

registerDispose:向dependentDisposables_中添加一个对象。


6,抽象方法

子类中必须重载的方法,否则就会报错。只需要将此方法赋值为goog.abstractMethod即可。包含抽象方法的类就是“抽象类”,(注意,在原生的js中并没有抽象方法和抽象类的概念)。

看下其实现:

goog.abstractMethod = function(){

     throw Error("unimplemented abstract method");

}

所以没有在子类中重载就会报错。

7,多重继承

js本身就没有真正的类和继承,更别谈多重继承了。closure本身不支持多重继承,现在也没有方法可以完美的实现多重继承。原因是:goog.inherits实际上是把当前类的prototype指向了一个新的类(这个类的prototype指向被继承的类的prototype),因此第二次使用goog.inherits会覆盖掉第一次继承的原型方法,所以不支持多重继承

但是可以通过goog.mixin方法把其他类的prototype上的属性全部copy过来,可以达到多重继承的一部分目的。

如下例:

goog.provide('example.Phone'); goog.provide('example.Mp3Player'); goog.provide('example.AndroidPhone');
/** @constructor */
example.Phone = function(phoneNumber) { /* ... */ };
example.Phone.prototype.makePhoneCall = function(phoneNumber) { /* ... */ };
/** @constructor */
example.Mp3Player = function(storageSize) { /* ... */ };
example.Mp3Player.prototype.playSong = function(fileName) { var mp3 = this.loadMp3FromFile(fileName);
mp3.play();
return mp3;
};
/**
* @constructor
* @extends {example.Phone} */
example.AndroidPhone = function(phoneNumber, storageSize) { example.Phone.call(this, phoneNumber);
example.Mp3Player.call(this, storageSize); };
goog.inherits(example.AndroidPhone, example.Phone); 
goog.mixin(example.AndroidPhone.prototype, example.Mp3Player.prototype);
var p = new example.AndroidPhone();
 console.log(p instanceof example.AndroidPhone);//(1)
 console.log(p instanceof example.Mp3Player);//(2)
 console.log(p instanceof example.Phone);//(3)


这种方式本质上是单继承,只是调用了其他类的构造函数并copy了prototype上的方法。只有Phone是真正的被继承的父类,而Mp3Player只是被调用了构造函数和copy了prototype上的方法。可以把这个多重继承的父类分为两部分:

一种是真正的父类,即Phone,另一种是多重继承的父类,即用goog.mixin加入的Mp3Player。其中第一类没有任何问题,但是第二类其实从语法上就不是父类,因此有两个主要缺点:

1,不能使用instanceof来测试第二类,即上例中的(2)会返回false。

2,无法用superClass_或者goog.base来访问父类中的方法,只能记住每个方法到底是在那个类中,然后用类名来调用,这样当继承树复杂时是很麻烦的。

第一个缺点由于js语言的问题,无法解决。但是第二个是可以解决的。一种解决方法如下:

example.mp3.playSongFromFile = function(fileName) {
// This implies that both Mp3Player and AndroidPhone have methods named // getPlayer() that return an object with a playFile() method. this.getPlayer().playFile(fileName);

};

example.Mp3Player.prototype.playSong = example.mp3.playSongFromFile; example.AndroidPhone.prototype.playSong = example.mp3.playSongFromFile;

即创建一个实例方法,然后把所有的父类上的同名方法都指向此方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值