构造函数 + 原型链继承 + 临摹面向对象模式的canvas动画框架
感謝謝帥shawn分享的canvas動(dòng)畫框架,我再來分一次
//動(dòng)畫框架
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開發(fā)游戲
http://www.cn-cuckoo.com/2011/08/10/game-development-with-javascript-and-the-canvas-element-2554.html
?
? ? ?之前學(xué)過的OC是純粹的面向?qū)ο笳Z言,所以我一直很想知道JS里的難道沒有“類”的概念嗎?直到小新大神講到了構(gòu)造函數(shù)和原型鏈的時(shí)候,我明白了——在JS中,沒有傳說中的“類”,而是利用“構(gòu)造函數(shù)+原型鏈”來模擬其他語言中“類”和“繼承”的思路。
? ? ?初看第一個(gè)鏈接中的動(dòng)畫框架,本以為有之前的OC基礎(chǔ),可以看懂,結(jié)果完敗。無奈之下只能翻書,把JS紅皮書第六章《面向?qū)ο蟮某绦蛟O(shè)計(jì)》通讀了一遍,練看帶琢磨,2天時(shí)間,看完直接低血糖了。。。在此要再次重點(diǎn)感謝小新大神之前的講解,要不2個(gè)月也看不明白。
? ? ?本章的知識(shí)結(jié)構(gòu)是遞進(jìn)完善式的,所以建議想要去看書的同學(xué),不要像我一樣,卡在某個(gè)你認(rèn)為不太合理的概念上苦苦糾結(jié),因?yàn)槟憧偸菚?huì)發(fā)現(xiàn),后面一節(jié)的概念,是用來完善或代替前一節(jié)的概念的(TMD!)。下面的總結(jié),也將采用遞進(jìn)完善式,具體代碼請(qǐng)看書P144。
?
構(gòu)建對(duì)象的方式:
? ? 工廠模式:很基礎(chǔ)的創(chuàng)建方式,問題是,不能識(shí)別對(duì)象的爹是誰,總是window。
? ? 構(gòu)造函數(shù)模式:完善了工廠模式,可以識(shí)別對(duì)象是屬于哪類。使用new操作符,來確保this的指向,不用new也可以創(chuàng)建,但是this還是window。問題是,方法應(yīng)當(dāng)被共享,而不是被每個(gè)新實(shí)例重復(fù)的創(chuàng)建。如果提到外面去聲明方法,就失去了封裝性。
? ? 原型模式:每個(gè)構(gòu)造函數(shù)被聲明時(shí),會(huì)產(chǎn)生一個(gè)他的原型對(duì)象prototype,可以將需要共享的屬性和方法放在這個(gè)對(duì)象里,就不會(huì)被重復(fù)創(chuàng)建了。問題是,對(duì)于基本數(shù)據(jù)類型的屬性,不能改。對(duì)于引用類型的屬性,牽一發(fā)而動(dòng)全身。
? ? 組合使用構(gòu)造函數(shù)和原型:將需要改的,放在構(gòu)造函數(shù)中,不需要改的即可以被共享的,放在原型中。
? ? 動(dòng)態(tài)原型模式:基于上面的組合式,再次優(yōu)化,將原型的初始化放在構(gòu)造函數(shù)中,只不過要加個(gè)if判斷,當(dāng)this.xxx != 'function'時(shí),將this.xxx初始化到 prototype中。這樣可以防止原型中的xxx被修改后,牽一發(fā)而動(dòng)全身。
? ?(寄生構(gòu)造函數(shù)模式:這個(gè)方式一般不用!原理是把工廠模式封裝在構(gòu)造函數(shù)模式里。沒看出有毛好處。)
? ?(穩(wěn)妥構(gòu)造函數(shù)模式:安全模式,不用this不用new,除了用顯式聲明的方法來訪問屬性之外,別無他法。)
層層遞進(jìn)有木有!!
?
繼承:存在一個(gè)“類”(父類),我要構(gòu)造一個(gè)新的類,讓他除了有自己的特有的屬性和方法之外,還具有老類的一切。其實(shí)就是你想擴(kuò)展一個(gè)類的功能,但是又不想直接去改他的原始代碼,因?yàn)樗赡芎苤匾?#xff0c;別人還要用。那么就繼承一個(gè)子類吧!
原型鏈:真是個(gè)相當(dāng)邏輯的東東,在撓墻的同時(shí)不得不佩服這種設(shè)計(jì)思路,誰想的啊這是。。。
書上的概念,簡(jiǎn)而言之就是另一個(gè)指向另一個(gè),而另一個(gè)又指向另一個(gè)。。。
我的理解就是:每個(gè)“類”(即構(gòu)造函數(shù))都會(huì)伴隨著一個(gè)原型對(duì)象,構(gòu)造函數(shù)和原型中都可以放屬性和方法。只不過,構(gòu)造函數(shù)中放的是未來很有可能被修改的屬性(私家?guī)?#xff09;,而原型里放的都是共享的(公共廁所)。上面的大段中提到,創(chuàng)建對(duì)象的合理方法是組合式,也就是說,構(gòu)造函數(shù)+原型,構(gòu)成了“類”的一個(gè)整體。當(dāng)我想繼承一個(gè)“新類”的時(shí)候,我就在新類的構(gòu)造函數(shù)中聲明新加入的屬性,然后讓新類的原型直接等于一個(gè)老類的new實(shí)例。此時(shí),新類的構(gòu)造函數(shù)+原型構(gòu)建完成了,也就成為了一個(gè)新的整體。這樣一來,新類不但具有自己的新屬性(在構(gòu)造函數(shù)中),還包含老類的所有屬性和方法(在原型中)。當(dāng)然,你依舊可以在新類的原型中,繼續(xù)加入那些適合被共享的新方法。
? ? 原型鏈的問題:牽一發(fā)而動(dòng)全身。還有,不敢給超類的構(gòu)造函數(shù)傳參,主要是因?yàn)?#xff0c;超類的構(gòu)造函數(shù)現(xiàn)在是子類的原型,你傳參了之后怕是會(huì)影響子類的所有實(shí)例。
借用構(gòu)造函數(shù):又是完善。。。這里第三次感謝小新大神,因?yàn)樗v了this,call和apply。利用call可以改變作用域來控制this指向,所以在子類的構(gòu)造函數(shù)中,可以用superClass.call(this,x,y,z)來調(diào)用父類的構(gòu)造函數(shù),從而解決了子類new時(shí)傳參的問題。問題是,這樣繼承,完全沒用上prototype共享的特性,還是會(huì)出現(xiàn)重復(fù)聲明方法的現(xiàn)象。
? ? 組合繼承:繼續(xù)完善。。。用call的方式在子類的構(gòu)造函數(shù)中繼承,再將子類的原型等于父類的new實(shí)例。這樣就是在借用構(gòu)造函數(shù)的基礎(chǔ)上,用上了prototype的共享特性。問題是,子類的構(gòu)造函數(shù)中繼承了一次父類的構(gòu)造函數(shù),在子類的原型中又繼承了一次,相當(dāng)于父類的構(gòu)造函數(shù)部分被繼承了倆次,也就是父類構(gòu)造函數(shù)中包含的屬性在子類的構(gòu)造函數(shù)中有一份,在子類的原型中還有一份,而調(diào)用屬性的時(shí)候,JS引擎會(huì)先找構(gòu)造函數(shù)再找原型,所以原型中的那一份相當(dāng)于被屏蔽掉了,浪費(fèi)了。看到這的時(shí)候,我卡了,因?yàn)闀蠜]有提到重復(fù)調(diào)用的問題,是我自己瞎琢磨的,卡了很久我才決定還是先往下看吧。而且我還在想另一個(gè)問題,為啥不能讓子類的原型=父類的原型呢?這樣不是一樣繼承了嗎?后來明白了,如果這樣,你給子類的原型添加新方法的時(shí)候,父類的原型也會(huì)被添加新方法,那也就是跟直接改父類源代碼沒區(qū)別了,失去了繼承的意義。而等于new實(shí)例的話,你給子類原型添加新方法的時(shí)候,相當(dāng)于是給這個(gè)new實(shí)例添加方法,并不會(huì)影響到父類的原型。
? ? 原型式繼承:可以這么理解,把一個(gè)老對(duì)象,包裝成一個(gè)新對(duì)象,把老對(duì)象作為新對(duì)象的原型。這是一種淺復(fù)制的過程。ECMAScript5標(biāo)準(zhǔn)自帶這種繼承的函數(shù)。
var anotherPerson = Object.create(person,自定義描述符); 這個(gè)繼承方法是個(gè)伏筆,是為了后面的終極方法做鋪墊的,過!
? ? 寄生式繼承:就是把原型式繼承的語句,和給新對(duì)象添加新方法語句,封裝在一個(gè)新的函數(shù)中,依舊是蛋疼的伏筆!
? ? 寄生組合式繼承(終極境界):
? ? 看到這的時(shí)候,確實(shí)低血糖了。讓我比較欣慰的是,這個(gè)終極方法,正是解決之前我卡住的那個(gè)問題的——“倆次調(diào)用問題”。回想一下,剛才我們說的,不希望擁有倆份父類的構(gòu)造函數(shù)中的屬性,那么,我們就需要減少一次調(diào)用。而在子類中用call調(diào)用父類的構(gòu)造函數(shù)這個(gè)是不能被刪掉的,因?yàn)樗鉀Q了傳參的問題。所以,只能拿subClass.prototype = new superClass()這個(gè)開刀了。其實(shí)我們就是想用這個(gè)語句,讓子類的原型=一個(gè)新的實(shí)例,這個(gè)實(shí)例并不需要是父類的new實(shí)例,而只要包含父類的原型就夠了,因?yàn)槲覀円呀?jīng)有一份父類的構(gòu)造函數(shù)中的屬性了。童鞋,有沒有一個(gè)疑問——那讓子類的原型=父類的原型不是正好嗎?呵呵,這不就是我剛才卡的時(shí)候的第二個(gè)問題么。。。請(qǐng)翻上去再看一下解釋吧。換言之,如果我們能夠構(gòu)造一個(gè)對(duì)象obj,讓他只擁有父類的原型,而不包含父類的構(gòu)造函數(shù),再讓subClass.prototype = obj; 這樣就解決問題了。這就用到了原型式繼承的淺復(fù)制原理,以及寄生式繼承的封裝方式增強(qiáng)對(duì)象(就是給對(duì)象添加新東西)。實(shí)現(xiàn)這個(gè)終極目標(biāo),我們需要?jiǎng)?chuàng)造一個(gè)函數(shù),引用書上的函數(shù)名
function inheritPrototype(子類,父類){
var obj = object(父類.prototype) ;//這是剛才說到的ECMAScript5支持的原型式繼承方法,與object.create()類似。這一步就是復(fù)制一個(gè)父類的原型。
obj.constructor = 子類; //由于下一句代碼屬于重寫子類原型,將丟失原型的constructor的默認(rèn)指向,所以先修復(fù)一下。
子類.prototype = obj; //把這個(gè)只擁有父類原型,而不包含父類構(gòu)造函數(shù)的新對(duì)象,賦給子類的原型,實(shí)現(xiàn)繼承。
}
有了這個(gè)函數(shù)后,當(dāng)我們繼承時(shí),需要寫?subClass.prototype = new superClass()的時(shí)候,就調(diào)用 inheritPrototype(subClass,superClass)來代替。
看完了這些知識(shí),我又去看那個(gè)游戲框架了,并且順著寫一遍,因?yàn)槭菓{感覺臨摹,所以其中有我自己的修改。寫完了,調(diào)試調(diào)了1個(gè)小時(shí),各種大小寫錯(cuò)誤,語法小錯(cuò)誤,其中還有倆處都用到了閉包來解決this指向問題,問題原因是當(dāng)一個(gè)函數(shù)作為參數(shù)傳入setInterval()時(shí),this會(huì)變window。
setInterval((function(self){
return function(){
alert(self.xxx);
}
})(this),30);
總之,由于臨摹欲望而觸發(fā)的學(xué)習(xí)過程,涉及到的知識(shí)還是非常全面的,關(guān)于canvas的相關(guān)操作,在這篇文章里就不寫了。
Ps:JS代碼寫很多之后,找錯(cuò)誤真是個(gè)麻煩事。由于我這編輯器不會(huì)報(bào)錯(cuò),我就得一行一行的用alert('1')去測(cè),看到底是哪行之后不彈出了。
?
下面附上我的臨摹框架代碼:
<body style="margin:0;padding:0;">
<canvas id='canvas' width="800" height="800"></canvas>
<script>
//寄生組合式繼承方式(復(fù)制函數(shù)+寄生繼承函數(shù))
function copy(o){
var Temp = function(){};
Temp.prototype = o;
return new Temp();
}
//構(gòu)建獨(dú)立繼承父類原型的函數(shù)
function inheritPrototype(subclass,superclass){
var proto = copy(superclass.prototype);
proto.constructor = subclass;
subclass.prototype = proto;
}
//精靈(動(dòng)畫元素的抽象類)
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(){
//所有需要重繪的精靈數(shù)組
this.sprites = [];
//重繪所需的定時(shí)器
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幀/秒
}
}
//動(dòng)畫實(shí)體
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');
//創(chuàng)建畫布刷新類Fps
var fps = new Fps();
fps.ctx = ctx;
fps.render();//啟動(dòng)
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>
? ??
轉(zhuǎn)載于:https://www.cnblogs.com/GeekHacker/archive/2012/06/25/2560660.html
總結(jié)
以上是生活随笔為你收集整理的构造函数 + 原型链继承 + 临摹面向对象模式的canvas动画框架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于兔子的网名66个
- 下一篇: 简单介绍下我使用了一年多还不知道的Sql