JS實現貪吃蛇—重點理解原型和自呼叫函式的沙箱
阿新 • • 發佈:2018-12-09
要點:
1.通過為建構函式的原型物件新增屬性或者方法,可以實現資料共享,節省記憶體空間,不需要每次重新定義。如果建構函式中的屬性或者方法跟原型物件中衝突,以建構函式為準,因為原型物件就是建立在建構函式的基礎之上的。
注意瀏覽器中使用的例項物件中的原型__proto__(兩個英文狀態下的下劃線)和建構函式中的原型prototype結構是一樣的,但是前一種是通過建構函式的原型建立的,指向後一種,並且後一種是標準寫法,我們使用這個。
2.原型方法中,可以呼叫此原型物件的其他的原型方法。
3.函式的自呼叫,後面必須要寫分號,不然瀏覽器這個不能和function()這種分開,造成各種錯誤。函式的自呼叫,是為了避免命名衝突。這就構成了沙箱,可以不影響其他部分。
4.函式自呼叫中定義的物件,是隻能在自呼叫函式內部使用的,如果想要在全域性範圍內進行呼叫的話,可以將這個物件,賦值給瀏覽器的頂級物件,window,這樣變成全域性物件。
5.在這個例子中,有大量地方使用this關鍵字,原來簡單的函式指代是明確,通過自呼叫或者新增一個定時器,這時this關鍵字可能不是指向這個例項物件,而是呼叫定時器的window物件,通過.bind()改變this的指向,以後再詳說。
程式碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> .map{ width: 960px; height: 600px; background-color: #C5ACAC; position: relative; margin: 0 auto; } </style> </head> <body> <div class="map"></div> <script type="text/javascript"> (function(){ var elements = []; // 用來存放吃了多少食物 function Food(x,y,width,height,color){ this.x = x || 0; this.y = y || 0; this.width = width || 20; this.height = height || 20; this.color = color || "blue"; } // 設定食物的基本樣式橫縱座標和寬高以及背景顏色,傳值使用傳值,不傳使用預設的引數 Food.prototype.init = function(map){ remove(); // 呼叫刪除函式,外部無法訪問的 var food = document.createElement("DIV"); map.appendChild(food); // 將食物新增到地圖上 food.style.width = this.width + "px"; food.style.height = this.height + "px"; food.style.backgroundColor = this.color; // 設定基本樣式,注意JS中不能設定連字元樣式,要用駝峰原則,不然報Invalid left-hand side in assignment food.style.position = "absolute"; this.x = parseInt(Math.random()*(map.offsetWidth/this.width)) * this.width; this.y = parseInt(Math.random()*(map.offsetHeight/this.height)) * this.height; food.style.left = this.x + "px"; food.style.top = this.y + "px"; elements.push(food); // 把生成的div新增到elements陣列中 } // 為食物的原型物件新增初始化的方法,這裡沒有傳入引數使用預設的值 function remove(){ for(var i=0; i<elements.length; i++){ var ele = elements[i]; ele.parentNode.removeChild(ele); // 找到陣列中的父元素後再刪除其子元素,來刪除這個元素,這是map中的元素 elements.splice(i,1); // 通過陣列操作,在陣列中的這個元素 } } window.Food = Food; // 將自呼叫函式內部宣告的區域性的函式,賦給瀏覽器的頂級物件window,轉換為全域性函式 }()); // 函式自呼叫的方式建立食物,必須要寫分號,不然瀏覽器這個不能和function()這種分開,造成各種錯誤 (function(){ var elements = []; // 存放貪吃蛇身體的部分 function Snake(width,height,direction){ this.width = width || 20; this.height = height || 20; // 貪吃蛇每個部分的寬高 this.body = [ {x:3,y:2,color:"red"}, {x:2,y:2,color:"green"}, {x:1,y:2,color:"green"} ]; // 貪吃蛇的身體,因為要不斷的變大,所以放在陣列中 this.direction = direction || "right"; // 貪吃蛇前進的方向 } // 構建貪吃蛇 Snake.prototype.init = function(map){ remove(); // 每次建立貪吃蛇,刪除前面一條,這裡是呼叫下面建立 for(var i=0; i<this.body.length; i++){ var obj = this.body[i]; // 獲取設定的樣式原來的物件 var snackBody = document.createElement("DIV"); map.appendChild(snackBody); snackBody.style.position = "absolute"; snackBody.style.width = this.width + "px"; snackBody.style.height = this.height + "px"; // 基本樣式 snackBody.style.left = obj.x * this.width + "px"; snackBody.style.top = obj.y * this.height + "px"; snackBody.style.backgroundColor = obj.color; // 將特定的座標位置和顏色複製下來 elements.push(snackBody); // 將貪吃蛇物件存放到陣列中 } function remove(){ var i = elements.length -1; for(i; i>=0; i--){ var ele = elements[i]; ele.parentNode.removeChild(ele); elements.splice(i,1); } } // 從後往前刪除貪吃蛇,使用者看到的加長部分是原來貪吃蛇的蛇尾部分 } // 為小蛇物件的原型物件新增方法,每次貪吃蛇移動的時候,刪除之前的建立新的,利用原型節省記憶體 Snake.prototype.move = function(food,map){ var i = this.body.length - 1; // 除了頭部 for( i; i>0; i--){ this.body[i].x = this.body[i-1].x; this.body[i].y = this.body[i-1].y; } // 頭部後面的,當移動一下,後面的小方塊佔用前面的小方塊的位置 switch(this.direction){ case "right":this.body[0].x += 1;break; case "left":this.body[0].x -= 1;break; case "up":this.body[0].y -= 1;break; case "down":this.body[0].y += 1;break; } // 頭部小方塊根據方向決定座標的改變 var headX = this.body[0].x * this.width; var headY = this.body[0].y * this.height; // 獲取貪吃蛇的頭部的座標 var foodX = food.x; var foodY = food.y; // 獲取食物的座標位置 if(headX == foodX && headY == foodY){ var last = this.body[this.body.length-1]; // 獲取貪吃蛇蛇尾元素 this.body.push({ x:last.x, y:last.y, color:last.color }); // 當蛇頭跟食物的座標一致時,在最後新增上一步獲取的元素,此時蛇尾兩個元素重合 food.init(map); // 重新初始化,這個函式呼叫時,會刪除之前的元素 } } window.Snake = Snake; // 將貪吃蛇物件給瀏覽器頂級物件 }()); // 函式自呼叫的方式建立貪吃蛇 (function(){ var that = null; //用來替代定時器使用的this,造成的么蛾子 function Game(map){ this.food = new Food(); this.snack = new Snake(); this.map = map; // 一個遊戲物件,需要食物,貪吃蛇和地圖三個物件 that = this; // 用that指代呼叫的物件 } Game.prototype.init = function(){ this.food.init(this.map); this.snack.init(this.map); // 初始化食物和貪吃蛇 this.runSnake(this.food,this.map); this.bindKey(); } Game.prototype.runSnake = function(food,map){ var intervalName = setInterval(function(){ this.snack.move(food,map); this.snack.init(map); // this指代的是瀏覽器頂級物件window var maxX = map.offsetWidth/this.snack.width; var maxY = map.offsetHeight/this.snack.height; // 蛇能移動到的最大的座標 var headX = this.snack.body[0].x; var headY = this.snack.body[0].y; if(headX<0 || headX>=maxX){ clearInterval(intervalName); alert("Game Over!"); } if(headY<0 || headY>=maxY){ clearInterval(intervalName); alert("Game Over!"); } }.bind(that),100); // 自動移動的方法.這裡的this,指代的是window,用.bind()方法進行物件的更換 } Game.prototype.bindKey = function(){ document.addEventListener("keydown",function(e){ switch (e.keyCode){ // .keyCode表示鍵盤碼 case 37:this.snack.direction = "left";break; case 38:this.snack.direction = "up";break; case 39:this.snack.direction = "right";break; case 40:this.snack.direction = "down";break; } }.bind(that),false) // 同樣的,這裡面使用this關鍵字的話,指代的是觸發鍵盤彈起事件的物件,用that替換 }; // 改變貪吃蛇移動的方向 window.Game = Game; // 設為全域性物件 }()); var gm = new Game(document.querySelector(".map")); gm.init(); </script> </body> </html>