3

I would like to ask, in ES6 how can I use getters only without setters (readOnly) properties? Why is Webstorm telling me that this is an error?

Here is my code:

class BasePunchStarter {

    constructor(id,name,manufacturer,description,genres,targetPrice) {
        if (new.target==BasePunchStarter) {
            throw new TypeError("BasePunchStarter class cannot be instantiated directly!");
        }
        if (typeof id =="number") {
            // noinspection JSUnresolvedVariable
            this.id = id;
        } else throw new TypeError("ID must be a number!");
        if (typeof name=="string") {
            // noinspection JSUnresolvedVariable
            this.name = name;
        } else throw new TypeError("Name must be a string!");
        if(typeof manufacturer=="string") {
            // noinspection JSUnresolvedVariable
            this.manufacturer = manufacturer;
        } else throw new TypeError("Manufacturer must be a string!");
        if (typeof description=="string") {
            // noinspection JSUnresolvedVariable
            this.description = description;
        } else throw new TypeError("Description must be a string!");
        if(typeof genres=="Object") {
            // noinspection JSUnresolvedVariable
            this.genres=genres;
        } else new TypeError("Genres must be an Array of strings!");
        if (typeof targetPrice=="number") {
            // noinspection JSUnresolvedVariable
            this.targetPrice = targetPrice;
        } else new TypeError("Target price must be a number!");
        this.accumulatedMoney=0;
    }

    get accumulatedMoney() {
        return this._accumulatedMoney;
    }
    set accumulatedMoney(money) {
        this._accumulatedMoney=money;
    }
    get id() {
        return this._id;
    }
    get name() {
        return this._name;
    }
    get manufacturer() {
        return this._manufacturer;
    }
    get description() {
        return this._description;
    }
    get genres() {
        return this._genres;
    }
    get targetPrice() {
        return this._targetPrice;
    }

}

I had put //noinspection JSUnresolvedVariable to suppress the warning. But there should be better solution than this.

Marius Mucenicu
  • 1,685
  • 2
  • 16
  • 25
  • 1
    If you're using ES6 classes, at the very least you can use `===`, too. Also, as a point of solid programming, making a constructor throw is a really bad plan. That's not the responsibility of a constructor. If you have type requirements, either make sure you're passing in already validated data (which you should do anyway), or use something like TypeScript so you get type safety baked in. – Mike 'Pomax' Kamermans Feb 17 '17 at 18:55
  • Off topic, but @Mike'Pomax'Kamermans, why shouldn't you make a constructor throw? That seems the safest, most natural way to signal errors (which may or may not be the fault of the caller). – A. L. Flanagan Feb 17 '17 at 19:31
  • Possibly unrelated error: in `if(typeof genres=="Object"){`, it should be `"object"`, lowercase. – A. L. Flanagan Feb 17 '17 at 19:34
  • 1
    @A.L.Flanagan things like http://stackoverflow.com/a/77797/740553 explain it better than I can in a comment. – Mike 'Pomax' Kamermans Feb 17 '17 at 22:01
  • 1
    Thanks @Mike'Pomax'Kamermans, that actually makes a lot of sense. – A. L. Flanagan Mar 10 '17 at 19:10

2 Answers2

5

It seems that you're assigning the values on the constructor to the getters instead of the backing fields prefixed with underscore.

constructor(id,name,manufacturer,description,genres,targetPrice){
    if(new.target==BasePunchStarter){
        throw new TypeError("BasePunchStarter class cannot be instantiated directly!");
    }
    if(typeof id =="number") {
        // use the backing field instead.
        this._id = id;
[..]

In case you're not doing it already, you should declare your backing fields before using them.

Leonardo Chaia
  • 2,755
  • 1
  • 17
  • 23
  • 2
    I upvoted your answer, but in Javascript, there is no reason to declare a property before assigning to it. – jfriend00 Feb 17 '17 at 22:24
-1

Your code is not idiomatic JS. The language is loosely typed and its philosophy is based on duck typing. What you do in your constructor is awful and should be avoided. JavaScript is not Java. If you want strong and static typing, use Flow or TypeScript.

Getters and setters are easy to use in ES6 classes, just like getters and setters in object literals.

If you want read-only properties, you can use the _ coding convention and simply avoid setters. If we take the simple example from the documentation, we have the following result:

class Person {
  constructor(firstname, lastname) {
    this._firstname = firstname;
    this._lastname = lastname;
  }

  get firstname() {
    return this._firstname;
  }

  get lastname() {
    return this._lastname;
  }
}

let person = new Person('John', 'Doe');

console.log(person.firstname, person.lastname); // John Doe

// This is ignored
person.firstname = 'Foo';
person.lastname = 'Bar';

console.log(person.firstname, person.lastname); // John Doe

In JavaScript, this solution is just fine. But if, for some reason, you really want true encapsulation, this is not the way to go. Indeed, internal properties with the _ prefix can still be accessed directly:

class Person {
  constructor(firstname, lastname) {
    this._firstname = firstname;
    this._lastname = lastname;
  }

  get firstname() {
    return this._firstname;
  }

  get lastname() {
    return this._lastname;
  }
}

let person = new Person('John', 'Doe');

console.log(person.firstname, person.lastname); // John Doe

// This is NOT ignored
person._firstname = 'Foo';
person._lastname = 'Bar';

console.log(person.firstname, person.lastname); // Foo Bar

The best solution for full encapsulation consists in using an IIFE to create a local scope and Object.freeze() on instances to prevent unwanted changes.

With getters, it works:

let Person = (() => {
  let firstname,
      lastname;

  class Person {
    constructor(first, last) {
      firstname = first;
      lastname = last;
    }

    get firstname() {
      return firstname;
    }

    get lastname() {
      return lastname;
    }
  }

  return Person;
})();

let person = new Person('John', 'Doe');
Object.freeze(person);

console.log(person.firstname, person.lastname); // John Doe

// This is ignored
person.firstname = 'Foo';
person.lastname = 'Bar';

console.log(person.firstname, person.lastname); // John Doe

Without getters, it does not work:

let Person = (() => {
  let firstname,
      lastname;

  class Person {
    constructor(first, last) {
      firstname = first;
      lastname = last;
    }
  }

  return Person;
})();

let person = new Person('John', 'Doe');
Object.freeze(person);

console.log(person.firstname, person.lastname); // undefined undefined

// This is ignored
person.firstname = 'Foo';
person.lastname = 'Bar';

console.log(person.firstname, person.lastname); // undefined undefined
Graham
  • 7,431
  • 18
  • 59
  • 84
Badacadabra
  • 8,043
  • 7
  • 28
  • 49
  • 1
    The 3rd example does not work as all instances of the class refer to the same variables in the IIFE so `let p1 = new Person( 'a', 'b' ); let p2 = new Person( 'c', 'd' ); console.log(p1.firstname, p1.lastname);` outputs `c d` and not `a b`. – MT0 Sep 30 '17 at 21:21