這章教會你如何經過內存中儲存的信息創造一個遊戲世界。介紹了基本類型和變量而且這些變量是如何儲存和改變信息的。接下來,你會看到如何用對象儲存更復雜的信息,裏面包含成員變量和方法。javascript
先前的章節幾回討論到了內存。你已經看到了如何執行一個簡單的指令譬如canvasContext.fillStyle = 「blue」;爲畫布設置一個顏色。在這章例子裏,你使用內存儲存臨時信息,是爲了記住一些簡單運算的結果。在這個例子裏,你使用通過的時間來改變背景色。java
類型,或者數據類型,表明不一樣類型的信息。先前的例子使用了不一樣類型的信息,當作參數傳遞給了函數。例如,fillRect函數,須要4個整數,start函數須要一個文本標識引用自canvas,update和draw函數不須要任何信息。瀏覽器/解釋器能夠區分這些不一樣類型的信息而且不少狀況下能夠把一個類型的信息轉換成另外一個類型的信息。好比,在JavaScript裏,你可使用單引號或者雙引號來標識文本。以下的兩句指令是同樣的:程序員
canvas = document.getElementById("myCanvas"); canvas = document.getElementById('myCanvas');
瀏覽器能夠自動的在不一樣類型的信息間進行轉換。好比,下面的語句不會致使語法錯誤:編程
canvas = document.getElementById(12);
這個當作參數傳遞的整數會被轉換成文本。在這種狀況下,固然,這裏沒有ID叫作12的canvas,因此這個程序是不正確的。若是你像下面同樣替換canvas的ID,程序就會正常運行:canvas
<canvas id="12" width="800" height="480"></canvas>
瀏覽器自動的在文本和數字之間進行轉換。瀏覽器
現代編程語言都比JavaScript嚴格的多。好比Java和C#,不一樣類型之間轉換是有限制的。大多時候,你必須明確的告訴編譯器這裏有一個類型轉換。markdown
爲何對類型轉換須要這麼嚴格的限制呢?首先,一個函數或方法中明確的類型參數可讓程序員簡單的使用函數。好比下面這樣:app
function playAudio (audioFileId)
簡單的看一下,你並不肯定audioFileId是一個數字仍是文本。在C#裏,這個函數會相似這樣:編程語言
void playAudio(string audioFileId)
能夠看出,不只有參數名,並且還附有參數類型。類型是string,也就是字符串。此外,在函數名前面還有void這個單詞,表示這個函數沒有返回值。函數
在JavaScript裏面儲存信息和使用信息都是很簡單的。你須要作的只是爲你要引用的信息提供一個名字。這個名字就叫作變量。當你想在程序裏使用變量時,在使用以前聲明它是一個好習慣。下面是如何聲明一個變量:
var red;
在這裏,red就是變量名。你能夠在你隨後的程序用它儲存信息。
當你聲明瞭一個變量,你不須要提供你須要儲存信息的類型。變量僅僅是內存裏有一個名字。有些編程語言要求在變量聲明時肯定變量的類型。好比在Java或者C++裏。然而,不少的腳本語言(包括JavaScript)容許你聲明一個變量而不須要指定其類型。當編程語言聲明變量時不要指定類型,這個編程語言就有鬆散類型。在JavaScript裏,你能夠聲明不止一個變量,好比:
var red, green, fridge, grandMa, applePie;
這裏你聲明瞭5個不一樣的變量。當你聲明這些變量時,你沒有賦值。在這種狀況下,變量是未定義的。你能夠用賦值指令爲變量賦值。好比,給red賦值:
red = 3;
賦值指令包含下面這些:
你能夠發現賦值指令中間有個等號。然而,在JavaScript裏,最好把」=「當作」變爲「而不是」等於「。畢竟,變量尚未等於右邊的值,它在指令執行以後才變成那個值。下面的語法圖描述了賦值指令。(圖3-1)
(省略圖3-1)
如今知道如何聲明一個變量和給變量賦值了。若是你在聲明變量時就知道給變量賦什麼值,你能夠聲明的同時進行賦值。好比:
var red = 3;
當執行這句後,內存中就會有3這個值,如圖3-2所示。
(省略圖3-2)
這裏有更多關於數值變量的聲明和賦值:
var age = 16; var numberOfBananas; numberOfBananas = 2; var a, b; a = 4; var c = 4, d = 15, e = -3; c = d; numberOfBananas = age + 12;
在第四行,你能夠發現一次能夠聲明多個變量。也能夠像第六行同樣有多個變量進行了聲明賦值。在賦值的右邊,你能夠放置其它變量或者數學表達式,正如最後兩行所示。指令 c = d;讓儲存在d中的值也儲存在了c裏面。由於d是15,因此執行這條指令後,c也是15.最後一條指令讓age加上12,把結果儲存在numberOfBananas裏面。歸納來講,執行這些指令後,這些內存值看起來就像圖3-3所示:
(省略圖3-3)
除了在使用變量以前進行聲明,javascript也能夠直接使用變量而不用聲明它。好比下面這樣:
var a = 3; var b; b = 4; x = a + b;
如上所示,前兩行指令經過var關鍵字聲明瞭變量a和b。變量x歷來沒有聲明,可是它用來儲存a和b的和。javascript容許這樣。這很糟糕,並且這也是它爲何糟糕的緣由。問題在於沒有聲明的變量javascript解釋器會自動的把它進行聲明,而你卻徹底不知道。若是你在其它地方使用一樣名字的變量,你的程序可能發生你意想不到的結果。另外,若是你使用了不少不一樣的變量,你最好同時記錄這些變量。可是最大的問題是下面這個:
var myDaughtersAge = 12; var myAge = 36; var ourAgeDifference = myAge - mydaughtersAge;
當編寫這些指令時,你但願的是ourAgeDifference的值是24.可是,這個值是未定義的。這是由於第三行這裏有個打字錯誤。變量的名字不是mydaughtersAge,而是myDaughtersAge。這種狀況下,瀏覽器/解釋器是悄悄的聲明一個叫作mydaughtersAge的變量而不是拋出一個錯誤中止運行腳本。由於變量沒有定義,全部與此變量有關的計算都是未定義的。所以,變量ourAgeDifference也是未定義的。
這個問題真的很棘手。幸運的是,新的EMCAScript5標準有個叫作嚴格模式的東西。當腳本用嚴格模式來解釋時,它不容許在沒有聲明變量以前使用變量。若是你想你的腳本在嚴格模式下執行,你須要作的僅僅只是在腳本開始加上下面這行,例如:
"use strict"; var myDaughtersAge = 12; var myAge = 36; var ourAgeDifference = myAge - mydaughtersAge;
「use strict」告訴瞭解釋器在嚴格模式下解釋腳本。若是你如今執行這段代碼,瀏覽器會中止運行且拋出錯誤告知有變量沒有進行聲明。
除了能檢查變量在使用以前是否聲明瞭,嚴格模式也包含了其餘一些能讓書寫正確javascript代碼更簡單的東西。
我很是推薦你在嚴格模式下書寫你全部的javascript代碼。因此本書的全部javascript代碼都是嚴格模式下書寫的,這能幫程序員省掉不少麻煩且這樣的代碼在將來版本的javascript也是無可挑剔的。
若是你看到語法圖裏面的元素,你可能已經注意到一些在賦值語句右邊的值或者程序片斷,它們被叫作表達式。那麼表達式和指令有什麼不一樣呢?二者不一樣的是指令某種方式上改變內存,而表達式有一個值。指令一般使用表達式。這裏有幾個表達式的例子:
16 numberOfBananas 2 a + 4 numberOfBananas + 12 - a -3 "myCanvas"
全部的這些表達式表明了一個肯定的類型。除了最後一行,全部的表達式都是數值。最後一行是字符串。除了數值和字符串,還有其餘類型的表達式。我討論的是本書最重要的表達式。好比,下節我會討論運算符的表達式,第7章會講使用函數或者方法做爲一個表達式。
這節討論javascript中不一樣的運算符。你會了解到運算符的優先級。你也能夠了解到有些時候,在javascript中表達式也能至關的複雜。好比,一個變量能夠包含多個值,或者能夠表明一個函數。
(省略)
(省略)
在javascript中,函數被儲存在內存裏。正由於如此,函數也是表達式。因此,能夠給一個變量賦值爲函數。例如:
var someFunction = function () { // do something }
這個例子聲明瞭一個變量並進行了賦值。這個變量的值是一個無名函數。若是你想執行這個函數,你能夠經過變量名字調用,以下:
someFunction();
那麼像下面這樣定義一個函數和你以前看到的有什麼不一樣呢?
function someFunction () { // do something }
實際上,這並無什麼不一樣。主要是若是不像傳統方式那樣定義一個函數,那麼這個函數在定義以前不能使用。當瀏覽器執行一個javascript文件,有兩個步驟。第一個步驟,瀏覽器構造一個函數的列表。第二步,瀏覽器解釋剩下的腳本。這對正確執行腳本頗有必要,瀏覽器須要知道哪一個函數是有效的。好比,下面的這段代碼能夠運行,即便這個在後面被定義:
someFunction(); function someFunction () { // do something }
然而,若是一個函數被賦值給一個變量,那麼就只剩上述的第二步了。意味着下面的代碼會報錯。
someFunction(); var someFunction = function () { // do something }
瀏覽器會告知有一個變量沒有進行聲明。在定義以後進行調用就能夠了,好比:
someFunction(); var someFunction = function () { // do something }
一個變量能夠由多個值組成而不是隻能單一值。這就像函數裏面作的同樣,把指令組合在一塊兒。好比:
function mainLoop () { canvasContext.fillStyle = "blue"; canvasContext.fillRect(0, 0, canvas.width, canvas.height); update(); draw(); window.setTimeout(mainLoop, 1000 / 60); }
跟函數同樣,你能夠把一些變量放在一個更大的變量裏面。這個更大的變量就有了更多的值。就像下面這個例子:
var gameCharacter = { name : "Merlin", skill : "Magician", health : 100, power : 230 };
這是一個複合變量的例子。變量gameCharacter有一些變量。這些變量有名字和值。所以,在某種意義上說,變量gameCharacter由其它一些變量組成。每一個子變量都有一個名字,冒號後面是值。這個包含名字的表達式和花括號之間的值被叫作對象字變量。圖3-6顯示了對象字變量的的語法圖:
(省略圖3-6)
在聲明和實例化變量gameCharacter以後,這塊內存看起來如圖3-7:
(省略3-7)
你能夠像下面這樣獲取一個複合變量的值。
gameCharacter.name = "Arjan"; var damage = gameCharacter.power * 10;
正如你看到的那樣,你能夠獲取gameCharacter變量中的某個變量,經過在gameCharacter後面加上一個點和子變量名。javascript甚至容許在聲明和實例化複合變量後修改這個複合變量的值。舉個例子,看下面這段代碼:
var anotherGameCharacter = { name : "Arthur", skill : "King", health : 25, power : 35000 }; anotherGameCharacter.familyName = "Pendragon";
如今anotherGameCharacter有5部分了,name, skill, health, power, familyName。
由於變量也能夠指向函數,因此你能夠包含一個指向函數的子變量。以下所示:
var anotherGameCharacter = { name : "Arthur", familyName : "Pendragon", skill : "King", health : 25, power : 35000, healMe : function () { anotherGameCharacter.health = 100; } };
像以前同樣,你能夠在這以後也能爲其子變量添加一個函數。
anotherGameCharacter.killMe = function () { anotherGameCharacter.health = 0; };
你能夠調用其餘變量同樣調用這些函數。下面的指令恢復了遊戲角色的生命:
anotherGameCharacter.healMe();
若是你想殺死角色,能夠調用anotherGameCharacter.killMe();組合變量和函數最棒的地方在於你能夠把相關的變量和函數放在一塊兒。這個例子就是把與同一個遊戲角色相關的變量放在了一塊兒,同時增長了幾個相關的函數。從如今開始,若是一個函數屬於一個變量,我把這個函數叫作方法。把一個由其餘變量組成的變量叫作對象。若是一個變量是對象的一部分,這個變量叫作成員變量。
你能夠想象對象和方法的能量有多強大。它們提供了一個進入複雜遊戲世界的方式。若是javascript沒有這種能力,那麼在程序的開頭,你會聲明一長串的變量,而且不知道這些變量之間如何相關且能作什麼。把變量裝進對象裏且給對象提供方法,你能夠寫出更容易理解的程序。在下節,你就要使用這種強大的能力來寫一個簡單的移動方塊程序。
這節實現了一個方塊在畫布上移動的簡單程序。主要有兩個目的:
在寫這個程序以前,讓咱們再一次看看BasicGame例子的代碼:
var canvas = undefined; var canvasContext = undefined; function start () { canvas = document.getElementById("myCanvas"); canvasContext = canvas.getContext("2d"); mainLoop(); } document.addEventListener('DOMContentLoaded', start); function update () { } function draw () { canvasContext.fillStyle = "blue"; canvasContext.fillRect(0, 0, canvas.width, canvas.height); } function mainLoop () { update(); draw(); window.setTimeout(mainLoop, 1000 / 60); }
咱們如今用上面學到的關於對象的知識來把這段代碼從新整理成一個遊戲程序。以下:
"use strict"; var Game = { canvas : undefined, canvasContext : undefined }; Game.start = function () { Game.canvas = document.getElementById("myCanvas"); Game.canvasContext = Game.canvas.getContext("2d"); Game.mainLoop(); }; document.addEventListener('DOMContentLoaded', Game.start); Game.update = function () { }; Game.draw = function () { Game.canvasContext.fillStyle = "blue"; Game.canvasContext.fillRect(0, 0, Game.canvas.width, Game.canvas.height); }; Game.mainLoop = function () { Game.update(); Game.draw(); window.setTimeout(mainLoop, 1000 / 60); };
你建立了一個叫作Game的複合變量。這個對象有兩個成員變量,canvas和canvasContext。此外,你給這個對象添加了幾個方法,包括構成這個遊戲循環的方法。你分開定義了這個對象的方法。這樣作的緣由是你能夠清楚的分辨出這些數據和方法組成的對象能夠如何與數據打交道。須要注意的是,像我推薦的同樣,添加了「use strict」。
如今,讓咱們對這個程序進行擴展。它須要顯示一個在屏幕上移動的方塊。你想隨着時間改變方塊的X座標值。爲了作到這些,你必須把如今的X座標值儲存在變量裏。那樣你就能夠在update裏面給變量進行賦值而且使用這個值在draw方法裏畫出這個方塊。放置這個變量的地方是在Game對象裏,你能夠像下面同樣聲明和實例化:
var Game = { canvas : undefined, canvasContext : undefined, rectanglePosition : 0 };
使用變量rectanglePosition來儲存在方塊的X座標值。在draw方法裏,你可使用這個值在屏幕上畫出方塊。這個例子裏,繪畫出一個不超過畫布大小的方塊,下面是draw方法的內容:
Game.draw = function () { Game.canvasContext.fillStyle = "blue"; Game.canvasContext.fillRect(Game.rectanglePosition, 100, 50, 50); }
如今你須要作的就是計算X的座標值。在update方法裏計算X值,由於改變X值覺得着更新着遊戲世界。在這個例子裏,咱們基於時間的流逝來改變方塊的座標值。在javascript獲取系統的時間值:
var d = new Date(); var currentSystemTime = d.getTime();
在此以前,你還沒看過像第一行那樣的符號。如今,假設new Date()創造了一個複合變量(對象),裏面有跟時間相關的信息,還有一些有用的方法。其中一個方法是getTime。你經過對象d調用此方法並儲存在currentSystemTime裏。如今這個變量就有了從1970年1月1日開始的時間。你能夠想象到這個變量值有點大。若是你想設置X座標值,那麼就要一個很大的屏幕。固然,不可能這樣作,你用時間對畫布的寬度求餘,餘數做爲X座標值。那樣,你始終獲得的是一個在0到畫布寬度之間的值。update方法以下:
Game.update = function () { var d = new Date(); Game.rectanglePosition = d.getTime() % Game.canvas.width; };
如你所知,update和draw方法順序調用,大約每秒60幀。每一次時間改變,系統時間也改變,意味着方塊的座標值也改變,那麼方塊顯示的地方就與以前不同。
在這個例子運行的如你所想以前你還要作一件事情。若是你像這樣運行程序,一個藍色的條將出如今屏幕上。由於你在舊的方塊上繪畫了新的方塊。爲了解決這個問題,每次畫方塊以前你須要清除畫布。清除畫布用clearRect方法。這個方法清除掉指定大小的畫布。舉例:
Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);
爲了方便,把這條指令放在一個叫作clearCanvas的方法裏,以下:
Game.clearCanvas = function () { Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height); };
你須要作的就是在遊戲循環裏,在update和draw方法以前調用上面的方法。
Game.mainLoop = function() { Game.clearCanvas(); Game.update(); Game.draw(); window.setTimeout(Game.mainLoop, 1000 / 60); };
這個例子就完成了。運行結果如圖3-8所示:
(省略圖3-8)
你聲明變量的地方決定了你能在哪些地方使用這些變量。看上面程序的d變量。它聲明在update方法裏,因此它只能在update方法裏面使用。就是說,你不能在draw方法裏面使用這個變量。固然,你能夠在draw方法裏面從新申請一個d變量,可是須要意識到的是這裏的d變量和update方法裏面的d變量是不同的。
相對的,你在一個對象的水平上聲明一個變量,你就能夠在任何地方使用這個變量。你須要在update和draw方法裏面使用方塊的X座標,由於在update裏面須要更新座標值,draw裏面須要繪畫出方塊。所以須要在對象這個水平上聲明變量,那樣對象的全部方法均可以使用這個變量。
變量能夠在哪裏使用叫作變量範圍。在這個例子裏面,d的變量範圍是update方法,Game.recentPostion是全局範圍。
在這章裏,你學到了: