构造函数 + 原型链继承 + 临摹面向对象模式的canvas动画框架
感謝謝帥shawn分享的canvas動畫框架,我再來分一次
//動畫框架
http://neekey.net/blog/2011/05/11/canvas-%E7%AE%80%E5%8D%95%E5%8A%A8%E7%94%BB%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF/
//使用JavaScript和Canvas開發游戲
http://www.cn-cuckoo.com/2011/08/10/game-development-with-javascript-and-the-canvas-element-2554.html
?
? ? ?之前學過的OC是純粹的面向對象語言,所以我一直很想知道JS里的難道沒有“類”的概念嗎?直到小新大神講到了構造函數和原型鏈的時候,我明白了——在JS中,沒有傳說中的“類”,而是利用“構造函數+原型鏈”來模擬其他語言中“類”和“繼承”的思路。
? ? ?初看第一個鏈接中的動畫框架,本以為有之前的OC基礎,可以看懂,結果完敗。無奈之下只能翻書,把JS紅皮書第六章《面向對象的程序設計》通讀了一遍,練看帶琢磨,2天時間,看完直接低血糖了。。。在此要再次重點感謝小新大神之前的講解,要不2個月也看不明白。
? ? ?本章的知識結構是遞進完善式的,所以建議想要去看書的同學,不要像我一樣,卡在某個你認為不太合理的概念上苦苦糾結,因為你總是會發現,后面一節的概念,是用來完善或代替前一節的概念的(TMD!)。下面的總結,也將采用遞進完善式,具體代碼請看書P144。
?
構建對象的方式:
? ? 工廠模式:很基礎的創建方式,問題是,不能識別對象的爹是誰,總是window。
? ? 構造函數模式:完善了工廠模式,可以識別對象是屬于哪類。使用new操作符,來確保this的指向,不用new也可以創建,但是this還是window。問題是,方法應當被共享,而不是被每個新實例重復的創建。如果提到外面去聲明方法,就失去了封裝性。
? ? 原型模式:每個構造函數被聲明時,會產生一個他的原型對象prototype,可以將需要共享的屬性和方法放在這個對象里,就不會被重復創建了。問題是,對于基本數據類型的屬性,不能改。對于引用類型的屬性,牽一發而動全身。
? ? 組合使用構造函數和原型:將需要改的,放在構造函數中,不需要改的即可以被共享的,放在原型中。
? ? 動態原型模式:基于上面的組合式,再次優化,將原型的初始化放在構造函數中,只不過要加個if判斷,當this.xxx != 'function'時,將this.xxx初始化到 prototype中。這樣可以防止原型中的xxx被修改后,牽一發而動全身。
? ?(寄生構造函數模式:這個方式一般不用!原理是把工廠模式封裝在構造函數模式里。沒看出有毛好處。)
? ?(穩妥構造函數模式:安全模式,不用this不用new,除了用顯式聲明的方法來訪問屬性之外,別無他法。)
層層遞進有木有!!
?
繼承:存在一個“類”(父類),我要構造一個新的類,讓他除了有自己的特有的屬性和方法之外,還具有老類的一切。其實就是你想擴展一個類的功能,但是又不想直接去改他的原始代碼,因為他可能很重要,別人還要用。那么就繼承一個子類吧!
原型鏈:真是個相當邏輯的東東,在撓墻的同時不得不佩服這種設計思路,誰想的啊這是。。。
書上的概念,簡而言之就是另一個指向另一個,而另一個又指向另一個。。。
我的理解就是:每個“類”(即構造函數)都會伴隨著一個原型對象,構造函數和原型中都可以放屬性和方法。只不過,構造函數中放的是未來很有可能被修改的屬性(私家廁所),而原型里放的都是共享的(公共廁所)。上面的大段中提到,創建對象的合理方法是組合式,也就是說,構造函數+原型,構成了“類”的一個整體。當我想繼承一個“新類”的時候,我就在新類的構造函數中聲明新加入的屬性,然后讓新類的原型直接等于一個老類的new實例。此時,新類的構造函數+原型構建完成了,也就成為了一個新的整體。這樣一來,新類不但具有自己的新屬性(在構造函數中),還包含老類的所有屬性和方法(在原型中)。當然,你依舊可以在新類的原型中,繼續加入那些適合被共享的新方法。
? ? 原型鏈的問題:牽一發而動全身。還有,不敢給超類的構造函數傳參,主要是因為,超類的構造函數現在是子類的原型,你傳參了之后怕是會影響子類的所有實例。
借用構造函數:又是完善。。。這里第三次感謝小新大神,因為他講了this,call和apply。利用call可以改變作用域來控制this指向,所以在子類的構造函數中,可以用superClass.call(this,x,y,z)來調用父類的構造函數,從而解決了子類new時傳參的問題。問題是,這樣繼承,完全沒用上prototype共享的特性,還是會出現重復聲明方法的現象。
? ? 組合繼承:繼續完善。。。用call的方式在子類的構造函數中繼承,再將子類的原型等于父類的new實例。這樣就是在借用構造函數的基礎上,用上了prototype的共享特性。問題是,子類的構造函數中繼承了一次父類的構造函數,在子類的原型中又繼承了一次,相當于父類的構造函數部分被繼承了倆次,也就是父類構造函數中包含的屬性在子類的構造函數中有一份,在子類的原型中還有一份,而調用屬性的時候,JS引擎會先找構造函數再找原型,所以原型中的那一份相當于被屏蔽掉了,浪費了。看到這的時候,我卡了,因為書上沒有提到重復調用的問題,是我自己瞎琢磨的,卡了很久我才決定還是先往下看吧。而且我還在想另一個問題,為啥不能讓子類的原型=父類的原型呢?這樣不是一樣繼承了嗎?后來明白了,如果這樣,你給子類的原型添加新方法的時候,父類的原型也會被添加新方法,那也就是跟直接改父類源代碼沒區別了,失去了繼承的意義。而等于new實例的話,你給子類原型添加新方法的時候,相當于是給這個new實例添加方法,并不會影響到父類的原型。
? ? 原型式繼承:可以這么理解,把一個老對象,包裝成一個新對象,把老對象作為新對象的原型。這是一種淺復制的過程。ECMAScript5標準自帶這種繼承的函數。
var anotherPerson = Object.create(person,自定義描述符); 這個繼承方法是個伏筆,是為了后面的終極方法做鋪墊的,過!
? ? 寄生式繼承:就是把原型式繼承的語句,和給新對象添加新方法語句,封裝在一個新的函數中,依舊是蛋疼的伏筆!
? ? 寄生組合式繼承(終極境界):
? ? 看到這的時候,確實低血糖了。讓我比較欣慰的是,這個終極方法,正是解決之前我卡住的那個問題的——“倆次調用問題”。回想一下,剛才我們說的,不希望擁有倆份父類的構造函數中的屬性,那么,我們就需要減少一次調用。而在子類中用call調用父類的構造函數這個是不能被刪掉的,因為他解決了傳參的問題。所以,只能拿subClass.prototype = new superClass()這個開刀了。其實我們就是想用這個語句,讓子類的原型=一個新的實例,這個實例并不需要是父類的new實例,而只要包含父類的原型就夠了,因為我們已經有一份父類的構造函數中的屬性了。童鞋,有沒有一個疑問——那讓子類的原型=父類的原型不是正好嗎?呵呵,這不就是我剛才卡的時候的第二個問題么。。。請翻上去再看一下解釋吧。換言之,如果我們能夠構造一個對象obj,讓他只擁有父類的原型,而不包含父類的構造函數,再讓subClass.prototype = obj; 這樣就解決問題了。這就用到了原型式繼承的淺復制原理,以及寄生式繼承的封裝方式增強對象(就是給對象添加新東西)。實現這個終極目標,我們需要創造一個函數,引用書上的函數名
function inheritPrototype(子類,父類){
var obj = object(父類.prototype) ;//這是剛才說到的ECMAScript5支持的原型式繼承方法,與object.create()類似。這一步就是復制一個父類的原型。
obj.constructor = 子類; //由于下一句代碼屬于重寫子類原型,將丟失原型的constructor的默認指向,所以先修復一下。
子類.prototype = obj; //把這個只擁有父類原型,而不包含父類構造函數的新對象,賦給子類的原型,實現繼承。
}
有了這個函數后,當我們繼承時,需要寫?subClass.prototype = new superClass()的時候,就調用 inheritPrototype(subClass,superClass)來代替。
看完了這些知識,我又去看那個游戲框架了,并且順著寫一遍,因為是憑感覺臨摹,所以其中有我自己的修改。寫完了,調試調了1個小時,各種大小寫錯誤,語法小錯誤,其中還有倆處都用到了閉包來解決this指向問題,問題原因是當一個函數作為參數傳入setInterval()時,this會變window。
setInterval((function(self){
return function(){
alert(self.xxx);
}
})(this),30);
總之,由于臨摹欲望而觸發的學習過程,涉及到的知識還是非常全面的,關于canvas的相關操作,在這篇文章里就不寫了。
Ps:JS代碼寫很多之后,找錯誤真是個麻煩事。由于我這編輯器不會報錯,我就得一行一行的用alert('1')去測,看到底是哪行之后不彈出了。
?
下面附上我的臨摹框架代碼:
<body style="margin:0;padding:0;">
<canvas id='canvas' width="800" height="800"></canvas>
<script>
//寄生組合式繼承方式(復制函數+寄生繼承函數)
function copy(o){
var Temp = function(){};
Temp.prototype = o;
return new Temp();
}
//構建獨立繼承父類原型的函數
function inheritPrototype(subclass,superclass){
var proto = copy(superclass.prototype);
proto.constructor = subclass;
subclass.prototype = proto;
}
//精靈(動畫元素的抽象類)
var Sprite = function(){
this.speed = {
x : 1,
y : 1
};
}
Sprite.prototype = {
move : function(){
setInterval((function(self){
return function(){
self.x += self.speed.x;
self.y += self.speed.y;
}
})(this),30);
},
draw : function(){}
};
//幀(控制畫布刷新的類)
var Fps = function(){
//所有需要重繪的精靈數組
this.sprites = [];
//重繪所需的定時器
this.render = function(){
setInterval((function(self){
//清空畫布
return function(){
self.ctx.clearRect(0, 0, 800, 800);
//重繪所有
for(var i in self.sprites){
self.sprites[i].draw();
}
}
})(this),30);//1000/30 = 33幀/秒
}
}
//動畫實體
var Circle = function(ctx,x,y,radius){
Sprite.call(this);
this.ctx = ctx;
this.x = x;
this.y = y;
this.radius = radius;
this.strokeStyle = 'rgba('+Math.floor(Math.random()*225)+','+Math.floor(Math.random()*225)+','+Math.floor(Math.random()*225)+','+Math.floor(Math.random()*5+5)/10+')';
this.fillStyle = 'rgba('+Math.floor(Math.random()*225)+','+Math.floor(Math.random()*225)+','+Math.floor(Math.random()*225)+','+Math.floor(Math.random()*5+5)/10+')';
this.lineWidth = Math.floor(Math.random()*10);
}
inheritPrototype(Circle,Sprite);
Circle.prototype.draw = function(){
this.ctx.beginPath();
this.ctx.lineWidth = this.lineWidth;
this.ctx.arc(this.x,this.y,this.radius,0,Math.PI*2,true);
this.ctx.strokeStyle = this.strokeStyle;
this.ctx.fillStyle = this.fillStyle;
this.ctx.stroke();
this.ctx.fill();
};
var ctx = document.getElementById('canvas').getContext('2d');
//創建畫布刷新類Fps
var fps = new Fps();
fps.ctx = ctx;
fps.render();//啟動
for (var i=0; i<50; i++){
var circle = new Circle(ctx,400,600,Math.random()*60+10);
circle.speed = {x:Math.random()*10-5,y:Math.random()*10-10};
circle.move();
fps.sprites.push(circle);
}
</script>
</body>
? ??
轉載于:https://www.cnblogs.com/GeekHacker/archive/2012/06/25/2560660.html
總結
以上是生活随笔為你收集整理的构造函数 + 原型链继承 + 临摹面向对象模式的canvas动画框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于兔子的网名66个
- 下一篇: 简单介绍下我使用了一年多还不知道的Sql