js之面向對象編碼風格

我們知道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,但是很快就會補上。
到此,一個面向過程的程序被徹底翻新改裝成了一個面向對象的程序。下期再見!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章