Skip to content
On this page

Creating Objects

JavaScript sets a prototype for every created object, pointing to its prototype object.

When we access a property of an object using obj.xxx, the JavaScript engine first searches the current object for that property. If it doesn't find it, it looks in the prototype object, continuing up to Object.prototype. If still not found, it returns undefined.

For example, creating an Array object:

javascript
let arr = [1, 2, 3];

Its prototype chain is:

       null


┌─────────────────┐
│Object.prototype │
└─────────────────┘


┌─────────────────┐
│ Array.prototype │
└─────────────────┘


┌─────────────────┐
│       arr       │
└─────────────────┘

Array.prototype defines methods like indexOf() and shift(), allowing direct calls on all Array objects.

When we create a function:

javascript
function foo() {
    return 0;
}

The function also has a prototype chain:

        null


┌───────────────────┐
│ Object.prototype  │
└───────────────────┘


┌───────────────────┐
│Function.prototype │
└───────────────────┘


┌───────────────────┐
│        foo        │
└───────────────────┘

Function.prototype defines methods like apply(), so all functions can call apply().

Long prototype chains can slow down property access, so keep them manageable.

Constructor Functions

Besides creating objects directly with { ... }, JavaScript can use constructor functions. First, define a constructor function:

javascript
function Student(name) {
    this.name = name;
    this.hello = function () {
        alert('Hello, ' + this.name + '!');
    }
}

You might wonder, isn't this just a regular function?

Indeed, it is a regular function, but in JavaScript, it can be called with the new keyword to return an object:

javascript
let xiaoming = new Student('Ming');
xiaoming.name; // 'Ming'
xiaoming.hello(); // Hello, Ming!

Without new, it’s a regular function returning undefined. With new, it acts as a constructor, binding this to the newly created object and returning this by default.

The prototype chain of the new xiaoming is:

       null


┌─────────────────┐
│Object.prototype │
└─────────────────┘


┌─────────────────┐
│Student.prototype│
└─────────────────┘


┌─────────────────┐
│    xiaoming     │
└─────────────────┘

This means xiaoming's prototype points to the prototype of the Student function. If you create xiaohong and xiaojun, their prototypes will be the same as xiaoming's:

              null


       ┌─────────────────┐
       │Object.prototype │
       └─────────────────┘


       ┌─────────────────┐
       │Student.prototype│
       └─────────────────┘
         ▲      ▲      ▲
         │      │      │
┌─────────┐┌─────────┐┌─────────┐
│xiaoming ││xiaohong ││ xiaojun │
└─────────┘└─────────┘└─────────┘

Objects created with new Student() also inherit a constructor property from the prototype, pointing to the Student function itself:

javascript
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true

The red arrows in the diagram represent the prototype chain. The object pointed to by Student.prototype is the prototype of xiaoming and xiaohong, which also has a constructor property pointing to the Student function.

In addition, the Student function has a prototype property pointing to the prototype object of xiaoming and xiaohong, but these objects do not have a prototype property; you can view it using the non-standard __proto__.

Now we consider xiaoming and xiaohong to "inherit" from Student.

A small observation:

javascript
xiaoming.name; // 'Ming'
xiaohong.name; // 'Hong'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false

xiaoming and xiaohong have different names, which is correct. Otherwise, we couldn't differentiate them.

While both xiaoming and xiaohong have their own hello function, they are two distinct functions, even though they share the same name and code.

If we create many objects with new Student(), these objects' hello functions could share the same instance to save memory.

To enable shared methods, we can move the hello function to the shared prototype:

javascript
function Student(name) {
    this.name = name;
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};

Creating prototype-based JavaScript objects with new is that simple!

Forgetting to Use new

If a function intended as a constructor is called without new, what happens?

In strict mode, this.name = name will throw an error because this is undefined. In non-strict mode, this.name = name does not throw an error because this refers to window, unintentionally creating a global variable name and returning undefined, which is worse.

So, always remember to use new with constructor functions. To distinguish between regular and constructor functions, it’s convention to capitalize constructor names and lowercase regular function names, helping syntax checkers like jslint catch forgotten new.

Finally, we can write a createStudent() function to encapsulate the new operation. A common programming pattern looks like this:

javascript
function Student(props) {
    this.name = props.name || 'anonymous'; // Default to 'anonymous'
    this.grade = props.grade || 1; // Default to 1
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};

function createStudent(props) {
    return new Student(props || {});
}

This createStudent() function has several advantages: it doesn't require new, and it allows flexible parameters, which can be omitted or passed like this:

javascript
let xiaoming = createStudent({
    name: 'Ming'
});

xiaoming.grade; // 1

If there are many properties for the created object, you only need to pass the desired ones; the rest can have default values. Since parameters are an object, we don’t need to remember their order. If we happen to have an object from JSON, we can directly create xiaoming.

Exercise

Please define a Cat constructor function that gives all Cat objects a name property and shares a method say(), returning the string 'Hello, xxx!':

javascript
function Cat(name) {
    // TODO:
}

// Test:
let kitty = new Cat('Kitty');
let doraemon = new Cat('Doraemon');
if (kitty && kitty.name === 'Kitty'
    && kitty.say
    && typeof kitty.say === 'function'
    && kitty.say() === 'Hello, Kitty!'
    && kitty.say === doraemon.say
) {
    console.log('test passed!');
} else {
    console.log('test failed!');
}
Creating Objects has loaded