再談javascript面向對象編程

openkk 12年前發布 | 42K 次閱讀 JavaScript開發 JavaScript

        前言:雖有陳皓《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 如何定義一個對象。下面是我們的一個對象定義:

        var o = {};

        還可以這樣定義一個對象

    function f () {

        }

        對,你們沒有看錯,在 Javascript 里面,函數也是對象。

        當然還可以

        var array1= [ 1,2,3];

        數組也是一個對象。

        其他關于對象的基本的概念的描述,還是請各位親們參見陳皓《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 增加元素:

    array1.push (4);

        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 成員,這個成員是一個對象,這個對象包含了一個構造子成員,這個構造子成員會指向這個函數對象。

        例如:

    function Base () {

            this.id = "base"

        }

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

        上面這段代碼在 Javascript 引擎的對象模型是這樣的

再談javascript面向對象編程

        new 操作符

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

        基于上面的例子,我們執行如下代碼

var obj = new Base ();

        這樣代碼的結果是什么,我們在 Javascript 引擎中看到的對象模型是:

再談javascript面向對象編程

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

var obj  = {};
obj.__proto__ = Base.prototype;
Base.call (obj);

        第一行,我們創建了一個空對象 obj

        第二行,我們將這個空對象的__proto__成員指向了 Base 函數對象 prototype 成員對象

        第三行,我們將 Base 函數對象的 this 指針替換成 obj,然后再調用 Base 函數,于是我們就給 obj 對象賦值了一個 id 成員變量,這個成員變量的值是”base”,關于 call 函數的用法,請參看陳皓《Javascript 面向對象編程》文章

        如果我們給 Base.prototype 的對象添加一些函數會有什么效果呢?

        例如代碼如下:

    Base.prototype.toString = function() {

            return this.id;

        }

        那么當我們使用 new 創建一個新對象的時候,根據__proto__的特性,toString 這個方法也可以做新對象的方法被訪問到。于是我們看到了:

        構造子中,我們來設置‘類’的成員變量(例如:例子中的 id),構造子對象 prototype 中我們來設置‘類’的公共方法。于是通過函數對象和 Javascript 特有的__proto__與 prototype 成員及 new 操作符,模擬出類和類實例化的效果。

        Pseudoclassical 繼承

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

        function Derive (id) {

            this.id = id;

        }

        Derive.prototype = new Base ();

        Derive.prototype.test = function(id){

            return this.id === id;

        }

        var newObj = new Derive ("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 函數。

function object (old) {

           function F () {};

           F.prototype = old;

           return new F ();

        }

        var newObj = object (oldObject);

        例如如下代碼段

    var base ={

          id:"base",

          toString:function(){

                  return this.id;

          }

        };

        var derive = object (base);

        上面函數的執行后的對象模型是:

再談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 是不是太強大了些?

 本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!