JavaScript實現類的private、protected、public、static以及繼承
基礎知識
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
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
就出場了。我們知道,通過這個函數能夠設定獲取屬性時返回的值,也能夠設定更改屬性時設置的值。
有了這個函數,我們能夠隨時跟蹤到某個屬性是不是在被獲取,或者是不是在被更改。我們還須要一個開關,我們在類內部的方法調用時,把這個開關打開,表明是在內部執行。方法調用結束後將開關關閉。表明回到外部執行狀態。
有了這兩個狀態,我們就能夠跟蹤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裏參看,歡迎各位研究。
歡迎大家繼續關註我的博客
轉載請註明出處:Yorhom’s Game Box
http://blog.csdn.net/yorhomwang
JavaScript實現類的private、protected、public、static以及繼承