【譯】JavaScript 工廠函式 vs 建構函式

前端小智發表於2019-05-06

譯者:前端小智

原文:medium.com/@chamikakas…

【譯】JavaScript 工廠函式 vs 建構函式

當談到JavaScript語言與其他程式語言相比時,你可能會聽到一些令人困惑東西,其中之一是工廠函式和建構函式。

工廠函式

所謂工廠函式,就是指這些內建函式都是類物件,當你呼叫他們時,實際上是建立了一個類例項”。意思就是當我呼叫這個函式,實際上是先利用類建立了一個物件,然後返回這個物件。由於 Javascript 本身不是嚴格的物件導向的語言(不包含類),實際上來說,Javascript 並沒有嚴格的“工廠函式”,但是在 Javascript中,我們能利用函式模擬類。來看下面一個例子:

function person(firstName, lastName, age) {
  const person = {};
  person.firstName = firstName;
  person.lastName = lastName;
  person.age = age;
  return person;
}
複製程式碼

上述程式碼,建立一個新物件,並將傳遞引數作為屬性附加到該物件上並返回新物件。 這是一個簡單的 JavaScript 工廠函式。

實際上工廠函式也很好理解了:

  1. 它是一個函式。
  2. 它用來建立物件。
  3. 它像工廠一樣,“生產”出來的函式都是“標準件”(擁有同樣的屬性)

建構函式

不同於其它的主流程式語言,JavaScript的建構函式並不是作為類的一個特定方法存在的;當任意一個普通函式用於建立一類物件時,它就被稱作建構函式,或構造器。一個函式要作為一個真正意義上的建構函式,需要滿足下列條件:

  1. 在函式內部對新物件(this)的屬性進行設定,通常是新增屬性和方法。

  2. 建構函式可以包含返回語句(不推薦),但返回值必須是this,或者其它非物件型別的值。

    function Person(firstName, lastName, age) { this.firstName = firstName; this.lastName = lastName; this.age = age; }

使用 new 關鍵字建立物件

正如上面所說的,我們可以使用 new 來類或者物件,那麼你可能會有以下幾個問題:

  1. 我們可以在工廠函式中使用 new 關鍵字嗎?

  2. 如果我們在工廠和建構函式中使用new關鍵字會發生什麼

  3. 如果在使用建構函式建立物件例項時不使用new關鍵字會發生什麼

好的,試著找出以上問題的答案之前,我們先做一個小練習來理解這裡面發生了什麼。

使用new關鍵字同時使用工廠和建構函式建立兩個物件,接著在控制檯列印這兩個物件。

使用工廠函式

function person(firstName, lastName, age){
  const person = {}
  person.firstName = firstName;
  person.lastName = lastName;
  person.age = age;
  return person;
}

const mike = new person('mike', 'grand', 23);
複製程式碼

【譯】JavaScript 工廠函式 vs 建構函式

正如我們在上述所看到的,這裡的__proto__ 指向其原型物件的指標,讓我們試著找出原型物件是什麼。為了找出上面mike物件的指向原型物件,讓我們做簡單的===等式檢查。

【譯】JavaScript 工廠函式 vs 建構函式

嗯,有趣的是,它指向 Object.prototype。好的,讓我們用建構函式做同樣的實驗。

理解 JavaScript 的原型

理解原型之前,需要記住以下幾點知識:

  • 所有的引用型別(陣列、物件、函式),都具有物件特性,即可自由擴充套件屬性(null除外)
  • 所有的引用型別(陣列、物件、函式),都有一個__proto__屬性,屬性值是一個普通的物件
  • 所有的函式,都有一個prototype屬性,屬性值也是一個普通的物件
  • 所有的引用型別(陣列、物件、函式),__proto__屬性值指向它的建構函式的prototype屬性值

通過程式碼解釋一下:

// 要點一:自由擴充套件屬性
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;

// 要點二:__proto__
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);

// 要點三:函式有 prototype
console.log(fn.prototype)

// 要點四:引用型別的 __proto__ 屬性值指向它的建構函式的 prototype 屬性值
console.log(obj.__proto__ === Object.prototype)
複製程式碼

使用建構函式

注意:在JavaScript中,這些建構函式也被稱為 constructor,因為它們用於建立物件。

function Person(firstName, lastName, age) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.age = age;
}
const mike = new Person('mike', 'grand', 23);
複製程式碼

【譯】JavaScript 工廠函式 vs 建構函式

當我們展開第一層的的__proto__時,它內部還有另一個__proto__,我們再次擴充套件它。

【譯】JavaScript 工廠函式 vs 建構函式

現在讓我們試著弄清楚原型物件是否像上面一樣。

【譯】JavaScript 工廠函式 vs 建構函式

他們是不同的。 當我們使用工廠函式建立物件時,它的__proto__指向Object.prototype,而當從建構函式建立物件時,它指向它的建構函式原型物件。 那麼這裡發生了什麼?

new 背後所做的事

當我們在建立物件時使用帶有建構函式的new關鍵字時,new 背後所做的事不多。

new 運算子建立一個使用者自定義的物件型別的例項或具有建構函式的內建物件的例項。 new 關鍵字會進行如下操作:

  1. 建立一個空的簡單 JavaScript 物件 (即 {})

  2. 連結該物件(即設定該物件的建構函式)到另一個物件

  3. 將步驟1新建立的物件作為 this 的上下文

  4. 如果該函式沒有返回物件,則返回 this

註釋行是虛擬碼,表示在 new 關鍵字,JS 背後幫我們做的事情。

function Person(firstName, lastName, age) {
    // this = {};
    // this.__proto__ = Person.prototype;

    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    
    // return this;
}
複製程式碼

另外,讓我們看看如果將上面的隱式程式碼新增到工廠函式中會發生什麼。

function person(firstName, lastName, age) {
    // this = {};
    // this.__proto__ = Person.prototype; 
 
  
    const person = {};
    person.firstName = firstName;
    person.lastName = lastName;
    person.age = age;
    return person;
    
    // return this;
}
複製程式碼

即使使用new關鍵字呼叫時將隱式程式碼新增到工廠函式中,也不會對結果產生任何影響。這是因為,由於我們沒有在函式中使用 this 關鍵字,而且我們顯式地返回了一個除this之外的自定義物件,因此沒有必要使用隱式程式碼。無論我們是否對工廠函式使用new關鍵字,對輸出都沒有影響。

如果忘記了 new 關鍵字怎麼辦

JavaScript 中有許多概念,有時難以掌握。 new 操作符就是其中之一。 如果你不能正確理解它,那麼在執行 JavaScript 應用程式時會產生令人討厭的後果。 在像 Java這 樣的語言中,嚴格限制瞭如何使用 new 關鍵字。 但是在 javascript 中,並不是那麼嚴格,如果你不能正確理解它們可能會導致很多問題。

在 JavaScript 中:

  • 可以對任何函式使用 new 運算子

  • 可以使用或不使用 new 關鍵字將函式作為建構函式呼叫

讓我們看看上面的例子,使用和不使用 new 關鍵情況

function Person(firstName, lastName, age) { this.firstName = firstName; this.lastName = lastName; this.age = age; } const mike = new Person('mike', 'grand', 23); const bob = Person('bob', 'grand', 23);

然後,如果檢視建立的物件例項,你希望看到什麼?

【譯】JavaScript 工廠函式 vs 建構函式

發生了什麼? 使用 new 運算子,正如我們所期待的一樣輸出正確的物件,但沒有new運算子,結果是undefined 怎麼可能呢?

如果你對 **JavaScript 作用域 ** 和 this 關鍵字的工作原理有所瞭解,那麼你可以猜到這裡發生了什麼? 讓我們來看看。

【譯】JavaScript 工廠函式 vs 建構函式

看起來我們傳遞給沒有new關鍵字的函式的所有屬性都已設定為window物件。 那是因為到那個時候函式內部的這個變數引用了globalwindow 物件,基本上我們在這裡做的就是汙染了全域性物件。

這是你可以對你的JavaScript程式做的非常討厭的事情。 因此,使用new運算子,JavaScript引擎將this 變數設定為引用新建立的物件例項,這就是為什麼我們可以看到傳遞給建構函式的所有屬性都已設定為 mike

但是在沒有new運算子的情況下呼叫建構函式的情況下,JavaScript 引擎會將 this 解釋為常規函式呼叫,而沒有顯式返回語句時返回undefined。 這就是理解new 運算子在JavaScript中的工作原理非常關鍵的原因。

你的點贊是我持續分享好東西的動力,歡迎點贊!

歡迎加入前端大家庭,裡面會經常分享一些技術資源。

【譯】JavaScript 工廠函式 vs 建構函式

相關文章