再談javascript物件導向程式設計

發表於2012-02-27

來源:酷殼

前言:雖有陳皓《Javascript 物件導向程式設計》珠玉在前,但是我還是忍不住再畫蛇添足的補上一篇文章,主要是因為javascript這門語言魅力。另外這篇文章是一篇入門文章,我也是才開始學習Javascript,有一點心得,才想寫一篇這樣文章,文章中難免有錯誤的地方,還請各位不吝吐槽指正

吐槽Javascript

初次接觸Javascript,這門語言的確會讓很多正規軍感到諸多的不適,這種不適來自於Javascript的語法的簡練和不嚴謹,這種不適也 來自Javascript這個悲催的名稱,我在想網景公司的Javascript設計者在給他起名稱那天一定是腦殼進水了,讓Javascript這麼多 年來受了這麼多不白之冤,人們都認為他是Java的附屬物,一個WEB玩具語言。因此才會有些人會對Javascript不屑,認為Javascript 不是一門真正的語言,但是這此他們真的錯了。Javascript不僅是一門語言,是一門真真正正的語言,而且他還是一門裡程碑式的語言,他獨創多種新的程式設計模式原型繼承,閉包,對後來的動態語言產生了巨大的影響。做為當今最流行的語言(沒有之一),看看git上提交的最多的語言型別就能明白。隨著 HTML5的登場,瀏覽器將在個人電腦上將大顯身手,完全有替換OS的趨勢的時候,Javascript做為瀏覽器上的一門唯一真真的語言,如同C之於 unix/linux,java之於JVM,Cobol之於MainFrame,我們也需要來重新的認真地認識和審視這門語言。另外Javascript 的正式名稱是:ECMAScript,這個名字明顯比Javascript帥太多了!

言歸正傳,我們切入主題——Javascript的物件導向程式設計。要談Javascript的物件導向程式設計,我們第一步要做的事情就是忘記我們所學的面向 物件程式設計。傳統C++或Java的物件導向思維來學習Javascript的物件導向會給你帶來不少困惑,讓我們先忘記我們所學的,從新開始學習這門特殊 的物件導向程式設計。既然是OO程式設計,要如何來理解OO程式設計呢,記得以前學C++,學了很久都不入門,後來有幸讀了《Inside The C++ Object Model》這本大作,頓時豁然開朗,因此本文也將以物件模型的方式來探討的Javascript的OO程式設計。因為Javascript 物件模型的特殊性,所以使得Javascript的繼承和傳統的繼承非常不一樣,同時也因為Javascript裡面沒有類,這意味著 Javascript裡面沒有extends,implements。那麼Javascript到底是如何來實現OO程式設計的呢?好吧,讓我們開始吧,一起 在Javascript的OO世界裡來一次漫遊

首先,我們需要先看看Javascript如何定義一個物件。下面是我們的一個物件定義:

還可以這樣定義一個物件

對,你們沒有看錯,在Javascript裡面,函式也是物件。

當然還可以

陣列也是一個物件。

其他關於物件的基本的概念的描述,還是請各位親們參見陳皓《Javascript 物件導向程式設計》文章。

物件都有了,唯一沒有的就是class,因為在Javascript裡面是沒有class關鍵字的,算好還有function,function的存在讓我們可以變通的定義類,在擴充套件這個主題前,我們還需要了解一個Javascript物件最重要的屬性,__proto__成員。

__proto__成員

嚴格的說這個成員不應該叫這個名字,__proto__是Firefox中的稱呼,__proto__只有在Firefox瀏覽器中才能被訪問到。做 為一個物件,當你訪問其中的一個成員或方法的時候,如果這個物件中沒有這個方法或成員,那麼Javascript引擎將會訪問這個物件的 __proto__成員所指向的另外的一個物件,並在那個物件中查詢指定的方法或成員,如果不能找到,那就會繼續通過那個物件的__proto__成員指 向的物件進行遞迴查詢,直到這個連結串列結束

好了,讓我們舉一個例子。

比如上上面定義的陣列物件array1。當我們建立出array1這個物件的時候,array1實際在Javascript引擎中的物件模型如下:

再談javascript物件導向程式設計

array1物件具有一個length屬性值為3,但是我們可以通過如下的方法來為array1增加元素:

push這個方法來自於array1的__proto__成員指向物件的一個方法(Array.prototye.push())。正是因為所有的 陣列物件(通過[]來建立的)都包含有一個指向同一個具有push,reverse等方法物件(Array.prototype)的__proto__成 員,才使得這些陣列物件可以使用push,reverse等方法。

那麼這個__proto__這個屬性就相當於物件導向中的”has a”關係,這樣的的話,只要我們有一個模板物件比如Array.prototype這個物件,然後把其他的物件__proto__屬性指向這個物件的話就 完成了一種繼承的模式。不錯!我們完全可以這麼幹。但是別高興的太早,這個屬性只在FireFox中有效,其他的瀏覽器雖然也有屬性,但是不能通過 __proto__來訪問,只能通過getPrototypeOf方法進行訪問,而且這個屬性是隻讀的。看來我們要在Javascript實現繼承並不是 很容易的事情啊。

函式物件prototype成員

首先我們先來看一段函式prototype成員的定義,

When a function object is created, it is given a prototype member which is an object containing a constructor member which is a reference to the function object

當一個函式物件被建立時,這個函式物件就具有一個prototype成員,這個成員是一個物件,這個物件包含了一個構造子成員,這個構造子成員會指向這個函式物件。

例如:

Base這個函式物件就具有一個prototype成員,關於構造子其實Base函式物件自身,為什麼我們將這類函式稱為構造子呢?原因是因為這類 函式設計來和new 操作符一起使用的。為了和一般的函式物件有所區別,這類函式的首字母一般都大寫。構造子的主要作用就是來建立一類相似的物件。

上面這段程式碼在Javascript引擎的物件模型是這樣的

再談javascript物件導向程式設計

new 操作符

在有上面的基礎概念的介紹之後,在加上new操作符,我們就能完成傳統物件導向的class + new的方式建立物件,在Javascript中,我們將這類方式成為Pseudoclassical。

基於上面的例子,我們執行如下程式碼


這樣程式碼的結果是什麼,我們在Javascript引擎中看到的物件模型是:
再談javascript物件導向程式設計

new操作符具體幹了什麼呢?其實很簡單,就幹了三件事情。

第一行,我們建立了一個空物件obj

第二行,我們將這個空物件的__proto__成員指向了Base函式物件prototype成員物件

第三行,我們將Base函式物件的this指標替換成obj,然後再呼叫Base函式,於是我們就給obj物件賦值了一個id成員變數,這個成員變數的值是”base”,關於call函式的用法,請參看陳皓《Javascript 物件導向程式設計》文章

如果我們給Base.prototype的物件新增一些函式會有什麼效果呢?

例如程式碼如下:

那麼當我們使用new建立一個新物件的時候,根據__proto__的特性,toString這個方法也可以做新物件的方法被訪問到。於是我們看到了:

構造子中,我們來設定‘類’的成員變數(例如:例子中的id),構造子物件prototype中我們來設定‘類’的公共方法。於是通過函式物件和Javascript特有的__proto__與prototype成員及new操作符,模擬出類和類例項化的效果。
Pseudoclassical 繼承

我們模擬類,那麼繼承又該怎麼做呢?其實很簡單,我們只要將構造子的prototype指向父類即可。例如我們設計一個Derive 類。如下

 

這段程式碼執行後的物件模型又是怎麼樣的呢?根據之前的推導,應該是如下的物件模型

再談javascript物件導向程式設計

這樣我們的newObj也繼承了基類Base的toString方法,並且具有自身的成員id。關於這個物件模型是如何被推匯出來的就留給各位同學了,參照前面的描述,推導這個物件模型應該不難。

Pseudoclassical繼承會讓學過C++/Java的同學略微的感受到一點舒服,特別是new關鍵字,看到都特親切,不過兩者雖然相似,但是機理完全不同。當然不關什麼樣繼承都是不能離不開__proto__成員的。

Prototypal繼承

這是Javascript的另外一種繼承方式,這個繼承也就是之前陳皓文章《Javascript 物件導向程式設計》中create函式,非常可惜的是這個是ECMAScript V5的標準,支援V5的瀏覽器目前看來也就是IE9,Chrome最新版本和Firefox。雖然看著多,但是做為IE6的重災區的中國,我建議各位還是 避免使用create函式。好在沒有create函式之前,Javascript的使用者已經設計出了等同於這個函式的。例如:我們看看Douglas Crockford的object函式。

例如如下程式碼段

上面函式的執行後的物件模型是:

再談javascript物件導向程式設計

如何形成這樣的物件模型,原理也很簡單,只要把object這個函式擴充套件一下,就能畫出這個模型,怎麼畫留給讀者自己去畫吧。

這樣的繼承方式被稱為原型繼承。相對來說要比Pseudoclassical繼承來的簡單方便。ECMAScript V5正是因為這原因也才增加create函式,讓開發者可以快速的實現原型繼承。

上述兩種繼承方式是Javascript中最常用的繼承方式。通過本文的講解,你應該對Javascript的OO程式設計有了一些‘原理’級的瞭解了吧

參考:

《Prototypes and Inheritance in JavaScript Prototypes and Inheritance in JavaScript》
Advance Javascript (Douglas Crockford 大神的視訊,一定要看啊)

題外話:

web2.0後,web應用可謂飛速發展,如今在HTML5釋出之際,瀏覽器的功能被大大強化,我感覺Browser遠遠在不是一個Browser 那麼簡單了。記得C++之父曾經這樣說過JAVA,JAVA不是跨平臺,JAVA本身就是一個平臺。如今的Browser也本身就是一個平臺了,好在這個 平臺是基於標準的。如果Browser是平臺,由於Browser安全沙箱的限制,個人電腦的資源被使用的很少,感覺Browser就是一個 NC(Network Computer)?我們居然又回到了Sun最初提出的構想,Sun是不是太強大了些?

 

相關文章