get and set - garevna/js-course GitHub Wiki

:mortar_board: Вычисляемые свойства

У объекта могут быть свойства, значения которых вычисляются на основании значений других свойств

:coffee: :one:

Предположим, есть объект commodity, описывающий товар

Цена товара priceUSD установлена в долларах

Предположим, курс доллара устанавливается значением переменной course

Для получения цены товара в гривне нужно умножить цену товара в долларах на курс доллара

Однако крайне неудобно производить подобные операции каждый раз, когда нужна цена товара в гривне

Создадим вычисляемое свойство priceUAH

Для этого объявим геттер и сеттер свойства

var course = 28

var commodity = {
    name: "Утюг",
    mark: "Tefal",
    priceUSD: 20,

    get priceUAH () {
        return this.priceUSD * course
    },
    set priceUAH ( newPriceUAH ) {
        this.priceUSD = newPriceUAH / course
    }
}

Геттер - это функция, которая будет вычислять актуальное значение цены товара в гривне и возвращать результат как значение свойства commodity.priceUAH

:warning: У геттера не может быть аргументов

Сеттер - это функция, которая будет получать новое значение цены товара в гривне ( newPriceUAH ) и пересчитывать цену товара в долларах ( commodity.priceUSD ) по текущему курсу ( course )

Каждый раз, когда мы будем обращаться к свойству commodity.priceUAH,

console.log ( commodity.priceUAH )

будет срабатывать геттер

Результат:
560

Каждый раз, когда мы будем присваивать новое значение свойству commodity.priceUAH, на самом деле будет вызываться функция-сеттер, которая будет изменять значение свойства commodity.priceUSD

commodity.priceUAH = 250

console.log ( commodity.priceUSD )
Результат:
8.928571428571429

Для вычисляемых свойств "под капотом" операция присваивания заменяется вызовом функции-сеттера

:warning: Поэтому категорически нельзя внутри функции-сеттера использовать присваивание значения этому же свойству:

:no_entry_sign:

var commodity = {
    name: "Утюг",
    mark: "Tefal",
    priceUSD: 20,

    get priceUAH () {
        return this.priceUSD * course
    },
    set priceUAH ( newPriceUAH ) {
        this.priceUAH = newPriceUAH
    }
}

Это приведет к бесконечной рекурсии и генерации исключения:

❌ ► Uncaught RangeError: Maximum call stack size exceeded

:coffee: :two:

Создадим простенький объект-калькулятор:

var calculator = {
    firstValue: 0,
    secondValue: 0,
    operations: [ "+", "-", "*", "/", "%" ],
    operation: "+",
    get result () {
        return eval( `${this.firstValue}${this.operation}${this.secondValue}` )
    },
    set result ( newValue ) {

        for ( var x of this.operations ) {
            let vars = newValue.split ( x )
            if ( vars.length === 1 ) continue
            this.operation = x
            this.firstValue = Number ( vars[0] )
            this.secondValue = Number ( vars[1] )
            break
        }
    }
}

Вычисляемое свойство result этого объекта имеет геттер и сеттер

Когда мы обращамся к свойству result для получения его значения, срабатывает геттер

console.log ( calculator.result )

Если же мы выполним присваивание нового значения свойству result

calculator.result = "5 - 8 "

сработает сеттер свойства, и в результате будут изменены значения свойств firstValue, secondValue и operation объекта calculator

▼ {firstValue: 5, secondValue: 8, operations: Array(5), operation: "-"}
    firstValue: 5
    operation: "-"
  ► operations: (5) ["+", "-", "*", "/", "%"]
    result: (...)
    secondValue: 8
  ► get result: ƒ result()
  ► set result: ƒ result( newValue )
  ► __proto__: Object

:coffee: :three:

Создадим вычисляемое свойство state объекта human

var human = {
    name: "Иван Сидоренко",
    states: [ "work", "relax", "enjoy" ],
    currentState: 0,

    get state () {
        return this.states [ this.currentState ]
    },

    set state ( newState ) {
        this.states.indexOf ( newState ) < 0 ? 
            this.states.push ( newState ) : null
        this.currentState = this.states.indexOf ( newState )
    },

    showState: function () {
        console.log ( `Current state: ${this.currentState} ( ${this.state} )` )
    }
}

human.showState()
Результат:
Current state: 0 ( work )

Если вычисляемое свойство state фигурирует в левой части оператора присваивания, то вызывается сеттер свойства

Сеттер проверяет наличие такого значения в массиве human.states, и если такого значения там нет, то добавляет его

Затем сеттер свойства state устанавливает значение свойства human.currentState равным индексу элемента массива human.states, значение которого будет отображать геттер свойства state

human.state = "swim"

human.showState()
Результат:
Current state: 3 ( swim )