Create a function that takes an object literal and create a class from that object's members

Viewed 132

My problem:

var Person = makeClass( // Make a class with 2 instance methods which are initialize and say 
    {
        initialize: function(name) {
            this.name = name;
        },
        say: function(message) {
            return this.name + " says: " + message;
        }
    }
);

In the documentation of the @lends tag of jsdoc, I found out they are using a helper function, which they say it can take an object literal and create a class from that object's members. In the example above, the makeClass function creates a Person class, that have 2 instance methods (or they also can be static methods) which are initialize and say. I wonder how did we can create that kind of function.

What i want to know:

I tried to recreate that kind of function, but it was really hard to me, I don't know is there any way to do it. So hopefully you guys can give me a way of recreate this makeClass function.
I also want to know if this kind of creating class way is strange or not, or you can think the kind of function like makeClass is useful or not.

The reason of why do I want to create that function:

If you ask me about the reason, well I want to know the "ingredients" inside of that of function in order to understand the @lends tag of JSDoc, I want to really understand the use cases of this @lends tag in JSDoc. But facing this kind of weird way of creating class (its strange to me, I always use ES6 class before) it really hard to me to understand the @lends tag.

1 Answers

To start, you might take a look through the MDN docs on classes. There you'll find a couple of important pieces of information:

Classes are in fact just "special functions"

And, paraphrasing a bit:

Just as you can define function expressions ... you can define class expressions

So, if class expressions can be created in the same way as function expressions, and classes are just "special functions", it might be easier to pause for a second and ask ourselves: How do we write a function that creates a function?

There's more than one way to go about it but hopefully this makes sense given your example from above:

const makeFunction = function(valToAdd) {
  return function(val) {
    return val + valToAdd;
  }
}

const addTwo = makeFunction(2);
addTwo(2)  // 4

And now, translating makeFunction into makeClass is hopefully a little more straightforward. The paradigm is the same but we'll have to do a little more work to create the instance methods:

const makeClass = function(obj) {
  // Anonymous class expression
  const dynamicClass = class {};
  
  // Define properties on our class
  for (const [name, value] of Object.entries(obj)) {
    // Notice we define properties on the prototype so instances of the class can access them
    Object.defineProperty(dynamicClass.prototype, name, {
      value,
      writable: true,
      configurable: true
    })
  }
  return dynamicClass;
}

// Create a Person class with methods `initialize` and `say`
const Person = makeClass({
  initialize: function(name) {
    this.name = name;
  },
  say: function(message) {
    return this.name + " says: " + message;
  }
})

// Create an instance
const person = new Person()

person.initialize("Sam")
person.say("Hello!") // Sam says: Hello!

And to answer some of your specific questions:

I also want to know if this kind of creating class way is strange or not, or you can think the kind of function like makeClass is useful or not.

I would consider this a strange way to create classes though there are some use cases where it could be useful. Generally speaking, this paradigm is known as metaprogramming and is typically used to consolidate shared functionality. The trade-off is that complexity can easily balloon and be confusing to others working with your code.

I want to ... understand the @lends tag of JSDoc.

Because of the way makeClass creates the instance methods (it essentially copies them), JSDoc doesn't understand what methods are available to the created class. So this should now make sense (from the JSDoc docs):

The @lends tag tells JSDoc that all the member names of that object literal are being "loaned" to a variable named Person.

The Person variable is our class and annotating the object literal with @lends tells JSDoc that we're "lending" the initialize and say members to it.

Related