如何遵循OOP范例和DRY原理,将这种计时器/秒表代码库最佳地重构为更好的代码重用?

问题描述

我在遵循javascript中的 OOP 时遇到问题。 如何使此代码面向对象和可重用?

我尝试阅读JS中的OOP概念,但找不到使该代码成为一体的方法有什么建议吗?

PS:这是制作秒表的代码

//Define variables to hold time values
let seconds = 0;
let minutes = 0;
let hours = 0;

//Define variable to  hold "display" value
let interval = null;

//Define variable to hold the clock status
let status = "paused";

//Clock function ( logic to determine when to increment next value,etc.)
function clock() {
  seconds++;

  //Logic to determine when to increment next value
  if (seconds >= 60) {
    seconds = 0;
    minutes++;

    if (minutes >= 60) {
      minutes = 0;
      hours++;
    }
  }

  //display updated time values to user
  document.getElementById("display").innerHTML =
    //If seconds/minutes/hours are only one digit,add a leading 0 to the value
    `${hours ? (hours > 9 ? hours : `0${hours}`) : "00"}:${minutes ? (minutes > 9 ? minutes : `0${minutes}`) : "00"}:${seconds > 9 ? seconds : `0${seconds}`}`;
}

function startPause() {
  if (status === "paused") {
    //Start the stopwatch (by calling the setInterval() function)
    interval = window.setInterval(clock,1000);
    document.getElementById("startPause").innerHTML = "Pause";
    status = "started";
  } else {
    window.clearInterval(interval);
    document.getElementById("startPause").innerHTML = "Resume";
    status = "paused";
  }
}

//Function to reset the stopwatch
function reset() {
  seconds = 0;
  minutes = 0;
  hours = 0;
  document.getElementById("display").innerHTML = "00:00:00";
  document.getElementById("startPause").innerHTML = "Start";
  window.clearInterval(interval);
  status = "paused";
}

解决方法

可以使用一种“基于组件” 的方法。提供的示例代码非常简单。构造函数确实将所有需要的引用作为公共属性分配给Stopwatch实例。用于读写组件的UI(/ DOM)部分的Getter和Setter被实现为原型方法。辅助方法(时间间隔处理,时间度量计算)不需要成为类实现本身的一部分,而将成为// module scope of e.g. 'Stopwatch.js' file. const MEASURE_STATE_RUNNING = 'running'; const MEASURE_STATE_STOPPED = 'stopped'; const UPDATE_CYCLE_MINIMUM = 100; // any value in msec. const UPDATE_CYCLE_DEFAULT = 200; // const UPDATE_CYCLE_MAXIMUM = 1000; // function getDisplayNumber(value) { return ((String(value).length === 1) && `0${ value }`) || value; } function getMeasureInMilliseconds(measure) { return (((measure.hours * 3600) + (measure.minutes * 60) + measure.seconds) * 1000); } function getMeasureFromMilliseconds(value) { let hours = (value / 3600000); let minutes = ((hours - Math.floor(hours)) * 60); let seconds = ((minutes - Math.floor(minutes)) * 60); hours = Math.floor(hours); minutes = Math.floor(minutes); seconds = Math.floor(seconds + 0.001); seconds = ((seconds < 60) && seconds) || 0; minutes = ((minutes < 60) && minutes) || 0; hours = ((hours < 100) && hours) || 0; return { hours,minutes,seconds }; } function handleStartStopForBoundStopwatch(/* evt */) { const stopwatch = this; if (stopwatch.measureState === MEASURE_STATE_STOPPED) { stopwatch.startMeasure(); } else { stopwatch.stopMeasure(); } } function updateStopwatchMeasure(stopwatch) { const dateNow = Date.now(); // has at least one second past since the last measure update? const isUpdateMeasure = (Math.floor((dateNow - stopwatch.updateTimestamp) / 1000) >= 1); if (isUpdateMeasure) { stopwatch.updateTimestamp = dateNow; // time differences in milliseconds since measuring has been started the last time. const timePassed = (dateNow - stopwatch.measureTimestamp); const messureValue = (timePassed + stopwatch.lastMeasuredMSecs); Object.assign(stopwatch.measure,getMeasureFromMilliseconds(messureValue)); stopwatch.setComponentMeasure(); } } class Stopwatch { constructor(node) { this.node = node; this.timerId = null; this.updateCycle = this.getComponentUpdateCycle(); // for synchronizing display values of a running measure. this.lastMeasuredMSecs = null; this.measureTimestamp = null; this.updateTimestamp = null; this.measure = this.getComponentMeasure(); this.measureState = this.getComponentMeasureState(); // synchronize component data initially. this.setComponentMeasure(); this.setComponentMeasureState(); if (this.measureState === MEASURE_STATE_RUNNING) { this.startMeasure(); } this.startStopHandler = handleStartStopForBoundStopwatch.bind(this); node.addEventListener('click',this.startStopHandler); } destroy() { if (this.node) { this.node.removeEventListener('click',this.startStopHandler); this.node.remove(); this.node = null; delete this.node; } this.timerId = this.updateCycle = this.lastMeasuredMSecs = null; this.measureTimestamp = this.updateTimestamp = null; this.measure = this.measureState = this.startStopHandler = null; delete this.timerId; delete this.updateCycle; delete this.lastMeasuredMSecs; delete this.measureTimestamp; delete this.updateTimestamp; delete this.measure; delete this.measureState; delete this.startStopHandler; } getComponentMeasure() { const result = (/^(?<hours>\d{1,2})\:(?<minutes>\d{1,2})\:(?<seconds>\d{1,2})$/) .exec( this.node.dateTime ); const { hours,seconds } = (result && result.groups) || { hours: 0,minutes: 0,seconds: 0 }; return { hours: parseInt(hours,10),minutes: parseInt(minutes,seconds: parseInt(seconds,10) }; } setComponentMeasure() { const { hours,seconds } = this.measure; const value = [ getDisplayNumber(hours),getDisplayNumber(minutes),getDisplayNumber(seconds) ].join(':'); this.node.dateTime = value; this.node.innerText = value; } getComponentMeasureState() { return ( ((this.node.dataset.measureState || '').trim() === 'running') && MEASURE_STATE_RUNNING || MEASURE_STATE_STOPPED ); } setComponentMeasureState() { this.node.dataset.measureState = this.measureState; if (this.measureState === MEASURE_STATE_RUNNING) { this.node.classList.add(MEASURE_STATE_RUNNING); this.node.classList.remove(MEASURE_STATE_STOPPED); } else { this.node.classList.add(MEASURE_STATE_STOPPED); this.node.classList.remove(MEASURE_STATE_RUNNING); } } getComponentUpdateCycle() { let value = parseInt(this.node.dataset.updateCycle,10); value = (Number.isNaN(value) && UPDATE_CYCLE_DEFAULT) || value; return Math.max(UPDATE_CYCLE_MINIMUM,Math.min(UPDATE_CYCLE_MAXIMUM,value)); } startMeasure() { this.measureTimestamp = this.updateTimestamp = Date.now(); this.lastMeasuredMSecs = getMeasureInMilliseconds(this.measure); this.timerId = setInterval( updateStopwatchMeasure,this.updateCycle,this ); this.measureState = MEASURE_STATE_RUNNING; this.setComponentMeasureState(); } stopMeasure() { clearInterval(this.timerId); this.lastMeasuredMSecs = null; this.measureTimestamp = null; this.updateTimestamp = null; this.measureState = MEASURE_STATE_STOPPED; this.setComponentMeasureState(); }/* resetMeasure() { Object.assign(this.measure,{ hours: 0,seconds: 0 }); this.setComponentMeasure(); }*/ static initialize(node) { return new Stopwatch(node); } } /*export default*/function initialize() { return Array .from(document.body.querySelectorAll('.stopwatch-component')) .map(Stopwatch.initialize); } /** * usage */ const timerList = initialize(); // console.log('timerList :',timerList);模块范围的居民...

dd {
  margin-bottom: 6px;
}
.stopwatch-component {
  font-family: monospace;
  font-size: x-large;
  cursor: pointer;
}
.stopwatch-component.stopped {
  text-decoration: line-through solid #999;
}
<dl>
  <dt>
    1st time measurement
  </dt>
  <dd>
    <time
      class="stopwatch-component"
      datetime="0:0:0"
      data-update-cycle="100"
      data-measure-state="running">0:0:0</time>
  </dd>
  <dt>
    2nd time measurement
  </dt>
  <dd>
    <time
      class="stopwatch-component"
      datetime="99:59:33"
      data-update-cycle="1000">99:59:33</time>
  </dd>
  <dt>
    3rd time measurement
  </dt>
  <dd>
    <time
      class="stopwatch-component"
      datetime="07:11:55"
      data-update-cycle="500"
      data-measure-state="stopped">7:11:55</time>
  </dd>
</dl>
<p>Click each time measure separately for toggling its pause/proceed state.</p>
data-update-cycle

注意:

为了保持(同步)传递的秒(分钟,小时)呈现,与OP提供的计时器方法相比,需要另一种计时器方法。

这些示例以不同的可配置更新周期运行,这些更新周期由<time class="stopwatch-component"/>元素的setInterval属性的值定义。由于$q = "INSERT INTO `gancxadebebi` ($keys) VALUES ($values)"; $var = 0; while(true){ sleep(1); $r = mysqli_query($c,$q); if($r){ echo 'success'; }else{ echo 'error'; } ++$var; echo $var; if($var == '100000'){ break; } } 不够精确,因此500毫秒或1毫秒的更新间隔不适合此类测量任务。正在运行的示例确实演示了这一点。

必须通过不断比较开始测量过程的时间戳和当前更新过程的时间戳来使显示的数据保持同步。仅在自上次(重新)呈现以来至少经过一秒钟时,才进行显示的原因值的(重新)呈现。但是,不断(但不是永久)触发(重新)渲染的更新周期必须以更高的频率运行。