javascript 類

由淺到深學習JavaScript類

類是什麼?

        許多剛接觸編程的朋友都可能理解不了類,其實類是對我們這個現實世界的模擬,把它說成“類別”或者“類型”可能會更容易理解

一些。比如“人”這種動物就是一個類,而具體某一個人就是“人”這個類的一個實例,“人”可以有許多實例(地球人超過六十億了),但

“人”這個類只有一個。你或許會說那男人和女人不也是人麼?怎麼只能有一個?其實這裏要談到一個繼承的東西,後邊纔講,請繼續看下去

如何建立一個類?
        在C++中是以class來聲明一個類的,JavaScript與C++不同,它使用了與函數一樣的function來聲明,這就讓許多學Jscript的朋友把

類與函數混在一起了,在Jscript中函數與類確實有些混,但使用久了自然而然會理解,這篇文章是針對想進攻面向對象編程的朋友而寫,就不

打算一下子討論得太深了。
        請看下邊這個類的定義:
       
        function WuYouUser()
        {
                this.Name; //名字
        }
       
        上邊的代碼定義了一個WuYouUser(無憂用戶)類,它有個屬性:Name(名字)。Name就是WuYouUser類的一個屬性。
        一個類有固定的屬性,但類的實例卻有不同的屬性值,就像我是屬於“人”這個類的,性別是男,而我有一個女同學,她也屬於“人

”類,但她的性別屬性值卻爲女。
        那麼如何聲明某個類的一個實例呢?非常簡單:
       
        var Wo = new WuYouUser(); //實例一:“我”
        var Biyuan = new WuYouUser(); //實例二:“碧原”(Biyuan哥,不好意思。。。嘿嘿)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
類的屬性

        這個Wo(我)就是WuYouUser類的一個實例,它擁有WuYouUser給它的一切:Name屬性、Sex屬性以及Age屬性,我們可以這樣子來設置

它的屬性:
       
        Wo.Name = "泣紅亭";
       
        很簡單是不是?試着運行
       
        window.document.write(Wo.Name);
       
        看看,是不是輸出了我的名字:泣紅亭?
       
        同樣設置一下碧原兄的屬性
       
        Biyuan.Name = "碧原";
       
        運行
       
                window.document.write(Biyuan.Name);
               
        可以看到輸出了"碧原",也就說明了Biyuan與Wo同樣是WuYouUser類的實例,但卻是不同的實體,具有不同的屬性值。
       
        屬性是可以設置默認值的,無憂裏都有記錄大家各自發了多少貼子,我們也同樣給WuYouUser類添加一個發貼數量的屬性ArticleCount
       
        function WuYouUser()
        {
                this.Name;
                this.ArticleCount = 0;
        }
       
        一個無憂新用戶剛註冊完之後他的發貼數量爲0,在上邊的代碼中可以看到直接給屬性ArticleCount設置值爲0。
       
        可以運行一下這樣的代碼:
       
        var Wo = new WuYouUser();
        window.document.write(Wo.ArticleCount);
       
        可以看到輸出了0,說明ArticleCount屬性被我們成功設置默認值爲0

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
類的方法
       
        方法這個詞不大好理解,我覺得說成行爲會更容易理解。一個人具有許多共同的行爲,比如睡覺、吃飯、走路等等,現在我們給

WuYouUser類添加一個發貼的方法。
       
        function WuYouUser()
        {
                this.Name;
                this.ArticleCount = 0;
               
                this.NewArticle = function()
                {
                        /*
                        *
                        *        具體如何發貼我們大家都知道,不就是打打字,加加圖片再按一下保存之類的按鈕麼?
                        *        關於具體如何發貼的代碼沒有必要在這裏寫出來,我們要了解的僅僅是方法的定義與使用
                        *        我們在這裏實現一個最簡單的功能,也是很重要的功能:給我們的發貼數量加上1!
                        *        注意:恐龍等級就是這樣加出來的,因此呀……大家狂發貼吧。。。
                        */
                       
                        this.ArticleCount++;
                }
        }
       
        既然定義好了這個方法,我們來試試效果如何:
       
        var Wo = new WuYouUser();
        Wo.NewArticle();
        document.write(Wo.ArticleCount);
       
        可以看到輸出了1,說明我們發貼成功了!真是有歷史紀念意義的一刻,離恐龍等級又近一步了。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
靜態屬性

        靜態屬性又稱公共屬性,它不屬於某個類的實例,而是直接屬於某個類。
       
        比如說無憂用戶有一個屬性:註冊用戶的數量,它是屬於整個無憂用戶的,而不是屬於泣紅亭或者誰的
        靜態屬性的聲明方法是:
       
        類名.prototype.屬性名 = 屬性值;
       
        比如給WuYouUser類定義一個註冊用戶的數量Count:
       
        WuYouUser.prototype.Count = 0;
       
        那麼如何讀取它呢?有兩種方法:
       
        1. 直接用 WuYouUser.prototype.Count
        2. 使用Wo.Count
       
        這兩者沒有區別,都是得到0
       
        雖然讀取方法可以有兩種,但在改變它的時候卻得特別小心了,請看下邊代碼
       
        var Biyuan = new WuYouUser();
        WuYouUser.prototype.Count++;
        document.write(Wo.Count);
        document.write(Biyuan.Count);
       
        你會發現兩者的Count屬性都是1,也就是說WuYouUser.prototype.Count改變了會影響到各個實例的相應屬性,其實原理就是Wo、

Biyuan的Count屬性與WuYouUser.prototype.Count根本就是同一個!
       
        現在來看另外一段代碼:
       
        var Biyuan = new WuYouUser();
       
        Biyuan.Count++; //特別注意一下這裏,這是直接改變Biyuan的Count屬性
        document.write(Biyuan.Count); // 輸出 1
        document.write(WuYouUser.prototype.Count); //輸出 0
        document.write(Wo.Count); //同樣輸出0,爲什麼?
       
        可以看到如果直接修改實例的靜態屬性值,那麼會出現其它實例甚至類的靜態屬性與它不同步了?這是因爲直接修改的時候,該實例

會生成一個屬於該實例的屬性Count,這個時候Biyuan.Count不再與WuYouUser.prototype.Count是同一個了,也不與Wo.Count是同一個,這個

Count屬性是屬於Biyuan自己所有的,以後改變了它也只是影響它自己而已。
       
        因此如果不是特別的需要,建議不管在讀取還是賦值的時候,都統一使用WuYouUser.prototype.Count這樣的方式,以做到萬無一失!
       
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
靜態方法
       
        與靜態屬性相似,它也有個另稱:公共方法,同樣屬於類本身的。
       
        靜態方法的定義方式是:
       
        類名.方法名 = function(參數1,參數2...參數n)
        {
                //方法代碼
        }
       
        我們現在就來定義一個無憂用戶類的註冊新用戶靜態方法:
       
        WuYouUser.prototype.AddOne = function()
        {
                //***  同樣具體代碼不寫出來,給靜態屬性Count增加1,表示註冊用戶數量又多一個
                WuYouUser.prototype.Count++;
        }
       
        現在我們來看看如何用它,同樣有兩種方法:
       
        1.直接使用WuYouUser.prototype.AddOne()
        2.使用某實例的AddOne()
       
        這兩種方法沒有什麼不同:
       
        var Wo = new WuYouUser();
        var Biyuan = new WuYouUser();
        document.write(WuYouUser.prototype.Count); // 0
       
        Wo.AddOne();
        document.write(WuYouUser.prototype.Count); // 1
        document.write(Wo.Count); // 1
        document.write(Biyuan.Count); // 1
       
        WuYouUser.prototype.AddOne();
        document.write(WuYouUser.prototype.Count); // 2
        document.write(Wo.Count); // 2
        document.write(Biyuan.Count); // 2
       
        可以看出不管是使用Wo.AddOne()還是WuYouUser.prototype.AddOne()效果都是一樣的,都是給WuYouUser.prototype.Count加上1
       
        現在再看一段代碼:
        function NewClass() //由於上邊的WuYouUser類不合適當這個例子的代碼,我聲明瞭一個新類NewClass
        {
                this.Name = "泣紅亭"; //這裏默認值爲我的名字
        }
       
        NewClass.prototype.ChangeName = function(NewName)
        {
                this.Name = NewName;
        }
       
        var Wo = new NewClass();
        Wo.ChangeName("鄭運濤"); //我的真名
       
        可以看到Wo.Name確實已經變成了"鄭運濤",這個方法似乎是可以用的,但裏邊是不是內有天機呢?
        再看下邊的代碼,類的定義以及ChangeName的定義我們照樣,但改變一下下邊的代碼:
       
        NewClass.prototype.ChangeName("鄭運濤");
        document.write(NewClass.Name); //undefined,即未定義
        document.write(NewClass.prototype.Name); //鄭運濤
        var Wo = new NewClass();
        document.write(Wo.Name); //泣紅亭
       
        可以看到我們並沒有定義NewClass.prototype.Name這個靜態屬性,但編譯器給我們自己加了一個。
        可是再看下邊輸出Wo.Name,它並不是爲"鄭運濤",而是原來的默認值"泣紅亭",說明了什麼?
        其實很簡單,看一下NewClass的定義裏已經有Name這個屬性,因此Wo也有自己的Name屬性,它跟NewClass.prototype.Name並不是同一

個的,因此就還是那樣子。
       
        那爲什麼前一個例子運行了Wo.ChangeName("鄭運濤")卻能夠實現改變Wo.Name屬性呢?其實在這裏跟改變Wo.Count的值是同一個道理

,編譯器自動給Wo增加了一個方法ChangeName,這個方法代碼與NewClass.prototype.ChangeName一樣,但Wo.ChangeName是Wo這個實例所特有

的,而非NewClass.prototype.ChangeName!
       
        分析可知道在靜態方法裏儘量不要使用this這樣的關鍵字來引用實例本身的屬性,除非你有特別的目的,而且能夠清楚地明白這裏邊

的運行機制!
       
        如果真的需要在靜態方法裏使用this,可以直接把this當作參數傳進去:
       
        NewClass.ChangeName = function(This,NewName) //注意這裏是This,不是this
        {
                This.Name = NewName;
        }
       
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
構造函數

        一個類在初始化的時候其實也是一個函數的執行過程,這個函數就是構造函數,我們看一下下邊的代碼:
       
        function WuYouUser()
        {
                this.Name = "泣紅亭"; //默認定義爲泣紅亭
                alert(this.Name);
        }
        var Wo = new WuYouUser();//可以看到出現一個窗口顯示泣紅亭三個字
       
        可以看出類的定義不僅僅是定義了它的屬性與方法,還同時可以加入一些代碼,而這些代碼就是該類的構造函數的代碼,在實例聲明

過程中被執行!
        其實說起來,類的屬性與類的方法都是在構造函數裏執行定義的,看下邊的代碼:
       
        function WuYouUser()
        {
                this.Name = "泣紅亭";
                return;
                this.Sex = "男";
        }
        var Wo = new WuYouUser();
        document.write(Wo.Name); //泣紅亭
        document.write(Wo.Sex); //undefined,即未定義
       
        看得出什麼?Sex屬性是在return;之後的,而WuYouUser類的構造函數遇到return即停止運行,換句話說this.Sex = "男";這一行是沒

有被執行,即Sex屬性根本沒有被定義!
       
        構造函數可以有參數,參數值在聲明實例的時候被傳入:
        function WuYouUser(Name)
        {
                this.Name = Name;
        }
        var Wo = new WuYouUser("泣紅亭");
        document.write(Wo.Name); //泣紅亭
       
        構造函數不需要返回值,但如果你設置了返回值,可以把它當成一個函數來使用。
        function Sum(a, b)
        {
                this.a = a;
                this.b = b;
                return this.a + this.b;
        }
        document.write(Sum(12, 23)); //輸出的是12與23的和35
        var Obj = new Sum(12,23);
        document.write(Obj.a) // 12
        document.write(Obj.b) // 23
       
        感覺挺奇妙,對吧?我寫這文章寫着寫着也覺得挺奇妙的,呵呵!
       
        但強烈建議不要把一個類當成一個函數來使用!如果你需要的是一個函數,請直接寫成函數而不要寫成類,以免搞混了。 

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
繼承

        繼承這個詞在面向對象的編程裏是非常重要的,雖然JavaScript並不是真正面向對象的語言,而是跟VB一樣是基於對象的語言,它同

樣提供了繼承機制。
       
        文章開頭時談到了男人與女人,這也同樣是兩個不同的類,但卻具有相同的一些屬性以及方法,而這些相同的特性是來自“人”這個

類的,換句話說男人與女人繼承了“人”的所有特性!但是男人與女人卻有其不同的地方,編程語言裏的繼承也一樣,一個類A繼承了另一個類

B,那麼類B就是類A的父類,類A就是類B的派生類,也稱爲子類。比如男人就是人的派生類,而人就是男人的父類。最高一級的類稱爲基類,想

象一下就可以明白,男人繼承自人,男孩繼承自男人,人就是男孩的基類,男人就是男孩的父類。
       

        >>>>>>>>>>>>>>>>>>>>
        題外:多重繼承
       
        這裏再涉及一個多重繼承的話題,但如果你僅僅是學JavaScript的話就沒有必要看下去,因爲JavaScript不提供多重繼承,準確一點

說沒有一種簡單而標準的方法來實現多重繼承(其實是有辦法實現的,只不過麻煩了一點,而且確實沒有必要)。
       
        在C++中是有多重繼承的概念的,這裏是討論JavaScript,因此不打算講,只是說說它的一點點思想以供參考。
       
        在上邊男孩的繼承問題中,男孩其實不僅僅是繼承自男人,還繼承自孩子(有男孩子,也有女孩子)這個類,因此,它同時繼承了兩

個類:男人與男孩,這就是所謂的多重繼承。
       
        好,這個問題打住,我們還是迴歸主題。
        >>>>>>>>>>>>>>>>>>>>
       
        先看第一個類的定義:
       
        function A()
        {
                this.Name = "泣紅亭";
                alert(this.Name);
        }

        這個類定義了一個屬性Name,默認值爲"泣紅亭"
       
        現在看第二個類的定義:
       
        function B()
        {
                this.Sex = "男";
                alert(this.Sex);
        }
       
        定義了一個屬性Sex,默認值爲"男"
       
        繼承的方式就是 子類.prototype = new 父類();       
        現在我們來讓B類繼承A類:
       
        B.prototype = new A();        
        
        
        運行這一段代碼:
       
        var Obj = new B(); //首先打開警告窗口顯示"泣紅亭",再顯示"男"
       
        可以從上邊的結果看出B類繼承了A類,擁有了A類的屬性Name,並且執行了A類的構造函數,而且A類的構造函數在B類的構造函數執行

之前執行。因此我們利用這個可以實現重寫父類的方法以及重設置父類某屬性的默認值:
       
        function A()
        {
                this.Name = "泣紅亭";
                this.Show = function()
                {
                        alert("這是A類的Show方法");
                }
                alert(this.Name);
        }
       
        function B()
        {
                this.Name = "鄭運濤";
                this.Show = function()
                {
                        alert("這是B類的Show方法");
                }
                alert(this.Name);
        }
       
        var Obj = new B();
        Obj.Show();
       
        結果出現了三次警告窗口,第一個內容爲泣紅亭,是執行A類的構造函數裏的alert(this.Name),那時候Name屬性值還爲"泣紅亭",因爲B類的構造函數還沒執行,第二次內容爲"鄭運濤",這是B類裏的alert(this.Name),因爲B類的構造函數裏給Name重賦值爲"鄭運濤"。最後是調用了Obj.Show(),執行了不是A類的Show方法裏的Show(顯示"這是A類的Show方法"),而是執行了B類的Show(顯示"這是B類的Show方法"),很明顯Show方法被重寫了。
       
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
類作爲一個對象時的屬性與方法(不知道如何簡潔地表達,因此用了這麼長的題目)

        不知道在這裏談這個話題是否有點混人耳目,但又覺得不談這篇文章就不算完整,因爲文章目的就是要讓人搞清楚類的方方面面。
       
        看了這一小節的題目,或許你會覺得奇怪,類就是類,怎麼會“作爲一個對象”呢?在JavaScript裏,一切都是對象,包括類!對象

可以有屬性,可以有方法,類也同樣可以有,但這個非常容易跟前邊說到的靜態屬性與靜態方法搞混了,因此要仔細看清楚兩者的分別!
       
        定義一個類:
        function WuYouUser()
        {
                this.Name = "泣紅亭";
        }
       
        定義類作爲一個對象時的屬性:
       
        WuYouUser.Url = "http://www.livebaby.cn"; //靜態屬性的定義是:WuYouUser.prototype.Url = "http://www.livebaby.cn";
        var Wo = new WuYouUser();
        document.write(WuYouUser.Url); //http://www.livebaby.cn
        document.write(Wo.Url); //undefined,即未定義!注意這裏的未定義
       
        從這裏可以看出Url這個屬性是WuYouUser自個所有,改變了它與其它類以及它的子類完全無關!
       
        引用類的屬性只有一個辦法,就是類名.屬性名,改變它也一樣。
       
        定義類作爲一個對象時的方法:
       
        WuYouUser.ChangeUrl = function()
        {
                this.Url = "http://www.livebaby.cn";
        }
       
        你或許會覺得奇怪,這裏的this是什麼?因爲ChangeUrl這個方法是屬於對象WuYouUser的,因此this指的就是WuYouUser本身!
       
        可以運行下邊的代碼試試:
       
        document.write(WuYouUser.Url); // http://www.livebaby.cn
        WuYouUser.ChangeUrl();
        document.write(WuYouUser.Url); // http://www.livebaby.cn
       
        明顯ChangeUrl直接修改了WuYouUser.Url的值,因此後邊才能輸出http://www.livebaby.cn
       
       
        如果你這一節看不明白,也不要着急,編程嘛,許多東東都只能意會不能言傳,而且我又沒口才,說不清楚,只要以後多寫寫代碼,

多用用類自然而然會體會到這一些,還有可以去看看JSVM的代碼,裏邊幾乎每個類都有用到類作爲一個對象時的屬性與方法。
       
       
       
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
後言

        首先感謝你能夠有耐心看到這裏,我也沒想到寫了這麼多才能夠寫得像樣一點,請別介意。

 
你真的會JavaScript嗎

很久沒有看到這樣讓人脣齒留香的好文了。上次看到的是一篇是 Douglas Crockford 的JavaScript, We Hardly new Ya(我簡單翻譯了一下,

譯文在後)。

同其他教你如何用面向對象的思想編寫JavaScript的其他文章一樣,該文也是着重在這麼幾個要素:
? JavaScript的對象就是一個關聯數組。
? JavaScript 函數也是一個對象。
? 原型(Prototype)
? 閉包(Closures)
? 繼承/私有屬性/靜態方法
? 命名空間
作者文筆很好,英文很容易看懂,沒有生僻的用詞(順便提一下,《PPK on JavaScript》作者的英文不敢恭維)。用來舉例的代碼也很貼切。
特別是文章的開頭很有意思,作者寫到他和一個據說已經寫了快4年JavaScript的女程序員聊天,女程序員認爲她的JS水平very good,後來作

者發現她確實會寫,但僅僅是會寫,其實對JavaScript的內涵所知甚少。
作者想用這個例子說明,有很多具備Java/C++/C#開發經驗的開發人員在編寫JavaScript或者轉行到FED(比如我)的時候,想當然的把那些標準

面嚮對象語言的思想套用在JavaScript上,反而走入迷途。
對此我深有體會,我正是在真正參與了一次Ajax的項目並真正讀懂了Prototype框架的源碼之後,對JavaScript有了完全全新的認識。
總之,推薦閱讀。附上JavaScript, We Hardly new Ya的譯文,譯得匆忙,定有行文不通之處,請客官見諒!
JavaScript 的 new, 好久不見啊
原文: JavaScript, We Hardly new Ya--Douglas Crockford。   
JavaScript是一門基於原型的語言,但它卻擁有一個 new 操作符使得其看起來象一門經典的面對對象語言。那樣也迷惑了程序員們,導致一些有問題的編程模式。
其實你永遠不需要在JavaScript使用 new Object()。用字面量的形式{}去取代吧。

同理,不要使用 new Array() ,而代之以字面量[]。

JavaScript中的數組並不象Java中的數組那樣工作的,使用類似Java的語法只會讓你糊塗。
同理不用使用 new Number, new String, 或者 new Boolean。這些的用法只會產生無用的類型封裝對象。就直接使用簡單的字面量吧。
不要使用 new Function 去創建函數對象。用函數表達式更好。比如:
frames[0].onfocus = new Function(”document.bgColor=’antiquewhite’”)
更好的寫法是:
frames[0].onfocus = function () {document.bgColor = ‘antiquewhite’;};
第二種形式讓腳本編譯器更快的看到函數主體,於是其中的語法錯誤也會更快被檢測出來。有時候程序員使用 new Function 是因爲他們沒有

理解內部函數是如何工作的。
selObj.onchange = new Function(”dynamicOptionListObjects[”+
        dol.index+”].change(this)”);
如果我們讓用字符串做函數體,編譯器不能看到它們。如果我們用字符串表達式做函數體,我們同樣也看不到它們。更好的方式就是不要盲目

編程。通過製造一個返回值爲函數的函數調用,我們可以明確的按值傳遞我們想要綁定的值。這允許我們在循環中初始化一系列 selObj 對象


selObj.onchange = function (i) {
    return function () {
        dynamicOptionListObjects[i].change(this);
    };
}(dol.index);
直接對一個函數使用new永遠不是一個好主意。比如, new function 對構造新對象沒有提供什麼優勢。
myObj = new function () {
    this.type = ‘core’;
};
更好的方式是使用對象字面量,它更輕巧,更快捷。
myObj = {
    type: ‘core’
};
假如我們需要創建的對象包含的方法需要訪問私有變量或者函數,更好的方式仍然是避免使用new.var foo = new function() {
    function processMessages(message) {
        alert(”Message: ” + message.content);
    }
    this.init = function() {
        subscribe(”/mytopic”, this, processMessages);
    }
}
通過使用 new 去調用函數,對象會持有一個無意義的原型對象。這隻會浪費內存而不會帶來任何好處。如果我們不使用new,我們就不用在對

象鏈維護一個無用的prototype對象。所以我們可以用()來正確的調用工廠函數。var foo = function () {
    function processMessages(message) {
        alert(”Message: ” + message.content);
    }
    return {
        init: function () {
            subscribe(”/mytopic”, this, processMessages);
        }
    };
}();
所以原則很簡單: 唯一應該要用到new操作符的地方就是調用一個古老的構造器函數的時候。當調用一個構造器函數的時候,是強制要求使用new的。有時候可以來new一下, 有的時候還是不要了吧。
1. Feedback:對於javaScript的Prototype我理解是一顆描述繼承樹的鏈子
使用javaScript進行OO開發是完全可以的
和java,C 等對比,有一點做不到:
對於父類的成員方法(非構造方法)要麼完全重寫,要麼原封不動,也就是無法在子類的成員方法中調用父類的同名方法其他的重寫、重載、構造、接口、繼承、多態都是沒問題的.當然腳本語言沒有編譯器就無法進行比如做接口實現是否完整等檢查 靠程序員自己控制好吧

JavaScript, We Hardly new Ya
JavaScript is a prototypal language, but it has a new operator that tries to make it look sort of like a classical language.

That tends to confuse programmers, leading to some problematic programming patterns.
You never need to use new Object() in JavaScript. Use the object literal {} instead. Similarly, don’t use new Array(), use

the array literal [] instead. Arrays in JavaScript work nothing like the arrays in Java, and use of the Java-like syntax will

confuse you.
Do not use new Number, new String, or new Boolean. These forms produce unnecessary object wrappers. Just use simple literals

instead.
Do not use new Function to create function values. Use function expressions instead. For example,
frames[0].onfocus = new Function("document.bgColor='antiquewhite'")
is better written as
frames[0].onfocus = function () {document.bgColor = 'antiquewhite';};
The second form allows the compiler to see the function body sooner, so any errors in it will be detected sooner. Sometimes

new Function is used by people who do not understand how inner functions work.
selObj.onchange = new Function("dynamicOptionListObjects["+
        dol.index+"].change(this)");
If we keep function bodies in strings, the compiler can’t see them. If we keep function bodies as string expressions, we can

’t see them either. It is better to not program in ignorance. By making a function that returns a function, we can

explicitly pass in the values we want to bind. This allows us to initialize a set of selObj in a loop.
selObj.onchange = function (i) {
    return function () {
        dynamicOptionListObjects[i].change(this);

    };
}(dol.index);
It is never a good idea to put new directly in front of function. For example, new function provides no advantage in

constructing new objects.
myObj = new function () {
    this.type = 'core';
};
It is better to use an object literal. It is smaller, faster.
myObj = {
    type: 'core'
};
If we are making an object containing methods that are bound to private variables and functions, it is still better to leave

off the new prefix.
var foo = new function() {
    function processMessages(message) {
        alert("Message: " + message.content);
    }
    this.init = function() {
        subscribe("/mytopic", this, processMessages);
    }
}
By using new to invoke the function, the object holds onto a worthless prototype object. That wastes memory with no

offsetting advantage. If we do not use the new, we don’t keep the wasted prototype object in the chain. So instead we will

invoke the factory function the right way, using ().
var foo = function () {
    function processMessages(message) {
        alert("Message: " + message.content);
    }
    return {
        init: function () {
            subscribe("/mytopic", this, processMessages);
        }
    };
}();
So the rule is simple: The only time we should use the new operator is to invoke a pseudoclassical Constructor function. When

calling a Constructor function, the use of new is mandatory.
There is a time to new, and a time to not.

Prototype的深度探索
1 什麼是prototype

       JavaScript中對象的prototype屬性,可以返回對象類型原型的引用。這是一個相當拗口的解釋,要理解它,先要正確理解對象類型

(Type)以及原型(prototype)的概念。
        前面我們說,對象的類(Class)和對象實例(Instance)之間是一種“創建”關係,因此我們把“類”看作是對象特徵的模型化,而

對象看作是類特徵的具體化,或者說,類(Class)是對象的一個類型(Type)。例如,在前面的例子中,p1和p2的類型都是Point,在JavaScript

中,通過instanceof運算符可以驗證這一點:
        p1 instanceof Point
        p2 instanceof Point

        但是,Point不是p1和p2的唯一類型,因爲p1和p2都是對象,所以Obejct也是它們的類型,因爲Object是比Point更加泛化的類,所以

我們說,Obejct和Point之間有一種衍生關係,在後面我們會知道,這種關係被叫做“繼承”,它也是對象之間泛化關係的一個特例,是面向對

象中不可缺少的一種基本關係。
        在面向對象領域裏,實例與類型不是唯一的一對可描述的抽象關係,在JavaScript中,另外一種重要的抽象關係是類型(Type)與原型

(prototype)。這種關係是一種更高層次的抽象關係,它恰好和類型與實例的抽象關係構成了一個三層的鏈,下圖描述了這種關係:
        //TODO:

        在現實生活中,我們常常說,某個東西是以另一個東西爲原型創作的。這兩個東西可以是同一個類型,也可以是不同類型。習語“依

葫蘆畫瓢”,這裏的葫蘆就是原型,而瓢就是類型,用JavaScript的prototype來表示就是“瓢.prototype =某個葫蘆”或者“瓢.prototype= new 葫蘆()”。

       要深入理解原型,可以研究關於它的一種設計模式——prototype pattern,這種模式的核心是用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。JavaScript的prototype就類似於這種方式。

        關於prototype pattern的詳細內容可以參考《設計模式》(《Design Patterns》)它不是本文討論的範圍

        注意,同類型與實例的關係不同的是,原型與類型的關係要求一個類型在一個時刻只能有一個原型(而一個實例在一個時刻顯然可以

有多個類型)。對於JavaScript來說,這個限制有兩層含義,第一是每個具體的JavaScript類型有且僅有一個原型(prototype),在默認的情況下,這個原型是一個Object對象(注意不是Object類型!)。第二是,這個對象所屬的類型,必須是滿足原型關係的類型鏈

例如p1所屬的類型是Point和Object,而一個Object對象是Point的原型。假如有一個對象,它所屬的類型分別爲ClassA、ClassB、ClassC和Object,那麼必

須滿足這四個類構成某種完整的原型鏈,例如:
        //TODO:
       

        下面這個圖描述了JavaScript中對象、類型和原型三者的關係:
        //TODO:

        有意思的是,JavaScript並沒有規定一個類型的原型的類型(這又是一段非常拗口的話),因此它可以是任何類型,通常是某種對象

,這樣,對象-類型-原形(對象)就可能構成一個環狀結構,或者其它有意思的拓撲結構,這些結構爲JavaScript帶來了五花八門的用法,其

中的一些用法不但巧妙而且充滿美感。下面的一節主要介紹prototype的用法。

 2 prototype使用技巧

      在瞭解prototype的使用技巧之前,首要先弄明白prototype的特性。首先,JavaScript爲每一個類型(Type)都提供了一個prototype屬性

,將這個屬性指向一個對象,這個對象就成爲了這個類型的“原型”,這意味着由這個類型所創建的所有對象都具有這個原型的特性。另外,

JavaScript的對象是動態的,原型也不例外,給prototype增加或者減少屬性,將改變這個類型的原型,這種改變將直接作用到由這個原型創建

的所有對象上,例如:
 <script>   
     function Point(x,y)
      {
         this.x = x;
         this.y = y;
     }
     var p1 = new Point(1,2);
     var p2 = new Point(3,4);
     Point.prototype.z = 0999; //動態爲Point的原型添加了屬性
     alert(p1.z);
     alert(p2.z);  //同時作用於Point類型創建的所有對象
 </script>


如果給某個對象的類型的原型添加了某個名爲a的屬性,而這個對象本身又有一個名爲a的同名屬性,則在訪問這個對象的屬性a時,對象本身的屬性“覆蓋”了原型屬性,但是原型屬性並沒有消失,當你用delete運算符將對象本身的屬性a刪除時,對象的原型屬性就恢復了可見性。利用

這個特性,可以爲對象的屬性設定默認值,例如:
 <script>
 function Point(x, y)
  {
     if(x) this.x = x;
     if(y) this.y = y;
 }
 Point.prototype.x = 0;
 Point.prototype.y = 0;
 var p1 = new Point;
 var p2 = new Point(1,2);
 </script>


上面的例子通過prototype爲Point對象設定了默認值(0,0),因此p1的值爲(0,0),p2的值爲(1,2),通過delete p2.x, delete p2.y; 可以將p2的值恢復爲(0,0)。下面是一個更有意思的例子:
 <script>
 function classA()
  {
     this.a = 100;
     this.b = 200;
     this.c = 300;
 
     this.reset = function()
      {
         for(var each in this)
          {
             delete this[each];
         }
     }
 }
 classA.prototype = new classA();
 
 var a = new classA();
 alert(a.a);
 a.a *= 2;
 a.b *= 2;
 a.c *= 2;
 alert(a.a);
 alert(a.b);
 alert(a.c);
 a.reset();   //調用reset方法將a的值恢復爲默認值
 alert(a.a);
 alert(a.b);
 alert(a.c);
 </script>


利用prototype還可以爲對象的屬性設置一個只讀的getter,從而避免它被改寫。下面是一個例子:
 <script>
 function Point(x, y)
  {
     if(x) this.x = x;
     if(y) this.y = y;
 }
 Point.prototype.x = 0;
 Point.prototype.y = 0;
 
 function LineSegment(p1, p2)
  {
     //私有成員
     var m_firstPoint = p1;
     var m_lastPoint = p2;
      var m_width = {
          valueOf : function(){return Math.abs(p1.x - p2.x)},
          toString : function(){return Math.abs(p1.x - p2.x)}
     }
      var m_height = {
          valueOf : function(){return Math.abs(p1.y - p2.y)},
          toString : function(){return Math.abs(p1.y - p2.y)}
     }
     //getter
     this.getFirstPoint = function()
      {
         return m_firstPoint;
     }
     this.getLastPoint = function()
      {
         return m_lastPoint;
     }
 
      this.length = {
          valueOf : function(){return Math.sqrt(m_width*m_width + m_height*m_height)},
          toString : function(){return Math.sqrt(m_width*m_width + m_height*m_height)}
     }
 }
 var p1 = new Point;
 var p2 = new Point(2,3);
 var line1 = new LineSegment(p1, p2);
 var lp = line1.getFirstPoint();
 lp.x = 100;  //不小心改寫了lp的值,破壞了lp的原始值而且不可恢復
 alert(line1.getFirstPoint().x);
 alert(line1.length); //就連line1.lenght都發生了改變
 </script>


將this.getFirstPoint()改寫爲下面這個樣子:
this.getFirstPoint = function()
{
        function GETTER(){};
        GETTER.prototype = m_firstPoint;
        return new GETTER();
}
則可以避免這個問題,保證了m_firstPoint屬性的只讀性。
 <script>
 function Point(x, y)
  {
     if(x) this.x = x;
     if(y) this.y = y;
 }
 Point.prototype.x = 0;
 Point.prototype.y = 0;
 
 function LineSegment(p1, p2)
  {
     //私有成員
     var m_firstPoint = p1;
     var m_lastPoint = p2;
      var m_width = {
          valueOf : function(){return Math.abs(p1.x - p2.x)},
          toString : function(){return Math.abs(p1.x - p2.x)}
     }
      var m_height = {
          valueOf : function(){return Math.abs(p1.y - p2.y)},
          toString : function(){return Math.abs(p1.y - p2.y)}
     }
     //getter
     this.getFirstPoint = function()
      {
              function GETTER(){};
             GETTER.prototype = m_firstPoint;
             return new GETTER();
     }
     this.getLastPoint = function()
      {
              function GETTER(){};
             GETTER.prototype = m_lastPoint;
             return new GETTER();
     }
 
      this.length = {
          valueOf : function(){return Math.sqrt(m_width*m_width + m_height*m_height)},
          toString : function(){return Math.sqrt(m_width*m_width + m_height*m_height)}
     }
 }
 var p1 = new Point;
 var p2 = new Point(2,3);
 var line1 = new LineSegment(p1, p2);
 var lp = line1.getFirstPoint();
 lp.x = 100;  //不小心改寫了lp的值,但是沒有破壞原始的值
 alert(line1.getFirstPoint().x);
 alert(line1.length); //line1.lenght不發生改變
 
 </script>

 

實際上,將一個對象設置爲一個類型的原型,相當於通過實例化這個類型,爲對象建立只讀副本,在任何時候對副本進行改變,都不會影響到

原始對象,而對原始對象進行改變,則會影響到副本,除非被改變的屬性已經被副本自己的同名屬性覆蓋。用delete操作將對象自己的同名屬

性刪除,則可以恢復原型屬性的可見性。下面再舉一個例子:
<script>
function Polygon()
{
//http://www.livebaby.cn
var m_points = [];
m_points = Array.apply(m_points, arguments);
function GETTER(){};
GETTER.prototype = m_points[0];
this.firstPoint = new GETTER();
this.length = {
valueOf : function(){return m_points.length},
toString : function(){return m_points.length}
}
this.add = function(){
m_points.push.apply(m_points, arguments);
}
this.getPoint = function(idx)
{
return m_points[idx];
}
this.setPoint = function(idx, point)
{
if(m_points[idx] == null)
{
m_points[idx] = point;
}
else
{
m_points[idx].x = point.x;
m_points[idx].y = point.y;
}
}
}
var p = new Polygon({x:1, y:2},{x:2, y:4},{x:2, y:6});
alert(p.length);
alert(p.firstPoint.x);
alert(p.firstPoint.y);
p.firstPoint.x = 100; //不小心寫了它的值
alert(p.getPoint(0).x);  //不會影響到實際的私有成員
delete p.firstPoint.x; //恢復
alert(p.firstPoint.x);
p.setPoint(0, {x:3,y:4}); //通過setter改寫了實際的私有成員
alert(p.firstPoint.x);  //getter的值發生了改變
alert(p.getPoint(0).x);
</script>


注意,以上的例子說明了用prototype可以快速創建對象的多個副本,一般情況下,利用prototype來大量的創建複雜對象,要比用其他任何方

法來copy對象快得多。注意到,用一個對象爲原型,來創建大量的新對象,這正是prototype pattern的本質。
下面是一個例子:
 <script>
//http://www.livebaby.cn
 var p1 = new Point(1,2);
 var points = [];
  var PointPrototype = function(){};
 PointPrototype.prototype = p1;
 for(var i = 0; i < 10000; i++)
  {
 points[i] = new PointPrototype();
 //由於PointPrototype的構造函數是空函數,因此它的構造要比直接構造//p1副本快得多。
 }
 </script>


除了上面所說的這些使用技巧之外,prototype因爲它獨特的特性,還有其它一些用途,被用作最廣泛和最廣爲人知的可能是用它來模擬繼承,

關於這一點,留待下一節中去討論。

3 prototype的實質

        上面已經說了prototype的作用,現在我們來透過規律揭示prototype的實質。
        我們說,prototype的行爲類似於C++中的靜態域,將一個屬性添加爲prototype的屬性,這個屬性將被該類型創建的所有實例所共享,

但是這種共享是隻讀的。在任何一個實例中只能夠用自己的同名屬性覆蓋這個屬性,而不能夠改變它。換句話說,對象在讀取某個屬性時,總

是先檢查自身域的屬性表,如果有這個屬性,則會返回這個屬性,否則就去讀取prototype域,返回protoype域上的屬性。另外,JavaScript允

許protoype域引用任何類型的對象,因此,如果對protoype域的讀取依然沒有找到這個屬性,則JavaScript將遞歸地查找prototype域所指向對

象的prototype域,直到這個對象的prototype域爲它本身或者出現循環爲止,我們可以用下面的圖來描述prototype與對象實例之間的關係:
        //TODO:

4 prototype的價值與侷限性

        從上面的分析我們理解了prototype,通過它能夠以一個對象爲原型,安全地創建大量的實例,這就是prototype的真正含義,也是它

的價值所在。後面我們會看到,利用prototype的這個特性,可以用來模擬對象的繼承,但是要知道,prototype用來模擬繼承儘管也是它的一

個重要價值,但是絕對不是它的核心,換句話說,JavaScript之所以支持prototype,絕對不是僅僅用來實現它的對象繼承,即使沒有了

prototype繼承,JavaScript的prototype機制依然是非常有用的。
        由於prototype僅僅是以對象爲原型給類型構建副本,因此它也具有很大的侷限性。首先,它在類型的prototype域上並不是表現爲一

種值拷貝,而是一種引用拷貝,這帶來了“副作用”。改變某個原型上引用類型的屬性的屬性值(又是一個相當拗口的解釋:P),將會徹底影

響到這個類型創建的每一個實例。有的時候這正是我們需要的(比如某一類所有對象的改變默認值),但有的時候這也是我們所不希望的(比

如在類繼承的時候),下面給出了一個例子:
 <script>
 function ClassA()
  {
 this.a=[];
 }
 function ClassB()
  {
  this.b=function(){};
 }
 ClassB.prototype=new ClassA();
 var objB1=new ClassB();
 var objB2=new ClassB();
 objB1.a.push(1,2,3);
 alert(objB2.a);
 //所有b的實例中的a成員全都變了!!這並不是這個例子所希望看到的。
 </script>


淺析Javascript中繼承和Prototype的關係

javascript中支持類的定義,而且定義的方式與函數基本上也相同。
1  function out(val){
2   document.write(val+"<br>");
3 };
4
5  function BaseClass() {
6   this.a="I'm BaseClass.a .";
7 };
第一行的內容可以看成是一個函數,第五行可以看成是一個類。

     我們繼續,現在我們來看看Javascript 中的繼承,以及 Prototype  與繼承的關係。先來看看下面這個代碼。你能想出運行的結果嗎?
 1 <script>
 2  // author: http://meil.livebaby.cn
 3 function out(val){
 4   document.write(val+"<br>");
 5 };
 6
 7 function BaseClass() {
 8   this.a="I'm BaseClass.a .";
 9 };
10 BaseClass.prototype.b="I'm BaseClass.prototype.b .";
11 BaseClass.c="I'm BaseClass.c .";
12
13 var cls1=function(){
14   this.a="I'm cls1.a .";
15 };
16 cls1.prototype.b="I'm cls1.prototype.b .";
17 cls1.c="I'm cls1.c .";
18
19 var cls2=function(){};
20 cls2.prototype=cls1.prototype;
21
22 out("BaseClass<br>");
23 out((new BaseClass).a);
24 out((new BaseClass).b);
25 out((new BaseClass).c);
26 out(BaseClass.c);
27 out("<hr>");
28
29 out("cls1<br>");
30 out(cls1.a);
31 out(cls1.b);
32 out(cls1.c);
33 out("<hr>");
34
35 out("new cls1<br>");
36 out((new cls1).a);
37 out((new cls1).b);
38 out((new cls1).c);
39 out("<hr>");
40
41 out("cls2<br>");
42 out((new cls2).a);
43 out((new cls2).b);
44 out((new cls2).c);
45
46 </script>


運行結果:

BaseClass

I'm BaseClass.a .
I'm BaseClass.prototype.b .
undefined
I'm BaseClass.c .
________________________________________
cls1

undefined
undefined
I'm cls1.c .
________________________________________
new cls1

I'm cls1.a .
I'm cls1.prototype.b .
undefined
________________________________________
 cls2

undefined
I'm cls1.prototype.b .
undefined


哈哈!有點暈了!?好像不太一樣。

下面來分析一下:

1.先看看這幾行:
22 out("BaseClass<br>");
23 out((new BaseClass).a);
24 out((new BaseClass).b);
25 out((new BaseClass).c);
26 out(BaseClass.c);
27 out("<hr>");

25行是調用了對象的c屬性,類中沒有定義,所以“undefined”
26行直接調用了,類的靜態屬性,就正常顯示了
其他的大家應該都明白了,就不多說了。

2.繼續
30 out(cls1.a);
31 out(cls1.b);
32 out(cls1.c);

首先大家應該清楚cls1在這裏是類,那就明瞭。這裏cls1只有一個靜態屬性,就是c,其他的屬性只能通過它的對象訪問。用類名來訪問對不起

,找不到只能顯示“undefined”,看下面的代碼就清楚了。

3.繼續
36 out((new cls1).a);
37 out((new cls1).b);
38 out((new cls1).c);

你不是說得用對象訪問嗎?我new這回可以了吧?恩!沒問題?
不過不是都沒問題這個不行-- out((new cls1).c); 那個是類的靜態屬性用這個  32 out(cls1.c); 就OK。

4.繼續
41 out("cls2<br>");
42 out((new cls2).a);
43 out((new cls2).b);
44 out((new cls2).c);

這個的結果有點疑惑,先等等。看看我們是怎麼寫的

cls2.prototype=cls1.prototype;

哦!用prototype來繼承的,對!
a是不能繼承的,c是靜態的也不能被繼承。

5.在補充點內容,讓你根多的瞭解JavaScript中繼承的特性
1 var cls3=function(){};
2 cls3.prototype=BaseClass.prototype;
3
4 cls3.prototype.d="I'm cls3"
5 out((new cls3).d);
6 out((new BaseClass).d);
 運行結果:
I'm cls3
I'm cls3


結束!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章