Comparing Object constructors

For some while now we have been creating new objects using the new keyword, then ES5 came along with Object.create(), then ES6 brought us classes, and finally we have a much easier solution.

The new keyword

Using the new keyword is how many people first learn about constructing objects in JavaScript.

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.getDisplayName = function () {
        return this.firstName + " " + this.lastName;
    };
}
var paul = new Person("Paul", "Wilkins");
console.log(paul.getDisplayName()); // Paul Wilkins

Then, the new keyword was found to be problematic because it disguised how prototypes work, and people from other languages didn’t really need to learn how JavaScript works when it came to the prototype.

About the most that they learned was that the getDisplayName() method could be acquired using inheritance over the prototype chain:

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}
Person.prototype.getDisplayName = function () {
    return this.firstName + " " + this.lastName;
};

Later on though when Person is inherited by Employee, you need to tell Employee that it’s prototype was Person, and then also to fix the constructor back to what it should have been.

function Employee(firstName, lastName, employeeId, hourlyRate) {
    this.employeeId = employeeId;
    this.hourlyRate = hourlyRate;
    return Person.call(this, firstName, lastName);
}
Employee.prototype = new Person();
Employee.prototype.constructor = Employee;

var paul = new Employee("Paul", "Wilkins", 1001, 120);
console.log(paul.getDisplayName()); // Paul Wilkins

Due these prototype and constructor issues, using the new keyword was all becoming quite a mess.

ES5 Object.create()

With ES5 came Object.create(), which was added by the request of Douglas Crockford to fix those prototype issues.

var person = {
    firstName: "",
    lastName: "",
    getDisplayName: function () {
        return this.firstName + " " + this.lastName;
    }
};
var employee = Object.create(person, {
    business: {value: "Disk Daemon Services"}
});
var paul = Object.create(employee);

The prototypes all worked well with Object.create(), but it came at the expense of how easy it was to create each object which required several statements to create an employee.

var paul = Object.create(employee);
paul.firstName = "Paul";
paul.lastName = "Wilkins";
paul.employeeId = 1001;
paul.hourlyWage = 120;

console.log(paul.getDisplayName()); // Paul Wilkins

It didn’t take long though for Crockford to stop using Object.create(), but it had nothing to do with the method itself. Instead, security investigations with adverts found that the this keyword along with other techniques are unsafe, and so under the ADSafe scheme the this keyword was banned.

ES6 Classes

Next came ES6 with the class syntax, which is more familiar to programmers coming from other languages and is widely said to be “just sugar” that implements what is already in JavaScript.

class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    getDisplayName() {
        return this.firstName + " " + this.lastName;
    }
}
class Employee extends Person {
    constructor(firstName, lastName, employeeId, hourlyWage) {
        super(firstName, lastName);
        this.employeeId = employeeId;
        this.hourlyWage = hourlyWage;
    }
}
var paul = new Employee("Paul", "Wilkins", 1001, 120);
console.log(paul.getDisplayName()); // Paul Wilkins

One of the problems with the class structure is that it hasn’t fixed any of the security dangers with the this keyword, and people using classes aren’t likely to learn about other potentially better aspects of JavaScript.

Simplifying object creation

Classes are a reasonable solution for memory conservation when you’re making hundreds of thousands of objects, but most of the time you won’t be, and there is still the danger of the this keyword.

We can do away with the this keyword entirely and use simple objects instead when creating objects.

function makePerson(spec) {
    function getDisplayName() {
        return spec.firstName + " " + spec.lastName;
    }
    return Object.freeze({
        getDisplayName
    });
}
var paul = makePerson(firstName: "Paul", lastName: "Wilkins");
console.log(paul.getDisplayName()); // Paul Wilkins

All functions inside of makePerson are private methods and can easily access the spec info.
The only strange thing here is Object.freeze(), where we use it to protect the public methods from being changed or replaced.

Separate data within the constructor is also easily dealt with, using a state object, and we now have better control over whether information can be accessed. For example, the firstName and lastName aren’t visible now.

function makeEmployee(spec) {
    let {employeeId, hourlyWage} = spec;
    let {getDisplayName} = makePerson(spec);

    function updateWage(wage) {
        hourlyWage = wage;
    }

    return Object.freeze({
        business: "Disk Daemon Services",
        getDisplayName,
        updateWage
    });
}
var paul = makeEmployee({
    firstName: "Paul",
    lastName: "Wilkins",
    employeeId: 1001,
    hourlyWage: 120
});
console.log(paul.getDisplayName()); // Paul Wilkins

This type of object construction also favors composition over inheritance, which is something has been recommended to us programmers now for many years.

Some good coverage about this classless type of Object construction is found in the following article: Arguably the best approach to create objects in JavaScript.

4 Likes

In addition to the “this” keyword, Crockford’s ADSafe also restricts access to the “arguments” array, it restricts access to “Date” and “Math.random”, and it even restricts usage of the subscript operator, like a[i]. So if your goal is to adhere to ADSafe, then you have to abandon a whole lot more than just “this”.

I’m not so sure of this. Both composition and inheritance are equally easy using either classes or factory functions.

[quote=“Jeff_Mott, post:2, topic:278263, full:true”]
In addition to the “this” keyword, Crockford’s ADSafe also restricts access to the “arguments” array, it restricts access to “Date” and “Math.random”, and it even restricts usage of the subscript operator, like a[i]. So if your goal is to adhere to ADSafe, then you have to abandon a whole lot more than just “this”.[/quote]

Thanks for the further info. a[i] is restricted because a property name might be there instead. I see that a[+i] is allowed though, as that forces it to be a numeric value.

The simplified object constructor (the last sets of code) don’t support inheritance, because there is no prototype work that allows it to occur. However, do you have a good example of how the simplified object constructor is used to achieve inheritance?

Hi and thanks for the interesting write, if you were so kind could you clarify the reason why ‘this’ is deemed unsafe, and what is exactly the definition of unsafe here? Many thanks

The ADSafe scheme is about providing verification that guest code is safe to include on your website, helping to prevent malicious or accidental damage.

With the this keyword, when a method is called as a function, the this keyword is bound to the global object, which has to be restricted.

It’s not normally an issue that most of us have to deal with, but there is a school of thought where when one technique results in problems, and another technique achieves the same thing without problems, it’s better to go with the latter.

As it says on the ADSafe page:

1 Like

Not all inheritance uses prototype chains. If instead we augment objects, then Crockford calls it parasitic inheritance. It would go like this (based off code in your linked article):

// car is the base type
var car = function(carSpec) {
  let {maker, year} = carSpec,
  ignite = function() {
    console.log('ignite.....');
  },
  getMaker = function() {
    return maker;
  },
  getAge = function() {
    return (new Date()).getFullYear() - year;
  };
  
  return {
    ignite,
    getMaker,
    getAge
  };
};

// sedan is the derived type
var sedan = function(carSpec) {
  let type = 'Sedan',
  {weels} = carSpec,
  carInfo = car(carSpec);

  // A sedan is a more specific type that incorporates structure and behavior from a more general type
  carInfo.drive = function() {
    console.log(`${carInfo.getMaker()} ${type} drive with ${weels} weels`);
  };
  
  return carInfo;
};

var honda = sedan({maker: 'Honda', year: 2008, weels: 4});

// A sedan IS-A car -- that is, any object that satisfies sedan's interface also satisfies car's interface
console.log(honda.getMaker());  // Honda
console.log(honda.getAge());   // 9

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.