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

mufengsm發表於2019-04-18

一、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()函式本質是依次遍歷原陣列中的每一項,將每一項都執行一遍函式中的語句,返回一個新的陣列。

提示:

函式需要有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);

二、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.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

 


四、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'}
                }}
            ]
        }
    }]
};
演示程式碼

 


五、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()
兩個模式的區別:
觀察者模式能主動推送訊息,每個收聽者能夠實時得到釋出者的資訊;
中介者模式不主動推送訊息,當學生要寫作業,需要作業資訊時,主動去找中介者拿,適合時效性不強的資訊。

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

有的時候,專案中的類很多,沒有所謂的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;
}

 

相關文章