Java代碼
var Player = (function(){
Player = function(){ //這隻是個空殼
throw new Error("Can not instantiate a Player object.");
};
Player.MIN_EXTENDED_TIME = 1;
Player.MAX_EXTENDED_TIME = 3;
Player._player = false;
Player.getInstance = function(){
if(!Player._player){
alert("Init…");
Player._player = {
_name : name,
setName : function(name){
this._name = name;
},
toString : function(name){
return "Player: " + this._name;
}
};
}
return Player._player;
};
return Player; //把修繕完工的Player這個組件方法返回
})();
//var player = new Player(); //new Player()會拋出異常
var player1 = Player.getInstance();
var player2 = Player.getInstance();
player2.setName("RealPlayer");
alert(player2.toString()); //輸出RealPlayer
終於要定義一個組件方法瞭,利用原型來實現。看看這樣如何:
Java代碼
function Player(name){
Player.MIN_EXTENDED_TIME = 1;
Player.MAX_EXTENDED_TIME = 3;
this._name = name;
};
Player.prototype.setName = function(name){
this._name = name;
};
Player.prototype.toString = function(){
return "Player: " + this._name;
};
var player = new Player("WindowsMediaPlayer");
alert(player.toString()); //輸出WindowsMediaPlayer
player.setName("RealPlayer");
alert(player.toString()); //輸出RealPlayer
alert(Player.MAX_EXTENDED_TIME);
恩,有封裝、有常量、也有復寫瞭Object的toString方法,至於繼承之類的事情,咱們後面再說,初看看還不錯。可是這樣的組件方法定義不夠優雅,也不夠直觀,方法都是放在獨立的位置定義的,並沒有和最開始的組件方法放置在一起,如果能像Java那樣定義豈不更好?
對瞭,可以用閉包來實現。試試看吧:
Java代碼
function Player(name){
Player.MIN_EXTENDED_TIME = 1;
Player.MAX_EXTENDED_TIME = 3;
this._name = name;
this.setName = function(name){
this._name = name;
};
this.toString = function(){
return "Player: " + this._name;
};
};
var player = new Player("WindowsMediaPlayer");
alert(player.toString()); //輸出WindowsMediaPlayer
player.setName("RealPlayer");
alert(player.toString()); //輸出RealPlayer
alert(Player.MAX_EXTENDED_TIME);
不像Groovy裡面,閉包做瞭很大程度上的強化,包括新的語法的支持;JavaScript的閉包是很簡單的閉包,它沒有特殊的需要額外學習的語法,任意一個function,裡面隻要包含未綁定變量,這些變量是在function所屬的上下文環境中定義的,那麼,這個function就是閉包。順便羅嗦一句,和閉包相反的,不正是不包含任何未綁定變量的函數式代碼嗎?
寫是寫好瞭,可是轉念一想,Player應當隻有一份,它是單例的,最好我也能像Java那樣弄一個單例模式出來 :),可是事不遂願,我沒有辦法在JavaScript做一個private的構造器,用這種思路去實現單例模式似乎不可行……
怎麼辦?
然而天無絕人之路,我控制不瞭你new一個Player的對象,我卻可以控制你new出來的這個Player對象的屬性和行為!當你需要使用你new出來的Player的對象的時候,你發現根本無法完成,或者它隻是一個空殼!真正的東西還是要靠單例中經典的getInstance方法來獲得:
Java代碼
function Player(){
throw new Error("Can not instantiate a Player object.");
}; //這隻是個空殼
(function(){ //這才是貨真價實的東西
Player.MIN_EXTENDED_TIME = 1;
Player.MAX_EXTENDED_TIME = 3;
Player._player = false;
Player.getInstance = function(){
if(!Player._player){
alert("Init…");
Player._player = {
_name : name,
setName : function(name){
this._name = name;
},
toString : function(name){
return "Player: " + this._name;
}
};
}
return Player._player;
};
})();
//var player = new Player(); //new Player()會拋出異常
var player1 = Player.getInstance();
var player2 = Player.getInstance();
player2.setName("RealPlayer");
alert(player2.toString()); //輸出RealPlayer
好,真不錯,單例模式在JavaScript下也成功實施瞭——你要膽敢new Player();就會拋出一個異常,這樣什麼也得不到,隻有用getInstance方法得到的對象才是真真正正的Player對象。上面的代碼整個執行的結果,隻彈出瞭一次"Init…"的對話框,說明真正的“構造器邏輯”隻調用瞭一次。
都做到這份上瞭,依然有小小的遺憾,Player的定義依然被拆分成瞭兩部分,一部分定義空殼,一部分是一個匿名函數來定義Player的常量和getInstance方法。這兩部分就不能合二為一麼?
能。隻需要用到一個小小的匿名函數,如果耐心從頭看到這裡,也一定能理解:
到此,終於如釋重負,深入理解JavaScript面向對象,用好原型和閉包這兩把鋒利的武器,才能寫出優秀的前端代碼來。有一些同事私下和我交流,後面我盡量貼簡潔的代碼,希望有面向對象基礎和JavaScript基礎的同事都能有所收獲。
作者“四火的BLOG”