感覺挺經典的js文章,傳個附件。
“ JavaScript 中沒有‘類’,類已化于無形,與對象融為一體。正是由于放下了‘類’這個概念,JavaScript的對象才有了其他編程語言所沒有的活力。 ”這句看了很有感悟,也許這就是Javascript強大之所在吧。Javascript因此具有了動態增刪對象功能的能力。
function myfunc(){ alert("hello"); } myfunc();//這里調用myfunc,輸出 yeah 而不是 hello function myfunc(){ alert("yeah"); } myfunc();//這里調用myfunc,當然輸出yeah
JavaScript 執行引擎并非一行一行地分析和執行程序,而是 一段一段地分析執行 的。而且,在同一段程序的分析執行中,定義式的函數語句會被提取出來優先執行。函數定義執行完之后,才會按順序執行其他語句代碼。也就是說,在第一次調用myfunc之前,第一個函數語句定義的代碼邏輯,已被第二個函數定義語句覆蓋了。所以,兩次都調用都是執行最后一個函數邏輯了。
如果把這個 JavaScript 代碼分成兩段,例如將它們寫在一個 html 中,并用<script/>標簽將其分成這樣的兩塊:
<script> function myfunc () { alert("hello"); }; myfunc(); //這里調用myfunc,輸出 hello </script> <script> function myfunc () { alert("yeah"); }; myfunc(); //這里調用myfunc,輸出 yeah </script>
這時,輸出才是各自按順序來的, 也證明了JavaScript的確是一段段地執行的 。
一段代碼中的定義式函數語句會優先執行,這似乎有點象靜態語言的編譯概念。所以,這一特征也被有些人稱為:JavaScript的“預編譯”。
大多數情況下,我們也沒有必要去糾纏這些細節問題。只要你記住一點: JavaScript 里的代碼也是一種數據,同樣可以被任意賦值和修改的,而它的值就是代碼的邏輯 。只是,與一般數據不同的是, 函數是可以被調用執行的 。
JavaScript 函數的神奇之處還體現在另外兩個方面:一是 函數 function類型本身也具有對象化的能力 ,二是 函數function與對象 object超然的結合能力 。
在JavaScript函數中,你只能把this看成當前要服務的“這個”對象。this是一個特殊的內置參數,根據this參數,您可以訪問到“這個”對象的屬性和方法,但卻不能給this參數賦值。在一般對象語言中,方法體代碼中的this可以省略的,成員默認都首先是“自己”的。但JavaScript卻不同,由于不存在“自我”,當訪問“這個”對象時,this不可省略!
我們已經知道,用 var anObject = new aFunction() 形式創建對象的過程實際上可以分為三步:
第一步是建立一個新對象 ; 第二步將該對象內置的原型對象設置為構造函數prototype引用的那個原型對
象 ; 第三步就是將該對象作為this參數調用構造函數,完成成員設置等初始化工作 。對象建立之后,對象
上的任何訪問和操作都只與對象自身及其原型鏈上的那串對象有關,與構造函數再扯不上關系了。換句話
說, 構造函數只是在創建對象時起到介紹原型對象和初始化對象兩個作用 。
那么,我們能否自己定義一個對象來當作原型,并在這個原型上描述類,然后將這個原型設置給新創建
的對象,將其當作對象的類呢?我們又能否將這個原型中的一個方法當作構造函數,去初始化新建的對象
呢?例如,我們定義這樣一個原型對象:
var Person = //定義一個對象來作為原型類 { Create: function(name, age) //這個當構造函數 { 28 this.name = name; this.age = age; }, SayHello: function() //定義方法 { alert("Hello, I'm " + this.name); }, HowOld: function() //定義方法 { alert(this.name + " is " + this.age + " years old."); } };
這個JSON形式的寫法多么象一個C#的類啊!既有構造函數,又有各種方法。如果可以用某種形式來
創建對象,并將對象的內置的原型設置為上面這個“類”對象,不就相當于創建該類的對象了嗎?
但遺憾的是,我們幾乎不能訪問到對象內置的原型屬性!盡管有些瀏覽器可以訪問到對象的內置原型,
但這樣做的話就只能限定了用戶必須使用那種瀏覽器。這也幾乎不可行。
那么,我們可不可以通過一個函數對象來做媒介,利用該函數對象的prototype 屬性來中轉這個原型,
并用new操作符傳遞給新建的對象呢?
其實,象這樣的代碼就可以實現這一目標:
function anyfunc(){}; //定義一個函數軀殼 anyfunc.prototype = Person; //將原型對象放到中轉站 prototype var BillGates = new anyfunc(); //新建對象的內置原型將是我們期望的原型對象
不過,這個 anyfunc 函數只是一個軀殼,在使用過這個軀殼之后它就成了多余的東西了,而且這和直
接使用構造函數來創建對象也沒啥不同,有點不爽。
可是,如果我們將這些代碼寫成一個通用函數,而那個函數軀殼也就成了函數內的函數,這個內部函數
不就可以在外層函數退出作用域后自動消亡嗎?而且,我們可以將原型對象作為通用函數的參數,讓通用
函數返回創建的對象。我們需要的就是下面這個形式:
function New(aClass, aParams) //通用創建函數 { function new_() //定義臨時的中轉函數殼 { aClass.Create.apply(this, aParams); //調用原型中定義的的構造函數,中轉構造邏輯及構造參數 }; new_.prototype = aClass; //準備中轉原型對象 return new new_(); //返回建立最終建立的對象 }; var Person = //定義的類 { Create: function(name, age) { this.name = name; this.age = age; }, SayHello: function() { alert("Hello, I'm " + this.name); }, HowOld: function() { alert(this.name + " is " + this.age + " years old."); } }; var BillGates = New(Person, ["Bill Gates", 53]); //調用通用函數創建對象,并以數組形式傳遞構造參數 BillGates.SayHello(); BillGates.HowOld(); alert(BillGates.constructor == Object); //輸出:true
這里的通用函數 New()就是一個“語法甘露”!這個語法甘露不但中轉了原型對象,還中轉了構造函數
邏輯及構造參數。
有趣的是,每次創建完對象退出New函數作用域時,臨時的new_函數對象會被自動釋放。由于new
_的 prototype 屬性被設置為新的原型對象,其原來的原型對象和 new_之間就已解開了引用鏈,臨時函
數及其原來的原型對象都會被正確回收了。上面代碼的最后一句證明,新創建的對象的constructor屬性
返回的是 Object 函數。其實新建的對象自己及其原型里沒有 constructor 屬性,那返回的只是最頂層原
型對象的構造函數,即Object。
當然,這個代碼僅僅展示了“語法甘露”的概念。我們還需要多一些的語法甘露,才能實現用簡潔而優雅
的代碼書寫類層次及其繼承關系。好了,我們再來看一個更豐富的示例吧:
//語法甘露: var object = //定義小寫的 object 基本類,用于實現最基礎的方法等 { isA: function(aType) //一個判斷類與類之間以及對象與類之間關系的基礎方法 { var self = this; while(self) { if (self == aType) return true; self = self.Type; }; return false; } }; function Class(aBaseClass, aClassDefine) //創建類的函數,用于聲明類及繼承關系 { function class_() //創建類的臨時函數殼 { this.Type = aBaseClass; //我們給每一個類約定一個 Type屬性,引用其繼承的類 for(var member in aClassDefine) this[member] = aClassDefine[member]; //復制類的全部定義到當前創建的類 }; class_.prototype = aBaseClass; return new class_(); }; function New(aClass, aParams) //創建對象的函數,用于任意類的對象創建 { function new_() //創建對象的臨時函數殼 { this.Type = aClass; //我們也給每一個對象約定一個 Type 屬性,據此可以訪問到對象所屬的類 if (aClass.Create) aClass.Create.apply(this, aParams); //我們約定所有類的構造函數都叫Create,這和DELPHI比較相似 }; new_.prototype = aClass; return new new_(); }; //語法甘露的應用效果: var Person = Class(object, //派生至 object 基本類 { Create: function(name, age) { this.name = name; this.age = age; }, SayHello: function() { alert("Hello, I'm " + this.name + ", " + this.age + " years old."); } }); var Employee = Class(Person, //派生至 Person 類,是不是和一般對象語言很相似? { Create: function(name, age, salary) { Person.Create.call(this, name, age); //調用基類的構造函數 this.salary = salary; }, ShowMeTheMoney: function() { alert(this.name + " $" + this.salary); } }); var BillGates = New(Person, ["Bill Gates", 53]); var SteveJobs = New(Employee, ["Steve Jobs", 53, 1234]); BillGates.SayHello(); SteveJobs.SayHello(); SteveJobs.ShowMeTheMoney(); var LittleBill = New(BillGates.Type, ["Little Bill", 6]); //根據 BillGate 的類型創建LittleBill LittleBill.SayHello(); alert(BillGates.isA(Person)); //true alert(BillGates.isA(Employee)); //false alert(SteveJobs.isA(Person)); //true alert(Person.isA(Employee)); //false alert(Employee.isA(Person)); //true
“語法甘露”不用太多,只要那么一點點,就能改觀整個代碼的易讀性和流暢性,從而讓代碼顯得更優雅。
有了這些語法甘露,JavaScript就很像一般對象語言了,寫起代碼了感覺也就爽多了!
令人高興的是,受這些甘露滋養的 JavaScript 程序效率會更高。因為其原型對象里既沒有了毫無用處
的那些對象級的成員,而且還不存在constructor屬性體,少了與構造函數間的牽連,但依舊保持了方法
的共享性。這讓JavaScript 在追溯原型鏈和搜索屬性及方法時,少費許多工夫啊。
我們就把這種形式稱為“甘露模型”吧!其實,這種“甘露模型”的原型用法才是符合prototype概念的本
意,才是的JavaScript原型的真諦!
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
