JS進階(1) —— 人人都能懂的建構函式

零和么發表於2018-03-08

大家都知道原型和原型鏈是 JavaScript 中最經典的問題之一,而建構函式又是原型和原型鏈的基礎,所以先了解清楚建構函式以及它的執行過程可以更好地幫助我們學習原型和原型鏈的知識。

本文將從以下幾個方面來探討建構函式:

1.什麼是建構函式

2.為什麼要使用建構函式

3.建構函式的執行過程

4.建構函式的返回值

1.什麼是建構函式

在 JavaScript 中,用 new 關鍵字來呼叫的函式,稱為建構函式。

2.為什麼要使用建構函式

學習每一個概念,不僅要知道它是什麼,還要知道為什麼,以及解決什麼樣的問題。

舉個例子,我們要錄入一年級一班中每一位同學的個人資訊,那麼我們可以建立一些物件,比如:

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遍。

這個時候,建構函式的優勢就體現出來了。我們發現,雖然每位同學都有 name、gender、hobby 這些屬性, 但它們都是不同的,那我們就把這些屬性當做建構函式的引數傳遞進去。而由於都是一年級的學生,age 基本都是6歲,所以我們就可以寫死,遇到特殊情況再單獨做處理即可。此時,我們就可以建立以下的函式:

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');
// ...
複製程式碼

此時你會發現,建立物件會變得非常方便。所以,雖然封裝建構函式的過程會比較麻煩,但一旦封裝成功,我們再建立物件就會變得非常輕鬆,這也是我們為什麼要使用建構函式的原因。

在使用物件字面量建立一系列同一型別的物件時,這些物件可能具有一些相似的特徵(屬性)和行為(方法),此時會產生很多重複的程式碼,而使用建構函式就可以實現程式碼的複用。

3.建構函式的執行過程

先說一點基本概念。

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 的例項。

JS進階(1) —— 人人都能懂的建構函式

(2) 函式體內部的 this 指向該記憶體

JS進階(1) —— 人人都能懂的建構函式

通過以上兩步,我們就可以得出這樣的結論。

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 的例項。

以上就是建構函式的整個執行過程。

4.建構函式的返回值

建構函式執行過程的最後一步是預設返回 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);  // '中性'
複製程式碼

關於建構函式的返回值,無非就是以上幾種情況,大家可以動手試一試,也就記住了。

最後總結一下,本文從四個方面介紹了建構函式,而建構函式是原型和原型鏈學習的基礎,所以大家有必要花點時間好好學習一下關於建構函式的知識,下篇文章我會來講講人人都能看懂的原型鏈,敬請期待。

最後的最後,我所說的不一定都對,你一定要自己試試!

(本文完)

相關文章