歡迎光臨
每天分享高質量文章

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

作者:Yorhom’s Game Box

網址:http://blog.csdn.net/yorhomwang/article/details/47807969

基礎知識

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中的定義非常簡單,先來看靜態類:

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就出場了。我們知道,透過這個函式可以設定獲取屬性時傳回的值,也可以設定更改屬性時設定的值。有了這個函式,我們可以隨時跟蹤到某個屬性是不是在被獲取,或者是不是在被更改。我們還需要一個開關,我們在類內部的方法呼叫時,把這個開關開啟,表明是在內部執行,方法呼叫結束後將開關關閉,表明回到外部執行狀態。有了這兩個狀態,我們就可以跟蹤private和protected屬性和方法了,一旦他們在開關關閉的時候被使用,就終止這個屬性或方法的獲取或設定。

於是乎,大難題就快解決了。

開源庫件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裡參看,歡迎各位研究。

贊(0)

分享創造快樂