如果你有以下建立物件的需求:
var p1 = { name : 'Justin', age : 35};
var p2 = { name : 'Momor', age : 32};
var p3 = { name : 'Hamimi', age : 2};
var p2 = { name : 'Momor', age : 32};
var p3 = { name : 'Hamimi', age : 2};
這些物件在建立時,具有相同的特性名稱,只不過特性值不同,其實你可以定義一個函式:
function Person(name, age) {
this.name = name;
this.age = age;
}
this.name = name;
this.age = age;
}
接著如下呼叫,就可以有相同的效果:
var p1 = new Person('Justin', 35);
var p2 = new Person('Momor', 32);
var p3 = new Person('Hamimi', 2);
var p2 = new Person('Momor', 32);
var p3 = new Person('Hamimi', 2);
像Person這樣的函式,接在new之後使用時,俗稱為建構式(Constructor),通常對從類別為基礎的語言過來的人,也會說這就像是一個類別(Class),不過這只是比擬,實際上當然與類別有所差別。
實際上你使用new運算子後接上一個函式時,相當於作這樣的動作(一部份是這樣,不過還有別的細節,之後再談):
js> function Person(name, age) {
> this.name = name;
> this.age = age;
> this.toString = function() {
> return '[' + this.name + ', ' + this.age + ']';
> };
> }
js> var p = {};
js> Person.call(p, 'Justin', 35);
js> p
[Justin, 35]
js>
> this.name = name;
> this.age = age;
> this.toString = function() {
> return '[' + this.name + ', ' + this.age + ']';
> };
> }
js> var p = {};
js> Person.call(p, 'Justin', 35);
js> p
[Justin, 35]
js>
這也說明了,為什麼使用new接上函式,傳回的物件會有name與age,因為Person中,this所參考的就是p所參考的物件,所以在this上新增特性,就相當於在p所參考物件上新增特性。
一個函式作為建構式使用時,基本上無需撰寫return,如果建構式有傳回值,那傳回值就會被當作最後名稱所參考的值。例如:
js> function Person(name, age) {
> return [];
> }
js> var o = new Person();
js> o instanceof Person;
false
js> o instanceof Array;
true
js>
> return [];
> }
js> var o = new Person();
js> o instanceof Person;
false
js> o instanceof Array;
true
js>
所以要以比擬的方式來說,new之後接上建構式,預設相當於這樣:
js> function Person(name, age) {
> this.name = name;
> this.age = age;
> this.toString = function() {
> return '[' + this.name + ', ' + this.age + ']';
> };
> return this;
> }
js> var p = {};
js> p = Person.call(p, 'Justin', 35);
[Justin, 35]
js>
> this.name = name;
> this.age = age;
> this.toString = function() {
> return '[' + this.name + ', ' + this.age + ']';
> };
> return this;
> }
js> var p = {};
js> p = Person.call(p, 'Justin', 35);
[Justin, 35]
js>
每個透過new建構的物件,都會有個constructor特性,參考至當初建構它的函式。例如:
js> function Person() {}
js> var p = new Person();
js> p.constructor == Person
true
js>
js> var p = new Person();
js> p.constructor == Person
true
js>
這可以作為物件類型的參考依據,不過要注意的是,constructor是可以修改的。
由於透過建構式所建立的物件,所有的特性都是直接新增在物件上,也因此可以直接透過 . 運算子加以存取。例如:
js> function Person(name, age) {
> this.name = name;
> this.age = age;
> }
js> var p = new Person('Justin', 35);
js> p.name;
Justin
js> p.age;
35
js>
> this.name = name;
> this.age = age;
> }
js> var p = new Person('Justin', 35);
js> p.name;
Justin
js> p.age;
35
js>
對熟悉物件導向私有性基本觀念的人來說,可能覺得這不安全,這相當於在物件導向觀念中,每個類別成員都是公開成員的意味。JavaScript本身並不支援物件導向公開、私用性等觀念,如果你想模擬,則可以如下:
js> function Person(name, age) {
> this.getName = function() {
> return name;
> };
> this.age = age;
> }
js> var p = new Person('Justin', 35);
js> print(p.name);
undefined
js> print(p.getName());
Justin
js> print(p.age);
35
js>
> this.getName = function() {
> return name;
> };
> this.age = age;
> }
js> var p = new Person('Justin', 35);
js> print(p.name);
undefined
js> print(p.getName());
Justin
js> print(p.age);
35
js>
以上假設的是,name不可以被設定,但可以透過getName()來取得,之所以會有這樣的效果,其實就是 閉包 的作用。上例中,在物件上新增了getName特性,參考至一個函式,該函式形成閉包綁定了參數name,參數也就是區域變數,並非物件上的特性,所以無法透過 . 運算子取得,因此模擬了私用性。
由於閉包綁定的是變數本身,所以也可以如下,在設定值(或取得值)時予以保護:
js> function Account() {
> var balance = 0;
> this.getBalance = function() {
> return balance;
> };
> this.setBalance = function(money) {
> if(money < 0) {
> throw new Error('can\'t set negative balance.');
> }
> balance = money;
> };
> }
js> var acct = new Account();
js> acct.getBalance();
0
js> acct.setBalance(1000);
js> acct.getBalance();
1000
js> acct.setBalance(-1000);
js: "<stdin>", line 113: exception from uncaught JavaScript throw: Error: can't
set negative balance.
at <stdin>:113
at <stdin>:111
js>
> var balance = 0;
> this.getBalance = function() {
> return balance;
> };
> this.setBalance = function(money) {
> if(money < 0) {
> throw new Error('can\'t set negative balance.');
> }
> balance = money;
> };
> }
js> var acct = new Account();
js> acct.getBalance();
0
js> acct.setBalance(1000);
js> acct.getBalance();
1000
js> acct.setBalance(-1000);
js: "<stdin>", line 113: exception from uncaught JavaScript throw: Error: can't
set negative balance.
at <stdin>:113
at <stdin>:111
js>