你們都知道原型和原型鏈是 JavaScript 中最經典的問題之一,而構造函數又是原型和原型鏈的基礎,因此先了解清楚構造函數以及它的執行過程能夠更好地幫助咱們學習原型和原型鏈的知識。函數
本文將從如下幾個方面來探討構造函數:學習
1.什麼是構造函數
2.爲何要使用構造函數
3.構造函數的執行過程
4.構造函數的返回值this
在 JavaScript 中,用 new
關鍵字來調用的函數,稱爲構造函數。spa
學習每個概念,不只要知道它是什麼,還要知道爲何,以及解決什麼樣的問題。code
舉個例子,咱們要錄入一年級一班中每一位同窗的我的信息,那麼咱們能夠建立一些對象,好比:對象
var p1 = { name: 'zs', age: 6, gender: '男', hobby: 'basketball' }; var p2 = { name: 'ls', age: 6, gender: '女', hobby: 'dancing' }; var p3 = { name: 'ww', age: 6, gender: '女', hobby: 'singing' }; var p4 = { name: 'zl', age: 6, gender: '男', hobby: 'football' }; // ...
像上面這樣,咱們能夠把每一位同窗的信息當作一個對象來處理。可是,咱們會發現,咱們重複地寫了不少無心義的代碼。好比 name、age、gender、hobby
。若是這個班上有60個學生,咱們得重複寫60遍。blog
這個時候,構造函數的優點就體現出來了。咱們發現,雖然每位同窗都有 name、gender、hobby
這些屬性, 但它們都是不一樣的,那咱們就把這些屬性當作構造函數的參數傳遞進去。而因爲都是一年級的學生,age
基本都是6歲,因此咱們就能夠寫死,遇到特殊狀況再單獨作處理便可。此時,咱們就能夠建立如下的函數:ip
function Person(name, gender, hobby) { this.name = name; this.gender = gender; this.hobby = hobby; this.age = 6; }
當建立上面的函數之後, 咱們就能夠經過 new
關鍵字調用,也就是經過構造函數來建立對象了。內存
var p1 = new Person('zs', '男', 'basketball'); var p2 = new Person('ls', '女', 'dancing'); var p3 = new Person('ww', '女', 'singing'); var p4 = new Person('zl', '男', 'football'); // ...
此時你會發現,建立對象會變得很是方便。因此,雖然封裝構造函數的過程會比較麻煩,但一旦封裝成功,咱們再建立對象就會變得很是輕鬆,這也是咱們爲何要使用構造函數的緣由。ci
在使用對象字面量建立一系列同一類型的對象時,這些對象可能具備一些類似的特徵(屬性)和行爲(方法),此時會產生不少重複的代碼,而使用構造函數就能夠實現代碼的複用。
先說一點基本概念。
function Animal(color) { this.color = color; }
當一個函數建立好之後,咱們並不知道它是否是構造函數,即便像上面的例子同樣,函數名爲大寫,咱們也不能肯定。只有當一個函數以 new
關鍵字來調用的時候,咱們才能說它是一個構造函數。就像下面這樣:
var dog = new Animal("black");
如下咱們只討論構造函數的執行過程,也就是以 new
關鍵字來調用的狀況。
咱們仍是以上面的 Person 爲例。
function Person(name, gender, hobby) { this.name = name; this.gender = gender; this.hobby = hobby; this.age = 6; } var p1 = new Person('zs', '男', 'basketball');
此時,構造函數會有如下幾個執行過程:
(1) 當以 new
關鍵字調用時,會建立一個新的內存空間,標記爲 Animal 的實例。
1 建立內存空間
(2) 函數體內部的 this
指向該內存
經過以上兩步,咱們就能夠得出這樣的結論。
var p2 = new Person('ls', '女', 'dancing'); // 建立一個新的內存 #f2 var p3 = new Person('ww', '女', 'singing'); // 建立一個新的內存 #f3
每當建立一個實例的時候,就會建立一個新的內存空間(#f2, #f3),建立 #f2 的時候,函數體內部的 this 指向 #f2, 建立 #f3 的時候,函數體內部的 this 指向 #f3。
(3) 執行函數體內的代碼
經過上面的講解,你就能夠知道,給 this 添加屬性,就至關於給實例添加屬性。
(4) 默認返回 this
。
因爲函數體內部的 this
指向新建立的內存空間,默認返回 this
,就至關於默認返回了該內存空間,也就是上圖中的 #f1。此時,#f1的內存空間被變量 p1
所接受。也就是說 p1
這個變量,保存的內存地址就是 #f1,同時被標記爲 Person 的實例。
以上就是構造函數的整個執行過程。
構造函數執行過程的最後一步是默認返回 this
。言外之意,構造函數的返回值還有其它狀況。下面咱們就來聊聊關於構造函數返回值的問題。
(1)沒有手動添加返回值,默認返回 this
。
function Person1() { this.name = 'zhangsan'; } var p1 = new Person1();
按照上面講的,咱們複習一遍。首先,當用 new
關鍵字調用時,產生一個新的內存空間 #f11,並標記爲 Person1 的實例;接着,函數體內部的 this
指向該內存空間 #f11;執行函數體內部的代碼;因爲函數體內部的 this
指向該內存空間,而該內存空間又被變量 p1 所接收,因此 p1 中就會有一個 name
屬性,屬性值爲 'zhangsan'。
p1: { name: 'zhangsan' }
(2) 手動添加一個基本數據類型的返回值,最終仍是返回 this
。
function Person2() { this.age = 28; return 50; } var p2 = new Person2(); console.log(p2.age); // 28
p2: { age: 28 }
若是上面是一個普通函數的調用,那麼返回值就是 50。
(3) 手動添加一個複雜數據類型(對象)的返回值,最終返回該對象
直接上例子
function Person3() { this.height = '180'; return ['a', 'b', 'c']; } var p3 = new Person3(); console.log(p3.height); // undefined console.log(p3.length); // 3 console.log(p3[0]); // 'a'
再來一個例子
function Person4() { this.gender = '男'; return { gender: '中性' }; } var p4 = new Person4(); console.log(p4.gender); // '中性'
關於構造函數的返回值,無非就是以上幾種狀況