Цепные методы в JS

283
23 февраля 2018, 20:07

Поставлена задача: написать калькулятор, который включает в себя различные операции (+ - * / ^) с поддержкой цепных методов. Так же должен учитываться приоритет операторов. Пример:

const calculator = new SmartCalculator(2);
  const value = calculator
    .add(2)
    .multiply(2);
  console.log(value); // 6

Т.е. нужно возвращать не только ответ, но и this, чтобы учитывать последовательность операторов. Как можно вернуть ответ, чтобы не потерять this?

Answer 1

Как я уже писал в комментарии к вопросу:

Основная идея "цепных методов" в том, что необходимо, чтобы метод возвращал тот же объект, на котором вызван этот метод. То есть метод add должен возвращать не число (или строку, или вообще что угодно), а объект calculator. Тогда при последующем вызове multiply выполнится метод multiply объекта calculator.

class SmartCalculator {
    constructor(initialValue) {
        this.value = initialValue;
    }
    add(operand) {
        this.value += operand;
        return this;
    }
    multiply(operand) {
        this.value *= operand;
        return this;
    }
}
const calculator = new SmartCalculator(2);
  const value = calculator
    .add(2)
    .multiply(2);
  console.log(value); // 6

Однако здесь возникает неоднозначная ситуация. console.log() в последней строчке выводит не число 6, а объект, у которого поле value содержит значение 6.

Если не принципиально, то можно так и вывести:

console.log(value.value);

Но если принципиально, то я пока вижу одно решение: добавить "завершающий метод", вызов которого будет возвращать результат, а не объект:

class SmartCalculator {
    constructor(initialValue) {
        this.value = initialValue;
    }
    add(operand) {
        this.value += operand;
        return this;
    }
    multiply(operand) {
        this.value *= operand;
        return this;
    }
    get result() {
        return this.value;
    }
}
const calculator = new SmartCalculator(2);
  const value = calculator
    .add(2)
    .multiply(2)
    .result;
  console.log(value); // 6

Кстати, ответ здесь будет не 6, а 8, поскольку методы вызываются по-порядку.

Набросал по-быстрому вариант с соблюдением приоритета:

class SmartCalculator {
    constructor(initialValue) {
        this.value = initialValue.toString();
        this.expression = "";
        this.valueUsed = false;
    }
    add(operand) {
        this.expression += `${this.useExpOrValue(operand)} + ${operand}`;
        return this;
    }
    multiply(operand) {
        this.expression += `${this.useExpOrValue(operand)} * ${operand}`;
        return this;
    }
    useExpOrValue(operand) {
        let res = !this.valueUsed ? this.value : "";
        this.valueUsed = true;
        return res;
    }
    get result() {
        console.log(this.expression);
        return eval(this.expression);
    }
}
const calculator = new SmartCalculator(2);
  const value = calculator
    .add(2)
    .multiply(2)
    .result;
  console.log(value); // 6

Однако использовать его я крайне не рекомендую по понятным причинам: во-первых - костыль, во-вторых - eval.

Answer 2

Примерно так:

 const value = calculator
    .add(2)
    .multiply(2)
    .calculate();

Всё до calculate это сборка выражения с учётом порядка операторов, а сам расчёт делается в calculate();

Answer 3

Можно сделать как-то в таком духе (возведение в степень делать поленился):

class SmartCalculator { 
  constructor(initValue) { 
    this.init = initValue; 
    this.operations = []; 
  } 
  get result() { 
    let rpn = [this.init]; // reverse polish notation 
    let ops = []; 
 
    this.operations.forEach(op => { // сортировочная станция 
      while (ops.length && (ops[ops.length - 1]).priority < op.priority) { 
        rpn.push(ops.pop().op); 
      } 
      ops.push(op); 
      rpn.push(op.val); 
    }); 
    while (ops.length) { 
      rpn.push(ops.pop().op) 
    } 
    let res = []; 
    rpn.forEach(token => { 
      if (typeof token === 'function') { 
        res.push(token(res.pop(), res.pop())); 
      } else { 
        res.push(token); 
      } 
    }); 
    return res.pop(); 
  } 
  add(val) { 
    this.operations.push({ 
      op: (a, b) => a + b, 
      priority: 2, 
      val: val 
    }); 
    return this; 
  } 
  substract(val) { 
    this.operations.push({ 
      op: (a, b) => b - a, 
      priority: 2, 
      val: val 
    }) 
    return this; 
  } 
  multiply(val) { 
    this.operations.push({ 
      op: (a, b) => a * b, 
      priority: 1, 
      val: val 
    }) 
    return this; 
  } 
  divide(val) { 
    this.operations.push({ 
      op: (a, b) => b / a, 
      priority: 1, 
      val: val 
    }) 
    return this; 
  } 
  valueOf() { 
    return this.result; 
  } 
} 
 
const value = new SmartCalculator(2) 
  .add(2) 
  .multiply(2); 
 
console.log(value == 6); 
console.log(+value);

READ ALSO
Кастомный синтаксис post-css

Кастомный синтаксис post-css

Прошу помощи в решении задачиПри написании плагина post-css хотелось бы ввести несколько ключевых слов в правила css, например:

240
CentOS Запуск .js файлов в фоновом режиме

CentOS Запуск .js файлов в фоновом режиме

Есть js файл и php файлPHP файл лежит на сервере и будет принимать данные post от js, который нужно чтобы работал в фоновом режиме целыми днями

269
array.sort() в microsoft edge

array.sort() в microsoft edge

Есть следующий код:

335
Document.onclick() не работает

Document.onclick() не работает

При нажатие Documentonclick() работает только Button 2, а при нажатие document

217