在下面的例子中,我們將要構建一個 Bingo 卡片游戲,每個示例演示 JavaScript 的不同方面,通過每次的改進將會得到最終有效的 Bingo 卡片。
Bingo 卡片的內容
美國 Bingo 卡片是 5 X 5 的方形,5 個列上標明 B-I-N-G-O,格子中包含 1~75 的數字。正中間是一個空格子,常常印着 free。每列可以包含的范圍如下:
- B 列包含數字 1~15
- I 列包含數字 16~30
- N 列包含數字 31~45
- G 列包含數字 46~60
- O 列包含數字 61~75
原始1、第一個簡單的 JavaScript 循環
bingo.html,這個頁面建立 Bingo 卡片的框架。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Make Your Own Bingo</title> <link rel="stylesheet" href="bingo.css"> <script src="bingo.js"></script> </head> <body> <h1>Create A Bingo Card</h1> <table> <tr> <th>B</th> <th>I</th> <th>N</th> <th>G</th> <th>O</th> </tr> <tr> <td id="square0"> </td> <td id="square5"> </td> <td id="square10"> </td> <td id="square14"> </td> <td id="square19"> </td> </tr> <tr> <td id="square1"> </td> <td id="square6"> </td> <td id="square11"> </td> <td id="square15"> </td> <td id="square20"> </td> </tr> <tr> <td id="square2"> </td> <td id="square7"> </td> <td id="free">Free</td> <td id="square16"> </td> <td id="square21"> </td> </tr> <tr> <td id="square3"> </td> <td id="square8"> </td> <td id="square12"> </td> <td id="square17"> </td> <td id="square22"> </td> </tr> <tr> <td id="square4"> </td> <td id="square9"> </td> <td id="square13"> </td> <td id="square18"> </td> <td id="square23"> </td> </tr> </table> <p><a href="../111.html" id="reload">Click here</a> to create a new card</p> </body> </html>
bingo.css 為 Bingo 卡片添加樣式
@charset "utf-8"; /* CSS Document */ body{ background-color: white; color: black; font-size: 20px; font-family: "Lucida Console", Verdana, Arial, Helvetica, sans-serif; } h1, th{ font-family: Georgia, "Times New Roman", Times, serif; } h1 { font-size: 28px; } table{ border-collapse: collapse; } th, td{ padding: 10px; border: 2px #666 solid; text-align: center; width: 20%; } #free, .pickedBG{ background-color: #f66; } .winningBG{ background-image: url(redFlash.gif); }
我們先看看上面的 HTML 和 CSS 頁面,這個是 Bingo 卡片的框架,包含 6 行 5 列的表格,表格的第一行是BINGO字母,且第三行中間是一個 Free 表格,每個單元格都有一個 id 屬性,腳本將使用此 id 操縱這些單元格內容。id 采用 square0、square1、square2,直到 square23 分布如下圖,在頁面的底部,有一個用來生成新卡片的鏈接。
我們將要用循環語句,最常用的就是 for 循環,這種循環使用一個計數器(counter),計數器是一個變量,它的初值是某個值。在測試條件得不到滿足的時候就會結束。
我們將 bingo.html 中 <head> 標簽中的 <script> 的 src 屬性設置為 bingo01.js。
bingo01.js,使用 for 循環將表格內容填寫為 1~75 中數,如下:
// JavaScript Document window.onload = initAll; function initAll(){ for(var i=0; i<24; i++){ var newNum = Math.floor(Math.random() * 75) + 1; document.getElementById('square' + i).innerHTML = newNum; } }
Math.random() 生成一個 0~1 的隨機數,比如 0.123456789。然后與最大值(75)相乘,就會生成 0~75 之間的結果。對結果進行 Math.floor() 操作獲得結果的整數部分。
該 bingo01.js 的顯示結果如下:
第一個示例產生的卡片還不是有效的 Bingo 卡片,因為 Bingo 卡片對特定列有一些要求。后面的示例將對腳本進行改進,直到產生有效的 Bingo 卡片。
改進2、將值傳遞給函數
我們繼續改進 bingo01.js,並保存為 bingo02.js,如下:
// JavaScript Document window.onload = initAll; function initAll(){ for(var i=0; i<24; i++){ //我們將i值傳遞給 setSquare() 函數 setSquare(i); } } function setSquare(thisSquare){ var currSquare = "square" + thisSquare; var newNum = Math.floor(Math.random() * 75) + 1; document.getElementById(currSquare).innerHTML = newNum; }
通過將值傳遞給 setSquare() 函數,使得腳本更加容易閱讀和理解了。
改進3、探測對象
在編寫腳本時,你可能希望檢查瀏覽器是否有能力理解你要使用的對象。進行這種檢查的方法稱為對象探測(object detection)。如下:
if (document.getElementById){
如果對象存在,if 語句就為 true,腳本繼續執行。但是如果瀏覽器不理解這個對象,就會返回 false,並執行條件語句 else 部分。
bingo03.js,增加了對象探測功能,如下:
// JavaScript Document window.onload = initAll; function initAll(){ if(document.getElementById){ //如果 document.getElementById 這個對象存在的話 for(var i=0; i<24; i++){ setSquare(i); } }else{ alert('Sorry, your browser doesn\'t support this script!'); } } function setSquare(thisSquare){ var currSquare = "square" + thisSquare; var newNum = Math.floor(Math.random() * 75) + 1; document.getElementById(currSquare).innerHTML = newNum; }
改進4、處理數組
在這個示例中,我們將使用一個有用的 JavaScript 對象,數組(array)。
數組是一種可以存儲一組信息的變量,與變量一眼個,可以使包含任何類型的數據。聲明數組時,將數組元素放在括號中,以逗號分隔,如下:
var newCars = new Array("Toyota", "Honda", "Nissan");
在這個示例中,我們要確保 Bingo 卡片是仍然不是有效的,每列雖然具有不同的數字范圍,但是一些列中出現重復的數字。
bingo04.js,這個腳本限制了每一列值的范圍。
window.onload = initAll; function initAll(){ if(document.getElementById){ for(var i=0; i<24; i++){ setSquare(i); } }else{ alert('Sorry, your browser doesn\'t support this script'); } } function setSquare(thisSquare){ var currSquare = 'square' + thisSquare; var colPlace = new Array(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4); var colBasis = colPlace[thisSquare] * 15; var newNum = colBasis + Math.floor(Math.random() * 15) + 1; document.getElementById(currSquare).innerHTML = newNum; }
該腳本執行后,顯示如下:
改進5、處理有返回值的函數、運行時更新數組
我們將一些計算轉移到一個函數中,這個函數為 Bingo 卡片上的單元格返回隨機數,然后另一個函數使用它返回的結果,這使得整個更加容易理解了。
我們還要糾正一個問題, 避免出現重復的數字,在腳本運行時通過計算修改數組中的值。
bingo05.js,增加了一個 getNewNum 函數,返回一個 0~14 之間的數,新增了一個 usedNums 數組,存放 true / false ,主要是避免重復的數字。
// JavaScript Document window.onload = initAll; var usedNums = new Array(76); //已經初始化為 false function initAll(){ if(document.getElementById){ for(var i=0; i<24; i++){ setSquare(i); } }else{ alert("Sorry, your browser doesn't support this script"); } } function setSquare(thisSquare){ var currSquare = 'square' + thisSquare; var colPlace = new Array(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4); var colBasis = colPlace[thisSquare] * 15; var newNum = colBasis + getNewNum() + 1; //如果 usedNums[newNum] 為 true ,說明有重復數據,不做任何處理 if(!usedNums[newNum]){ usedNums[newNum] = true; document.getElementById(currSquare).innerHTML = newNum; } } function getNewNum(){ return Math.floor(Math.random() * 15); }
腳本執行后,顯示如下:
改進6、使用 do/while 循環
有時候,需要讓代碼循環許多次,但是無法確定需要循環多少次。在這種情況下,就要使用 do/while 循環,只要某個條件為 true, 就執行某種操作。
bingo06.js 腳本,在將數字放入單元格之前,首先檢查是否已經使用了這個數字。如果這個數字已經使用過了,腳本就將生成新的隨機數並重復該過程,直到找到不重復的數字為止,該腳本最終生成了有效的 Bingo 卡片。
// JavaScript Document window.onload = initAll; var usedNums = new Array(76); function initAll(){ if(document.getElementById){ for(var i=0; i<24; i++){ setSquare(i); } }else{ alert('Sorry, your browser doesn\'t support this script'); } } function setSquare(thisSquare){ var currSquare = 'square' + thisSquare; var colPlace = new Array(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4); var colBasis = colPlace[thisSquare] * 15; var newNum; do{ newNum = colBasis + getNewNum() + 1; }while (usedNums[newNum]); //檢查 usedNums[] 數組中 newNum 上的值, //從而判斷 newNum 是否已經使用過了 document.getElementById(currSquare).innerHTML = newNum; }
腳本執行后,我們得到了有效的 Bingo 卡片,如下:
改進7、多種方式調用腳本
到目前為止,我們看到的腳本都是在加載頁面的時候自動運行的。但是在顯示環境中,常常希望用戶對腳本有更多的控制能力,甚至允許他們控制腳本在何時運行。
bingo07.js 腳本中,允許我們通過頁面底部的鏈接來重新運行腳本,這樣就可以完全在瀏覽器中生成 Bingo 卡片,而不需要從服務器重新加載頁面。這向用戶提供了快速的響應,而且不會產生服務器負載。
// JavaScript Document window.onload = initAll; var usedNums = new Array(76); function initAll(){ if(document.getElementById){ document.getElementById("reload").onclick = anotherCard; newCard(); }else{ alert("Sorry, your browser doesn't support this script"); } } function newCard(){ for(var i=0; i<24; i++){ setSquare(i); } } function setSquare(thisSquare){ var currSquare = 'square' + thisSquare; var colPlace = new Array(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4); var colBasis = colPlace[thisSquare] * 15; var newNum; do{ newNum = colBasis + getNewNum() + 1; }while(usedNums[newNum]) usedNums[newNum] = true; document.getElementById(currSquare).innerHTML = newNum; } function getNewNum(){ return Math.floor(Math.random() * 15); } function anotherCard(){ for(var i=0; i<usedNums.length; i++){ usedNums[i] = false; } newCard(); return false; //是瀏覽器不加載 href 中的鏈接地址 }
其實到了現在,你已經知道如何使用 JavaScript 進行重新加載一部分的頁面了,而不需要向服務器請求整個頁面,這正是 Ajax 的基本特色。
改進8、組合使用 JavaScript 和 CSS
該示例中我們將演示如何讓用戶能夠操作所生成的 Bingo 卡片。
bingo08.js 腳本中我們將添加一些 JavaScript 來利用 CSS 的功能。
// JavaScript Document window.onload = initAll; var usedNums = new Array(76); function initAll(){ if(document.getElementById){ document.getElementById("reload").onclick = anotherCard; newCard(); }else{ alert("Sorry, your browser doesn't support this script"); } } function newCard(){ for(var i=0; i<24; i++){ setSquare(i); } } function setSquare(thisSquare){ var currSquare = 'square' + thisSquare; var colPlace = new Array(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4); var colBasis = colPlace[thisSquare] * 15; var newNum; do{ newNum = colBasis + getNewNum() + 1; }while(usedNums[newNum]); usedNums[newNum] = true; document.getElementById(currSquare).innerHTML = newNum; //確保 Bingo 卡片最初狀態都是 class 屬性都是空字符串 document.getElementById(currSquare).className = ""; //設置 每個 td 的 onmousedown 事件 document.getElementById(currSquare).onmousedown = toggleColor; } function getNewNum(){ return Math.floor(Math.random() * 15); } function anotherCard(){ for(var i=0; i<usedNums.length; i++){ usedNums[i] = false; } newCard(); return false; } function toggleColor(evt){ //為了處理 IE 和 非IE 瀏覽器,如果是 非IE 則會有一個 evt 傳遞給函數 if(evt){ var thisSquare = evt.target; }else{ //如果是 IE 瀏覽器 則必須通過下面的語句獲得 var thisSquare = window.event.srcElement; } if(thisSquare.className == ""){ thisSquare.className = "pickedBG"; }else{ thisSquare.className = ""; } /* //自行通過 this 也可以獲取當前元素進行修改,代碼如下 if(this.className == ""){ this.className = "pickedBG"; }else{ this.className = ""; } alert(this.className); */ }
腳本執行后,可以通過鼠標點擊單元格,點擊后的單元格背景變成紅色,如下:
改進9、檢查狀態
用戶能夠給格子添加標記后,還可以檢查格子是否構成了獲勝模式。在下面的腳本中,用戶選擇那些已經被叫過的號碼,bingo09.js 腳本會讓用戶知道什么時候他們獲勝了,該腳本使用了復雜的數學計算來判斷獲勝組合。
// JavaScript Document window.onload = initAll; var usedNums = new Array(76); function initAll(){ if(document.getElementById){ document.getElementById("reload").onclick = anotherCard; newCard(); }else{ alert("Sorry, your browser doesn't support this script"); } } function newCard(){ for(var i=0; i<24; i++){ setSquare(i); } } function setSquare(thisSquare){ var currSquare = 'square' + thisSquare; var colPlace = new Array(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4); var colBasis = colPlace[thisSquare] * 15; var newNum; do{ newNum = colBasis + getNewNum() + 1; }while(usedNums[newNum]); usedNums[newNum] = true; document.getElementById(currSquare).innerHTML = newNum; document.getElementById(currSquare).className = ""; document.getElementById(currSquare).onmousedown = toggleColor; } function getNewNum(){ return Math.floor(Math.random() * 15); } function anotherCard(){ for(var i=0; i<usedNums.length; i++){ usedNums[i] = false; } newCard(); return false; } function toggleColor(evt){ if(evt){ var thisSquare = evt.target; }else{ var thisSquare = window.event.srcElement; } if(thisSquare.className == ""){ thisSquare.className = "pickedBG"; }else{ thisSquare.className = ""; } //此處增加 checkWin 函數,隨時檢查是否獲勝 checkWin(); } function checkWin(){ //存儲用戶的獲勝選項 var winningOption = -1; //存儲已經點擊的格子 var setSquares = 0; //存儲獲勝組合的數組 var winners = new Array(31, 992, 15360, 507904, 541729, 557328, 1083458, 2162820, 4329736, 8519745, 8659472, 16252928); //檢查每一個格子是否點擊過 for(var i=0; i<24; i++){ var currSquare = "square" + i; if(document.getElementById(currSquare).className != ""){ document.getElementById(currSquare).className = "pickedBG"; //按位運算,或 操作,記錄用戶點擊的組合 setSquares = setSquares | Math.pow(2, i); } } //與 操作,判斷是否獲勝 for(var i=0; i<winners.length; i++){ if((winners[i] & setSquares) == winners[i]){ winningOption = i; //i就是用戶獲勝的狀態 } } //如果是獲勝者,則遍歷每個格子,判斷改格子是否獲勝的格子 if(winningOption > -1){ for(var i=0; i<24; i++){ if(winners[winningOption] & Math.pow(2, i)){ currSquare = "square" + i; document.getElementById(currSquare).className = "winningBG"; //winningBG 將設置背景為 GIF 動畫 } } } }
腳本執行后, 我們嘗試選擇對角線上格子,背景圖案閃爍,說明獲勝了,如下圖:
改進10、處理字符串數組
到目前為止,我們所使用的數組都是由布爾值或者數字組成的,bingo10.js 腳本將演示使用字符串數組綜合前面的所有技術,創建一個 Buzzword Bingo 游戲。
//buzzwords 數組至少需要24個元素 var buzzwords = new Array("Aggregate", "Ajax", "API", "Bandwidth", "Beta", "Bleeding edge", "Convergence", "Design pattern", "Disruptive", "DRM", "Enterprise", "Facilitate", "Folksonomy", "Framework", "Impact", "Innovate", "Long tail", "Mashup", "Microformats", "Mobile", "Monetize", "Open social", "Paradigm", "Podcast", "Proactive", "Rails", "Scalable", "Social bookmarks", "Social graph", "Social software", "Spam", "Synergy", "Tagging", "Tipping point", "Truthiness", "User-generated", "Vlog", "Webinar", "Wiki", "Workflow"); var usedWords = new Array(buzzwords.length); window.onload = initAll; function initAll(){ if(document.getElementById){ document.getElementById("reload").onclick = anotherCard; newCard(); }else{ alert("Sorry, your browser doesn't support this script"); } } function newCard(){ for(var i=0; i<24; i++){ setSquare(i); } } function setSquare(thisSquare){ do{ var randomWord = Math.floor(Math.random() * buzzwords.length); } while(usedWords[randomWord]); usedWords[randomWord] = true; var currSquare = 'square' + thisSquare; document.getElementById(currSquare).innerHTML = buzzwords[randomWord]; document.getElementById(currSquare).className = ""; document.getElementById(currSquare).onmousedown = toggleColor; } function anotherCard(){ for(var i=0; i<buzzwords.length; i++){ usedWords[i] = false; } newCard(); return false; } function toggleColor(evt){ if(evt){ var thisSquare = evt.target; }else{ var thisSquare = window.event.srcElement; } if(thisSquare.className == ""){ thisSquare.className = "pickedBG"; }else{ thisSquare.className = ""; } checkWin(); } function checkWin(){ var winningOption = -1; var setSquares = 0; var winners = new Array(31, 992, 15360, 507904, 557328, 1083458, 2162820, 4329736, 8519745, 8659472, 16252928); for(var i=0; i<24; i++){ var currSquare = 'square' + i; if(document.getElementById(currSquare).className != ''){ document.getElementById(currSquare).className = 'pickedBG'; setSquare = setSquare | Math.pow(2, i); } } for(var i=0; i<winners.length; i++){ if((winners[i] & setSquare) == winners[i]){ winningOption = i; } } if(winningOption > -1){ for(var i=0; i<24; i++){ if(winners[winningOption] & Math.pow(2, i)){ currSquare = 'square' + i; document.getElementById(currSquare).className = "winningBG"; } } } }
腳本執行后效果如下: