Классы JavaScript с геттером и сеттером вызывают RangeError: превышен максимальный размер стека вызовов

В настоящее время я экспериментирую с классами ECMA6. Мой текущий класс выглядит следующим образом

class Player {
  constructor(id) {
    this.id = id;
    this.cash = 350;
  }

  get cash() {
    return this.cash;
  }

  set cash(value) { // line 19
    this.cash = value; // line 20
  }
};

Когда я сейчас создаю новый объект, вызывая let playerObject = new Player(1); Я получаю следующую ошибку

...\node_modules\mysql\lib\protocol\Parser.js:82
        throw err;
              ^
RangeError: Maximum call stack size exceeded
    at Player.cash (player.js:19:11)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
Press enter to exit

Какое это имеет отношение к библиотеке mysql? Почему ошибка несколько раз в одной строке? Я звоню только один раз.

5 ответов

Решение

Ваш установщик "cash" вызывает установщик "cash", который вызывает установщик "cash", который вызывает установщик "cash"...

Доступ к установщику свойств по его собственному имени внутри установщика создает бесконечный рекурсивный вызов функции.

Я знаю, что опоздал, но я думаю, что могу уточнить один или два момента здесь:

Во-первых, это вопрос конфиденциальности, который является долгосрочным обсуждением в сообществе JavaScript.

class Player {
   constructor(id) {
      this.cash = 350; // this._cash, alternatively
   }

   get cash() {
      return this.cash;
   }

   set cash(value) {
      this.cash = value;
   }
};

let player1 = new Player();

В этом случае this.cash является общедоступным свойством, поэтому вам не нужны методы getter и setter для его обработки, потому что вы можете получить его с помощью player1.cash и установить его с player1.cash = newCash; и он выдает ошибку, потому что метод получения и метод вызова вызываются рекурсивно, как упоминалось другими.

Однако, если вы просто переименуете свойство в this._cash, вы должны понимать, что это НЕ ЧАСТНАЯ СОБСТВЕННОСТЬ. Если вы попытаетесь получить доступ к player1._cash, вы получите доступ к значению свойства так же, как и к player1.cash.

Итак, как мы можем обеспечить частную реализацию конфиденциальности?

Есть два основных способа сделать это с ES6/ES2015: используя новый тип примитива Symbol или используя WeakMaps. Я не буду вдаваться в подробности об этих двух новых функциях языка, но покажу, как это будет реализовано в этом случае.

Используя символы:

const CASH = Symbol();

class Player {

   constructor () {
      this[CASH] = 350;
   }

   get cash(){
      return this[CASH];
   }

   set cash(cash) {
      this[CASH] = cash;
   }

}

Использование WeakMaps

let map =  new WeakMap();

class Player {

   constructor () {
      map.set(this, {
         cash: 350
      });    
   }

   get cash(){
      return map.get(this).cash;
   }

   set cash(cash) {
      map.get(this).cash = cash;
   }

}

ВАЖНЫЙ

Хотя синтаксис для символов лучше, для его работы требуется встроенная поддержка браузера. Вы можете написать это с помощью транспилятора, но под капотом он будет издеваться над старыми стандартами ES5. Встроенная поддержка WeakMaps лучше, и, с другой стороны, эта функция работает только с GC и с перечисляемой опцией свойств объектов. Итак, в конце концов, это ваш выбор.

cash представляет собой метод получения / установки, _cash - свойство private.

  set cash(value) { // line 19
      this._cash = value; // line 20
  }

Посмотрите на эту страницу для наглядного примера.

Вы рекурсивно звоните своему получателю.

Следует возможная альтернатива:

class Player {
    constructor(id) {
        this.id = id;
        this._cash = 350;
    }

    get cash() {
        return this._cash;
    }

    set cash(value) {
        this._cash = value;
    }
};

Еще один, использующий Object.defineProperty:

class Player {
    constructor(id) {
        this.id = id;

        var _cash = 350;
        Object.defineProperty(this, 'cash', {
            get: function() {
                return _cash;
            }

            set: function(v) {
                _cash = v;
            }
        });
    }
};

Классы Get & Set ES6 предоставляют новый синтаксис для методов получения и установки свойств объекта. Get and set позволяет нам запускать код для чтения или записи свойства. ES5 также имел геттеры и сеттеры, но из-за старых браузеров IE его не использовали широко. Получатели и установщики ES5 не имели такого приятного синтаксиса, как ES6. Итак, давайте создадим get и set для нашего свойства name.

Источник: JavaScript ES6 Class Синтаксис

Пример:

// ES6 get and set
class Person {
    constructor(name) {
        this._name = name;
    }

    get name() {
        return this._name.toUpperCase();
    }

    set name(newName) {
        this._name = newName;   // validation could be checked here such as only allowing non numerical values
    }

    walk() {
        console.log(this._name + ' is walking.');
    }
}

let bob = new Person('Bob');
console.log(bob.name);  // Outputs 'BOB'
Другие вопросы по тегам