原型擴展
想必君的悟性極高,可能你會這樣想:如果在JavaScript內置的那些如Object和Function等函數(shù)的prototype上添加些新的方法和屬性,是不是就能擴展JavaScript的功能呢?
那么,恭喜你,你得到了!
在AJAX技術迅猛發(fā)展的今天,許多成功的AJAX項目的JavaScript運行庫都大量擴展了內置函數(shù)的prototype功能。比如微軟的ASP.NET AJAX,就給這些內置函數(shù)及其prototype添加了大量的新特性,從而增強了JavaScript的功能。
我們來看一段摘自MicrosoftAjax.debug.js中的代碼:
String.prototype.trim = function String$trim() { if (arguments.length !== 0) throw Error.parameterCount(); return this.replace(/^\s+|\s+$/g, ''); }
這段代碼就是給內置String函數(shù)的prototype擴展了一個trim方法,于是所有的String類對象都有了trim方法了。有了這個擴展,今后要去除字符串兩段的空白,就不用再分別處理了,因為任何字符串都有了這個擴展功能,只要調用即可,真的很方便。
當然,幾乎很少有人去給Object的prototype添加方法,因為那會影響到所有的對象,除非在你的架構中這種方法的確是所有對象都需要的。
前兩年,微軟在設計AJAX類庫的初期,用了一種被稱為“閉包”(closure)的技術來模擬“類”。其大致模型如下:
function Person(firstName, lastName, age) { //私有變量: var _firstName = firstName; var _lastName = lastName;
//公共變量: this.age = age;
//方法: this.getName = function() { return(firstName + " " + lastName); }; this.SayHello = function() { alert("Hello, I'm " + firstName + " " + lastName); }; }; var BillGates = new Person("Bill", "Gates", 53); var SteveJobs = new Person("Steve", "Jobs", 53); BillGates.SayHello(); SteveJobs.SayHello(); alert(BillGates.getName() + " " + BillGates.age); alert(BillGates.firstName); //這里不能訪問到私有變量
很顯然,這種模型的類描述特別象C#語言的描述形式,在一個構造函數(shù)里依次定義了私有成員、公共屬性和可用的方法,顯得非常優(yōu)雅嘛。特別是“閉包”機制可以模擬對私有成員的保護機制,做得非常漂亮。
所謂的“閉包”,就是在構造函數(shù)體內定義另外的函數(shù)作為目標對象的方法函數(shù),而這個對象的方法函數(shù)反過來引用外層外層函數(shù)體中的臨時變量。這使得只要目標對象在生存期內始終能保持其方法,就能間接保持原構造函數(shù)體當時用到的臨時變量值。盡管最開始的構造函數(shù)調用已經(jīng)結束,臨時變量的名稱也都消失了,但在目標對象的方法內卻始終能引用到該變量的值,而且該值只能通這種方法來訪問。即使再次調用相同的構造函數(shù),但只會生成新對象和方法,新的臨時變量只是對應新的值,和上次那次調用的是各自獨立的。的確很巧妙!
但是前面我們說過,給每一個對象設置一份方法是一種很大的浪費。還有,“閉包”這種間接保持變量值的機制,往往會給JavaSript的垃圾回收器制造難題。特別是遇到對象間復雜的循環(huán)引用時,垃圾回收的判斷邏輯非常復雜。無獨有偶,IE瀏覽器早期版本確實存在JavaSript垃圾回收方面的內存泄漏問題。再加上“閉包”模型在性能測試方面的表現(xiàn)不佳,微軟最終放棄了“閉包”模型,而改用“原型”模型。正所謂“有得必有失”嘛。
原型模型需要一個構造函數(shù)來定義對象的成員,而方法卻依附在該構造函數(shù)的原型上。大致寫法如下:
//定義構造函數(shù) function Person(name) { this.name = name; //在構造函數(shù)中定義成員 }; //方法定義到構造函數(shù)的prototype上 Person.prototype.SayHello = function() { alert("Hello, I'm " + this.name); }; //子類構造函數(shù) function Employee(name, salary) { Person.call(this, name); //調用上層構造函數(shù) this.salary = salary; //擴展的成員 }; //子類構造函數(shù)首先需要用上層構造函數(shù)來建立prototype對象,實現(xiàn)繼承的概念 Employee.prototype = new Person() //只需要其prototype的方法,此對象的成員沒有任何意義! //子類方法也定義到構造函數(shù)之上 Employee.prototype.ShowMeTheMoney = function() { alert(this.name + " $" + this.salary); }; var BillGates = new Person("Bill Gates"); BillGates.SayHello(); var SteveJobs = new Employee("Steve Jobs", 1234); SteveJobs.SayHello(); SteveJobs.ShowMeTheMoney();
原型類模型雖然不能模擬真正的私有變量,而且也要分兩部分來定義類,顯得不怎么“優(yōu)雅”。不過,對象間的方法是共享的,不會遇到垃圾回收問題,而且性能優(yōu)于“閉包”模型。正所謂“有失必有得”嘛。
在原型模型中,為了實現(xiàn)類繼承,必須首先將子類構造函數(shù)的prototype設置為一個父類的對象實例。創(chuàng)建這個父類對象實例的目的就是為了構成原型鏈,以起到共享上層原型方法作用。但創(chuàng)建這個實例對象時,上層構造函數(shù)也會給它設置對象成員,這些對象成員對于繼承來說是沒有意義的。雖然,我們也沒有給構造函數(shù)傳遞參數(shù),但確實創(chuàng)建了若干沒有用的成員,盡管其值是undefined,這也是一種浪費啊。
唉!世界上沒有完美的事情!
出處:軟件真諦
責任編輯:moby
上一頁 初看原型 下一頁 原型真諦
◎進入論壇網(wǎng)頁制作、WEB標準化版塊參加討論,我還想發(fā)表評論。
|