DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> JavaScript是一門面向對象語言( 二)
JavaScript是一門面向對象語言( 二)
編輯:關於JavaScript     
續前一篇

使用函數構造器構造對象
除了 字面式聲明(literal notation)方式之外,ECMAScript 允許通過 構造器(constructor)創建對象。每個構造器實際上是一個 函數(function) 對象, 該函數對象含有一個“prototype”屬性用於實現 基於原型的繼承(prototype-based inheritance)和 共享屬性(shared properties)。對象可以由“new 關鍵字 + 構造器調用”的方式來創建,如 程序清單 3:

清單 3. 使用構造器 (constructor) 創建對象

// 構造器 Person 本身是一個函數對象
function Person() {
// 此處可做一些初始化工作
}
// 它有一個名叫 prototype 的屬性
Person.prototype = {
name: “張三”,
age: 26,
gender: “男”,
eat: function( stuff ) {
alert( “我在吃” + stuff );
}
}
// 使用 new 關鍵字構造對象
var p = new Person();

由於早期 JavaScript 的發明者為了使這門語言與大名鼎鼎的 Java 拉上關系 ( 雖然現在大家知道二者是雷鋒和雷鋒塔的關系 ),使用了 new 關鍵字來限定構造器調用並創建對象,以使其在語法上跟 Java 創建對象的方式看上去類似。但需要指出的是,這兩門語言的 new含義毫無關系,因為其對象構造的機理完全不同。也正是因為這裡語法上的類似,眾多習慣了類式面向對象語言中對象創建方式的程序員,難以透徹理解 JS 對象原型構造的方式,因為他們總是不明白在 JS 語言中,為什麼“函數名可以作為類名”的現象。而實質上,JS 這裡僅僅是借用了關鍵字 new,僅此而已;換句話說,ECMAScript 完全可以用其它 非 new 表達式來用調用構造器創建對象。


徹底理解原型鏈 (prototype chain)
在 ECMAScript 中,每個由構造器創建的對象擁有一個指向構造器 prototype 屬性值的 隱式引用(implicit reference),這個引用稱之為 原型(prototype)。進一步,每個原型可以擁有指向自己原型的 隱式引用(即該原型的原型),如此下去,這就是所謂的 原型鏈(prototype chain) (參考資源)。在具體的語言實現中,每個對象都有一個 __proto__ 屬性來實現對原型的 隱式引用。程序清單 4說明了這一點。

清單 4. 對象的 __proto__ 屬性和隱式引用

function Person( name ) {
this.name = name;
}
var p = new Person();
// 對象的隱式引用指向了構造器的 prototype 屬性,所以此處打印 true
console.log( p.__proto__ === Person.prototype );

// 原型本身是一個 Object 對象,所以他的隱式引用指向了
// Object 構造器的 prototype 屬性 , 故而打印 true
console.log( Person.prototype.__proto__ === Object.prototype );

// 構造器 Person 本身是一個函數對象,所以此處打印 true
console.log( Person.__proto__ === Function.prototype );

有了 原型鏈,便可以定義一種所謂的 屬性隱藏機制,並通過這種機制實現繼承。ECMAScript 規定,當要給某個對象的屬性賦值時,解釋器會查找該對象原型鏈中第一個含有該屬性的對象(注:原型本身就是一個對象,那麼原型鏈即為一組對象的鏈。對象的原型鏈中的第一個對象是該對象本身)進行賦值。反之,如果要獲取某個對象屬性的值,解釋器自然是返回該對象原型鏈中首先具有該屬性的對象屬性值。圖 1說名了這中隱藏機制:

圖 1. 原型鏈中的屬性隱藏機制


在圖 1 中,object1->prototype1->prototype2 構成了 對象 object1 的原型鏈,根據上述屬性隱藏機制,可以清楚地看到 prototype1 對象中的 property4 屬性和 prototype2 對象中的 property3 屬性皆被隱藏。理解了原型鏈,那麼將非常容易理解 JS 中基於原型的繼承實現原理,程序清單 5 是利用原型鏈實現繼承的簡單例子。

清單 5. 利用原型鏈 Horse->Mammal->Animal 實現繼承

// 聲明 Animal 對象構造器
function Animal() {
}
// 將 Animal 的 prototype 屬性指向一個對象,
// 亦可直接理解為指定 Animal 對象的原型
Animal.prototype = {
name: animal",
weight: 0,
eat: function() {
alert( "Animal is eating!" );
}
}
// 聲明 Mammal 對象構造器
function Mammal() {
this.name = "mammal";
}
// 指定 Mammal 對象的原型為一個 Animal 對象。
// 實際上此處便是在創建 Mammal 對象和 Animal 對象之間的原型鏈
Mammal.prototype = new Animal();

// 聲明 Horse 對象構造器
function Horse( height, weight ) {
this.name = "horse";
this.height = height;
this.weight = weight;
}
// 將 Horse 對象的原型指定為一個 Mamal 對象,繼續構建 Horse 與 Mammal 之間的原型鏈
Horse.prototype = new Mammal();

// 重新指定 eat 方法 , 此方法將覆蓋從 Animal 原型繼承過來的 eat 方法
Horse.prototype.eat = function() {
alert( "Horse is eating grass!" );
}
// 驗證並理解原型鏈
var horse = new Horse( 100, 300 );
console.log( horse.__proto__ === Horse.prototype );
console.log( Horse.prototype.__proto__ === Mammal.prototype );
console.log( Mammal.prototype.__proto__ === Animal.prototype );

理解清單 5 中對象原型繼承邏輯實現的關鍵在於 Horse.prototype = new Mammal() 和 Mammal.prototype = new Animal() 這兩句代碼。首先,等式右邊的結果是構造出一個臨時對象,然後將這個對象賦值給等式左邊對象的 prototype 屬性。也就是說將右邊新建的對象作為左邊對象的原型。讀者可以將這兩個等式替換到相應的程序清單 5 代碼最後兩行的等式中自行領悟。


JavaScript 類式繼承的實現方法
從代碼清單 5 可以看出,基於原型的繼承方式,雖然實現了代碼復用,但其行文松散且不夠流暢,可閱讀性差,不利於實現擴展和對源代碼進行有效地組織管理。不得不承認,類式繼承方式在語言實現上更具健壯性,且在構建可復用代碼和組織架構程序方面具有明顯的優勢。這使得程序員們希望尋找到一種能夠在 JavaScript 中以類式繼承風格進行編碼的方法途徑。從抽象的角度來講,既然類式繼承和原型繼承都是為實現面向對象而設計的,並且他們各自實現的載體語言在計算能力上是等價的 ( 因為圖靈機的計算能力與 Lambda 演算的計算能力是等價的 ),那麼能不能找到一種變換,使得原型式繼承語言通過該變換實現具有類式繼承編碼的風格呢?
目前一些主流的 JS 框架都提供了這種轉換機制,也即類式聲明方法,比如 Dojo.declare()、Ext.entend() 等等。用戶使用這些框架,可以輕易而友好地組織自己的 JS 代碼。其實,在眾多框架出現之前,JavaScript 大師 Douglas Crockford 最早利用三個函數對 Function 對象進行擴展,實現了這種變換,關於它的實現細節可以(參考資源)。此外還有由 Dean Edwards實現的著名的 Base.js(參考資源)。值得一提的是,jQuery 之父 John Resig 在搏眾家之長之後,用不到 30 行代碼便實現了自己的 Simple Inheritance。使用其提供的 extend 方法聲明類非常簡單。程序清單 6是使用了 Simple Inheritance庫實現類的聲明的例子。其中最後一句打印輸出語句是對 Simple Inheritance實現類式繼承的最好說明。

清單 6. 使用 Simple Inheritance 實現類式繼承

// 聲明 Person 類
var Person = Class.extend( {
_issleeping: true,
init: function( name ) {
this._name = name;
},
isSleeping: function() {
return this._issleeping;
}
} );
// 聲明 Programmer 類,並繼承 Person
var Programmer = Person.extend( {
init: function( name, issleeping ) {
// 調用父類構造函數
this._super( name );
// 設置自己的狀態
this._issleeping = issleeping;
}
} );
var person = new Person( "張三" );
var diors = new Programmer( "張江男", false );
// 打印 true
console.log( person.isSleeping() );
// 打印 false
console.log( diors.isSleeping() );
// 此處全為 true,故打印 true
console.log( person instanceof Person && person instanceof Class
&& diors instanceof Programmer &&
diors instanceof Person && diors instanceof Class );

如果您已對原型、函數構造器、閉包和基於上下文的 this 有了充分的理解,那麼理解 Simple Inheritance 的實現原理也並非相當困難。從本質上講,var Person = Class.extend(...)該語句中,左邊的 Person 實際上是獲得了由 Class 調用 extend 方法返回的一個構造器,也即一個 function 對象的引用。順著這個思路,我們繼續介紹 Simple Inheritance 是如何做到這一點,進而實現了由原型繼承方式到類式繼承方式的轉換的。圖 2 是 Simple Inheritance 的源碼及其附帶注釋。為了方便理解,用中文對代碼逐行補充說明。

圖 2.Simple Inheritance 源碼解析


拋開代碼第二部分,整體連貫地考察第一和第三部分會發現,extend 函數的根本目的就是要構造一個具有新原型屬性的新構造器。我們不禁感歎 John Resig的大師手筆及其對 JS 語言本質把握的細膩程度。至於 John Resig是如何想到這樣精妙的實現方法,感興趣的讀者可以閱讀本文 (參考資源),其中有詳細介紹關於最初設計 Simple Inheritance 的思維過程。


JavaScript 私有成員實現
到此為止,如果您任然對 JavaScript 面向對象持懷疑態度,那麼這個懷疑一定是,JavaScript 沒有實現面向對象中的信息隱藏,即私有和公有。與其他類式面向對象那樣顯式地聲明私有公有成員的方式不同,JavaScript 的信息隱藏就是靠閉包實現的。見 程序清單 7:

清單 7. 使用閉包實現信息隱藏

// 聲明 User 構造器
function User( pwd ) {
// 定義私有屬性
var password = pwd;
// 定義私有方法
function getPassword() {
// 返回了閉包中的 password
return password;
}
// 特權函數聲明,用於該對象其他公有方法能通過該特權方法訪問到私有成員
this.passwordService = function() {
return getPassword();
}
}
// 公有成員聲明
User.prototype.checkPassword = function( pwd ) {
return this.passwordService() === pwd;
};
// 驗證隱藏性
var u = new User( "123456" );
// 打印 true
console.log( u.checkPassword( "123456" ) );
// 打印 undefined
console.log( u.password );
// 打印 true
console.log( typeof u.gePassword === "undefined" );

JavaScript 必須依賴閉包實現信息隱藏,是由其函數式語言特性所決定的。本文不會對函數式語言和閉包這兩個話題展開討論,正如上文默認您理解 JavaScript 中基於上下文的 this 一樣。關於 JavaScript 中實現信息隱藏,Douglas Crockford在《 Private members in JavaScript 》(參考資源)一文中有更權威和詳細的介紹。


結束語
JavaScript 被認為是世界上最受誤解的編程語言,因為它身披 c 語言家族的外衣,表現的卻是 LISP 風格的函數式語言特性;沒有類,卻實也徹底實現了面向對象。要對這門語言有透徹的理解,就必須扒開其 c 語言的外衣,從新回到函數式編程的角度,同時摒棄原有類的面向對象概念去學習領悟它。隨著近些年來 Web 應用的普及和 JS 語言自身的長足發展,特別是後台 JS 引擎的出現 ( 如基於 V8 的 NodeJS 等 ),可以預見,原來只是作為玩具編寫頁面效果的 JS 將獲得更廣闊發展天地。這樣的發展趨勢,也對 JS 程序員提出了更高要求。只有徹底領悟了這門語言,才有可能在大型的 JS 項目中發揮她的威力。
XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved