javascript全栈开发实践-web-9

123 阅读3分钟

webpack环境已经搭好,我们开始分拆整个功能成到不同的文件里面。 首先,我们把ActionData和DrawTool分开,放在不同的文件里面,目录结构如下:

src
 - data
    ActionData.js
    PathData.js
    RectData.js
    EllipseData.js
 - tool
    DrawingTool.js
    PathTool.js
    RectTool.js
    EllipseTool.js

首先我们看看ActionData.js里面的代码:

export default class ActionData {
  //
  constructor(type, lineWidth, strokeStyle) {
    this._type = type;
    this._lineWidth = lineWidth;
    this._strokeStyle = strokeStyle;
  }
  //
  draw(ctx) { throw new Error('draw not implemented'); }
}

注意,我们直接通过export default导出了class ActionData 。 同样,PathData.js文件类似:

import ActionData from './ActionData';
export default class PathData extends ActionData {
  //
  constructor(points, lineWidth, strokeStyle) {
    super('path', lineWidth, strokeStyle);
    //
    this._points = points;
  }
  //
  draw(ctx) {
    //
    ctx.beginPath();
    ctx.lineWidth = this._lineWidth;
    ctx.strokeStyle = this._strokeStyle;
    //
    let points = this._points;
    if (points.length == 0) {
      return;
    }
    //
    let firstPoint = points[0];
    ctx.moveTo(firstPoint.x, firstPoint.y);
    for (let j = 1; j < points.length; j++) {
      const point = points[j];
      ctx.lineTo(point.x, point.y);
    }
    ctx.stroke();
    //
  }      
}

我们通过import语法,引入了ActionData,然后才可以继承这个class。整个语法和java类似。 其他文件Data和tool文件,也都是类似。 最后,我们修改以下index.js文件。 在之前的代码中,我们都直接把actions,undoCursor作为全局变量使用。通常情况,我们应该避免使用全局变量。因此,在这里,我们编写一个新的class HandwrittenPad。在这个class里面,我们把actions,undoCursor作为成员变量封装起来。同时对外提供选择选择各种工具,undo/redo等方法。 我们在src下面建立一个文件:HandwrittenPad.js,代码如下:

import PathTool from './tool/PathTool';
import RectTool from './tool/RectTool';
import EllipseTool from './tool/EllipseTool';
import { EventEmitter } from 'events';

export default class HandwrittenPad extends EventEmitter{
  //
  constructor(canvas) {
    super();
    //
    this._actions = [];
    this._undoCursor = -1;
    this._canvas = canvas;
    this._canvas.width = this._canvas.clientWidth;
    this._canvas.height = this._canvas.clientHeight;
    //
    this._ctx = this._canvas.getContext('2d');
    this._tool = new PathTool(this._ctx, 2, 'blue');
    //
    this._updateButtonStatus();
    //
    this._canvas.addEventListener('mousedown', this.handleMouseDown);
  }

  //
  handleMouseDown = (event) => {
    //
    if (this._undoCursor != -1) {
      this._actions = this._actions.slice(0, this._undoCursor);
    }
    this._undoCursor = -1;
    //
    this._canvas.addEventListener('mousemove', this.handleMouseMove);
    this._canvas.addEventListener('mouseup', this.handleMouseUp);
    //
    this._tool.handleMouseDown(event.offsetX, event.offsetY);
  }
  //
  handleMouseMove = (event) => {
    this._tool.handleMouseMove(event.offsetX, event.offsetY);
  }
  //
  handleMouseUp = (event) => {
    this._canvas.removeEventListener('mousemove', this.handleMouseMove);
    this._canvas.removeEventListener('mouseup', this.handleMouseUp);
    //
    let actionData = this._tool.handleMouseUp(event.offsetX, event.offsetY);
    this._actions.push(actionData);
    //
    this._updateButtonStatus();
  }
  //
  choosePencil() {
    this._tool = new PathTool(this._ctx, 2, 'blue');
  }
  //
  chooseHighlighter() {
    this._tool = new PathTool(this._ctx, 8, 'rgba(255, 255, 0, 0.5)');
  }
  //
  chooseEraser() {
    this._tool = new PathTool(this._ctx, 8, 'white');
  }
  //
  drawRect() {
    this._tool = new RectTool(this._ctx, 2, 'red');
  }
  //
  drawEllipse() {
    this._tool = new EllipseTool(this._ctx, 2, 'green');
  }
  //
  canUndo() {
    if (this._actions.length == 0) {
      return false;
    }
    //
    if (this._undoCursor == 0) {
      return false;
    }
    //
    return true;
  }
  //
  canRedo() {
    //
    if (this._actions.length == 0) {
      return false;
    }
    //
    if (this._undoCursor == -1 || this._undoCursor == this._actions.length) {
      return false;
    }
    //
    return true;
  }
  //
  undo() {
    if (!this.canUndo()) {
      return;
    }
    //
    if (this._undoCursor == -1) {
      this._undoCursor = this._actions.length;
    }
    //
    this._undoCursor--;
    //
    this._repaint();
    //
    this._updateButtonStatus();
  }
  //
  redo() {
    if (!this.canRedo()) {
      return;
    }
    //
    this._undoCursor++;
    //
    this._repaint();
    //
    this._updateButtonStatus();
  }
  //
  //
  _updateButtonStatus() {
    this.emit('updateUndoRedo', this);
  }
  //
  _repaint() {
    const canvas = this._canvas;
    this._ctx.clearRect(0, 0, canvas.width, canvas.height);
    //
    let toIndex = this._undoCursor == -1 ? this._actions.length : this._undoCursor;
    for (let i = 0; i < toIndex; i++) {
      //
      let actionData = this._actions[i];
      actionData.draw(this._ctx);
    }
  }
}

在这里,我们在构造函数里面,要求传入一个canvas。然后定义了几个成员变量:actions,undoCursor,canvas,ctx以及tool。在这里,我们可以看到,定义的这几个变量,前面都加了下划线。这表明这些变量都是私有的,不建议外部使用(虽然js无法强制禁止外部使用)。同样,如果方法前面也加了下划线,同样表示这是一个私有方法,不建议外部使用。 另外,我们还让HandwrittenPad 继承了 EventEmitter ,这样我们可以向外发送消息。目前我们向外发送了updateUndoRedo这个消息,表示undo/redo的状态改变了。 从代码中我们可以看到,HandwrittenPad已经不在和document有关联了,我们的代码中也不再使用document对象。 最后,我们修改index.js文件:

import HandwrittenPad from './HandwrittenPad';
function updateUndoRedo(pad) {
  const undoButton = document.getElementById('undo');
  const redoButton = document.getElementById('redo');
  undoButton.disabled = !pad.canUndo();
  redoButton.disabled = !pad.canRedo();
}
function init() {
  //
  const pad = new HandwrittenPad(document.getElementById('pad'));
  //
  document.getElementById('pencil').addEventListener('click', () => pad.choosePencil());
  document.getElementById('highlighter').addEventListener('click', () => pad.chooseHighlighter());
  document.getElementById('eraser').addEventListener('click', () => pad.chooseEraser());
  document.getElementById('rect').addEventListener('click', () => pad.drawRect());
  document.getElementById('ellipse').addEventListener('click', () => pad.drawEllipse());
  document.getElementById('undo').addEventListener('click', () => pad.undo());
  document.getElementById('redo').addEventListener('click', () => pad.redo());
  //
  pad.on('updateUndoRedo', (pad) => {
    updateUndoRedo(pad);
  });
  //
  updateUndoRedo(pad);
}
init();

在index.js里面,我们开始和html/dom交互。获取对象,绑定事件,更新状态。

至此,我们用来测试手写的功能已经基本完成,并且使用webpack,来把整个工程进行拆分,并使用了面向对象的思想,重构了代码,让代码可维护性更高。