## 介紹
構造函數大家都很熟悉了,不過如果你是新手,還是有必要來了解一下什么叫構造函數的。構造函數用于創建特定類型的對象——不僅聲明了使用的對象,構造函數還可以接受參數以便第一次創建對象的時候設置對象的成員值。你可以自定義自己的構造函數,然后在里面聲明自定義類型對象的屬性或方法。
## 基本用法
在JavaScript里,構造函數通常是認為用來實現實例的,JavaScript沒有類的概念,但是有特殊的構造函數。通過new關鍵字來調用定義的否早函數,你可以告訴JavaScript你要創建一個新對象并且新對象的成員聲明都是構造函數里定義的。在構造函數內部,this關鍵字引用的是新創建的對象。基本用法如下:
~~~
function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
this.output= function () {
return this.model + "走了" + this.miles + "公里";
};
}
var tom= new Car("大叔", 2009, 20000);
var dudu= new Car("Dudu", 2010, 5000);
console.log(tom.output());
console.log(dudu.output());
~~~
上面的例子是個非常簡單的構造函數模式,但是有點小問題。首先是使用繼承很麻煩了,其次output()在每次創建對象的時候都重新定義了,最好的方法是讓所有Car類型的實例都共享這個output()方法,這樣如果有大批量的實例的話,就會節約很多內存。
解決這個問題,我們可以使用如下方式:
~~~
function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
this.output= formatCar;
}
function formatCar() {
return this.model + "走了" + this.miles + "公里";
}
~~~
這個方式雖然可用,但是我們有如下更好的方式。
## 構造函數與原型
JavaScript里函數有個原型屬性叫prototype,當調用構造函數創建對象的時候,所有該構造函數原型的屬性在新創建對象上都可用。按照這樣,多個Car對象實例可以共享同一個原型,我們再擴展一下上例的代碼:
~~~
function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
}
/*
注意:這里我們使用了Object.prototype.方法名,而不是Object.prototype
主要是用來避免重寫定義原型prototype對象
*/
Car.prototype.output= function () {
return this.model + "走了" + this.miles + "公里";
};
var tom = new Car("大叔", 2009, 20000);
var dudu = new Car("Dudu", 2010, 5000);
console.log(tom.output());
console.log(dudu.output());
~~~
這里,output()單實例可以在所有Car對象實例里共享使用。
另外:我們推薦構造函數以大寫字母開頭,以便區分普通的函數。
## 只能用new嗎?
上面的例子對函數car都是用new來創建對象的,只有這一種方式么?其實還有別的方式,我們列舉兩種:
~~~
function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
// 自定義一個output輸出內容
this.output = function () {
return this.model + "走了" + this.miles + "公里";
}
}
//方法1:作為函數調用
Car("大叔", 2009, 20000); //添加到window對象上
console.log(window.output());
//方法2:在另外一個對象的作用域內調用
var o = new Object();
Car.call(o, "Dudu", 2010, 5000);
console.log(o.output());
~~~
該代碼的方法1有點特殊,如果不適用new直接調用函數的話,this指向的是全局對象window,我們來驗證一下:
~~~
//作為函數調用
var tom = Car("大叔", 2009, 20000);
console.log(typeof tom); // "undefined"
console.log(window.output()); // "大叔走了20000公里"
~~~
這時候對象tom是undefined,而window.output()會正確輸出結果,而如果使用new關鍵字則沒有這個問題,驗證如下:
~~~
//使用new 關鍵字
var tom = new Car("大叔", 2009, 20000);
console.log(typeof tom); // "object"
console.log(tom.output()); // "大叔走了20000公里"
~~~
## 強制使用new
上述的例子展示了不使用new的問題,那么我們有沒有辦法讓構造函數強制使用new關鍵字呢,答案是肯定的,上代碼:
~~~
function Car(model, year, miles) {
if (!(this instanceof Car)) {
return new Car(model, year, miles);
}
this.model = model;
this.year = year;
this.miles = miles;
this.output = function () {
return this.model + "走了" + this.miles + "公里";
}
}
var tom = new Car("大叔", 2009, 20000);
var dudu = Car("Dudu", 2010, 5000);
console.log(typeof tom); // "object"
console.log(tom.output()); // "大叔走了20000公里"
console.log(typeof dudu); // "object"
console.log(dudu.output()); // "Dudu走了5000公里"
~~~
通過判斷this的instanceof是不是Car來決定返回new Car還是繼續執行代碼,如果使用的是new關鍵字,則(this instanceof Car)為真,會繼續執行下面的參數賦值,如果沒有用new,(this instanceof Car)就為假,就會重新new一個實例返回。
## 原始包裝函數
JavaScript里有3中原始包裝函數:number, string, boolean,有時候兩種都用:
~~~
// 使用原始包裝函數
var s = new String("my string");
var n = new Number(101);
var b = new Boolean(true);
// 推薦這種
var s = "my string";
var n = 101;
var b = true;
~~~
推薦,只有在想保留數值狀態的時候使用這些包裝函數,關于區別可以參考下面的代碼:
~~~
// 原始string
var greet = "Hello there";
// 使用split()方法分割
greet.split(' ')[0]; // "Hello"
// 給原始類型添加新屬性不會報錯
greet.smile = true;
// 單沒法獲取這個值(18章ECMAScript實現里我們講了為什么)
console.log(typeof greet.smile); // "undefined"
// 原始string
var greet = new String("Hello there");
// 使用split()方法分割
greet.split(' ')[0]; // "Hello"
// 給包裝函數類型添加新屬性不會報錯
greet.smile = true;
// 可以正常訪問新屬性
console.log(typeof greet.smile); // "boolean"
~~~
## 總結
本章主要講解了構造函數模式的使用方法、調用方法以及new關鍵字的區別,希望大家在使用的時候有所注意。
參考:http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#constructorpatternjavascript
- (1)編寫高質量JavaScript代碼的基本要點
- (2)揭秘命名函數表達式
- (3)全面解析Module模式
- (4)立即調用的函數表達式
- (5)強大的原型和原型鏈
- (6)S.O.L.I.D五大原則之單一職責SRP
- (7)S.O.L.I.D五大原則之開閉原則OCP
- (8)S.O.L.I.D五大原則之里氏替換原則LSP
- (9)根本沒有“JSON對象”這回事!
- (10)JavaScript核心(晉級高手必讀篇)
- (11)執行上下文(Execution Contexts)
- (12)變量對象(Variable Object)
- (13)This? Yes, this!
- (14)作用域鏈(Scope Chain)
- (15)函數(Functions)
- (16)閉包(Closures)
- (17)面向對象編程之一般理論
- (18)面向對象編程之ECMAScript實現
- (19)求值策略
- (20)《你真懂JavaScript嗎?》答案詳解
- (21)S.O.L.I.D五大原則之接口隔離原則ISP
- (22)S.O.L.I.D五大原則之依賴倒置原則DIP
- (23)JavaScript與DOM(上)——也適用于新手
- (24)JavaScript與DOM(下)
- (25)設計模式之單例模式
- (26)設計模式之構造函數模式
- (27)設計模式之建造者模式
- (28)設計模式之工廠模式
- (29)設計模式之裝飾者模式
- (30)設計模式之外觀模式
- (31)設計模式之代理模式
- (32)設計模式之觀察者模式
- (33)設計模式之策略模式
- (34)設計模式之命令模式
- (35)設計模式之迭代器模式
- (36)設計模式之中介者模式
- (37)設計模式之享元模式
- (38)設計模式之職責鏈模式
- (39)設計模式之適配器模式
- (40)設計模式之組合模式
- (41)設計模式之模板方法
- (42)設計模式之原型模式
- (43)設計模式之狀態模式
- (44)設計模式之橋接模式
- (45)代碼復用模式(避免篇)
- (46)代碼復用模式(推薦篇)
- (47)對象創建模式(上篇)
- (48)對象創建模式(下篇)
- (49)Function模式(上篇)
- (50)Function模式(下篇)
- (結局篇)