We've seen that constructor functions can be used to initialize JavaScript objects in a consistent manner. The constructor can be used to ensure that an object is always initialized with a known set of properties and default values for those properties. But it's also possible to assign functions to object properties from within a constructor function.

function Neighbor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = function () {
        return this.firstName + " " + this.lastName;
    }
}

var friend = new Neighbor("Tom", "Smith");
var name = friend.fullName();

Assigning a function to the fullName property provides a convenient way to access a Neighbor's full name. However, using an anonymous function is an inefficient way to add this functionality. If I create another Neighbor instance, JavaScript will create another instance of this anonymous function.

var friend1 = new Neighbor("Tom", "Smith");
var name1 = friend1.fullName();

var friend2 = new Neighbor("Sally", "Jones");
var name2 = friend2.fullName();

The sample code runs as expected, but if we compare instances of the fullName property, we can see they do not reference the same function. Every time we execute our constructor function, JavaScript creates a new instance of the anonymous function and assigns it to the fullName property.

if (friend1.fullName !== friend2.fullName) {
    alert("The fullName properties are NOT identical.");
}

Creating one extra instance of this anonymous function may not be a big deal, but what if our object has a half-dozen functions and we have hundreds of Neighbor instances? Each instance needs to be created, which requires some amount of time and memory.

One solution would be to simply define a named function outside of our constructor function.

function Neighbor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = concatenateFullName;
}

function concatenateFullName() {
    return this.firstName + " " + this.lastName;
}

It may look a bit strange, but it does work. However, our function is no longer tightly coupled to the Neighbor object, which brings us to the JavaScript prototype function. All JavaScript objects have a prototype property that allows us to attach new properties, not just to an object instance, but to a specific object type. In this case, we can associate a fullName property with the Neighbor type.

function Neighbor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

Neighbor.prototype.fullName = function () {
    return this.firstName + " " + this.lastName;
}

Now, all objects created with the Neighbor constructor function will have an associated fullName property through Neighbor's prototype. If we create two Neighbor instances, they will share a common fullName function. When we reference friend.fullName, JavaScript will look for a fullName property on the friend object, but if it doesn't exist, it will also look for a fullName property on the prototype of the Neighborconstructor function that was used to create the object.

if (friend1.fullName === friend2.fullName) {
    alert("The fullName properties are identical.");
}

Of course, prototype properties don't have to reference functions. They can also be used to share values across object instances.

function Tank(level) {
    this.level = level;
}

function.prototype.alertLevel = 10;

function.prototype.isLevelSafe = function () {
    return this.level >= this.alertLevel;
}

var tank = new Tank(100);
var isSafe = tank.isLevelSafe(); // returns true
tank.level = 2;
isSafe = tank.isLevelSafe(); // returns false

Prototypes allow us to associate a single property instance with all instances of a particular object type, which gives us the ability to extend an object's functionality beyond what's simply provided within it's constructor function.