1. 程式人生 > >JavaScript實現類的private、protected、public、static以及繼承

JavaScript實現類的private、protected、public、static以及繼承

iter web 支持 原理 pan 現象 static 遊戲 inter

基礎知識

JavaScript中的類

JavaScript實際上是一種弱類型語言,與C++和Java等語言不同。因此,在JavaScript中。沒有強調類(class)這一概念,但實際運用中,類還是非常重要的,比方寫一款遊戲。假設我們不停地調用函數來完畢創建角色,移動角色的話,那會是什麽樣的呢?可能會出現非常多的反復代碼,因此我們須要一個類來統一這些代碼。所謂的類,就是把程序中的代碼分類,比方說遊戲中的關於角色的代碼算作一類,遊戲背景算作一類,遊戲特效又是一類。這樣一來,我們對類進行操作。就不會使代碼顯得非常淩亂。冗雜。盡管Js是弱類型語言,可是也提供了類這一概率。


定義Js中的類,實際上用的是function。總所周知,這個語法事實上是用來定義函數的。不同於定義函數的是,我們能夠在function中通過this.xxx的方式來定義屬性和方法。

比方說:

function People () {
    this.name = "Yorhom";

    this.getName = function () {
        return this.name
    };
}

使用的時候使用new

var yorhom = new People();
// "Yorhom"
alert(yorhom.getName());

能夠看到。這樣就能夠使用到我們定義的類和類中的方法了。
或許你會問this.xxx

僅僅能定義公有屬性和方法。那私有屬性和方法怎麽辦呢?這個能夠用到js閉包的知識來解決:

function People () {
    this.name = "Yorhom";

    var age = 16;

    this.getName = function () {
        return this.name
    };

    this.getAge = function () {
        return age;
    };
}

var yorhom = new People();
// undefined
alert(yorhom.age);
// 16
alert(yorhom.getAge());

能夠看到,這裏的age就是一個私有屬性了。

JavaScript中的prototype

上面的代碼美中不足的地方就是,假設一個類有非常多方法,同一時候用到這個類的地方又有非常多(也就是new出來的對象有非常多),那麽用上面的代碼就會出現內存占用過剩的問題。問題的根本原因在於,每次實例化一個對象,這個類就會執行構造器裏的代碼(以People類為例就是function People () {…}執行的代碼)。因此每當這個類被實例化的時候,這些方法和屬性就會被復制到實例化出來的對象中。這樣一來,就會造成“吃”內存的現象。
於是js中的prototype就誕生了。prototype的作用一般是給一個類加入一系列常量或者方法。 每當一個類被實例化之後。實例化出來的對象會自己主動獲取類的prototype中定義的方法和屬性。

僅僅只是這裏的獲取相似於C++裏面的引用,不會在內存裏對這些方法和屬性進行復制,而是指向這些方法和屬性。演示樣例:

function People () {
    this.name = "Yorhom";
}

People.prototype.getName = function () {
    return this.name;
};

var yorhom = new People();
// "Yorhom"
alert(yorhom.getName());

這樣的方法盡管能夠節約內存,可是。美中不足的是,無法定義私有屬性。

類的繼承

Javascript沒有提供繼承的函數。所以僅僅有自己寫了。

這裏借用lufylegend.js中的繼承方法向大家展示怎樣實現繼承:

function base (d, b, a) {
    var p = null, o = d.constructor.prototype, h = {};

    for (p in o) {
        h[p] = 1;
    }
    for (p in b.prototype) {
        if (!h[p]) {
            o[p] = b.prototype[p];
        }
    }

    b.apply(d, a);
}

這裏的base就是繼承函數了。繼承函數的原理莫過於復制類的方法和屬性。因此。僅僅要做到這點,就能夠實現類的繼承了。能夠在上面的代碼中看見,我們通過遍歷prototype來獲取原型鏈中定義的方法和屬性。通過apply調用父類的構造器進行構造器中屬性和方法的復制。使用演示樣例:

function People () {
    this.name = "Yorhom";
}

People.prototype.getName = function () {
    return this.name;
};

function Student () {
    base(this, People, []);
}

var yorhom = new Student();
// "Yorhom"
alert(yorhom.getName());

靜態屬性和方法的定義

靜態屬性和方法以及靜態類在js中的定義非常easy。先來看靜態類:

var StaticClass = {};

這麽寫不是在定義一個Object嗎?是的,不錯。只是js中的靜態類也是能夠這樣定義的。假設要加入靜態類中的方法和屬性,就能夠這麽寫:

var StaticClass = {
    id : 5,
    sayHello : function () {
        alert("Hello"); 
    }
};

假設是要向類中加入靜態屬性或者方法,能夠採用這樣的寫法:

function People () {
    this.name = "Yorhom";
}

People.prototype.getName = function () {
    return this.name;
};

People.TYPE = "people";
People.sayHello = function () {
    alert("Hello");
};

實現一個功能豐富的類

我們在上文中提到了,節省內存和定義私有屬性兩者無法兼得,是啊,和“魚和熊掌不可兼得”是一個道理。在通常的使用過程中,我們須要對這兩項進行取舍。可是如今這個年代。哪有不可兼得的呢?魚和熊掌不能同一時候吃?當然不行……由於吃熊掌是違法的(有待考證)?只是至少雞和魚是能夠同一時候吃的吧。
由於js沒有實現私有屬性的定義。所以這事實上是一個沒有頭緒的工作,由於在標準的做法中,我們除了閉包能夠阻止外部訪問。沒有別的辦法了。所以這裏我們要用點歪門邪道的方法了。

JavaScript Set/Get訪問器

什麽是set/get訪問器呢?假設你熟悉python,那麽你能夠理解為@property@xxx.setter,可是簡陋的js裏也有?當然有,僅僅只是是ES5的標準,能夠採用這樣的寫法:

Object.defineProperty(this, "name", {
    get : funtion () {
        return name;
    },

    set : function (v) {
        name = v;
    }
});

詳細有什麽用呢?大致就是this.name屬性在被獲取的時候調用get訪問器,在被更改值的時候調用set
你能夠從上面的代碼了解大致的寫法,只是假設你想深究,能夠參考這篇文章:http://blog.csdn.net/teajs/article/details/22738851

註意以上的這樣的使用方法會有兼容性問題。瀏覽器支持情況例如以下:

PC端

Firefox Google Chrome Internet Explorer Opera Safari
4.0 5 9 11.6 5.1

移動端

Firefox Mobile Android IE Mobile Opera Mobile Safari Mobile
4.0 Yes 9 11.5 Yes

來自: https://developer.mozilla.org/…/defineProperty#Browser_compatibility

怎樣“歪門邪道”地做到禁止訪問私有和保護屬性?

這是個比較頭疼的問題,正如本節開篇所說,我們在常規開發下。僅僅能通過閉包來阻止某變量的訪問。

可是假設你使用了prototype。那麽閉包這條路就走不通了。在這樣的情況下,我們的Object.defineProperty就出場了。我們知道,通過這個函數能夠設定獲取屬性時返回的值,也能夠設定更改屬性時設置的值。

有了這個函數,我們能夠隨時跟蹤到某個屬性是不是在被獲取,或者是不是在被更改。我們還須要一個開關,我們在類內部的方法調用時,把這個開關打開,表明是在內部執行。方法調用結束後將開關關閉。表明回到外部執行狀態。

有了這兩個狀態,我們就能夠跟蹤privateprotected屬性和方法了,一旦他們在開關關閉的時候被使用。就終止這個屬性或方法的獲取或設置。
於是乎,大難題就快攻克了。

開源庫件jpp.js

秉著這個歪門邪道的思想,我把這個功能封裝到jpp.js這個庫件中,庫件的github地址例如以下:
https://github.com/yuehaowang/jpp.js
當然這個庫件不限於創建一個類。還能夠實現函數的重載等。眼下庫件還處於開發階段,歡迎各位提交建議。

使用jpp.js創建一個類

var People = jpp.class({
    extends : null,
    private : {
        id : null,
        hobby : null
    },
    protected : {
        money : null,
        phoneNumber : null
    },
    public : {
        firstName : null,
        lastName : null,
        age : null,
        birthday : null,
        occupation : null,

        constructor : function (name, id) {
            if (name) {
                var nameArray = name.split(" ");

                this.firstName = nameArray[0];
                this.lastName = nameArray[1];
            }

            if (id) {
                this.id = id;
            }
        },

        setBirthday : function (date) {
            if (date) {
                this.birthday = date;
            }
        },

        getBirthday : function () {
            return this.birthday;
        },

        askForId : function () {
            return this.id;
        },

        findHobby : function () {
            return this.hobby;
        }
    },
    static : {
        OCCUPATION_PROGRAMMER : "programmer",
        OCCUPATION_ARTIST : "artist",
        OCCUPATION_MUSICIAN : "musician",
        OCCUPATION_STUDENT : "student"
    }
});

var peter = new People("Peter Wong", 543232123565);
peter.occupation = People.OCCUPATION_PROGRAMMER;

peter.setBirthday("19980727");

// result: Peter
alert(peter.firstName);
// result: 19990727
alert(peter.getBirthday());
// result: 51092028
alert(peter.askForId());
// result: null
alert(peter.findHobby());
// result: programmer
alert(peter.occupation);
// error
alert(peter.id);

對上面的代碼進行分析:
使用jpp.class函數創建一個類,函數的參數是一個Object,這個Object可加入的屬性例如以下:

  • extends 繼承時的父類
  • private 裝載私有屬性,裏面定義的成員外部不可使用且不能繼承給子類
  • protected 裝載保護屬性,裏面定義的成員外部不可使用但能夠繼承給子類
  • public 裝載公有屬性
  • static 裝載靜態方法和屬性

在創建類的過程中,在public中加入constructor方法初始化構造器,this.super可訪問父類構造器。

執行代碼,能夠看到瀏覽器正常執行前5個alert。而最後一個執行的時候瀏覽器報錯:

技術分享

詳細的實現過程有點復雜。只是原理在上文已經詳細講述了。代碼能夠在github裏參看,歡迎各位研究。


歡迎大家繼續關註我的博客

轉載請註明出處:Yorhom’s Game Box

http://blog.csdn.net/yorhomwang

JavaScript實現類的private、protected、public、static以及繼承