做者:Jakob Klamser翻譯:瘋狂的技術宅javascript
原文:https://dev.to/jakobkit/oop-v...css
未經容許嚴禁轉載html
先簡要介紹一下面向對象和函數式編程。前端
二者都是編程範式,在容許和禁止的技術上有所不一樣。java
有僅支持一種範式的編程語言,例如 Haskell(純函數式)。程序員
還有支持多種範式的語言,例如 JavaScript,你能夠用 JavaScript 編寫面向對象的代碼或函數式代碼,甚至能夠將二者混合。面試
在深刻探究這兩種編程範式之間的差別以前,先建立一個階乘計算器項目。編程
首先建立所需的全部文件和文件夾,以下所示:bootstrap
$ mkdir func-vs-oop $ cd ./func-vs-oop $ cat index.html $ cat functional.js $ cat oop.js
接下來在 index.html
內建立一個簡單的表單。segmentfault
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> <script src="functional.js" defer></script> </head> <body> <div class="container mt-5"> <div class="container mt-3 mb-5 text-center"> <h2>Functional vs OOP</h2> </div> <form id="factorial-form"> <div class="form-group"> <label for="factorial">Factorial</label> <input class="form-control" type="number" name="factorial" id="factorial" /> </div> <button type="submit" class="btn btn-primary">Calculate</button> </form> <div class="container mt-3"> <div class="row mt-4 text-center"> <h3>Result:</h3> <h3 class="ml-5" id="factorial-result"></h3> </div> </div> </div> </body> </html>
爲了使界面看上去不那麼醜陋,咱們把 bootstrap 做爲 CSS 框架。
若是在瀏覽器中顯示這個 HTML,應該是這樣的:
如今這個表單尚未任何操做。
咱們的目標是實現一種邏輯,在該邏輯中你能夠輸入一個最大爲 100 的數字。單擊「Calculate」按鈕後,結果應顯示在 result-div
中。
下面分別以面向對象和函數式的方式來實現。
首先爲函數式編程方法建立一個文件。
$ cat functional.js
首先,須要一個在將此文件加載到瀏覽器時要調用的函數。
該函數先獲取表單,而後把咱們須要的函數添加到表單的提交事件中。
function addSubmitHandler(tag, handler) { const form = getElement(tag); form.addEventListener('submit', handler); } addSubmitHandler("#factorial-form", factorialHandler);
首先聲明一個名爲 addSubmitHandler
的函數。
這個函數有兩個參數,第一個是要在 HTML 中查找的標籤,第二個是要綁定到 Element
的 commit-event
的函數。
接下來,經過傳入#factorial-form
和函數名 factorialHandler
來調用此函數。
標籤前面的 #
代表咱們正在尋找 HTML 中的 id
屬性。
若是如今嘗試運行該代碼,則會拋出錯誤,由於在任何地方都尚未定義函數 getElement
和 factorialHandler
。
所以,首先在 addSubmitHandler
函數前面定義 getElement
,以下所示:
function getElement(tag) { return document.querySelector(tag); }
這個函數很是簡單,只返回經過傳入的標記找到的 HTML元素。可是稍後咱們將重用此功能。
如今添加 factorialHandler
函數來建立核心邏輯。
function factorialHandler(event) { event.preventDefault(); const inputNumber = getValueFromElement('#factorial'); try { const result = calculateFactorial(inputNumber); displayResult(result); } catch (error) { alert(error.message); } }
把事件傳回後當即調用 preventDefault
。
這將阻止 Submit 事件的默認行爲,你能夠試試不調用 preventDefault
時單擊按鈕後會發生什麼。
以後,經過調用 getValueFromElement
函數從輸入字段中獲取用戶輸入的值。在獲得數字後,用函數 calculateFactorial
計算階乘,而後經過將結果傳遞給函數 displayResult
將結果展現到頁面。
若是該值的格式不正確或者數字大於 100,將會拋出錯誤並彈出 alert。
下一步,建立另外兩個輔助函數: getValueFromElement
和 displayResult
,並將它們添加到 getElement
函數後面。
function getValueFromElement(tag) { return getElement(tag).value; } function displayResult(result) { getElement('#factorial-result').innerHTML = result }
這兩個函數都使用咱們的 getElement
函數。這種可重用性是爲何函數式編程如此有效的一個緣由。
爲了使它更加可重用,能夠在 displayResult
上添加名爲 tag
第二個參數。
這樣就能夠動態設置應該顯示結果的元素。
可是在本例中,我用了硬編碼的方式。
接下來,在 factoryHandler
前面建立 calculateFactorial
函數。
function calculateFactorial(number) { if (validate(number, REQUIRED) && validate(number, MAX_LENGTH, 100) && validate(number, IS_TYPE, 'number')) { return factorial(number); } else { throw new Error( 'Invalid input - either the number is to big or it is not a number' ); } }
接着建立一個名爲 validate
的函數來驗證參數 number
是否爲空且不大於 100,且類型爲 number。若是檢查經過,就調用 factorial
函數並返回其結果。若是沒有經過,則拋出在 factorialHandler
函數中捕獲的錯誤。
const MAX_LENGTH = 'MAX_LENGTH'; const IS_TYPE = 'IS_TYPE'; const REQUIRED = 'REQUIRED'; function validate(value, flag, compareValue) { switch (flag) { case REQUIRED: return value.trim().length > 0; case MAX_LENGTH: return value <= compareValue; case IS_TYPE: if (compareValue === 'number') { return !isNaN(value); } else if (compareValue === 'string') { return isNaN(value); } default: break; } }
在這個函數中,用 switch
來肯定要執行的驗證範式類型。這只是一個簡單的值驗證。
而後在 calculateFactorial
聲明的前面添加實際的 factor
函數。這是最後一個函數。
function factorial(number) { let returnValue = 1; for (let i = 2; i <= number; i++) { returnValue = returnValue * i; } return returnValue; }
最終的 functional.js 文件下所示:
const MAX_LENGTH = 'MAX_LENGTH'; const IS_TYPE = 'IS_TYPE'; const REQUIRED = 'REQUIRED'; function getElement(tag) { return document.querySelector(tag); } function getValueFromElement(tag) { return getElement(tag).value; } function displayResult(result) { getElement('#factorial-result').innerHTML = result } function validate(value, flag, compareValue) { switch (flag) { case REQUIRED: return value.trim().length > 0; case MAX_LENGTH: return value <= compareValue; case IS_TYPE: if (compareValue === 'number') { return !isNaN(value); } else if (compareValue === 'string') { return isNaN(value); } default: break; } } function factorial(number) { let returnValue = 1; for (let i = 2; i <= number; i++) { returnValue = returnValue * i; } return returnValue; } function calculateFactorial(number) { if (validate(number, REQUIRED) && validate(number, MAX_LENGTH, 100) && validate(number, IS_TYPE, 'number')) { return factorial(number); } else { throw new Error( 'Invalid input - either the number is to big or it is not a number' ); } } function factorialHandler(event) { event.preventDefault(); const inputNumber = getValueFromElement('#factorial'); try { const result = calculateFactorial(inputNumber); displayResult(result); } catch (error) { alert(error.message); } } function addSubmitHandler(tag, handler) { const form = getElement(tag); form.addEventListener('submit', handler); } addSubmitHandler("#factorial-form", factorialHandler);
在這種方法中,咱們專門處理函數。每一個函數都只有一個目的,大多數函數能夠在程序的其餘部分中重用。
對於這個簡單的 Web 程序,使用函數式的方法有些過度了。接着將編寫相同的功能,只不過此次是面向對象的。
首先,須要將 index.html
文件的腳本標籤中的 src
更改成如下內容。
<script src="oop.js" defer></script>
而後建立 oop.js 文件。
$ cat oop.js
對於面向對象方法,咱們要建立三種不一樣的類,一種用於驗證,一種用於階乘計算,另外一種用於處理表單。
先是建立處理表單的類。
class InputForm { constructor() { this.form = document.getElementById('factorial-form'); this.numberInput = document.getElementById('factorial'); this.form.addEventListener('submit', this.factorialHandler.bind(this)); } factorialHandler(event) { event.preventDefault(); const number = this.numberInput.value; if (!Validator.validate(number, Validator.REQUIRED) || !Validator.validate(number, Validator.MAX_LENGTH, 100) || !Validator.validate(number, Validator.IS_TYPE, 'number')) { alert('Invalid input - either the number is to big or it is not a number'); return; } const factorial = new Factorial(number); factorial.display(); } } new InputForm();
在構造函數中獲取 form-element
和 input-element
並將其存儲在類變量(也稱爲屬性)中。以後將方法 factorialHandler
添加到 Submit-event
中。在這種狀況下須要把類的 this
綁定到方法。若是不這樣作,將會獲得一個引用錯誤,例如調用 this.numberInput.value
將會是 undefined
。以後以事件爲參數建立類方法 factorialHandler
。
該方法的代碼看起來應該有點熟悉,例如 if 語句檢查輸入值是否有效,就像在 calculateFactorial
函數中所作的那樣。Validator.validate
是對咱們仍然須要建立的 Validator
類中的靜態方法的調用。若是使用靜態方法,則無需初始化對象的新實例。驗證經過後建立 Factorial
類的新實例,傳遞輸入值,而後將計算的結果顯示給用戶。
接下來在 InputForm
類 前面建立 Validator
類。
class Validator { static MAX_LENGTH = 'MAX_LENGTH'; static IS_TYPE = 'IS_TYPE'; static REQUIRED = 'REQUIRED'; static validate(value, flag, compareValue) { switch (flag) { case this.REQUIRED: return value.trim().length > 0; case this.MAX_LENGTH: return value <= compareValue; case this.IS_TYPE: if (compareValue === 'number') { return !isNaN(value); } else if (compareValue === 'string') { return isNaN(value); } default: break; } } }
這個類內部的全部內容都是靜態的,因此咱們不須要任何構造函數。
這樣作的好處是不須要在每次使用它時都初始化該類。
validate
與 validate
函數與咱們的 functional.js
幾乎徹底相同。
接下來在 Validator
類的後面建立 Factorial
類。
class Factorial { constructor(number) { this.resultElement = document.getElementById('factorial-result'); this.number = number; this.factorial = this.calculate(); } calculate() { let returnValue = 1; for (let i = 2; i <= this.number; i++) { returnValue = returnValue * i; } return returnValue; } display() { this.resultElement.innerHTML = this.factorial; } }
在初始化這個類的實例後,咱們得到 resultElement 並將其存儲爲屬性以及咱們傳入的數字。
以後調用方法 calculate
並將其返回值存儲在屬性中。calculate
方法包含與 functional.js 中的 factor
函數相同的代碼。最後是 display
方法,該方法將結果元素的 innerHTML 設置爲現實計算出的階乘數。
完整的 oop.js 文件以下所示。
class Validator { static MAX_LENGTH = 'MAX_LENGTH'; static IS_TYPE = 'IS_TYPE'; static REQUIRED = 'REQUIRED'; static validate(value, flag, compareValue) { switch (flag) { case this.REQUIRED: return value.trim().length > 0; case this.MAX_LENGTH: return value <= compareValue; case this.IS_TYPE: if (compareValue === 'number') { return !isNaN(value); } else if (compareValue === 'string') { return isNaN(value); } default: break; } } } class Factorial { constructor(number) { this.resultElement = document.getElementById('factorial-result'); this.number = number; this.factorial = this.calculate(); } calculate() { let returnValue = 1; for (let i = 2; i <= this.number; i++) { returnValue = returnValue * i; } return returnValue; } display() { this.resultElement.innerHTML = this.factorial; } } class InputForm { constructor() { this.form = document.getElementById('factorial-form'); this.numberInput = document.getElementById('factorial'); this.form.addEventListener('submit', this.factorialHandler.bind(this)); } factorialHandler(event) { event.preventDefault(); const number = this.numberInput.value; if (!Validator.validate(number, Validator.REQUIRED) || !Validator.validate(number, Validator.MAX_LENGTH, 100) || !Validator.validate(number, Validator.IS_TYPE, 'number')) { alert('Invalid input - either the number is to big or it is not a number'); return; } const factorial = new Factorial(number); factorial.display(); } } new InputForm();
咱們建立了三個類來處理程序的三個不一樣的功能:
Validation
類Factorial
類InputForm
類兩種方法都是編寫代碼的有效方法。我喜歡在本身不一樣項目中嘗試最有效的方法。在不少狀況下,甚至不可能如此清晰地分離這兩種範式。
但願這篇文章可使你對不一樣的方法有一個基本的瞭解。