(二)個人JavaScript系列:JavaScript面向對象旅程(下)

剪不斷,理還亂,是離愁。javascript

前面已經提到過新語言開發的兩個步驟,分別是:1、定義基本的數據類型,完善結構化編程語言的設計;2、爲函數類型綁定this的概念,好在對象的方法中能夠引用到對象自身。下面是繼續下去的思路,其主體思想是儘量地引用傳統面嚮對象語言的相關概念(如類、繼承等)到新語言中來。java

3、讓對象屬於某個類

此次要引入類的概念來。可是注意的是,仍是前面提到過的思路,是讓對象看起來屬於某個類,而不是真正地構造基於類的種種語義概念。編程

通常來講,類包括類符號和類模板。最簡單的類符號能夠是一個字符串屬性。好比隨便一個對象,它的class屬性來指明類名,即調用obj.class返回類名字符串(如"Cat", "Dog"等)。不過麻煩的是類模板。咱們要在統一的地方定義類模板,以便於在用統一的模式建立類實例。這樣建立的類實例(即對象)纔是真正有意義的。由於咱們不只要在類符號上來區別類;更重要的是,要在類行爲上來統一類。編程語言

這時想到的是函數。由於在JavaScript中,一切對象的建立經過一段代碼塊來實現的,而函數又可以將這段代碼塊組合起來。因此,可讓通常的函數做爲類模板的定義;進一步地,將它視爲構造方法。函數

通常的函數定義是:ui

function createStudent(name, age) {
    var student = {};
    student.name = name;
    student.age = age;
    student.toString = function() {
        return this.name + " " + this.age;
    };
    student.class = 'Student';
    return student;
}

這樣纔可以完整地表現咱們以前的一些概念。並且,這種方式沒有引入任何新的語言概念。只不過,這種構造方式要徹底依賴開發人員去實現。語言自己並不能自動支持其中的任何一個概念。this

所以,當時的JavaScript設計者進一步地推進了在語言自身中去自動實現一些概念。分爲如下幾步:prototype

1. 引入構造函數的概念

上面的createStudent多多少少不是構造函數的樣子。像Java和C++那樣的面嚮對象語言,當調用構造函數時,對象已經建立好了。構造函數完成的是一些初始化的工做。根本就不須要像第2行代碼那樣去顯示地建立對象。因此,前面的代碼要改寫成下面的樣子:設計

function Student(name, age) {
    this.name = name;
    this.age = age;
    this.toString = function() {
        return this.name + " " + this.age;
    };
}

這裏主要作了如下幾點改動:code

1. 函數名再也不是createStudent,而是Student,這看起來更像是構造函數的名字。
2. 再也不顯示地建立對象;而是當函數做爲構造函數調用時,會默認構造一個空對象,並可以經過this訪問。
3. 函數再也不return返回任何對象;而是默認地,應該返回this指向地對象。
4. 再也不有任何顯示地構造this.class = "Student"之類的語句;而是默認地,這一構建應該在構造過程當中自動完成。

只不過,這裏說是這樣說,要達到這一系列的改動必需要作一些工做。若是還像之前那樣調用Student函數時達不到上面提到的四點效果的(說錯了,其實第一點效果達到了)。有興趣的同窗能夠本身揣摩下。

爲達到上面提到的四點效果,JavaScript設計者引入一個新的new語句。它像Java構造對象時那樣調用。像下面:

new Student("Sam", 18);

而上面語句實際作的工做,用JavaScript描述大體就是:

var obj = {};
Student.call(obj, "Sam", 18);
obj.i_was_build_by = Student;
return obj;

這裏特別注意的是代碼的第三行。咱們再也不是經過增長一個class屬性來區分對象的類,而是經過加入一個i_was_build_by屬性,它引用了構造函數Student。這個至關於前面的class屬性,不過它引用的不是一個單純的字符串,而是一個函數了。這樣也行。咱們也能夠相似的判斷一個對象是否屬於某個類:

s instanceof Student //等效於 s.i_was_build_by == Student

我寫出這麼一個奇葩的名字,是不想誤導讀者。若是要深追究,JavaScript當中不是經過這種方式來區別對象的,其機制要稍微複雜些,不過大致思想是一致的。

讓方法只定義一次

咱們看到改動後的代碼,其依然有個不足之處。在基於類模板的語言中,方法是屬於類的,只須要定義一次。而在咱們的版本中,方法是屬於對象的,其在每次Student函數調用過程中都會被定義一次。且不說帶來的內存消耗吧。這樣離看上去像Java也是差了些。因此這裏又要作些改動,使得方法只須要定義一次。

思路就是新建立的對象要保持一個對象引用,這個對象囊括了對象所屬類的方法集合。首先,這個引用的名字是prototype;其次,它的來源是構造函數同爲名prototype的引用;最後,全部在本對象中找不到的方法,都推到prototype中去查找。例如,咱們要把以前的案例像下面這樣寫:

function Student(name, age) {
    this.name = name;
    this.age = age;
}

Student.prototype.toString = function() {
    return this.name + " " + this.age;
};

var s = new Student();
//s.prototype == Student.prototype;
//s.toString() == s.prototype.toString.call(s);

解釋:Student函數自己有個屬性prototype。經過new Student()語句構造的s對象,它的prototype屬性指向了函數Student的prototype屬性。最後當調用s.toString()時,因爲s中不存在toString屬性,繼而跳到prototype對象中去查找。就好像prototype當中的屬性是本身的屬性同樣。

那麼真正地new Student("Sam", 18)語句執行邏輯能夠總結以下:

var obj = {};
Student.call(obj, "Sam", 18);
obj.i_was_build_by = Student;
obj.prototype = Student.prototype;
return obj;

經過這種方式,咱們能夠只須要在prototype處定義方法一次便可;另外,prototype也能夠定義類的共有屬性。這就是prototype處的做用。下面咱們還會看到,經過prototype鏈的方式,它也開拓了通往繼承之門的道路。

4、繼承並不神祕,它就是prototype鏈

真的快要寫完了。也許在JavaScript中,最值得着墨的地方就是繼承了。不過我寫的有些累了,這裏再也不多提了。

其思路就是擴展prototype下去。咱們以前提過,若是一個對象的屬性找不到,就會在它的prototype引用中去找;若是在prototype引用中還找不到呢?那麼就會在prototype引用的prototype引用中再去找,一直到找到爲止或者prototype引用爲空。

但這與繼承有什麼聯繫呢?事實上,經過巧妙地構造prototype鏈,就能夠實現繼承的效果了。不便說了,上例子吧:

function Animal() {}

function Dog() {}

Dog.prototype = new Animal();

這便實現了繼承的魔法。乍一看也許沒明白,須要拆解開:

let animal = new Animal();
animal.prototype == Animal.prototype;

let Dog.prototype = animal;

let dog = new Dog();
dog.prototype == Dog.prototype == animal;

上面的例子顯示了,若是新建一個dog對象,它的prototype對象(暫且取箇中間變量)爲animal,而animal對象的prototype對象就會回到Animal的prototype中去。對於dog的某個方法調用,它首先在animal中尋找(這個是Dog的prototype);若是找不到,就會在animal的prototype中尋找(這個是Animal的prototype)。這樣,咱們不只能夠調用Dog.prototype中定義的方法(這是子類的方法),也能夠調用Animal.prototype的方法(這個是繼承於父類的方法)。這樣操做便實現了繼承。

5、總結

JavaScript按照這種思路就創造得差很少了。這種思路差很少就是JavaScript面向對象的一種概述了。而實際上,JavaScript真正地內部機制比起這個要複雜一些;不過我相信它也有本身的考量。總之,JavaScript有着本身的面向對象思想,又要引入傳統的基於類模板的面向對象概念進來,就變成了如今這樣了。

相關文章
相關標籤/搜索