一、什麼是建構函式?
建構函式 ,是一種特殊的方法。主要用來為物件成員變數賦初始值,總與new運算子一起使用在建立物件的語句中,對於JavaScript的內建物件Number()、String()、Boolean()、Object()、Array()、Function()、Date()、RegExp()、Error()等都是建構函式;
建構函式的特點:
- 建構函式的首字母大寫,用來區分於普通函式;
- 內部使用的this物件,來指向即將要生成的例項物件;
- 使用New來生成例項物件;
舉個栗子:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayHello = function(){
console.log(this.name+":Hello!");
};
}
var person1 = new Person("lilei", 26, "Teacher"); //例項物件 person1
var person2 = new Person("xiaom", 27, "Doctor"); //例項物件 person2
複製程式碼
建構函式與其他函式的唯一區別,就在於呼叫它們的方式不同。建構函式畢竟也是函式,不存在定義建構函式的特殊語法。任何函式,只要通過 new 操作符來呼叫,那它就可以作為建構函式
二、建構函式與物件
上一節我們說到在JavaScript中,幾乎所有的事物都是物件:物件只是帶有屬性和方法的特殊資料型別
但是基本型別值不是物件,因而從邏輯上講它們不應該有方法,實際上我們建立 String 型別時後臺自動做了一些處理:
- 建立 String 型別的一個例項;
- 在例項上呼叫指定的方法;
- 銷燬這個例項。
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;
複製程式碼
基本型別值不是物件,只有引用型別是物件,但是 我們可以讓String成為引用型別:
var s1 = new String("some text");
複製程式碼
當我們手動new一個字串s1的時候,s1既是String又是引用型別,所以他是一個物件;
引用型別與基本包裝型別的主要區別就是物件的生存期。使用 new 操作符建立的引用型別的例項, 在執行流離開當前作用域之前都一直儲存在記憶體中。而自動建立的基本包裝型別的物件,則只存在於一 行程式碼的執行瞬間,然後立即被銷燬;
三、建立例項物件
要建立 Person 的新例項,必須使用 new 操作符,呼叫建構函式建立物件經過了以下幾個過程:
- 建立一個新物件;
- 將建構函式的作用域賦給新物件(因此 this 就指向了這個新物件);
- 執行建構函式中的程式碼(為這個新物件新增屬性);
- 返回新物件;
在前面例子中,person1 和 person2分別儲存著Person的一個不同的例項。這兩個物件都有一個constructor(建構函式) 屬性,該屬性指向 Person:
console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true
複製程式碼
四、判斷物件型別
在JS中判斷一個變數的型別經常會用 typeof 運算子,但是在使用 typeof 運算子來判斷引用型別時,無論引用的是什麼型別的物件,它都返回"object"。所以在判斷物件的型別時我們可以使用 instanceof 運算子:
console.log(person1 instanceof Object); //true
複製程式碼
五、 建構函式缺點
使用建構函式時每個方法都要在每個例項上重新建立一遍:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayHello = function(){
console.log(this.name+":Hello!");
};
//this.sayHello = new Function('console.log(this.name+":Hello!")')
}
複製程式碼
new Function與宣告函式在邏輯上是一樣的,所以每次例項化Person物件時,sayHello 方法也是一個新例項,所以:
console.log(person1.sayName == person2.sayName); //false
複製程式碼
注意:person1.sayName 與person1.sayName()不一樣,person1.sayName()是指函式返回值,person1.sayName是指函式本身;
建立兩個完成同樣任務的 Function 例項的確沒有必要,因此,我們可以通過把函式定義轉移到建構函式外部來解決這個問題。例如:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayHello = sayHello;
}
function sayHello(){
console.log(this.name+":Hello!");
}
複製程式碼
這樣將 sayName 屬性設定成指向全域性函式 sayName() 的指標。sayName()只被例項化一次; 但是如果物件需要定義很多方法,那麼就要定義很多個全域性函式。而且在全域性作用域中定義的函式實際上只被某個物件呼叫,這樣不符合全域性函式的理念。我們這個自定義的引用型別也絲毫沒有封裝性可言。好在這些問題可以通過使用原型模式來解決。所以下一節《 prototype(原型)》
文章參考:
《JavaScript 高階程式設計》中文譯本 第三版