一、ES6語法
ES6中對陣列新增了幾個函式:map()、filter()、reduce()
ES5新增的forEach()。
都是一些語法糖。
1.1 forEach()遍歷陣列
forEach()方法用來迴圈遍歷陣列,方法中的function回撥函式接收3個引數
引數1是遍歷的陣列內容(item);引數2是對應的陣列索引(index),引數3是是陣列本身(array)。
[].forEach(function(item,index,array){ ... }) var arr = ["白板","么雞","紅中","發財","八萬"]; arr.forEach(function(item,index,array){ console.log(item,index,array) })
forEach函式沒有返回值
for今後是建立陣列,遍歷或運算元組可以交給forEach方法。
1.2 map()對映
map方法的作用,“對映”也就是原陣列被“對映”成對應的新陣列。
[].map(function(item,index,array){ ... }) var arr = ["白板","么雞","紅中","發財","八萬"]; arr.map(function(item,index,array){ console.log(item,index,array) })
案例:比如建立一個新陣列,新陣列的每一項是原陣列的兩倍
var arr1 = [6,8,10,88,100,200]; var arr2 = arr1.map(function(item){ return item * 2; }) console.log(arr1) console.log(arr2)
map()函式本質是依次遍歷原陣列中的每一項,將每一項都執行一遍函式中的語句,返回一個新的陣列。
提示:
l 函式需要有return值,如果沒有,陣列所有想都被對映成undefined
l map返回的陣列一定和原陣列長度一樣。
1.3 filter()過濾
filter為“過濾”、“篩選”之意,指從原陣列中filter某些項後,返回過濾後的新陣列,用法和map相似。
案例:從原陣列中,挑選所有的偶數,返回新的陣列。
var arr1 = [6,8,10,77,88,100,200,1,3,5]; var arr2 = arr1.filter(function(item){ return item % 2 == 0; }); console.log(arr1) console.log(arr2)
概述:arr中的每一項依次的執行函式,filter的回撥函式需要返回布林值,true則將值返回到新陣列中,false則捨棄,進入下一次迴圈。
filter和map相同:都會遍歷陣列每一項。
filter和map不相同:map返回陣列不會少項,filter可能會少項。
1.4取整運算子
console.log(~~11.5); console.log(~~"11.5"); console.log(~~"10px"); console.log(~~true); console.log(~~false);
二、underscore.js
jQuery是DOM之王,那麼underscore就是數學之王(擅長計算)。
Underscore一個JavaScript實用庫,提供了一整套函數語言程式設計的實用功能,但是沒有擴充套件任何JavaScript內建物件。
Underscore提供了100多個函式,包括常用的: map, filter, invoke 當然還有更多專業的輔助函式,如:函式繫結, JavaScript模板功能,建立快速索引, 強型別相等測試, 等等.
Underscore不依賴環境,不限制使用場景,可以載入到HTML中在瀏覽器執行,也可以中Nodejs伺服器環境中使用。封裝了一堆實用函式,這些函式基本都是針對:陣列、物件、函式的。
中文文件:http://www.css88.com/archives/5443
CDN公共資源庫:http://cdn.code.baidu.com/
生成0~100的隨機數: _.random(0,100); //生成0~100的隨機數
建立一個範圍整數陣列: _.range(1,10) //[1, 2, 3, 4, 5, 6, 7, 8, 9]
取陣列中的最大和最小值: var num = [10, 5, 100, 2, 1000]; console.log(_.min(num)); console.log(_.max(num));
把陣列轉成物件: _.object(['a', 'b', 'c'], [10, 20, 30]); //{ a: 10, b: 20, c: 30 }
each()遍歷方法,對集合迴圈操作,可以遍歷陣列、類陣列元素,arguments _.each(['小王','大王','鬼王'],function(item, index){ console.log(item,index) });
JSON遍歷: _.each({'小王':'100','大王':'200','鬼王':'300'},function(item, index){ console.log(item,index) });
map(): 對集合以map方式遍歷,產生一個新陣列 var arr3 = _.map({a: 1, b: 2, c: 3}, function(item, key){ return item * 3; }); console.log(arr3); //[3, 6, 9]
filter(): 過濾集合中符合條件的元素 var arr4 = _.filter([1, 2, 3, 4, 5, 6], function(item){ return item % 2 == 0; }); console.log(arr4) //[ 2, 4, 6 ]
sortBy() 自定義比較方法 var sort = _.sortBy([3, 4, 2, 1 , 6 ,88], function(item){ return Math.max(item); }) console.log(sort)
三、模板引擎
3.1 underscore模板引擎
template()方法可接受三個引數:
引數1:是必須的引數是模版字串,你可以通過<%= %> 來插入變數,還可以通過<% %>來插入js程式碼,也可以通過<%- %>來進行html轉義,如果變數很多,可以使用<% print() %>來簡化。
引數2:是傳入模版的資料,如果不傳第二個引數,那麼這個方法會返回一個模版函式,這個模版函式可以傳入資料返回完成的模版,如果傳入data引數則會直接返回一個已完成的模版。
引數3:是設定,比如這個方法預設是尋找<% %>來進行替換,可以通過設定它來改變具體的方法。
_.template 支援以下三種模板: <% %> 執行一些程式碼 <%= %> 在模板中列印或者說成輸出一些值 <%- %> 列印一些HTML轉義的值
解釋:
<% %> 裡包裹的是一些可執行的 JavaScript 語句,比如 if-else 語句,for 迴圈語句等等。
<%= %> 會列印傳入資料相應的 key 的值,
<%- %> 和前者相比,多了步 HTML 實體編碼的過程,可以有效防止 XSS 攻擊。
//模板 var str = "我很<%= xinqing %>啊!買了一個<%= dongxi%>,花了<%= price%>元"; //通過move字串生成一個資料繫結函式 var compile = _.template(str); //資料 var obj = { xinqing:"高興", dongxi:"iPhone手機", price:8888 } //字串和資料進行繫結生成 str = compile(obj); console.log(str)
還可以將HTML作為模板,將JS的資料,注入進去,將模板中“寫死”的內容都用模板的標記代替。
<head> <meta charset="UTF-8" /> <title>Document</title> <style type="text/css"> .bgColor{ background: red; } </style> </head> <body> <table id="table"> <tr> <td>學號</td> <td>姓名</td> <td>年齡</td> <td>性別</td> </tr> </table> </body> <!-- 我們使用一個故意寫錯的type的標籤存放模板 --> <script type="text/template" id="template"> <tr class="<%= leiming %>"> <td><%= id %></td> <td><%= name %></td> <td><%= age %></td> <td><%= sex %></td> </tr> </script> <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script> <script type="text/javascript" src="js/underscore-min.js"></script> <script type="text/javascript"> //通過模板字串生成一個資料繫結函式 var compile = _.template($("#template").html()); //Ajax讀取資料 $.get("data/student.json", function(data){ //遍歷資料 _.each(data.result, function(obj){ obj.leiming = obj.age >= 18 ? "" : "bgColor"; //資料繫結,得到DOM字串 var str = compile(obj); // 上樹 $("table").append(str); }) }) </script>
3.2模板引擎原理(JS)
拼接字串很不爽,容易出錯。
所以就有工程師在大量的實戰中,提出模板引擎的概念,就是在一個完整的字串中,把未定的量用特殊的語法來表示
@xinqing@
然後把這些資料替換成標記,這個操作叫資料繫結。
<script type="text/javascript"> //模板 var str = "我很@xinqing@啊!買了一個@dongxi@,花了@price@元"; //資料 var obj = { xinqing:"高興", dongxi:"iPhone手機", price:8888 } //封裝資料繫結方法 function complie(tplStr,tplObj){ tplStr = tplStr.replace(/\@([a-zA-Z]+)\@/g, function(match,$1){ return tplObj[$1]; }) return tplStr; } //呼叫資料繫結函式 str = complie(str, obj) console.log(str) </script>
使用Ajax
<body> <table id="table"> <tr> <td>學號</td> <td>姓名</td> <td>年齡</td> <td>性別</td> </tr> </table> </body> <!-- 我們使用一個故意寫錯的type的標籤存放模板 --> <script type="text/template" id="template"> <tr class="@leiming@"> <td>@id@</td> <td>@name@</td> <td>@age@</td> <td>@sex@</td> </tr> </script> <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script> <script type="text/javascript"> //通過模板字串生成一個資料繫結函式 var str = $("#template").html(); //Ajax讀取資料 $.get("data/student.json", function(data){ data.result.forEach(function(item){ var domStr = complie(str, item); $("#table").append(domStr) }) }) //封裝資料繫結方法 function complie(tplStr,tplObj){ tplStr = tplStr.replace(/\@([a-zA-Z]+)\@/g, function(match,$1){ return tplObj[$1]; }) return tplStr; } </script>
四、EChart.js(前端資料視覺化)
API配置項:http://echarts.baidu.com/option.html#title
第一步:引入JS檔案: <script type="text/javascript" src="js/echarts.min.js"></script> 第二步:準備一個放圖表的容器 <div id="main" style="width:600px;height:400px;"></div>
第三步:設定引數,初始化圖表
注意:這裡案例是最基礎,但在使用echarts時一定要配置xAxis,yAxis,series這三個引數。如果不想設定也要初始化,將它設定為空JSON即可,要不然會報錯。同時要保證在echarts.init之前的物件是有寬高的,要不然也會報錯。
// 基於準備好的dom,初始化echarts例項 var myChart = echarts.init(document.getElementById('main')); // 指定圖表的配置項和資料 var option = { title: { text: '資料統計' }, tooltip: {}, //懸浮提示 legend: { data:['訪問量'] }, xAxis: { data: ["Android","IOS","PC","Ohter"] }, yAxis: {}, series: [{ name: '訪問量', //nam == legend.data的時候才能顯示圖例 type: 'bar', //這裡可以改成line或pie data: [500, 200, 360, 100] }] }; // 使用剛指定的配置項和資料顯示圖表。 myChart.setOption(option);
簡單的統計圖表就出來了,官網使用的是柱狀圖,可以改成其他形狀
//基於準備好的dom,初始化echarts例項 var myChart = echarts.init(document.getElementById('main')); // 指定圖表的配置項和資料 var option = { title: { text: 'ECharts資料統計' }, tooltip: {}, //懸浮提示 legend: { data:['佔有率'] }, xAxis: { data: ["Android","IOS","PC","Other"] }, yAxis: {}, series: [{ name: '佔有率', //name==legend.data相等的時候才能顯示圖例 type: 'line', data: [50, 120, 36, 100] }] }; // 使用剛指定的配置項和資料顯示圖表。 myChart.setOption(option);
餅狀圖和折線圖、柱狀圖有一點區別,主要是在引數和資料繫結上。餅狀圖沒有X和Y軸座標,資料繫結也是採用value和name對應的形式。
var option = { title:{ text:'周銷量統計', subtext:'虛擬資料' }, tooltip:{ formatter:'系列名:{a}<br />類目:{b}<br />數值:{c}' }, legend:{ data:['購買金額','銷售金額'] }, xAxis:{ data:["週一","週二","週三","週四","週五","週六","週日"] }, yAxis:{}, series:[{ name:'購買金額', type:'bar', data:[200,312,431,241,175,275,369], markPoint: { data: [ {type: 'max', name: '最大值'}, {type: 'min', name: '最小值'} ] }, markLine:{ data:[ {type:'average',name:'平均值',itemStyle:{ normal:{color:'green'} }} ] } },{ name:'銷售金額', type:'line', data:[321,432,543,376,286,298,400], markPoint: { data: [ {type: 'max', name: '最大值'}, {type: 'min', name: '最小值'} ] }, markLine:{ data:[ {type:'average',name:'平均值',itemStyle:{ normal:{color:'blue'} }} ] } }] };
五、JS設計模式
到目前為止,我們每個案例都是一個建構函式:Ballon類、Girl類等等,單打獨鬥。此時要學習多個類之間一起配合工作,最重要的就是資訊的傳遞(就是類和類之間的資料的傳遞)。
什麼是設計模式?
在大的專案中,一定有很多個類一起協作完成一個事,工程師們經過多年的經驗,寫了很多類和類之間的配合和協調工作的一些套路,這些套路我們叫“設計模式”。
設計模式的定義就是在物件導向的開發、設計過程中,針對特定的問題的簡潔而又優雅的解決方案,通俗的就是說給程式的思想起了一個名字 “設計模式”。
設計模式一共23種:
圖所示23種設計模式,實際上被分為了三個大種類。現在要學習的兩種設計模式,是最著名的設計模式。
比如現在有兩個類:老師類和學生類,老師類有留作業方法(liuzuoye),要求呼叫這個方法後,每個學生都擁有zuoye這個屬性,初學者往往寫出這樣的程式碼:
//老師類 function Teacher(){ } Teacher.prototype.liuzuoye = function(content){ alert("老師留了作業:" + content); xiaoming.zuoye = content; //動用了其他類的例項的名字在此,耦合性高 xiaohong.zuoye = content; } //學生類 function Student(){ this.zuoye = ''; } var zhulaoshi = new Teacher(); var xiaoming = new Student(); var xiaohong = new Student(); zhulaoshi.liuzuoye("完成貪吃蛇!"); alert(xiaoming.zuoye) alert(xiaohong.zuoye)
而軟體設計(尤其是物件導向開發時)講究的是“高內聚、低耦合”。耦合性就是類儘量不要用到其他類的例項,如果其他類改名了,你的類就錯了。
為了降低耦合性,經過大量實踐,總結了很多設計模式,降低耦合性。
5.1觀察者模式
觀察者模式(observer)也叫釋出-訂閱模式(publish-subscribe)。它定義了物件間的一種1對n的依賴關係。當一個物件的狀態發生改變時,所有“訂閱”了它的物件都將得到通知。
剛剛老師釋出作業的案例,老師就是釋出者(publisher),學生就是訂閱者、觀察者(subscriber)。老師是1,學生是n。釋出者(老師)要維護自己的訂閱者(學生)列表,自己有一個屬性students存放著所有訂閱自己的列表(例項陣列),當釋出作業時,用for迴圈陣列清單,分別呼叫每個訂閱者相應的方法即可。
精髓:釋出者維持一個訂閱自己的陣列,當自己要釋出資訊的時候,迴圈遍歷自己的陣列呼叫訂閱者的方法。
//老師類 function Teacher(){ //維護自己訂閱者的列表 this.students = []; } //提供一個註冊(關注訂閱)方法 Teacher.prototype.regist = function(obj){ this.students.push(obj) } Teacher.prototype.liuzuoye = function(content){ alert("老師留了作業:" + content ); //遍歷所有學生,分別呼叫它們的listen監聽作業方法,把資訊通過實參傳遞過去 for(var i = 0;i < this.students.length;i++){ this.students[i].listen(content) } } //學生類 function Student(teacher){ // 去註冊成為指定老師的學生 teacher.regist(this) } Student.prototype.listen = function(zuoye){ this.zuoye = zuoye; } //例項化 var zhulaoshi = new Teacher(); //釋出者要先例項化 var kaola = new Teacher(); //釋出者要先例項化 var xiaoming = new Student(zhulaoshi); //註冊成為zhulaoshi的學生 var xiaohong = new Student(zhulaoshi); //註冊成為zhulaoshi的學生 var xiaogang = new Student(kaola); //註冊成為kaola的學生 var xiaobai = new Student(kaola); //註冊成為kaola的學生 //老師留作業 zhulaoshi.liuzuoye("完成貪吃蛇"); kaola.liuzuoye("寫程式碼"); alert(xiaoming.zuoye) alert(xiaohong.zuoye) alert(xiaogang.zuoye) alert(xiaobai.zuoye)
設計模式的好處在於程式足夠大的時候使用。
案例:匯率轉換小程式
人民幣換美元、歐元、日元、英鎊、泰銖
當使用者輸入人民幣的時候,需要實時顯示對應的外幣的數值。
人民幣類(RMB)就是釋出者,外幣類waibi就是訂閱者
//人民幣類 function RMB(){ //維護自己訂閱者的列表 this.listen = []; this.init(); this.bindEvent(); } RMB.prototype.init = function(){ this.p = document.createElement('p'); this.p.innerHTML = "人民幣:"; this.input = document.createElement('input'); this.p.appendChild(this.input) document.body.appendChild(this.p); } //提供一個註冊(關注訂閱)方法,可以將某一個幣種新增到自己的陣列中 RMB.prototype.regist = function(obj){ this.listen.push(obj) } //監聽釋出者改變時的狀態 RMB.prototype.bindEvent = function(){ //告訴使用者輸入人民幣金額時,遍歷自己所有的訂閱者,呼叫他們的監聽方法,將數值告訴他們 //“告訴”是通過呼叫他們的方法實現,通過實參把資料傳遞給他們 var self = this; this.input.oninput = function(){ for(var i = 0;i < self.listen.length;i++){ self.listen[i].listen(this.value); } } } //訂閱者,外幣類 function Waibi(name, huilv){ this.name = name; this.huilv = huilv; //觀察者模式要求,就是去釋出者哪裡註冊自己 //訂閱人民幣類,訂閱者就是要訂閱釋出者 rmb.regist(this); this.init(); } Waibi.prototype.init = function(){ this.p = document.createElement('p'); this.p.innerHTML = this.name + ":"; this.input = document.createElement('input'); this.input.disabled = true; this.p.appendChild(this.input) document.body.appendChild(this.p); } //收聽人民幣的最新數值,此時改變自己dom中的資料 //訂閱者有一個listen監聽釋出者的方法,用來接收響應釋出者的最新訊息 Waibi.prototype.listen = function(content){ this.input.value = content / this.huilv; } var rmb = new RMB(); new Waibi("美元", 6.8894); new Waibi("韓元", 0.0061); new Waibi("港幣", 0.8799); new Waibi("英鎊", 8.9542); new Waibi("日元", 0.0609);
5.2中介者模式
觀察者模式的精髓在於“主動通知”,當老師的狀態改變的時候,能夠實時通知學生,通過呼叫學生的方法來實現的。中介者模式簡單一點,不能主動通知。
老師要釋出作業,此時釋出到QQ群裡;學生看作業去QQ群看就行了!QQ群就是中介者,相當於全域性變數。
後面製作一些複雜的DOM程式,中介者模式是使用最多的。99%的DOM效果都是中介者模式來製作的。
//********中介者QQ群類********* function QQQun(){ this.zuoye = ''; } //老師類 function Teacher(){ } Teacher.prototype.liuzuoye = function(content){ qqqun.zuoye = content; //釋出作業到qq群 } //學生類 function Student(){ } Student.prototype.xiezuoye = function(){ alert("我要寫作業啦!"+ qqqun.zuoye) } var qqqun = new QQQun(); //先例項化中介者 //例項化老師 var zhulaoshi = new Teacher(); zhulaoshi.liuzuoye("完成貪吃蛇!"); //例項化學生 var xiaoming = new Student(); var xiaohong = new Student(); xiaoming.xiezuoye() xiaohong.xiezuoye()
兩個模式的區別:
觀察者模式能主動推送訊息,每個收聽者能夠實時得到釋出者的資訊;
中介者模式不主動推送訊息,當學生要寫作業,需要作業資訊時,主動去找中介者拿,適合時效性不強的資訊。
六、貪吃蛇遊戲-中介者模式
有的時候,專案中的類很多,沒有所謂的1:n(1對n)的關係,它們感覺“互為資訊源”,此時中介者模式是最簡單的。比如蛇需要食物,食物也要蛇的資訊。
用貪吃蛇來舉例:
遊戲有三個類:遊戲類(Game)、蛇類(Snake)、食物類(Food)
遊戲類其實就是中介者,蛇和食物都需要通過Game來交換獲取資訊。
Game類是中介者類,Snake、Food類是普通類,被Game管理。
注意:
l 中介者必須有唯一的例項化物件,這個物件的名字不能更改,比如群號碼不能更改。在貪吃蛇遊戲中,我們就要:
var game = new Game(); 此時game變數名一旦確定就不要改了。
除了中介者之外,其他所有的物件,都需要由中介者來例項化,在貪吃蛇遊戲中,Snake蛇、Food食物類由Game類來例項化,它們都是Game類的子屬性。
function Game(){ this.snake = new Snake(); this.food = new Food(); }
Food、Snake之間如果互相要用資訊,比如蛇要知道食物的位置,用中介者:
function Snake(){ console.log(game.food.x) }
這是我們第一次將一個類單獨寫在js中,每個js檔案就是一個類
第一步:建立index檔案,建立Game.js
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>貪吃蛇</title> </head> <body> <div id="app"> </div> </body> <script type="text/javascript" src="js/Game.js"></script> <script type="text/javascript" src="js/Food.js"></script> <script type="text/javascript" src="js/Snake.js"></script> <script type="text/javascript"> var game = new Game(); //唯一的中介者 </script> </html>
第二步:建立表格,在遊戲中,表格是Game類的DOM屬性,寫Game類的init方法,建立行和列的個數屬性。
注意:所有的程式碼都寫在閉包中,利用window物件暴露唯一的Game遊戲類即可。
(function(){ window.Game = function(){ console.log(this) } })();
(function(){ // 注意:所有的程式碼都寫在閉包中,利用window物件暴露唯一的Game遊戲類即可。 window.Game = function(){ this.rowAmount = 16; //行數 this.colAmount = 20; //列數 this.init(); //初始化UI介面,建立DOM表格 } //初始化UI介面,建立DOM表格 Game.prototype.init = function(){ this.dom = document.createElement('table'); document.getElementById("app").appendChild(this.dom); var tr,td; for(var i = 0; i < this.rowAmount;i++){ tr = document.createElement('tr'); //遍歷插入行 this.dom.appendChild(tr); //tr上樹 for(var j = 0; j < this.colAmount;j++){ td = document.createElement('td');//遍歷插入列 tr.appendChild(td); //td上樹 } } } })();
第三步:建立蛇類,建立Snake.js檔案(每一個類都是單獨一個js檔案)
蛇類有自己的身體屬性,有render渲染方法。
(function(){ window.Snake = function(){ //蛇的身體 this.body = [ {"row" : 4, "col":7}, {"row" : 4, "col":6}, {"row" : 4, "col":5}, {"row" : 4, "col":4}, {"row" : 4, "col":3} ]; } //渲染蛇的身體方法 Snake.prototype.render = function(){ for(var i = 0;i < this.body.length; i++){ //這裡寫違反了高內聚低耦合的原則,改一改東西的屬性應該要呼叫人家提供的方法 //game.dom.getElementsByTagName('tr')[this.body[i].row].getElementsByTagName('td')[this.body[i].col].style.background = 'red'; game.setColor(this.body[i].row,this.body[i].col,'red'); } } })();
在Game類例項化蛇類,同時提供一個setColor方法:
(function(){ // 注意:所有的程式碼都寫在閉包中,利用window物件暴露唯一的Game遊戲類即可。 window.Game = function(){ this.rowAmount = 16; //行數 this.colAmount = 20; //列數 this.init(); //初始化UI介面,建立DOM表格 //例項化蛇類 this.snake = new Snake(); //渲染蛇方法 this.snake.render(); } //初始化UI介面,建立DOM表格 Game.prototype.init = function(){ ... } //設定蛇身的顏色 Game.prototype.setColor = function(row,col,color){ this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col] .style.background = color; } })();
此時會發現報錯了:
原因是在Game類的建構函式中,使用game這個例項的名字,此時是undefined,因為建構函式還沒執行完畢,還沒執行完四步走。
解決方法兩種:
方法1:在Game類中new Snake(this)傳入上下文,this表示即將返回的Game物件。
window.Game = function(){ this.rowAmount = 16; //行數 this.colAmount = 20; //列數 this.init(); //初始化UI介面,建立DOM表格 //例項化蛇類 this.snake = new Snake(this); //渲染蛇方法 this.snake.render(); }
Snake類中接收this
window.Snake = function(mediator){ //接收Game類當做子屬性(中介者) this.mediator = mediator; //蛇的身體,可以讓蛇運動起來,頭增尾刪 this.body = [ {"row" : 3, "col":8}, {"row" : 3, "col":7}, {"row" : 3, "col":6}, {"row" : 3, "col":5}, {"row" : 3, "col":4} ]; } //渲染蛇的身體方法 Snake.prototype.render = function(){ this.mediator.setColor(this.body[0].row,this.body[0].col,'green'); for(var i = 1;i < this.body.length; i++){ this.mediator.setColor(this.body[i].row,this.body[i].col,'red'); } }
方法2:利用定時器解決
定時器20毫秒一幀,第一幀就發生在20毫秒之後,此時建構函式已經執行完畢,game物件就已經返回。
建立定時器,在定時器中,渲染蛇。同時加上清屏的語句,在每一幀都是清屏,重新繪製蛇。
window.Game = function(){ this.rowAmount = 16; //行數 this.colAmount = 20; //列數 this.init(); //初始化UI介面,建立DOM表格 //例項化蛇類 this.snake = new Snake(); //開啟遊戲定時器 this.start(); } //初始化UI介面,建立DOM表格 Game.prototype.init = function(){ this.dom = document.createElement('table'); document.getElementById("app").appendChild(this.dom); var tr,td; for(var i = 0; i < this.rowAmount;i++){ tr = document.createElement('tr'); //遍歷插入行 this.dom.appendChild(tr); //tr上樹 for(var j = 0; j < this.colAmount;j++){ td = document.createElement('td');//遍歷插入列 tr.appendChild(td); //td上樹 } } } //設定蛇身的顏色 Game.prototype.setColor = function(row,col,color){ this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].style.background = color; } //清屏,遍歷行和列,設為白色 Game.prototype.clear = function(){ for(var i = 0;i < this.rowAmount; i++){ for (var j = 0; j < this.colAmount; j++) { this.dom.getElementsByTagName('tr')[i].getElementsByTagName('td')[j].style.background = "#fff"; }; } } // 遊戲開始方法 Game.prototype.start = function(){ var self = this; this.f = 0; //幀編號 setInterval(function(){ self.f++; document.getElementById("info").innerHTML = "幀編號:" + self.f; //清屏 self.clear(); //每隔30幀更新一下 self.f % 30 == 0 && self.snake.update(); //渲染蛇方法 self.snake.render(); },20); }
第五步:讓蛇動起來,套路是,每一幀都清屏,然後更新蛇、渲染蛇...
this.body = [ {"row" : 4, "col" : 7}, {"row" : 4, "col" : 6}, {"row" : 4, "col" : 5}, {"row" : 4, "col" : 4}, {"row" : 4, "col" : 3} ];
變為:
this.body = [ {"row" : 4, "col" : 8} {"row" : 4, "col" : 7}, {"row" : 4, "col" : 6}, {"row" : 4, "col" : 5}, {"row" : 4, "col" : 4}, ];
在Snake控制方向:
//更新方法,這個方法最關鍵 Snake.prototype.update = function(){ this.body.pop(); //尾刪 this.body.unshift({"row":this.body[0].row, "col":this.body[0].col+1});//頭插 }
Snake.prototype.update = function(){ this.body.pop(); //尾刪 //根據方向頭插 switch(this.direction){ case "R": var toucha = {"row": this.body[0].row, "col" : this.body[0].col + 1}; this.body.unshift(toucha); break; case "L": var toucha = {"row": this.body[0].row, "col" : this.body[0].col - 1}; this.body.unshift(toucha); break; case "U": var toucha = {"row": this.body[0].row - 1, "col" : this.body[0].col}; this.body.unshift(toucha); break; case "D": var toucha = {"row": this.body[0].row + 1, "col" : this.body[0].col}; this.body.unshift(toucha); break; } }
Snake.prototype.changeDireciton = function(str){ this.direction = str; }
//繫結鍵盤監聽,呼叫changeDireciton改變方向方法
Game.prototype.bindEvent = function(){ var self = this; document.onkeydown = function(e){ switch(e.keyCode){ case 37: //按左鍵,如果當前往右走,不允許掉頭 if(self.snake.direction == "R") return; self.snake.changeDirection("L"); break; case 38: if(self.snake.direction == "D") return; self.snake.changeDirection("U"); break; case 39: if(self.snake.direction == "L") return; self.snake.changeDirection("R"); break; case 40: if(self.snake.direction == "U") return; self.snake.changeDirection("D"); break; } } }
第六步:食物類
l 我們採用每吃到一次食物,就重新new一個食物
l 食物不能隨機到蛇的身上(所在的表格中)
l 我們採用的是每一幀要清除所有小格的html內容,然後重新渲染所有小格
window.Game = function(){ ... this.snake = new Snake(); //例項化食物 this.food = new Food(this); this.start();//開啟遊戲定時器 this.bindEvent();//監聽事件 }
在Game.js中:
Game.prototype.start = function(){ this.timer = setInterval(function(){ self.clear();//清除螢幕 //更新蛇 self.f % 30 == 0 && self.snake.update(); self.snake.render();//渲染蛇 self.food.render();//渲染食物 }, 20); }
//設定食物的方法 Game.prototype.setHTML = function(row,col,html){ this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col] .innerHTML = html; }
Food.js類:
(function(){ window.Food = function(mediator){ //隨機食物位置,不能在蛇的身上 var self = this; do{ this.row = ~~(Math.random() * mediator.rowAmount); this.col = ~~(Math.random() * mediator.colAmount); }while((function(){ //IIFE的執行,返回true或fasle for(var i = 0;i < mediator.snake.body.length;i++){ if(mediator.snake.body[i].row == self.row && mediator.snake.body[j].col == self.col){ return true; //食物隨機到蛇身上,重新隨機一次 } return false; //如果食物不在蛇身上,終止迴圈 } })()); } Food.prototype.render = function(){ game.setHTML(this.row,this.col,"♥"); } })();
以下在Snake.js寫:
第七步:吃到食物蛇身變長
// 食物判斷 if(toucha.row == game.food.row && toucha.col == game.food.col){ //當你吃到食物的時候,不用刪尾巴,而且需要重新new一個食物 game.food = new Food(game); //傳上下文,要中介者(game) game.f = 0; }else{ //當沒有吃到食物時候,刪除尾巴一項 this.body.pop(); //尾刪 }
第八步:死亡判定,撞牆和撞自己
//撞牆判斷 if(toucha.row<0 ||toucha.col<0 || toucha.col > game.colAmount-1 || toucha.row > game.rowAmount-1){ alert("你撞牆了,長度是:" + this.body.length); this.body.shift(); //撞牆繼續頭插不合法 clearInterval(game.timer); }
// 撞自己判斷 for(var i = 1;i < this.body.length;i++){ if(toucha.row == this.body[i].row && toucha.col == this.body[i].col){ alert("撞自己啦!傻缺,長度是:" + this.body.length); this.body.shift(); //繼續頭插不合理 clearInterval(game.timer); } }
更新蛇,蛇越長速度越快
var s = self.snake.body.length < 10 ? 30 : 5; self.f % s == 0 && self.snake.update();
最後,解決bug。
Snake.prototype.changeDirection = function(str){ //更改未來的方向 this.willDirection = str; }
var Snake = window.Snake = function(){ //蛇的身體 .... //蛇當前動態的方向 this.direction = "R"; //即將設定的方向,這裡是為了防止使用者按很快出bug。 this.willDirection = "R"; }
Snake.prototype.update = function(){ //讓當前的方向和即將設定的方向一致,這裡是為了放置使用者按很快出bug。 this.direction = this.willDirection; }