1. 程式人生 > >JS實現貪吃蛇—重點理解原型和自呼叫函式的沙箱

JS實現貪吃蛇—重點理解原型和自呼叫函式的沙箱

要點:

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>