JavaScript中的工廠方法、構造函數與class

来源:http://www.cnblogs.com/wwhhq/archive/2017/12/21/8080642.html
-Advertisement-
Play Games

JavaScript中的工廠方法、構造函數與class 本文轉載自: "眾成翻譯" 譯者: "謝於中" 鏈接: "http://www.zcfy.cc/article/1129" 原文: "https://medium.com/javascript scene/javascript factory ...


JavaScript中的工廠方法、構造函數與class

本文轉載自:眾成翻譯
譯者:謝於中
鏈接:http://www.zcfy.cc/article/1129
原文:https://medium.com/javascript-scene/javascript-factory-functions-vs-constructor-functions-vs-classes-2f22ceddf33e#.wby148xu6

在ES6出現之前,人們常常疑惑JavaScript中的工廠模式和構造函數模式到底有什麼區別。ES6中提出了_class關鍵字,許多人認為這解決了構造函數模式中的諸多問題。事實上,問題並沒有得到解決。下麵就讓我們來看看工廠模式、構造函數模式和class_的一些重要區別。

首先,讓我們看看這三種方式的例子:

// class
class ClassCar {
  drive () {
    console.log('Vroom!');
  }
}

const car1 = new ClassCar();
console.log(car1.drive());


// constructor(構造函數模式)
function ConstructorCar () {}

ConstructorCar.prototype.drive = function () {
  console.log('Vroom!');
};

const car2 = new ConstructorCar();
console.log(car2.drive());


// factory (工廠模式)
const proto = {
  drive () {
    console.log('Vroom!');
  }
};

function factoryCar () {
  return Object.create(proto);
}

const car3 = factoryCar();
console.log(car3.drive());

這些方式都將方法存儲於共用的原型中,然後通過構造函數的閉包有選擇的支持私有數據。換句話說,他們幾乎擁有相同的特性,所以通常來說也能交替使用。

> 在JavaScript中,任何函數都能返回一個新的對象。當這個函數不是構造函數或_class_時,它就叫做工廠函數。

ES6中的_class_其實是構造函數的語法糖,所以它具有構造函數的一切優點和缺點:

class Foo {}
console.log(typeof Foo); // function

構造函數和_class_的優點

  • 大多數書本都教了_class_或者構造函數。
  • _this_指向新的對象。
  • 一些人喜歡_myFoo = new Foo()_這種寫法。
  • 構造函數和_class_存在許多微小的優化,不過除非你已經定量的分析了代碼,並且這些優化確實對程式的性能很重要,否則這不應該被列入考慮範圍。

構造函數和_class_的缺點

1.需要new

在ES6之前,漏寫_new_是一個普遍的bug。為了對付它,許多人使用了以下方式:

function Foo() {
  if (!(this instanceof Foo)) { return new Foo(); }
}

在ES6(ES2015)中,如果你試圖不使用_new來調用class構造函數,將拋出錯誤。只有用工廠模式包裹class,才有可能避免必須使用new。對於未來版本的JavaScript,有人提出建議,希望能夠定製忽略new_時class構造函數的行為。不過這種方式依然增加了使用class時的開銷(這也意味著會有更少的人使用它)。

2. 調用API時,實例化的細節被泄露(從new的角度展開)

構造函數的調用方法與構造函數的實現方式緊密耦合。當你需要其具有工廠方法的靈活性時,重構起來將會是巨大的變化。將class放入工廠方法進行重構是非常常見的,它甚至被寫入了Martin Fowler, Kent Beck, John Brant, William Opdyke, 和 Don Roberts的 “Refactoring: Improving the Design of Existing Code”

3. 構造函數模式違背了開/閉原則

由於對_new_的要求,構造函數違背了開/閉原則:即一個API應該對擴展開放,對改裝關閉。

我的意見是,既然從類到工廠方法的重構是非常常見的,那麼應該將不應該造成任何破壞作為所有構造函數進行擴展時的標準。

如果你開放了一個構造函數或者類,而用戶使用了這個構造函數,在這之後,如果需要增加這個方法的靈活性,(例如,換成使用對象池的方式進行實現,或者跨執行上下文的實例化,或者使用替代原型來擁有更多的繼承靈活性),都需要用戶同時進行重構。

不幸的是,在JavaScript中,從構造函數或者類切換到工廠方法需要進行巨大的改變

// Original Implementation:
// 原始實現:

// class Car {
//   drive () {
//     console.log('Vroom!');
//   }
// }

// const AutoMaker = { Car };

// Factory refactored implementation:
// 工廠函數重構實現:
const AutoMaker = {
  Car (bundle) {
    return Object.create(this.bundle[bundle]);
  },

  bundle: {
    premium: {
      drive () {
        console.log('Vrooom!');
      },
      getOptions: function () {
        return ['leather', 'wood', 'pearl'];
      }
    }
  }
};

// The refactored factory expects:
// 重構後的方法希望這樣調用
const newCar = AutoMaker.Car('premium');
newCar.drive(); // 'Vrooom!'

// But since it's a library, lots of callers
// in the wild are still doing this:
// 但是由於這是一個庫,許多用戶依然這樣使用
const oldCar = new AutoMaker.Car();

// Which of course throws:
// TypeError: Cannot read property 'undefined' of
// undefined at new AutoMaker.Car
// 這樣的話,就會拋出:
// TypeError: Cannot read property 'undefined' of
// undefined at new AutoMaker.Car

在上面的例子中,我們首先提供了一個類,但是接下來希望提供不同的汽車種類。於是,工廠方法為不同的汽車種類使用了不同的原型。我曾經使用這種技術來存儲不同的播放器介面,然後通過要處理的文件格式選擇合適的原型。

4. 使用構造函數會導致instanceof具有欺騙性

與工廠方法相比,構造函數帶來的巨大變化就是_instanceof的表現。人們有時使用instanceof進行類型檢查。這種方式其實是經常會出問題的,我建議你避免使用instanceof_。

> instanceof會撒謊。

// instanceof is a prototype identity check.
// NOT a type check.

// instanceof是一個原型檢查
// 而不是類型檢查

// That means it lies across execution contexts,
// when prototypes are dynamically reassigned,
// and when you throw confusing cases like this
// at it:

function foo() {}
const bar = { a: 'a'};

foo.prototype = bar;

// Is bar an instance of foo? Nope!
console.log(bar instanceof foo); // false

// Ok... since bar is not an instance of foo,
// baz should definitely not be an instance of foo, right?
const baz = Object.create(bar);

// ...Wrong.
console.log(baz instanceof foo); // true. oops.

_instanceof進行類型檢查的方式和強類型語言不一樣,它會將對象的[[Prototype]]對象和Constructor.prototype_屬性進行一致性檢查。

例如,當執行上下文發生變化時,_instanceof會發生錯誤。當Constructor.prototype變化了之後,instanceof_一樣不會正常工作。

當你從一個class或構造函數開始(這將會返回指向 Constructor.prototypethis),然後轉而探索另外一個對象(沒有指向 Constructor.prototype),這同樣會導致instanceof的失敗。這種情況在將構造函數轉換為工廠函數時會出現。

簡而言之,_instanceof_是另外一種將構造函數轉換為工廠函數時會發生的巨大變化

使用class的優點

  • 方便的,自包含的語法。
  • 是JavaScript中使用類的一種單一、規範的方式。在ES6之前,在一些流行的庫中已經出現了其實現方式。
  • 對於有基於類的語言的背景的人來說,會更加熟悉。

使用class的缺點

除了具有構造函數的缺點外,還有:

  • 用戶可能會嘗試使用extends關鍵字來創建導致問題的多層級的類。

多層級的類將會導致許多在面向對象程式設計中廣為人知的問題,包括脆弱的基類問題,香蕉猴子雨林問題,必要性重覆問題等等。不幸的是,class可以用來extends就像球可以用來扔,椅子可以用來坐一樣自然。想瞭解更多內容,請閱讀“The Two Pillars of JavaScript: Prototypal OO” and “Inside the Dev Team Death Spiral”.

值得指出的是,構造函數和工廠函數都有可能導致有問題的層次繼承,但通過_extends關鍵字,class提供了一個讓你犯錯的功能可見性。換句話說,它鼓勵你思考不靈活的的而且通常是錯誤的_is-a_關係,而不是更加靈活的_has-a 或者 _can-do_組件化關係。

> 功能可見性是讓你能夠執行一定動作的機會。例如,旋鈕可以用來旋轉,杠桿可以用來拉,按鈕可以用來按,等等。

使用工廠方法的優點

工廠方法比構造函數或類都要靈活,並且它不會誘惑人們使用_extends_來構造太深的繼承層級。你可以使用多種方法來繼承工廠函數。特別的,如果想瞭解組合式工廠方法,請查看Stamp Specification

1. 返回任意對象與使用任意原型

例如,你可以通過同一API輕鬆的創建多種類型的對象,例如,一個能夠針對不同類型視頻實例化播放器的媒體播放器,活著能夠出發DOM事件或web socket事件的事件庫。

工廠函數還能跨越之行上下文來實例化對象,充分利用對象池,並且允許更靈活的原型模型繼承。

2. 沒有重構的憂慮

你永遠不需要從一個工廠轉換到一個構造函數,所以重構將永遠不會是一個問題。

3. 沒有_new_

關於要不要使用_new只有一個選擇,那就是不要用。(這將會使this_表現不好,原因見下一點)。

4. 標準的_this_行為

this和它通常的表現一樣,所以你可以用它來獲取其父級對象。例如,在player.create()中,this指向player,正如其他方法調用那樣。call() 和_apply()也會同樣的指向this_。

5. 不會有欺騙性的_instanceof_問題

6. 有些人喜歡myFoo = createFoo()這種寫法

工廠方法的缺點

  • 不會創建一個指向_Factory.prototype的鏈接——但這其實是件好事,因為這樣你就不會得到一個具有欺騙性的instanceof。相反,instanceof_會一直失敗。詳情見工廠方法的優點。
  • _this_不會指向工廠方法中的新對象。詳情見工廠方法的優點。
  • 在經過微優化的基準中,工廠方法可能會稍微比構造函數模式慢一些。如果這對你有影響,請務必在你程式的上下文中進行測試。

結論

在我看來,_class或許有簡單的語法形式,但這不能彌補它引誘粗心的用戶在類繼承中犯錯的事實。對於未來它也是有風險的,因為你有可能會想將其升級成為一個工廠函數,而由於new_關鍵字,你的所有調用都將和構造函數緊密耦合,於是從class向工廠方法遷移將會是一個巨大的改變。

你也許會想可以只重構調用部分,不過在大的團隊中,或者你使用的class是公共API的一部分,你就有可能要破壞不在你掌控中的代碼。換句話說,不能假設只重構調用部分永遠是一個可選項。

關於工廠方法,有趣的事情在於它們不僅更加強大和靈活,而且是鼓勵整個團隊,以及所有API用戶使用簡單、靈活和安全的模式的最簡單的方法。

關於工廠函數的好處,特別是關於對象組合的能力,還有許多內容可以詳述。想要瞭解更多內容,以及這種方式與類繼承的區別,請閱讀“3 Different Kinds of Prototypal Inheritance”.


For more training in in prototypal inheritance techniques, factory functions, and object composition, be sure to check out “The Two Pillars of JS: Composition with Prototypes” — free for members. Not a member yet?

Learn JavaScript with Eric Elliott


Eric Elliott_ is the author of “Programming JavaScript Applications” (O’Reilly), and “Learn JavaScript with Eric Elliott”. He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean,Metallica, and many more._

He spends most of his time in the San Francisco Bay Area with the most beautiful woman in the world.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • zTree 優秀的jquery樹插件,文檔詳細,渲染快 使用方法: 1、引用zTree的js和css文件 2、ztree的html為 需加Class:ztree; 3、初始化樹 後臺介面返回數據示例: 4、加入滑鼠移動到顯示的自定義按鈕 5、文檔地址 http://www.treejs.cn/v3/ ...
  • 自從ES6流行起來,Promise 的使用變得更頻繁更廣泛了,比如非同步請求一般返回一個 Promise 對象,Generator 中 yield 後面一般跟 Promise 對象,ES7中 Async 函數中 await 後面一般也是 Promise 對象,還有更多的 NodeAPI 也會返回 Pr ...
  • 如果覺得設置樣式太麻煩,或者頁面上選中的樣式太複雜,也可以用背景圖去修改樣式<div class=""> <label><input type="radio" name="1"><i class="spot"></i>123456</label> <label><input type="radio" ...
  • 近年來,前端技術的發展迅速,但因為前端知識面龐大,在實際學習當中往往無法理清其中的脈絡。下麵從各種庫、框架、插件的層面上,對前端知識點做一些簡單的梳理。從軟體工程上,將前端分為四個由淺及深的層面或階段。 一、基礎層(瀏覽器原生支持html/css/js) HTML超文本標記語言,用標簽構建網頁的內容 ...
  • 總體過了一下後面的流程,發現Compiler模塊確實不適合單獨講解,這裡繼續講解後面的代碼: 這行代碼與之前設置options預設值非常相似,但是複雜程度根本不是一個次元的。 這一節只能簡單的看一眼內部到底有多少東西,整理後源碼如下: 這個模塊除去父類引入,其餘插件光頂部引入就有34個,簡直就是插件 ...
  • openlayers3教材詳解及demo(完整) OpenLayers 3對OpenLayers網路地圖庫進行了根本的重新設計。版本2雖然被廣泛使用,但從JavaScript開發的早期發展階段開始,已日益現實出它的落後。 OL3已運用現代的設計模式從底層重寫。 最初的版本旨在支持第2版提供的功能,提 ...
  • HTML5 var uaTest = /Android|webOS|Windows Phone|iPhone|ucweb|ucbrowser|iPod|iPad|BlackBerry/i.test(navigator.userAgent.toLowerCase());var touchTest = ...
  • 深入ES6 模塊系統 本文轉載自: "眾成翻譯" 譯者: "neck" 鏈接: "http://www.zcfy.cc/article/4436" 原文: "https://ponyfoo.com/articles/es6 modules in depth the es6 module system ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...