當初始化SpaceShip原型時,我們尚未創建任何能作為第一個參數來傳遞的場景。並且SpaceShip的原型加入到場景的註冊表中,而這絕不是我們想做的。這是一種使用子類時常用的方法。應當僅僅在子類構造函數中調用父類構造函數,而不是當創建子類原型時調用它。 一旦創建了SpaceShip的原型對象,我們... ...
示例
場景類
場景圖(scene)是在可視化的過程中(如游戲或圖形模擬場景)描述一個場景的對象集合。一個簡單的場景包含了在該場景中的所有對象(稱角色),以及所有角色的預載入圖像數據集,還包含一個底層圖形顯示的引用(通常被稱為context)。
function Scene(context,width,height,images){
this.context=context;
this.width=width;
this.height=height;
this.images=images;
this.actors=[];
}
Scene.prototype.register=function(actor){
this.actors.push(actor);
};
Scene.prototype.unregister=function(actor){
var i=this.actors.indexOf(actor);
if(i>=0){
this.actors.splice(i,1);
}
};
Scene.prototype.draw=function(){
this.context.clearRect(0,0,this.width,this.height);
for(var a=this.actors,i=0,n=a.length;i < n;i++){
a[i].draw();
}
};
角色基類
場景中所有的角色都是繼承自基類Actor。基類Actor抽象出了一些通用的方法。每個角色都存儲了其自身場景的引用以及坐標位置,然後將自身添加到場景的角色註冊表中。
function Actor(scene,x,y){
this.scece=scene;
this.x=0;
this.y=0;
scene.register(this);
}
為了能改變角色在場景中的位置,我們提供了一個moveTo方法。該方法改變角色坐標,然後重繪場景。
Actor.prototype.moveTo=function(x,y){
this.x=x;
this.y=y;
this.scene.draw();
};
當一個角色離開了場景,我們從場景圖的註冊表中刪除它,並重新繪製場景。
Actor.prototype.exit=function(){
this.scene.unregister(this);
this.scene.draw();
};
想要繪製一個角色,我們需要查找它在場景圖圖像表中的圖像。我們假設每個actor有一個type欄位,可以用來查找它在圖像表中的圖像。一旦我們有了這個圖像數據,就可以使用底層圖形庫將其繪製到圖形上下文中。
Actor.prototype.draw=function(){
var image=this.scene.images[this.type];
this.scene.context.drawImage(image,this.x,this.y);
};
同樣,我們可以通過角色的圖像數據確定尺寸。
Actor.prototype.width=function(){
return this.scene.images[this.type].width;
};
Actor.prototype.height=function(){
return this.scene.images[this.type].height;
}
角色子類
我們將角色的特定類型實現為Actor的子類。例如,在街機游戲中太空飛船就會有一個擴展自Actor的SpaceShip類。像所有的類一樣,SpaceShip被定義為一個構造函數。但是為了確保SpaceShip的實例能作為角色被正確地初始化,其構造函數必須顯式地調用Actor的構造函數。通過將接收者綁定到該新對象來調用Actor可以達到此目的.
function SpaceShip(scene,x,y){
Actor.call(this,scene,x,y);
this.points=0;
}
首先調用Actor的構造函數能確保通過Actor創建的所有實例屬性都被添加到了新對象中。然後,SpaceShip可以定義自身的實例屬性,如飛船當前的積分數。為了使SpaceShip成為Actor的正確的子類,其原型必須繼承自Actor.prototype。做這種擴展的最好方式是使用ES5的Object.create方法。
SpaceShip.prototype=Object.create(Actor.prototype);
構造函數創建子類原型問題
如果我們試圖使用Actor的構造函數來創建SpaceShip的原型對象,會有幾個問題。
第一個問題是沒有任何合理的參數傳遞給Actor。
SpaceShip.prototype=new Actor();
當初始化SpaceShip原型時,我們尚未創建任何能作為第一個參數來傳遞的場景。並且SpaceShip的原型加入到場景的註冊表中,而這絕不是我們想做的。這是一種使用子類時常用的方法。應當僅僅在子類構造函數中調用父類構造函數,而不是當創建子類原型時調用它。
一旦創建了SpaceShip的原型對象,我們就可以向其添加所有的可被實例共用的屬性,包含一個用於在場景的圖像數據表中檢索的type名,以及一些太空飛船的特定方法。
SpaceShip.protoype.type='spaceShip';
SpaceShip.prototype.scorePoint=function(){
this.points++;
};
SpaceShip.prototype.left=function(){
this.moveTo(Math.max(this.x-10,0),this.y);
};
SpaceShip.prototype.right=function(){
var maxWidth=this.scene.width-this.width();
this.moveTo(Math.min(this.x+10,maxWidth),this.y);
}
圖示角色及子類關係
紅框為基類與子類關係,構造函數也是Function類的一個實例,Function類的原型對象也繼承自是Object的原型對象。
註意:scene,x,y屬性只被定義在實例對象中,而不是被定義在原型對象中,儘管SpaceShip是被Actor構造函數創建的。
提示
-
在子類構造函數中顯式地傳入this作為顯式的接收者調用父類構造函數
-
使用Object.create函數來構造子類的原型對象以避免調用父類的構造函數