Jeroen
Jeroen

Reputation: 63830

Create read only (get) property in constructor function

I'm trying to create a read only property on a constructor function with Javascript. I've remixed the syntax I've found on MDN but this doesn't work:

function Person(data) {
  var self = this;

  self.firstName = data.firstName || "John";
  self.lastName = data.lastName || "Doe";

  get fullName() { return self.firstName + " " + self.lastName; }
}

var mike = new Person({ firstName: "Mike", lastName: "Michaelson" });

document.write(mike.fullName);

This will throw an exception:

Uncaught SyntaxError: Unexpected identifier

I've tried other options, including:

{ get fullName() { return self.firstName + " " + self.lastName; } }

And:

get this.fullName() { return self.firstName + " " + self.lastName; } 

And:

get self.fullName() { return self.firstName + " " + self.lastName; } 

But none of those options work.

This one does work:

function Person(data) {
  var self = this;

  self.firstName = data.firstName || "John";
  self.lastName = data.lastName || "Doe";
  
  Object.defineProperty(self, "fullName", { get: function () { return self.firstName + " " + self.lastName; } });
}

var mike = new Person({ firstName: "Mike", lastName: "Michaelson" });

document.write(mike.fullName);

But obviously this way with defineProperty feels clunky.

What is the correct syntax for defining get-only properties inside constructor functions?

Upvotes: 3

Views: 617

Answers (1)

yurisich
yurisich

Reputation: 7119

I'd recommend making it a POJO with options passed in all the way, but you'll still have that "clunkiness" of having Object.defineProperties, just without the ceremony of actually calling it (I like the shorthand way better, personally).

var Person = function (data) {
    if (data === undefined) {
        data = {};
    }

    data = _.defaults(data, {
        firstName: 'John',
        lastName: 'Doe'
    });

    return {
        get firstName() { return data.firstName; },
        get lastName() { return data.lastName; },
        get fullName() { return data.firstName + ' ' + data.lastName; }
    }
}

EDIT: Now that I've thought about it more, I think the reason you want to include the defineProperty syntax at all is because you want to have read/write access to the first and last name, but read-only access to the fullName. If this assumption is correct, this is a pattern that I'll typically use.

var Person = function (data) {
    if (data === undefined) {
        data = {};
    }

    data = _.defaults(data, {
        firstName: 'John',
        lastName: 'Doe'
    });

    return {
        get firstName() { return data.firstName; },
        set firstName(name) { data.firstName = name; },

        get lastName() { return data.lastName; },
        set lastName(name) { data.lastName = name; },

        get fullName() { return data.firstName + ' ' + data.lastName; }
    }
}

p = Person();
p.fullName === 'John Doe'; // true
p.firstName = 'Jane';
p.fullName === 'Jane Doe'; // true

However, there are some caveats to be aware of, mainly with object references.

data = { firstName: 'Jane' };
p = Person(data);
p.fullName === 'Jane Doe'; // true
data.lastName = 'Goodall';
p.fullName === 'Jane Doe'; // false!
console.log(p.fullName); // 'Jane Goodall'

I typically prefer having arguments passed in as something immutable, or freezing those traits by copying them out of the object and into something immutable so that updates via an object reference don't cause subtle bugs in your behaviors.

Upvotes: 1

Related Questions