天天看點

JavaScript建立對象(二)——構造函數模式

在JavaScript建立對象(一)—— 工廠模式中留下了一個問題,就是建立一個對象怎麼判斷一個對象的類型。換句話說使用下面這種方式:

function createPerson(name, age, job){
var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);
    }
    return o;
}
           

隻是函數的名字叫

createPerson

,其實傳回的對象的本質還是一個Object。現在隻能你說他是人他就是人,你說他是狗他就是狗,有沒有一種方法比如類似下面這樣:

var p1 = createPerson('zhangsan', 18, 'JavaScript');
if(p1 == Person){
    alert('p1是人');
}
if(p1 == Dog){
    alert('p1是狗');
}
           

在程式上有一種判斷邏輯可以區分對象的類型呢?答案是有的,這就是本篇文章要讨論的構造函數模式。

現在使用構造函數模式改寫上面的工廠模式:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }
}
var p1 = new Person('zhangsan', 18, 'JavaScript');
var p2 = new Person('lisi', 20, 'Java');
           

我們比較構造函數模式和工廠模式發現構造函數模式有以下不同:

  1. 沒有顯式的建立對象,如工廠模式 var o = new Object();
  2. 直接将屬性和方法賦給了this,而不是o
  3. 沒有return語句
  4. 建立對象使用new關鍵字,而不是直接調用函數

現在我們可以來讨論如何解決判斷對象類型的問題了,很簡單,可以使用如下代碼:

alert(p1.constructor == Person); //true
alert(p2.constructor == Person); //true
           

同理,判斷一個對象是否是狗,也可以這樣:

function Dog(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }
}
var d1 = new Dog('xiaohei', 2, 'kanjia');
var d2 = new Dog('xiaohua', 3, 'huyuan');
alert(d1.constructor == Dog); //true
alert(d2.constructor == Dog); //true
           

這樣就可以用代碼在邏輯上判斷一個對象的類型了。

檢測對象的類型除了可以使用對象的

constructor

屬性還可以使用

instanceof

關鍵字。JavaScript和Java一樣,所有對象均繼承自

Object

,使用

instanceof

可以檢測到繼承鍊,但是

constructor

不行,是以從這個角度來說

instanceof

更可靠一些,因為一個對象是子類型,當然也是父類型。看下面代碼:

alert(p1.constructor == Object); //false
alert(p1.constructor == Person); //true
alert(p1 instanceof Object); //true
alert(p1 instanceof Person); //true
           

至此構造函數函數不止具有了工廠模式的優點,還解決了工廠模式的缺點,那麼構造函數模式有沒有缺點呢?當然也是有的。

以上述

p1

p2

為例,這兩個對象都具有

sayName

函數,而且功能也一樣。在構造函數中

sayName

是一個指針,

function(){alert(this.name)}

是一個匿名函數,我們知道js中函數也是對象,每次建立對象都要執行構造函數,那麼也就會建立一個匿名函數對象,若是建立若幹個對象,那麼就要建立若幹個匿名函數對象,功能都相同,沒必要不說,還占用記憶體。

通過以下代碼可以說明

p1

sayName

p2

sayName

确實是兩個執行個體。

alert(p1.sayName == p2.sayName); //false
           

那麼我們可以嘗試像下面這樣解決這個問題,把函數定義到構造函數之外:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
}
var p1 = new Person('zhangsan', 18, 'JavaScript');
var p2 = new Person('lisi', 20, 'Java');
alert(p1.sayName == p2.sayName); //true
           

這樣雖然解決了多個函數做同一件事的問題,但是又帶來了新的問題:定義在全局作用域中的函數實際上隻能被某個對象調用,這讓全局作用域有點名不副實。比如這裡的

sayName()

,實際上是為

Person

準備的,但因為是定義在全局作用域是以可以直接調用

sayName()

,也就是

window.sayName()

, 那麼此時的

this.name

就是

window.name

了,違背了定義的初衷。另一個問題是:如果

Person

上需要定義很多方法,那麼就要定義很多個全局函數,若再加上

Dog

等等其他對象的方法,這些方法全都定義為全局函數,互相揉雜在一起,那麼我們自定義的引用類型就絲毫沒有封裝性可言了。

那麼,構造函數模式多個函數做同一件事的問題到底怎麼解決呢?請看下一篇——原型模式。

文章參考:《JavaScript進階程式設計》第3版