前端筆記之JavaScript面向對象(三)初識ES6&underscore.js&EChart.js&設計模式&貪吃蛇開發

1、ES6語法

ES6中對數組新增了幾個函數:map()filter()reduce()javascript

ES5新增的forEach()css

都是一些語法糖。html

1.1 forEach()遍歷數組

forEach()方法用來循環遍歷數組,方法中的function回調函數接收3個參數前端

參數1是遍歷的數組內容(item);參數2是對應的數組索引(index),參數3是是數組自己(array)java

[].forEach(function(item,index,array){
...
})
var arr = ["白板","幺雞","紅中","發財","八萬"];
arr.forEach(function(item,index,array){
   console.log(item,index,array)
})

forEach函數沒有返回值jquery

for從此是建立數組,遍歷或操做數組能夠交給forEach方法。ajax


1.2 map()映射

map方法的做用,「映射」也就是原數組被「映射」成對應的新數組編程

[].map(function(item,index,array){
...
})

var arr = ["白板","幺雞","紅中","發財","八萬"];
arr.map(function(item,index,array){
   console.log(item,index,array)
})

 

 

案例:好比建立一個新數組,新數組的每一項是原數組的兩倍json

var arr1 = [6,8,10,88,100,200];
var arr2 = arr1.map(function(item){
   return item * 2;
})
console.log(arr1)
console.log(arr2)

map()函數本質是依次遍歷原數組中的每一項,將每一項都執行一遍函數中的語句,返回一個新的數組。小程序

提示:

函數須要有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則捨棄,進入下一次循環。

 

filtermap相同:都會遍歷數組每一項。

filtermap不相同:map返回數組不會少項,filter可能會少項。


1.4取整運算符

console.log(~~11.5);
console.log(~~"11.5");
console.log(~~"10px");
console.log(~~true);
console.log(~~false);

2、underscore.js

jQueryDOM之王,那麼underscore就是數學之王(擅長計算)。

Underscore一個JavaScript實用庫,提供了一整套函數式編程的實用功能,可是沒有擴展任何JavaScript內置對象。

Underscore提供了100多個函數,包括經常使用的: map, filter, invoke 固然還有更多專業的輔助函數,:函數綁定, JavaScript模板功能,建立快速索引, 強類型相等測試, 等等.

Underscore不依賴環境,不限制使用場景,能夠加載到HTML中在瀏覽器運行,也能夠中Nodejs服務器環境中使用。封裝了一堆實用函數,這些函數基本都是針對:數組、對象、函數的。

官網:http://underscorejs.org/

中文文檔: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、模板引擎

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>
使用ajax

 


4、EChart.js(前端數據可視化)

官網:http://echarts.baidu.com

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);

餅狀圖和折線圖、柱狀圖有一點區別,主要是在參數和數據綁定上。餅狀圖沒有XY軸座標,數據綁定也是採用valuename對應的形式。

 

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'}
                }}
            ]
        }
    }]
};
演示代碼

 


5、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。它定義了對象間的一種1n的依賴關係。當一個對象的狀態發生改變時,全部「訂閱」了它的對象都將獲得通知。

剛剛老師發佈做業的案例,老師就是發佈者(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()
兩個模式的區別:
觀察者模式能主動推送消息,每一個收聽者可以實時獲得發佈者的信息;
中介者模式不主動推送消息,當學生要寫做業,須要做業信息時,主動去找中介者拿,適合時效性不強的信息。

6、貪吃蛇遊戲-中介者模式

有的時候,項目中的類不少,沒有所謂的1:n1n)的關係,它們感受「互爲信息源」,此時中介者模式是最簡單的。好比蛇須要食物,食物也要蛇的信息。

 

用貪吃蛇來舉例:

遊戲有三個類:遊戲類(Game)、蛇類(Snake)、食物類(Food

遊戲類其實就是中介者,蛇和食物都須要經過Game來交換獲取信息。

 

Game類是中介者類,SnakeFood類是普通類,被Game管理。

注意:

中介者必須有惟一的實例化對象,這個對象的名字不能更改,好比羣號碼不能更改。在貪吃蛇遊戲中,咱們就要:

var game = new Game();
此時game變量名一旦肯定就不要改了。

 

除了中介者以外,其餘全部的對象,都須要由中介者來實例化,在貪吃蛇遊戲中,Snake蛇、Food食物類由Game類來實例化,它們都是Game類的子屬性。

function Game(){
this.snake = new Snake();
this.food = new Food();
}

 

FoodSnake之間若是互相要用信息,好比蛇要知道食物的位置,用中介者:

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;
        }
    }
}
鍵盤監聽

 

 

 

第六步:食物類

咱們採用每吃到一次食物,就從新new一個食物

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,"♥");
    }
})();
Food.js

 

 

如下在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;
}
相關文章
相關標籤/搜索