如何用Prototype避免重複建立Methods節省記憶體?

這段JavaScript code有什麼問題?

1
2
3
4
5
6
7
8
function Human(name, age) {
this.name = name;
this.age = age;

this.speak = function () {
console.log("Hi! I'm " + this.name + ", I'm " + this.age + " years old!");
};
}

原來我們如果這麼寫constructor function,看上去是reuse了speak方法的程式碼,但在實際建立Human物件時,speak方法是會跟著被建立的。這樣真的會有問題嗎?直接看看下面的範例程式碼:

1
2
3
4
5
var humans = [];
for (var i = 0; i < 999; i++) {
var human = new Human(i.toString(), i);
humans.push(human);
}

執行這段程式碼之後,我們會得到999個Human物件和999個speak方法物件,如果我們在記憶體中建立愈多個Human的實例,Humanspeak方法就會跟著在記憶體中被建立愈多個,因為每當Human constructor function被呼叫時,Human function body都會被執行一次,在第5行的function expression也就會被運算一次,產生新的function物件來設定給Human物件的speak屬性。那麼,有避免這種問題發生的方法嗎?有除了reuse method程式碼以外,還可以避免不斷增加記憶體使用量的方法嗎?

使用Prototype建立Method

是的,我們可以把method建立在constructor function的prototype屬性裡,因為所有用new運算子建立的JavaScript物件,都會有一個prototype屬性指向constructor function的prototype物件,也就是共用constructor function的prototype啦!這麼一來,不論我們用constructor function和new運算子建立多少個實例,都不會重複建立要共用的method囉!我們先看看,如果把上面的程式碼改成使用prototype的版本,會長什麼樣子:

1
2
3
4
5
6
7
function Human(name, age) {
this.name = name;
this.age = age;
}
Human.prototype.speak = function () {
console.log("Hi! I'm " + this.name + ", I'm " + this.age + " years old!");
};

這麼一來,我們就把speak方法建立在Human constructor function的prototype屬性上啦!下面是我們建立Human物件的實例,並呼叫它的speak方法的結果:

呼叫建立在prototype的方法

那這樣寫為什麼可以避免方法被重複建立呢?其實是prototype chain的關係啦!

Prototype Chain

我們可以試想一下,如果我們對某個物件存取它不包含的屬性,會發生什麼事?當JavaScript遇到這種情形時,它會去找物件的prototype屬性,看看裡面找不找得到,如果有找到,就會把存取屬性的程式碼的結果,運算成找到的這個屬性。這就可以解釋,為什麼在上面的範例中,我們即使沒有把speak方法直接設定給Human constructor function,也可以對Human實例物件存取到speak方法囉!很有趣的一件事是,如果我們把建立出來的物件印出來,可以看到它的prototype屬性裡面會有一個constructor屬性,而它的值就是用來建立這個物件的constructor function呢!

藏在prototype裡的constructor屬性

那麼如果在prototype裡面也找不到要找的數性呢?既然這套機制叫做prototype chain,就表示JavaScript會始終如一得一直找下去、一直找下去,直到找到為止。那如果一直找到Object的prototype都找不到呢?那就去找global物件裡面有沒有這個屬性,當然,如果連global屬性裡都找不到,那整個存取屬性的程式碼的結果就會被運算成undefined啦!

以上就是如何使用prototype來避免重複建立methods,達到節省記憶體的目的啦!