1. 程式人生 > >從零開始學 Web 之 JavaScript 高階(一)原型,貪吃蛇案例

從零開始學 Web 之 JavaScript 高階(一)原型,貪吃蛇案例

一、複習

例項物件和建構函式之間的關係:

1、例項物件是通過建構函式來建立的,建立的過程叫例項化。

2、如何判斷一個物件是不是某種資料型別?

  • 通過構造器的方法。例項物件.constructor === 建構函式名字
  • (推薦使用)例項物件 instanceof 建構函式名字

二、原型

1、原型的引入

由來:建構函式的問題。如果一個建構函式中有一個匿名方法,那麼每例項化一個物件,然後在物件呼叫這個方法的時候,由於每個物件的方法都是各自的,所以每次呼叫這個方法的時候都會在記憶體中開闢一塊空間儲存這個方法,這樣就造成記憶體資源的浪費。

解決方法:定義一個函式代替匿名方法。

由這個思想,提出原型的概念。

原型的作用之一:共享資料,節省記憶體空間。

1.1、用原型物件新增建構函式中的方法

function Person(name, age) {
        this.name = name;
        this.age= age;
    }
Person.prototype.eat = function () {
    console.log("haha");
};

var per1 = new Person("Daotin", 18);
var per2 = new Person("lvonve", 18);
console.log(per1);
console.log(Person);
console.log(per1.eat === per2.eat); // true

1、為 Person 建構函式新增 eat 方法,使得 Person 建立的例項物件呼叫的 eat 方法,共享記憶體空間。

2、例項物件 per 中有個屬性 __proto__ 也是物件,叫原型,它不是標準的屬性(IE8 不支援,谷歌和火狐支援)。

3、建構函式中有一個屬性 prototype 也是物件,叫原型。它是標準屬性,供開發人員使用。

4、per1.eat === per2.eat; 所以原型的作用之一:共享資料,節省記憶體空間。

2、案例:點選按鈕改變div屬性

(使用面向物件思想)

面向物件思想:按鈕是一個物件,div 是一個物件,樣式時一種屬性

<body>
<input type="button" value="按鈕" id="btn">
<div id="dv"></div>

<script src="common.js"></script>
<script>
    /*
    * 操作 Obj1 設定 Obj2 的屬性
    * 屬性列表在 json 裡面
    * */
    function ChangeStyle(Obj1, handle, Obj2, json) {
        this.Obj1 = Obj1;
        this.Obj2 = Obj2;
        this.handle = handle;
        this.json = json;
    }

    ChangeStyle.prototype.init = function () {
        var that = this;
        that.Obj1[that.handle] = function () {
            for (var key in that.json) {
                that.Obj2.style[key] = that.json[key];
            }
        };
    };

    var json = {"width": "200px", "height": "200px", "backgroundColor": "red"};
    var cs = new ChangeStyle(my$("btn"), "onclick", my$("dv"), json);

    cs.init();
</script>
</body>

3、建構函式,例項物件,原型物件三者的關係

1、例項物件是由建構函式建立的;

2、建構函式中有個屬性prototype,指向原型物件;

3、原型物件中有一個構造器,指向建構函式;

4、例項物件中的下劃線原型物件__proto__ 指向原型物件 prototype

5、原型物件中的方法可以被例項物件訪問,雖然例項物件中沒有這個方法,但是例項物件中 __proto__ 指向 prototype,所以所有的例項物件共享原型物件中的方法。

4、原型物件的簡單語法

什麼樣的資料需要新增到原型物件呢?

需要資料共享的資料,不論是屬性還是方法。

既然 prototype 是一個物件,那麼需要新增的屬性和方法就可以以物件的方法新增:

<script>
    function Person(name, age) {
        this.name = name;
        this.age= age;
    }

    Person.prototype = {
        // 手動修改構造器指向
        constructor: Person,
        sex: "man",
        eat: function () {
            console.log("eat");
        },
        study: function () {
            console.log("study");
        }
    };

    var per = new Person("lvovne", 18);
    console.log(per);
</script>

需要注意的是:這種寫法需要手動修改構造器指向,原型物件中將無這個構造器。

5、原型物件中的方法相互訪問

我們知道,例項物件中的方法是可以相互訪問的,那麼原型物件中的方法可以相互訪問嗎?

也是可以的。

6、原型中屬性和方法的使用順序

例項物件使用的屬性和方法會先在例項中查詢,找不到才會到__proto__ 指向的建構函式的原型物件 prototype 中找,找到了則使用,找不到則報錯。

7、為內建物件新增原型方法

像正常為自定義建構函式新增原型方法一樣。

8、把區域性變數變成全域性變數

把函式中的區域性變數暴露給瀏覽器頂級物件 window,那麼這個區域性變數將變成 window 的一個屬性,可以被整個瀏覽器所訪問。

(function () {
    var num = 10;
    window.num = num;
})();
console.log(num);

9、把區域性物件變成全域性物件

<script>
    // 產生隨機數物件
    (function () {
        function Random() {}
        Random.prototype.getRandom = function (min, max) { // 範圍 min ~ max-1
            return Math.floor(Math.random()*(max-min) + min);
        };
        window.Random = Random;
    })();

    var rd = new Random();
    var num = rd.getRandom(0,5);

    console.log(num);
    
</script>

這裡把自定義的產生隨機數的 Random 物件暴露給頂級物件 window,那麼 Random 從區域性物件變成全域性物件。

10、案例:隨機小方塊

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .map {
            width: 800px;
            height: 600px;
            background-color: #ccc;
            position: relative;
        }
    </style>
</head>
<body>
<div class="map"></div>

<script src="common.js"></script>
<script>

    // 獲取地圖物件
    var map = document.getElementsByClassName("map")[0];

    // 產生隨機數物件
    (function () {
        // 產生隨機數的建構函式
        function Random() {

        }
        // 在原型物件中新增方法
        Random.prototype.getRandom = function (min, max) { // 範圍 min ~ max-1
            return Math.floor(Math.random()*(max-min) + min);
        };

        window.Random = Random;
    })();

    // 產生小方塊物件
    (function () {
        // 食物的建構函式
        function Food(width, height, color) {
            this.width = width||20;
            this.height = height||20;
            this.color = color||"green";
            this.x = 0; // left屬性
            this.y = 0; // top屬性
            this.element = document.createElement("div"); // 食物例項物件
        }

        // 初始化小方塊的顯示效果及位置
        Food.prototype.init = function () {
            var div = this.element;

            div.style.position = "absolute";
            div.style.width = this.width + "px";
            div.style.height = this.height + "px";
            div.style.backgroundColor = this.color;
            map.appendChild(div);
            this.product(map);
        };

        // 產生小方塊的隨機位置
        Food.prototype.product = function () {
            var div = this.element;
            var x = new Random().getRandom(0, map.offsetWidth/this.width) * this.width;
            var y = new Random().getRandom(0, map.offsetHeight/this.height) * this.height;
            this.x = x;
            this.y = y;

            div.style.left = this.x + "px";
            div.style.top = this.y + "px";
        };

        window.Food = Food;

    })();

    var food = new Food(20,20,"red");
    food.init(map);

</script>
</body>
</html>

1、產生小方塊物件也是個自呼叫函式,這裡面有一個建構函式 Food,兩個原型函式 init 和 product,其中建構函式中包括小方塊的所有屬性,比如小方塊是個 div,小方塊的寬高,顏色,left,top 的值等。

2、init 方法初始化小方塊的寬高,顏色以及將 div 加入到地圖 map 中。

3、product 方法是產生隨機位置,並賦值給小方塊的 left,top。

4、最後,在產生小方塊物件的最後,將 Food 物件暴露給 window,這樣在 Food 自呼叫函式的外面也可以產生小方塊。

11、案例:貪吃蛇

這個案例按照面向物件的思想,我們把它分成四個模組。

第一,地圖模組;

第二,食物模組,也就是上面小方塊的模組;

第三,小蛇模組;

第四,整個遊戲也可以封裝成一個模組,這個模組是將上面三個模組再次封裝起來。

11.1、地圖模組

地圖模組最簡單了,就是一塊 div,設定寬高,背景就可以了。

注意:由於食物和小蛇都要在背景之上移動,所以食物和小蛇是脫標的,所以地圖需要設定 position: relative;

<style>
    .map {
        width: 800px;
        height: 600px;
        background-color: #ccc;
        position: relative;
    }
</style>

<div class="map"></div>

11.2、食物模組

首先,食物的主體是由一個 div 組成,另外還有寬高,背景顏色屬性,在食物脫標之後還有left,top屬性,所以為了建立一個食物物件,就需要一個食物的建構函式,這個建構函式要設定食物的屬性就是上面提到的屬性。

function Food(width, height, color) {
        this.width = width || 20;
        this.height = height || 20;
        this.color = color || "green";
        this.x = 0; // left屬性
        this.y = 0; // top屬性
        this.element = document.createElement("div"); // 食物例項物件
    }

別忘了將 Food 暴露給 window

window.Food = Food;

之後需要初始化食物的顯示效果和位置。

1、由於經常使用 init 函式,所以將其寫入原型物件。

2、每次在建立食物之前先刪除之前的小方塊,保證map中只有一個食物

3、我們需要在自呼叫函式中定義一個數組,用來儲存食物,方便每次建立食物之前的刪除和後來小蛇吃掉食物後的刪除。

Food.prototype.init = function (map) {
        // 每次在建立小方塊之前先刪除之前的小方塊,保證map中只有一個小方塊
        remove();

        var div = this.element;
        map.appendChild(div);

        div.style.width = this.width + "px";
        div.style.height = this.height + "px";
        div.style.backgroundColor = this.color;
        div.style.borderRadius = "50%";
        div.style.position = "absolute";

        this.x = new Random().getRandom(0, map.offsetWidth / this.width) * this.width;
        this.y = new Random().getRandom(0, map.offsetHeight / this.height) * this.height;
        div.style.left = this.x + "px";
        div.style.top = this.y + "px";

        // 把div加到陣列中
        elements.push(div);
    };

食物的刪除函式。設定為私有函式,其實就是自呼叫函式中的一個函式,保證不被自呼叫函式外面使用。

function remove() {
        for (var i = 0; i < elements.length; i++) {
            elements[i].parentElement.removeChild(elements[i]);
            elements.splice(i, 1); // 清空陣列,從i的位置刪除1個元素
        }
    }

11.3、小蛇模組

小蛇模組也得現有建構函式。

1、direction是小蛇移動的方向;

2、beforeDirection 是小蛇在鍵盤點選上下左右移動之前移動的方法,作用是不讓小蛇回頭,比如小蛇正往右走,不能點選左按鍵讓其往左走,這時只能上下和右走。

3、小蛇最初的身體是三個 div,所以每個 div 都是一個物件,有自己的寬高和背景顏色和座標。,所以這裡用一個數組儲存小蛇的身體。

function Snack(width, height, direction) {
        // 小蛇每一塊的寬高
        this.width = width || 20;
        this.height = height || 20;
        this.direction = direction || "right";
        this.beforeDirection = this.direction;
        // 小蛇組成身體的每個小方塊
        this.body = [
            {x: 3, y: 2, color: "red"},
            {x: 2, y: 2, color: "orange"},
            {x: 1, y: 2, color: "orange"}
        ];
    }

還是需要暴露給 window

window.Snack = Snack;

小蛇的初始化函式就就是設定建構函式中的屬性。由於有多個 div 組成,所以要迴圈設定。初始化之前也要先刪除小蛇。

Snack.prototype.init = function (map) {
        // 顯示小蛇之前刪除小蛇
        remove();

        // 迴圈建立小蛇身體div
        for (var i = 0; i < this.body.length; i++) {
            var div = document.createElement("div");
            map.appendChild(div);

            div.style.width = this.width + "px";
            div.style.height = this.height + "px";
            div.style.borderRadius = "50%";
            div.style.position = "absolute";

            // 移動方向,移動的時候設定

            // 座標位置
            var tempObj = this.body[i];
            div.style.left = tempObj.x * this.width + "px";
            div.style.top = tempObj.y * this.width + "px";
            div.style.backgroundColor = tempObj.color;

            // 將小蛇新增到陣列
            elements.push(div);
        }
    };

接下來是小蛇移動的方法。

1、小蛇移動的方法分兩步,第一步是身體的移動;第二步是頭部的移動。

2、當小蛇頭座標和食物的座標相同時,表示吃到食物,這個時候小蛇要增長,怎麼增長呢?將小蛇的尾巴賦值一份新增到小蛇的尾部。

3、之後刪除食物,並重新生成食物。

Snack.prototype.move = function (food, map) {
        var index = this.body.length - 1; // 小蛇身體的索引
        // 不考慮小蛇頭部
        for (var i = index; i > 0; i--) { // i>0 而不是 i>=0
            this.body[i].x = this.body[i - 1].x;
            this.body[i].y = this.body[i - 1].y;
        }

        // 小蛇頭部移動
        switch (this.direction) {
            case "right" :
                // if(this.beforeDirection !== "left") {
                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;
            default:
                break;
        }

        // 小蛇移動的時候,當小蛇偷座標和食物的座標相同表示吃到食物
        var headX = this.body[0].x * this.width;
        var headY = this.body[0].y * this.height;

        // 吃到食物,將尾巴複製一份加到小蛇body最後
        if ((headX === food.x) && (headY === food.y)) {
            var last = this.body[this.body.length - 1];
            this.body.push({
                x: last.x,
                y: last.y,
                color: last.color
            });

            // 刪除食物
            food.init(map);
        }
    };

11.4、遊戲模組

首先建立遊戲物件需要遊戲建構函式,這個建構函式應該包含三個部分:食物,小蛇和地圖。

這個 that 是為了以後進入定時器後的 this 是 window,而不是 Game 做的準備。

function Game(map) {
        this.food = new Food(20, 20, "purple");
        this.snack = new Snack(20, 20);
        this.map = map;
        that = this;
    }

然後是遊戲初始化函式,初始化遊戲的目的就是讓遊戲開始,使用者可以開始玩遊戲了。

這裡面呼叫了兩個函式:autoRun 和 changeDirection。

Game.prototype.init = function () {
    this.food.init(this.map);
    this.snack.init(this.map);

    this.autoRun();
    this.changeDirection();
};

autoRun 是小蛇自動跑起來函式。這個函式主要是讓小蛇動起來,並且在碰到邊界時結束遊戲。

注意:這裡有一個在函式後面使用 bind 函式:使用bind,那麼 setInterval 方法中所有的 this 都將被bind 的引數 that 替換,而這個 that 就是 Game。

Game.prototype.autoRun = function () {
    var timeId = setInterval(function () {
        this.snack.move(this.food, this.map);
        this.snack.init(this.map);

        // 判斷最大X,Y邊界
        var maxX = this.map.offsetWidth / this.snack.width;
        var maxY = this.map.offsetHeight / this.snack.height;

        // X方向邊界
        if ((this.snack.body[0].x < 0) || (this.snack.body[0].x >= maxX)) {
            // 撞牆了
            clearInterval(timeId);
            alert("Oops, Game Over!");
        }

        // Y方向邊界
        if ((this.snack.body[0].y < 0) || (this.snack.body[0].y >= maxY)) {
            // 撞牆了
            clearInterval(timeId);
            alert("Oops, Game Over!");
        }

    }.bind(that), 150); // 使用bind,那麼init方法中所有的this都將被bind的引數that替換
};

changeDirection 是監聽按鍵,改變小蛇的走向。

這裡面按鍵按下的事件是 onkeydown 事件。每個按鍵按下都會有一個對應的 keyCode 值,通過這個值就可以判斷使用者按下的是哪個鍵。

Game.prototype.changeDirection = function () {

    addAnyEventListener(document, "keydown", function (e) {

        // 每次按鍵之前儲存按鍵方向
        this.snack.beforeDirection = this.snack.direction;

        switch (e.keyCode) {
            case 37: // 左
                this.snack.beforeDirection !== "right" ? this.snack.direction = "left" : this.snack.direction = "right";
                break;
            case 38: // 上
                this.snack.beforeDirection !== "down" ? this.snack.direction = "up" : this.snack.direction = "down";
                break;
            case 39: // 右
                this.snack.beforeDirection !== "left" ? this.snack.direction = "right" : this.snack.direction = "left";
                break;
            case 40: // 下
                this.snack.beforeDirection !== "up" ? this.snack.direction = "down" : this.snack.direction = "up";
                break;
            default:
                break;
        }
    }.bind(that));

};

11.5、開始遊戲

最後,我們只需要兩行程式碼就可以開啟遊戲。

var game = new Game(document.getElementsByClassName("map")[0]);
game.init();

Web前端之巔

相關推薦

開始 Web JavaScript 高階原型貪吃案例

一、複習 例項物件和建構函式之間的關係: 1、例項物件是通過建構函式來建立的,建立的過程叫例項化。 2、如何判斷一個物件是不是某種資料型別? 通過構造器的方法。例項物件.constructor === 建構函式名字 (推薦使用)例項物件 instanceof 建構函式名字 二、原型 1、原型的引入 由

開始 Web JS 高階原型原型的繼承

一、原型鏈 原型連結串列示的是例項物件與原型物件之間的一種關係,這種關係是通過__proto__原型來聯絡的。 1、原型的指向改變 例項物件的原型 __proto__ 指向的是該物件的建構函式中的原型物件 prototype,如果該物件的建構函式的 prototype 指向改變了,那麼例項物件中的原型 _

開始 Web JS 高階apply與callbind閉包和沙箱

一、apply 和 call 方法 apply 和 call 都可以改變呼叫其的函式或方法中的 this 指向。 不同的是傳入引數時,apply 有兩個引數,第二個引數是陣列;call 從第二個引數開始是呼叫其的函式的所有引數。 使用方法: 1、apply的使用語法: 函式名.apply(物件,[引數1

開始 Web Vue.js過濾器按鍵修飾符自定義指令

大家好,這裡是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 部落格園:http://www.cnblogs.com/lvonve/ CSDN:https://blog.csdn.net/lvonve/

開始 Web Vue.jsVue.js概述基本結構指令事件修飾符樣式

大家好,這裡是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 部落格園:http://www.cnblogs.com/lvonve/ CSDN:https://blog.csdn.net/lvonve/

開始 Web Vue.jsVue實例的生命周期

報錯 web 前端 cnblogs 前端 eth code vue 公眾 des 大家好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公眾號:Web前端之巔

開始 Web Vue.jsVue的Ajax請求和跨域

在線安裝 配置 name php文件 splay .json alert 參考 1.0 大家好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公眾號:Web前端

開始 Web Vue.jsVue的動畫

一、Vue的動畫 為什麼要有動畫:動畫能夠提高使用者的體驗,幫助使用者更好的理解頁面中的功能; Vue 中也有動畫,不過遠沒有 css3 中的那麼炫酷。只能有一些簡單的變換,但是卻可以配合第三方css動畫庫完成炫酷的變換。 1、過渡的類名 在進入/離開的過渡

開始 Web Vue.jsVue的元件

大家好,這裡是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 部落格園:http://www.cnblogs.com/lvonve/ CSDN:https://blog.csdn.net/lvonve/

開始 Web JavaScript陣列

大家好,這裡是「 Daotin的夢囈 」從零開始學 Web 系列教程。此文首發於「 Daotin的夢囈 」公眾號,歡迎大家訂閱關注。在這裡我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的專案。現在就讓我們一起進入 Web 前端學習的冒險之旅吧! 一、陣列 1、陣列

開始 Web JavaScript變數

大家好,這裡是「 Daotin的夢囈 」從零開始學 Web 系列教程。此文首發於「 Daotin的夢囈 」公眾號,歡迎大家訂閱關注。在這裡我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的專案。現在就讓我們一起進入 Web 前端學習的冒險之旅吧! 一、變數 1、變數

開始 Web JavaScript面向物件

大家好,這裡是「 Daotin的夢囈 」從零開始學 Web 系列教程。此文首發於「 Daotin的夢囈 」公眾號,歡迎大家訂閱關注。在這裡我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的專案。現在就讓我們一起進入 Web 前端學習的冒險之旅吧! 一、面向物件 1、

開始 Web JavaScriptJavaScript概述

大家好,這裡是「 Daotin的夢囈 」從零開始學 Web 系列教程。此文首發於「 Daotin的夢囈 」公眾號,歡迎大家訂閱關注。在這裡我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的專案。現在就讓我們一起進入 Web 前端學習的冒險之旅吧! 一、JavaScr

開始 Web JavaScript函式

大家好,這裡是「 Daotin的夢囈 」從零開始學 Web 系列教程。此文首發於「 Daotin的夢囈 」公眾號,歡迎大家訂閱關注。在這裡我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的專案。現在就讓我們一起進入 Web 前端學習的冒險之旅吧! 一、函式 1、函式

轉載:高德地圖API學習 開始高德JS API地圖展現

摘要:關於地圖的顯示,我想大家最關心的就是麻點圖,自定義底圖的解決方案了吧。在過去,marker大於500之後,瀏覽器開始逐漸卡死,大家都開始尋找解決方案,比如聚合marker啊,比如麻點圖啊。聚合marker裡面還有一些複雜的演算法,而麻點圖,最讓大家頭疼的,就是如何生成麻點圖,如何切圖,如何把圖片貼到地圖

開始 Web DOMDOM的概念對標簽操作

關註 1.5 pan 什麽 tin p標簽 nod text == 大家好,這裏是「 Daotin的夢囈 」從零開始學 Web 系列教程。此文首發於「 Daotin的夢囈 」公眾號,歡迎大家訂閱關註。在這裏我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,

開始 Web DOM節點

def clas scrip while p標簽 設置 ner 操作 text 大家好,這裏是「 Daotin的夢囈 」從零開始學 Web 系列教程。此文首發於「 Daotin的夢囈 」公眾號,歡迎大家訂閱關註。在這裏我會從 Web 前端零基礎開始,一步步學習 Web 相

開始 Web BOMoffsetscroll變速動畫函數

樣式 清理 java mar dde sof mov har width 大家好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公眾號:Web前端之巔 博客園:

開始 Web JS 高級原型原型的繼承

console 多少 程序 cat hub inf 當前 構造函數 調用 大家好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公眾號:Web前端之巔 博客園:

開始 Web JS 高級apply與callbind閉包和沙箱

master 操作 console 概念 釋放 分享圖片 成功 num 命名沖突 大家好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公眾號:Web前端之巔