3

The computed properties feature is common in popular JS frameworks (React, VueJS) but how do we implement this in vanilla JS?

Let's say that given a User class, with a dateOfBirth property, we would like to compute its age, is there a better way to perform this task than the code below?

function User(name, dateOfBirth) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;

    this.age = function() {
        return now() - this.dateOfBirth;
    }
}

var driver = new User('Steve', new Date('12 December, 1990'))
driver.age()

In the code above, we are retrieving the age value by calling a method. However, is it possible to be able to retrieve the value with just driver.age?

puoygae
  • 573
  • 7
  • 19

2 Answers2

5

Whether it's "better" or not is a matter of style/opinion, but if you want a property rather than a method, you can create an accessor property, in your case an accessor with a getter but no setter.

Making minimal changes to your code, you'd do it like this:

function User(name, dateOfBirth) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;

    Object.defineProperty(this, "age", {
        get() {
            return now() - this.dateOfBirth;
        }
    });
}

Live Example:

function now() { return Date.now(); }
function User(name, dateOfBirth) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;

    Object.defineProperty(this, "age", {
        get() {
            return now() - this.dateOfBirth;
        }
    });
}

var steve = new User("Steve", new Date(1990, 11, 12));
console.log(steve.age);

You can define those more concisely using an object initializer (note that this example throws away the object created via new User and returns a different one instead, which doesn't have User.prototype as its prototype):

function User(name, dateOfBirth) {
    return {
        name,
        dateOfBirth,
        get age() {
            return now() - this.dateOfBirth;
        }
    };
}

Live Example:

function now() { return Date.now(); }
function User(name, dateOfBirth) {
    return {
        name,
        dateOfBirth,
        get age() {
            return now() - this.dateOfBirth;
        }
    };
}

var steve = new User("Steve", new Date(1990, 11, 12));
console.log(steve.age);

I've also used the new (ES2015+) shorthand property syntax there for name and dateOfBirth.

This is also compatible with ES2015's class syntax:

class User {
    constructor(name, dateOfBirth) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
    }
    get age() {
        return now() - this.dateOfBirth;
    }
}

Live Example:

function now() { return Date.now(); }
class User {
    constructor(name, dateOfBirth) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
    }
    get age() {
        return now() - this.dateOfBirth;
    }
}

var steve = new User("Steve", new Date(1990, 11, 12));
console.log(steve.age);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Is there an obvious advantage of the second code example where the user.prototype is not created? – puoygae May 15 '19 at 08:38
  • 1
    @puoygae - It's *created*, it's just not used. That's also a matter of style. Personally, I don't like to use `new X` unless I'm making use of `X.prototype` (directly, or via `class` syntax). So if I were going with that second option, I'd probably call it `createUser` and call it without `new`. But I like prototypes and I like `new`, so I tend toward `class` syntax these days. – T.J. Crowder May 15 '19 at 08:39
3

Yes. You can use a getter to return a computed value, like so:

class User {
  constructor(name, dateOfBirth) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;
  }

  get age() {
    return new Date() - this.dateOfBirth;
  }
}

var driver = new User('Steve', new Date('12 December, 1990'));
console.log(driver.age);

More information here.

Tim VN
  • 1,183
  • 1
  • 7
  • 19
  • 1
    Thank you for this answer. Although this answer is correct and even uses the latest ES6 class, I have opted to choose T.J. Crowder's answer because it corresponds to the version of JS that I have used in my question. – puoygae May 15 '19 at 08:39