作者: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裡參看,歡迎各位研究。