#javascript #oop #refactoring #dry #code-reuse
#javascript #ооп #рефакторинг #dry #повторное использование кода
Вопрос:
У меня возникли проблемы с соблюдением ООП в javascript. Как я могу сделать этот код более объектно-ориентированным и повторно используемым?
Я попытался ознакомиться с концепциями ООП в JS, но не смог найти способ сделать этот код единым. Есть предложения?
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";
}
Ответ №1:
Можно было бы использовать подход, основанный на компонентах. Приведенный пример кода довольно прост. Конструктор присваивает экземпляру все необходимые ссылки в качестве общедоступных свойств Stopwatch
. Геттеры и сеттеры для чтения и записи части пользовательского интерфейса компонента (/ DOM) реализованы как прототипные методы. Вспомогательные методы (обработка интервалов, вычисления временных измерений) не обязательно должны быть частью самой реализации класса, но будут находиться Stopwatch
в области модуля…
// 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) amp;amp; `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) amp;amp; seconds) || 0;
minutes = ((minutes < 60) amp;amp; minutes) || 0;
hours = ((hours < 100) amp;amp; 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,
minutes,
seconds
} = (result amp;amp; result.groups) || { hours: 0, minutes: 0, seconds: 0 };
return {
hours: parseInt(hours, 10),
minutes: parseInt(minutes, 10),
seconds: parseInt(seconds, 10)
};
}
setComponentMeasure() {
const { hours, minutes, 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')
amp;amp; 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) amp;amp; 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,
minutes: 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>
Примечание:
Чтобы сохранить (повторный) рендеринг секунд (минут, часов), которые были переданы синхронно, нужен другой подход к таймеру, чем тот, который предоставляется OP.
Примеры выполняются с различными настраиваемыми циклами обновления, определяемыми значением data-update-cycle
атрибута <time class="stopwatch-component"/>
элемента. Интервал обновления 500 мсек или всего 1 сек. не подходит для такого рода задач измерения из-за setInterval
недостаточной точности. Запущенный пример демонстрирует именно это.
Необходимо синхронизировать отображаемые данные, постоянно сравнивая временную метку с момента запуска процесса измерения с временной меткой из текущего процесса обновления. (Повторный) рендеринг отображаемого значения cause выполняется только в том случае, если с момента последнего (повторного) рендеринга прошло не менее секунды. Но циклы обновления, которые постоянно (но не постоянно) запускают (повторный) рендеринг, должны выполняться с более высокой частотой.
Комментарии:
1. @Roshin … есть ли еще вопрос, на который нет ответа?