2016年6月22日 星期三

[筆記] 談談JavaScript中的function constructor和prototype的建立


在上一篇筆記中我們說明了如何透過函式建構式(function constructor)來建立物件,但其實這樣只學了一半,在這篇我們會補齊另一半,說明function constructor如何用來設定該物件的原型(prototype)

我們之前有提到,在JavaScript中的函式其實也是一種物件,其中包含一些屬性像是該函式的名稱(Name)和該函式的內容(Code),但其實function這裡面還有一個屬性,這個屬性稱做"prototype",這個屬性會以空物件的型式呈現

除非你是把function當做function constructor來使用,否則這個屬性就沒有特別的用途;但如果你是把它當做function constructor,透過new這個關鍵字來執行這個function的話,它就有特別的意義了。


要進入這個function的prototype屬性只要直接透過 .prototype 就可以了。然而,有一點很容易令人困惑的地方,我們會以為如果我使用 .prototype 時,就可以進入這個函式的原型,但實際上並不是這樣的!

函式當中prototype這個屬性並不是這個函式的prototype,它指的是所有透過這個function constructor所建立出來的物件的prototype,聽起來好像有聽沒有懂...沒關係,讓我們來看一些程式碼幫我們理解這個概念。

1. function中的prototype屬性一開始是空物件

我們先執行上篇筆記最後所寫的程式碼:

function Person(firstname, lastname){

    this.firstname = firstname;
    this.lastname = lastname;
}

var john = new Person('John', 'Doe');
console.log(john);

var jane = new Person('Jane', 'Doe');
console.log(jane);

到chrome的console視窗中,我們輸入 Person.prototype得到的結果會得到一個空物件,如下圖:
當我們輸入Person.prototype時,會得到一個空物件。

2. 透過function constructor所建立的物件會繼承該function中prototype的內容

接著,讓我們在Person.prototype裡面增加一個getFullName的函式:

function Person(firstname, lastname){
    this.firstname = firstname;
    this.lastname = lastname;
}

Person.prototype.getFullName = function(){
    return this.firstname + ' ' + this.lastname;
}

var john = new Person('John', 'Doe');
console.log(john);

var jane = new Person('Jane', 'Doe');
console.log(jane);

在上面的程式第7-9行中,我們為Person.prototype添加了一個函式,所以當我們在chrome console視窗中呼叫Person.prototype時,會多了這個函式在內:


剛剛,我們有提到很重要的一句話,函式當中prototype這個屬性並不是這個函式的prototype,它指的是所有透過這個function constructor所建立出來的物件的prototype,翻成程式可能比較好說明,這句話的意思是說Person.prototype並不是Person.__proto__,但是所有透過Person這個function constructor所建立的物件,在該物件的__proto__中,會包含有Person.prototype的內容。

也就是說,當我們使用new這個運算子來執行函式建構式時,它會先建立一個空物件,同時將該建構式中prototype這個屬性名稱內的內容(Person.prototype),設置到該物件的prototype中(john.__proto__)。

因此,當我們在chrome console中輸入 john.__proto__ 時,我們就可以看到剛剛在Person.prototype所建立的函式getFullName已經繼承在裡面了:


實際運用


由於Person.prototype中的方法已經被繼承到由Person這個function constructor所建立的物件john中,所以這時侯,我們就可以順利的使用 john.getFullName 這個方法:

function Person(firstname, lastname){

    this.firstname = firstname;
    this.lastname = lastname;

}

Person.prototype.getFullName = function(){
    return this.firstname + ' ' + this.lastname;
}

var john = new Person('John', 'Doe');
console.log(john);
console.log(john.getFullName());


如此,可以正確的執行getFullName這個function並得到如下的結果:


透過函式建構式與Prototype的實用處


透過這樣的方法,我們可以讓所有根據這個函式建構式(function constructor)所建立的物件都包含有某些我們想要使用的方法。如果我們有1000個物件是根據這個函式建構式所建立,那麼我們只需要使用 .prototype 這樣的方法,就可以讓這1000個物件都可以使用到我們想要執行的某個method。

有的人可能會好奇說,為什麼我們不要把getFullName這個method直接寫在函式建構式當中呢?

我們不該把方法放在function constructor中

這麼做雖然是可以的,也就是說程式執行上可以正常執行也可以得到結果,但是這麼做會有個問題,如果我們是把這個方法直接寫在函式建構式中,那麼每一個物件都會包含有這個方法,如果我們有1000個物件根據這個函式建構式所建立,那麼這1000個物件都會包含這個方法在內,如此將會占據相當多的記憶體;但如果是建立在prototype中,我們只會有一個這樣的方法。

所以,為了效能上的考量,通常會把方法(method)放在建構式的prototype中,因為它們可以是通用的;把屬性放在建構式當中,因為每一個物件可能都會有不同的屬性內容,如此將能有效減少記憶體的問題。

程式範例


function Person(firstname, lastname){

    this.firstname = firstname;
    this.lastname = lastname;

}

Person.prototype.getFullName = function(){
    return this.firstname + ' ' + this.lastname;
}

var john = new Person('John', 'Doe');
console.log(john);
console.log(john.getFullName());

Person.prototype.getFormalFullName = function(){
    return this.lastname + ',' + this.firstname;
}

var jane = new Person('Jane', 'Doe');
console.log(jane);
console.log(jane.getFormalFullName());


→回到此系列文章目錄


Share:

0 意見:

張貼留言