JavaScript 工廠函數 vs 構造函數

阿里雲最近在作活動,低至2折,有興趣能夠看看:
https://promotion.aliyun.com/...

爲了保證的可讀性,本文采用意譯而非直譯。javascript

當談到JavaScript語言與其餘編程語言相比時,你可能會聽到一些使人困惑東西,其中之一是工廠函數和構造函數。html

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!前端

工廠函數

所謂工廠函數,就是指這些內建函數都是類對象,當你調用他們時,其實是建立了一個類實例」。意思就是當我調用這個函數,其實是先利用類建立了一個對象,而後返回這個對象。因爲 Javascript 自己不是嚴格的面向對象的語言(不包含類),實際上來講,Javascript 並無嚴格的「工廠函數」,可是在 Javascript中,咱們能利用函數模擬類。來看下面一個例子:java

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

上述代碼,建立一個新對象,並將傳遞參數做爲屬性附加到該對象上並返回新對象。 這是一個簡單的 JavaScript 工廠函數。git

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

  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);

clipboard.png

正如咱們在上述所看到的,這裏的__proto__ 指向其原型對象的指針,讓咱們試着找出原型對象是什麼。爲了找出上面mike對象的指向原型對象,讓咱們作簡單的===等式檢查。

clipboard.png

嗯,有趣的是,它指向 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);

clipboard.png

當咱們展開第一層的的__proto__時,它內部還有另外一個__proto__,咱們再次擴展它。

clipboard.png

如今讓咱們試着弄清楚原型對象是否像上面同樣。

clipboard.png

他們是不一樣的。 當咱們使用工廠函數建立對象時,它的__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);

而後,若是查看建立的對象實例,你但願看到什麼?

clipboard.png

發生了什麼? 使用 new 運算符,正如咱們所期待的同樣輸出正確的對象,但沒有new運算符,結果是undefined 怎麼可能呢?

若是你對 JavaScript 做用域 this 關鍵字的工做原理有所瞭解,那麼你能夠猜到這裏發生了什麼? 讓咱們來看看。

clipboard.png

看起來咱們傳遞給沒有new關鍵字的函數的全部屬性都已設置爲window對象。 那是由於到那個時候函數內部的這個變量引用了globalwindow 對象,基本上咱們在這裏作的就是污染了全局對象。

這是你能夠對你的JavaScript程序作的很是討厭的事情。 所以,使用new運算符,JavaScript引擎將this 變量設置爲引用新建立的對象實例,這就是爲何咱們能夠看到傳遞給構造函數的全部屬性都已設置爲 mike

可是在沒有new運算符的狀況下調用構造函數的狀況下,JavaScript 引擎會將 this 解釋爲常規函數調用,而沒有顯式返回語句時返回undefined。 這就是理解new 運算符在JavaScript中的工做原理很是關鍵的緣由。

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

歡迎加入前端你們庭,裏面會常常分享一些技術資源。

clipboard.png

相關文章
相關標籤/搜索