做者: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
类两种方法都是编写代码的有效方法。我喜欢在本身不一样项目中尝试最有效的方法。在不少状况下,甚至不可能如此清晰地分离这两种范式。
但愿这篇文章可使你对不一样的方法有一个基本的了解。