在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');
我們比較構造函數模式和工廠模式發現構造函數模式有以下不同:
- 沒有顯式的建立對象,如工廠模式 var o = new Object();
- 直接将屬性和方法賦給了this,而不是o
- 沒有return語句
- 建立對象使用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版