胖蔡叨叨叨
你听我说

【JavaScript】Pseudo-classical模式

在pseudo-classical模式中,对象是通过构造函数创建的,并且它的方法是直接被放到prototype中的。

pseudo-classical模式也应用在一些框架中,例如Google Closure Library, Native JavaScript objects等。

 
Pseudo-class 声明

“pseudo-class”这个词其实不是很准确,因为与PHP, Java,C++等语言相比,javaScript事实上没有类,但是pseudo-class模式在某种程度上是与它们相近的。

这篇文章假设你已经对原型继承的机制很了解了,如果不了解请查看原型继承这篇文章。

一个pseudo-class由构造函数和方法组成。例如下面的 Animal pseudo-class,有一个sit方法和两个属性。

function Animal(name){    
this.name = name;
}
Animal.prototype = {
canWalk: true,
sit: function(){
this.canWalk = false;
alert(this.name + ' sits down.');
} }
var animal = new Animal('Pet');
alert(animal.canWalk); //true animal.sit(); //pet sits down alert(animal.canWalk); //false

1.   当new Animal(name) 被调用时,生成新对象的__proto__指向Animal.prototype,请看下面图片的左边部分。

2. animal.sit方法改变了实例中的animal.canWalk,因此该animal对象不能走,而其他对象仍可以走。

总结pseudo-class:

(1)方法和默认的属性是在原型中的。

(2)在prototype中的方法使用的this, 指的是当前对象,因为this的值仅仅依赖调用的上下文,因此animal.sit()将this设为animal。

在以上的两点中隐藏着一些使用的危险,请看下面的例子。

你是仓鼠农场的老大,让你下面的程序员接受一个任务去创建Hamster构造函数和原型。

Hamster应该有一个food数组来存储和found方法来操作food数组,这两个方法都被添加到了原型中。

你的程序员给你带来了以下的解决方案。代码看起来还挺好的,当你创建两个Hamster时,本来饲料是属于其中一个的,但现在两个都有了。

那该怎么解决这种问题呢?

function Hamster(){}; 
Hamster.prototype = {     
food: [],     
found: function(something){         
this.food.push(something);     
} } 

//创建一个懒惰的hamster,和一个勤快的hamster,勤快的有饲料 
 speedy = new Hamster();
lazy = new Hamster(); 
speedy.found("apple");
speedy.found("orange"); 
alert(speedy.food.length);   
 
//2alert(lazy.food.length);    
//2

解决方法:

让我们仔细分析下,speedy.found(“apple”)执行时发生了什么事?

(1)解释器查找在speedy中查找found,但是speedy是一个空对象,因此会失败。

(2)解释器到speedy.__proto__(==Hamster.prototype)查找,然后找到了found方法后执行。

(3)在先前的执行阶段,由于调用speedy.__proto__,this是被设置成speedy对象。

(4)this.food在speedy中没有被找到,但是可以在speedy.__proto__中被找到。

(5)”apple”被加到了speedy.__proto__.food中。

在__proto__中的food被改变时,却在两个hamster对象中共享了,必须要解决这个问题。

解决这个问题

为了解决这个问题,我们要确定每个hamster都有他们自己的食物,可以通过在构造函数中赋值做到。

function Hamster(){     
this.food = [],};
 Hamster.prototype = {    
 found: function(something){        
 this.food.push(something);   
  } }  
speedy = new Hamster();
lazy = new Hamster();
 speedy.found("apple");
speedy.found("orange"); 
alert(speedy.food.length);    //2alert(lazy.food.length);    //0
继承

让我们创建一个新的从animal继承的一个Rabbit类。

function Rabbit(name){     
this.name = name; 
} 
Rabbit.prototype.jump = function(){     
this.canWalk = true;    
 alert(this.name + ' jumps!');
}  
var rabbit = new Rabbit('John');

正如你所看到的,结构与Animal很相似。

为了从Animal中继承,我们需要Rabbit.prototype.__proto__ == Animal.prototype. 这是一个自然的想法,因为如果一个方法不能在Rabbit.prototype中找到,我们可以在Animal.prototype中去寻找。具体看下图说明。

768998df1a9f841
1453496fb616ebf

为了实现这条链,我们需要创建一个从Animal.prototype继承来的Rabbit.prototype空对象,然后再向其中添加方法。

function Rabbit(name){    
 this.name = name; }
Rabbit.prototype = inherit(Animal.prototype); 
Rabbit.prototype.jump = function(){...}

在上面的代码中,inherit是一个通过给定的__proto__属性来创建空对象的方法。

function inherit(proto){     
function F(){};    
 F.prototype = proto;     
return new F; }

最后两个对象完整的代码如下所示。

function Animal(name){   
  this.name = name;} 
Animal.prototype = {    
 canWalk: true,     
sit: function(){        
 this.canWalk = false;        
alert(this.name + ' sits down.');     
} }  
 function Rabbit(name){    
 this.name = name; } 
//继承
Rabbit.prototype = inherit(Animal.prototype); //Rabbit方法Rabbit.prototype.jump = function(){       
 this.canWalk = true;     
alert(this.name + ' jumps!'); } 
var rabbit = new Rabbit('Sniffer'); 
rabbit.sit();    //Sniffer sitsrabbit.jump()    //sniffer jumps!    
function inherit(proto){  
   function F(){};    
 F.prototype = proto;   
  return new F; 
}

不要通过new Animal()来继承。这种方法应用得非常多,但是通过Rabbit.prototype = new Animal()是一种错误的继承方式。因为new Animal()这种方式并没有传递参数name,构造函数也许严格限制需要传递参数才行,所以这种情况下使用这种继承方式是不行的。
事实上,我们只想从Animal中继承,而不是去创建一个Animal实例吧?这才符合继承的实质。所以Rabbit.prototype = inherit(Animal.prototype)是更好点。

调用父类的构造函数

”superclass”构造函数不会被自动调用,我们可以通过apply来实现。

function Rabbit(name){  
   Animal.apply(this,arguments);
}

在当前对象的上下文执行Animal构造函数,从而达到改变name值的目的。

重写方法(多态)

为了重写父类方法,在子类的原型中代替它。

Rabbit.prototype.sit = function(){    
 alert(this.name + ' sits in a rabbity way.');
}

当调用rabbit.sit()时,搜索链为:rabbit -> Rabbit.prototype -> Animal.prototype,如果在Rabbit.prototype中找到,就会停止去Animal.prototype寻找。

当然我们也可以直接在对象中重写方法。

rabbit.sit = function(){       
 alert('a special sit of this very rabbit '+ this.name); 
}
重写后再调用父类的方法

当一个方法被重写时,我们还想调用之前老的方法,如果可能我们可以直接请求父类的prototype来实现。

Rabbit.prototype.sit = function() {    
alert('calling superclass sit:')    
Animal.prototype.sit.apply(this, arguments)
}

所有父类方法通过apply/call方法传递当前对象this来调用,Animal.prototype.sit()调用将 Animal.prototype作为this。

去除对父类的直接引用

在上面的例子当中,我们可以通过Animal.apply...或者Animal.prototype.sit.apply....直接调用父类。

正常情况下,我们不应该那样做。以后重构代码也许会改变父类的名字,或者在层级中引入新的类。

一般来说,程序语言允许调用父类方法通过使用特别的关键字,如parent.method()或者 super()。

javaScript没有这样的特性,但我们可以模拟它。

下面的extend方法可以实现继承,却不需要直接指向父类的引用。

function extend(child, parent) {     
child.prototype = inherit(parent.prototype);     
child.prototype.constructor = child;     
child.parent = parent.prototype; 
}

用法:

function Rabbit(name){     
Rabbit.parent.constructor.apply(this.arguments);    //调用父类构造器} extend(Rabbit,Animal); 
Rabbit.prototype.run = function(){     Rabbit.parent.run.apply(this,arguments); //调用父类方法 
}

事实上现在我们可以重命名Animal或者创建一个中间类GrassEatingAnimal,但是实际情况的改变只会涉及到Animal和extend(...)。

private或protected方法(封装)

可保护的方法和属性通过命名的习惯来支持。因此,一个以下划线开始的方法不应该被外面直接调用。

768998df1a9f841
9e5cc780fb8fe9f

private方法经常不支持。

赞(0) 打赏
转载请附上原文出处链接:胖蔡叨叨叨 » 【JavaScript】Pseudo-classical模式
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏