我们知道js是一门弱类型语言,相比于强类型语言,它有很多不支持的语法特性,但是这并不代表它不能够实现面向对象的编程特性。虽然前端代码在实现功能的操作上,用面向过程的风格会感觉更容易,也更简便,但是深层考虑到设计模式的代码规范还是建议使用面向对象的编码风格。
这里我以之前写过的计算器为例来阐述如何把面向过程风格的代码改成面向对象风格的代码。
之前编写的面向过程的代码博客:计算器
在正式开始之前,我先做一个说明,这次我采用的面向对象是以es6的语法,只要就是class和export、import的使用。
如果有感觉很陌生的伙伴可以先点击链接学习这两个板块。es6教程
github源码链接:完整代码
构建思路:
首先,我们要创建对象就必须先明白一个计算器里面有哪些对象,说得通俗就是有哪些东西。
宏观来看,一个计算器本身就是一个对象,但是对于此问题来说,抽象得这么大没有什么实际意义。
细化来看,一个计算器里面主要有屏幕和按钮,而每一个屏幕和按钮都可以看成一个对象。
在这里屏幕的功能过于单一,没有抽象成对象实际意义也不大,这里我们主要考虑按钮部分。
我们从效果图中可以看出,24个按钮可以大致分成三类,分别是数字按钮、功能按钮以及求等按钮。进一步运用面向对象的继承特性,我们完全可以抽象出一个最顶层的父类–普通按钮,并让这些按钮去继承它。
明白了对象构建思路,我们就可以考虑,对象里面成员的构建思路。
首先,每一个按钮的基本样式几乎相近,我们可以将其抽象成一个公有属性,不同的子类去具体实现其额外不同的地方。然后每一个按钮里面的显示内容也可以用此思路进行抽象。每一个按钮都有点击事件的功能,也能提取出来,最后让子类去实现,送佛送到西,最后,我们还能将这些元素全部封装在一起,变成一个完整的按钮直接创达给外界。
部分代码:
父元素button:
export default class Button {
constructor(text) {
this._className = 'button';
this._text = text;
}
get className() {
return this._className;
}
set className(value) {
this._className = value;
}
get text() {
return this._text;
}
set text(value) {
this._text = value;
}
createButton() {
const button = document.createElement('div');
button.className = this._className;
button.innerText = this._text;
this.addClickEvent(button);
return button;
}
addClickEvent(button) {
// Arrow function guarantees that this points to
button.addEventListener('click', () => {
this.clickHandler();
}, false);
}
}
其中一个子元素numberButton:
import Button from './button.js';
import { dealNumber } from '../calculateService/dealData.js';
export default class NumberButton extends Button {
constructor(text, dealData) {
super(text);
this._dealData = dealData;
this._className = `${super.className} numberButton`;
}
get className() {
return this._className;
}
clickHandler() {
dealNumber(this._text);
}
}
button都以及整装好了,万事俱备,只欠封装。
创建计算器的代码:
/* eslint-disable max-len */
import NumberButton from '../buttons/numberButton.js';
import OperationButton from '../buttons/operationButton.js';
import EqualButton from '../buttons/equalButton.js';
import screenTexts from './screenTextSign.js';
export default class InitCalculator {
static createCalculator() {
const body = document.getElementsByTagName('body')[0];
const calculator = document.createElement('div');
calculator.classList.add('calculator');
calculator.appendChild(InitCalculator.createScreen('smallScreen', ''));
calculator.appendChild(InitCalculator.createScreen('bigScreen', 0));
calculator.appendChild(InitCalculator.appendButtons([
new OperationButton(screenTexts.percentSign).createButton(), new OperationButton(screenTexts.clearCurrentOperation).createButton(),
new OperationButton(screenTexts.clearAllOperation).createButton(), new OperationButton(screenTexts.deleteNumber).createButton()]));
calculator.appendChild(InitCalculator.appendButtons([
new OperationButton(screenTexts.reciprocal).createButton(), new OperationButton(screenTexts.square).createButton(),
new OperationButton(screenTexts.rootSquare).createButton(), new OperationButton(screenTexts.division).createButton()]));
calculator.appendChild(InitCalculator.appendButtons([
new NumberButton(screenTexts.seven).createButton(), new NumberButton(screenTexts.eight).createButton(),
new NumberButton(screenTexts.nine).createButton(), new OperationButton(screenTexts.multiplication).createButton()]));
calculator.appendChild(InitCalculator.appendButtons([
new NumberButton(screenTexts.four).createButton(), new NumberButton(screenTexts.five).createButton(),
new NumberButton(screenTexts.six).createButton(), new OperationButton(screenTexts.subtraction).createButton()]));
calculator.appendChild(InitCalculator.appendButtons([
new NumberButton(screenTexts.one).createButton(), new NumberButton(screenTexts.two).createButton(),
new NumberButton(screenTexts.three).createButton(), new OperationButton(screenTexts.addition).createButton()]));
calculator.appendChild(InitCalculator.appendButtons([
new NumberButton(screenTexts.sign).createButton(), new NumberButton(screenTexts.zero).createButton(),
new NumberButton(screenTexts.decimalPoint).createButton(), new EqualButton(screenTexts.equal).createButton()]));
body.appendChild(calculator);
}
static createScreen(className, text) {
const screen = document.createElement('div');
screen.classList.add(className);
screen.innerText = text;
return screen;
}
static appendButtons(buttons) {
const rowDiv = document.createElement('div');
for (let i = 0; i < buttons.length; i++) {
rowDiv.appendChild(buttons[i]);
}
return rowDiv;
}
}
注意: 这里我之所以把button里面的内容抽象成一个静态属性文件主要就是为了提高程序的灵活性和易扩展性,如果后面需要修改按钮所显示的内容,直接修改静态文件即可。
到这里,最主要的面向对象的构建以及基本完成,然后,html文件只需调用main函数即可启动程序。后面还有一个后台运算算法的文件和显示数据的文件。
注意: 为何我不把后台算法的文件封装成一个类传给外界,而是只给外界暴露了三个函数接口。一方面,js里面没有访问修饰符,所以无法很有效的实现类里面细节的封装,另一方面,我不想给外界暴露过多的内部算法实现细节,所以我只把外部需要用到的方法导出,这样也从侧面实现了封装功能。
还有一点,这个版本我没有使用上个版本类似eval()函数的算法来实现计算功能,我是使用几乎完全模拟win10计算器的算法步骤,但是由于时间缘故,此算法还未完全实现目前还有一些bug,但是很快就会补上。
到此,一个面向过程的程序被彻底翻新改装成了一个面向对象的程序。下期再见!