Я пытался реализовать текстовый процессор на JavaScript, который может обрабатывать 4 различных типа операций: добавление, возврат, отмена, повтор.
Входом для этого текстового процессора является массив массивов из одного элемента или кортежей. Например:
const input = [['APPEND', 'Hey'], ['APPEND', ' there'], ['APPEND', '!']]
textProcessor.process(input)
textProcessor.text // Hey there!
const input = [['APPEND', 'Hey'], ['APPEND', ' there'], ['APPEND', '!'], ['UNDO'], ['UNDO']]
textProcessor.process(input)
textProcessor.text // Hey
const input = [['APPEND', 'Hey'], ['APPEND', ' there'], ['APPEND', '!'], ['UNDO'], ['UNDO'], ['REDO'], ['REDO']]
textProcessor.process(input)
textProcessor.text // Hey there!
const input = [['APPEND', 'Hey'], ['APPEND', ' there'], ['APPEND', '!'], ['BACKSPACE']]
textProcessor.process(input)
textProcessor.text // Hey there
Вот моя реализация
class TextProcessor {
constructor() {
this.undos = []
this.redos = []
this.text=""
this.operations = {
APPEND: (text) => {
this.undos.push(this.text)
this.redos.length = 0
return this.text + text
},
BACKSPACE: () => {
this.undos.push(this.text)
this.redos.length = 0
return this.text.slice(0, -1)
},
UNDO: () => {
const undo = this.undos.pop() ?? ''
this.redos.push(this.text)
return undo
},
REDO: () => {
this.undos.push(this.text)
return this.redos.pop() ?? this.text
},
}
}
process(input) {
input.forEach(([operation, text]) => {
this.text = this.operations[operation](text) ?? this.text
})
}
}
Я использовал объект для сопоставления различных имен операций с соответствующими функциями. Не уверен, что это лучше, чем операторы switch-case.
1 ответ
Необычная отмена повтора
Ваши операции отмены и повтора имеют странное поведение, поскольку сами по себе их нельзя отменить. Обычно операции отмены и повтора сами по себе не подлежат отмене.
Ошибка?
Неизвестные команды вызовут ошибку TypeError: this.operations[operation] is not a function
ПОЛУКОЛОНЫ!
Мне кажется, я упоминал точку с запятой в прошлый раз, когда просматривал ваш код. Если вы не хотите их использовать, вам следует ознакомиться с О
Идентификаторы
Старайтесь избегать строковых идентификаторов, так как они требуют много памяти. В зависимости от количества и схожести идентификаторов реклама может быть намного медленнее.
Один из методов создания уникальных идентификаторов — это объект перечисления.
Пример
const operators = {
append: 0,
undo: 1,
redo: 2,
backspace: 3,
}
Перезапись использует функцию Enum который возвращает объект, содержащий именованные целочисленные идентификаторы.
Инкапсуляция
Защитите состояние объекта. Всем свойствам объекта, доступным для другого кода, нельзя доверять. Без доверенного состояния вы не можете гарантировать, что ваш код будет работать должным образом.
Хорошо спроектированный инкапсулированный объект (однажды протестированный) НЕ может выйти из строя.
JavaScript имеет отличную модель инкапсуляции объектно-ориентированного программирования.
- Закрытие используется для инкапсуляции состояния внутри объекта, не требующего дополнительных языковых токенов для доступа (например,
this) - Объект функции
- Object.freeze установит для всех свойств значение Writable: false, а объект — неизменяемый.
- Object.seal свойства остаются доступными для записи, но объект остается неизменным.
Используя закрытие для удержания состояния объекта, вы затем создаете интерфейс для обеспечения интерфейса к состоянию. Интерфейс — это просто объект (замороженный или запечатанный), который использует методы получения, установки и функции для доступа к состоянию и управлению поведением.
Будьте эффективны
Всегда пишите код, чтобы он был максимально эффективным.
Да, это баланс между производительностью и эффективностью кода, но, постоянно следя за эффективностью, вы становитесь более искусными в написании эффективного кода.
Неэффективный буфер отмены
Отмена обходится дорого, поскольку хранит полный текст для каждого действия, которое можно отменить. Вам нужно только сохранить каждую операцию и связанные данные в буфере отмены.
Это сделает ваш код более сложным, но для больших документов он будет работать намного быстрее и потреблять гораздо меньше памяти. Клиенты не видят и не заботятся о том, как что-то делается, они заботятся только о том, что он делает, если это включает в себя медленный отклик и голодание по мощности, это никогда не будет плюсом.
Переписать
Перезапись добавляет небольшую функциональность, чтобы помочь продемонстрировать некоторые из вышеперечисленных пунктов, касающихся геттеров и сеттеров интерфейса.
Использование остается прежним
Поведение немного другое
Отменить и повторить нельзя.
Он использует именованные целочисленные значения для идентификации операций. Это статическое свойство
TextProcessor.operatorsи внутренний доступ черезoperatorsФункция
TextProcessor.processгарантирует, что ввод является массивом, отфильтровывает не массивы, чтобы избежать ошибок, и проверяет правильность операции перед попыткой вызова операции, опять же, чтобы избежать ошибок.Для каждой отменяемой операции существует эквивалентная операция повтора. Это означает, что в буфере отмены должны храниться только операции и данные операции.
Все объекты, доступные для несвязанного кода, замораживаются, чтобы гарантировать, что состояние можно доверять и поддерживать.
Отдельные операции недоступны через интерфейс, но при необходимости их можно легко добавить.
const Enum = (baseId, ...names) => Object.freeze(
names.reduce((obj, name, i) => (obj[name] = i + baseId | 0, obj), {})
);
const TextProcessor = (() => {
function TextProcessor() {
const undos = [];
const undoOperations = {
[operators.APPEND](txt) { text = text.slice(0, -txt.length) },
[operators.BACKSPACE](txt) { text += txt },
[operators.CLEAR](txt) { text = txt },
};
const undoable = (operation, ...data) => {
undos.length = undoPos++;
undos.push({operation, data});
}
const redoUndoable = () => undoPos++;
const operations = {
[operators.APPEND](txt) {
txt = "" + txt; // forces txt to be a string
addUndoable(operators.APPEND, txt);
text += txt;
},
[operators.BACKSPACE]() {
addUndoable(operators.BACKSPACE, text[text.length - 1]);
text = text.slice(0, -1);
},
[operators.CLEAR]() {
addUndoable(operators.CLEAR, text);
text = "";
},
[operators.UNDO]() {
if (undoPos) {
const op = undos[--undoPos];
undoOperations[op.operation](...op.data);
}
},
[operators.REDO]() {
if (undoPos < undos.length) {
const op = undos[undoPos];
addUndoable = redoUndoable;
operations[op.operation](...op.data);
addUndoable = undoable;
}
}
};
var undoPos = 0, text = "", addUndoable = undoable;
return Object.freeze({
get text() { return text },
set text(txt) {
operations[operators.CLEAR]();
operations[operators.APPEND](txt);
},
process(input) {
if (Array.isArray(input)) {
input = input.filter(operation => Array.isArray(operation));
for (const [opName, data] of input) {
operations[operators[opName]]?.(data);
}
}
}
});
}
const operators = Enum(1, "APPEND", "BACKSPACE", "UNDO", "REDO", "CLEAR");
return Object.freeze(Object.assign(
TextProcessor,
operators
));
})();
const textProcessor = TextProcessor()
textProcessor.process([
['APPEND', 'Hey'],
['APPEND', ' there'],
['APPEND', '!'],
['BACKSPACE'], ['BACKSPACE'], ['BACKSPACE'], ['BACKSPACE'],
['APPEND', '$'],
['UNDO'], ['REDO'],
['UNDO'], ['UNDO'], ['UNDO'], ['UNDO'], ['UNDO'], ['UNDO']
['REDO'], ['REDO'], ['REDO'], ['REDO'], ['REDO'], ['REDO'],
['UNDO'],
['APPEND', 'e'],
['APPEND', 'r'],
['APPEND', 'e'],
['APPEND', '$'], ['UNDO'],
['APPEND', '!'],
['CLEAR'],
['UNDO'],
]);
console.log("'" + textProcessor.text + "'" + " === 'Hey there!' " + (textProcessor.text === "Hey there!"));
Отличный ответ. Я заметил, что ты использовал
varвместоlet, можно ли использоватьletили в этом случае лучше использоватьvarпо какой-то конкретной причине я пропал?— дариозицилий