CanJS - Construct

canjs

What is can.Construct?

Provides a way to easily use the power of prototypal inheritance without worrying about hooking up all the particulars yourself. Use can.Construct.extend to create an inheritable constructor function of your own.

var Animal = can.Construct.extend({
        legs: 4
    }, {
        init: function(sound) {
            this.sound = sound;
        },
        speak: function() {
            console.log(this.sound);
        }
    }
);

In the example below, Animal is a constructor function returned by can.Construct.extend. All instances of Animal will have a speak method, and the Animal constructor has a legs property. You can make instances of your object by calling your constructor function with the new keyword. When an object is created, the init method gets called (if you supplied one):

var panther = new Animal('growl');
panther.speak(); // "growl"
panther instanceof Animal; // true

can.Construct is a basic constructor helper used to create objects with shared properties.

var Person = can.Construct({
    init : function (name) {
        this.name = name;
    }
});

var curtis = new Person("Curtis");
curtis.name; // Curtis

var Alien = Person({
    phoneHome: function(){...}
})

It sets up the prototype chain so subclasses can be further extended and sub-classed. can.Model and can.Control inherit from it.

Constructor functions are used by JavaScript to create objects with shared properties. It's similar in concept to a class. Constructor functions can be created by using can.Construct. Constructs are used to create instantiable objects with shared properties and help make managing inheritance in JavaScript easier.

How can we create your own class or constructor function?

To create a Construct of your own, call can.Construct.extend with an object of static properties (which will be attached directly to the constructor object) along with an object of instance properties (which will be attached to the constructor's prototype):

var Todo = can.Construct.extend({}, {
    isSecret: function(){
        return false;
    },
    allowedToEdit: function() {
        return ! this.isSecret();
    }
});

var t = new Todo();
t.allowedToEdit(); // true

How many parameters can the extend function take?

The extend function can take upto 3 parameters. The signature for the extend function:

can.Construct.extend([name,] [staticProperties,] instanceProperties)

If all 3 parameters are specified, the first parameter must be a string, and it is used to create the necessary properties and objects that point from the window to the created constructor function:

can.Construct.extend("company.project.Constructor",{})

creates a company object on window if it does not find one, a project object on company if it does not find one, and it will set the Constructor property on the project object to point to the constructor function. Finally, it sets "company.project.Constructor" as fullName and "Constructor" as shortName.

How does can.Construct help with inheritance?

can.Construct automatically sets up the prototype chain so that Constructs are easy to extend (similar to sub classing). To extend one of your Constructs, call the constructor's extend method, passing it the same arguments that you would pass to can.Construct.extend:

// If only one argument is passed, they are considered prototype properties.
var PrivateTodo = Todo.extend({},{
    isSecret: function() {
        return true;
    }
});

var p = new PrivateTodo();
p.allowedToEdit(); // false

How can we create an instance of a class with CanJS?

To create an instance of a class, use the new operator:

var t = new Todo();

What happens when we create an instance of a class?

When a constructor function is called with new, can.Construct creates a new instance of that class. If you've supplied an prototype method called init, can.Construct will call init with this as the new instance and the arguments passed to the constructor:

var Todo = can.Construct.extend({
    init: function(owner) {
        this.owner = owner;
    },
    allowedToEdit: function() {
        return true;
    }
});

var t = new Todo("me");
t.owner; // 'me'

In other words, inside the init method, the this keyword refer to the new instance. Whatever parameters that was passed to the constructor function Todo() is passed to the init method. Perhaps we can use more than one parameters with the constructor function.

Inside our init method, how can we invoke the parent init method?

If you're extending a Construct, you probably want to make sure you call the base's init method inside the child's init:

var PrivateTodo = can.Construct.extend({
    init: function(owner, isShared) {
        can.Construct.prototype.init.apply(this, arguments);
        this.isShared = isShared;
    },
    allowedToEdit: function() {
        return this.owner === "me" || this.isShared;
    }
});

What is the purpose of the super plugin?

If you find yourself using inheritence a lot, checkout can.Construct's super plugin.

How can we access the constructor's static properties?

var Counter = can.Construct.extend({
        count: 0
    }, {
        init: function() {
            this.constructor.count++;
        }
    }
);

var childCounter = new Counter();
console.log(childCounter.constructor.count); // 1
console.log(Counter.count); // 1

In the above code, Counter is a class, any class. If we want to access the static count property from inside the class, use this.constructor.count. If we want to access the static count property, use an instance of the class, followed by .constructor.count.

var Example = can.Construct.extend(
    {
        staticCount: 0,
    },
    {
        protoCount: 0
    }
);
var example1 = new Example();
var example2 = new Example();
example1.constructor.staticCount = 2;
example1.protoCount = 2;
Example.staticCount; // returns 2
example2.constructor.staticCount; // returns 2
example2.protoCount; // returns 0

Notice how static properties are accessed from an instance via its constructor property or are accessed directly from the constructor function itself, as in Example.staticCount.

Remember that if you pass only one parameter to can.Construct, you define prototype properties. If you need a constructor function with only static properties, be sure to pass two parameters, with an empty object as the second, like so:

var ExampleStatic = can.Construct.extend(
    {
        staticCount: 4
    },
    {
    }
);

What is the purpose of the init method?

If a prototype init method is provided, init is called when a new Construct is created. The init method is where the bulk of your initialization code should go. A common thing to do in init is save the arguments passed into the constructor.

var Person = can.Construct.extend({
    init: function(first, last) {
        this.first = first;
        this.last  = last;
    }
});

var justin = new Person("Justin", "Meyer");
justin.first; // "Justin"
justin.last; // "Meyer"
var Programmer = Person.extend({
    init: function(first, last, language) {
        // call base's init
        Person.prototype.init.apply(this, arguments);
        // other initialization code
        this.language = language;
    },
    bio: function() {
        return "Hi! I'm "" + this.first + " " + this.last +
            " and I write " + this.language + ".";
    }
});

var brian = new Programmer("Brian", "Moschel", 'ECMAScript');
brian.bio(); // "Hi! I'm Brian Moschel and I write ECMAScript.";

What is the purpose of the setup function?

I am not sure what is the purpose of the setup function, but it returns an array or undefined. If an array is returned, the array's items are passed as arguments to the init method. Otherwise, the arguments to the constructor are passed to init and the return value of setup is discarded.

What is constructorExtend and how can I use it?

Toggles the behavior of a constructor function called with or without the new keyword to extend the constructor function or create a new instance.

var animal = Animal();
// vs
var animal = new Animal();

In the code above, the first line create an instance of the Animal class without using the new keyword, and the third line create an instance of the Animal class using the new keyword.

If constructorExtends is:

  • true - the constructor extends
  • false - a new instance of the constructor is created

This property defaults to false.

var Animal = can.Construct.extend({
        constructorExtends: true // the constructor extends
    },{
        sayHi: function() {
            console.log("hai!");
        }
    }
);

var Pony = Animal({
    gallop: function () {
        console.log("Galloping!!");
    }
}); // Pony is now a constructor function extended from Animal

var frank = new Animal(); // frank is a new instance of Animal
var gertrude = new Pony(); // gertrude is a new instance of Pony
gertrude.sayHi(); // "hai!" - sayHi is "inherited" from Animal
gertrude.gallop(); // "Galloping!!" - gallop is unique to instances of Pony

The default behavior is shown in the example below:

var Animal = can.Construct.extend({
        constructorExtends: false // the constructor does NOT extend
    },{
        sayHi: function() {
            console.log("hai!");
        }
    }
);

var pony = Animal(); // pony is a new instance of Animal
var frank = new Animal(); // frank is a new instance of Animal
pony.sayHi() // "hai!"
frank.sayHi() // "hai!"

By default to extend a constructor, you must use extend. When in doubt, use extend.

What is the purpose of extend?

Extends can.Construct, or constructor functions derived from can.Construct, to create a new constructor function.

var Animal = can.Construct.extend({
    sayHi: function(){
        console.log("hi")
    }
})

var animal = new Animal()
animal.sayHi();

What does CanJS do with the staticProperties object that we pass to the extend function?

The static properties are added the constructor function directly.

var Animal = can.Construct.extend(
    {
        findAll: function() {
            return can.ajax({url: "/animals"})
        }
    },
    {} // need to pass an empty instanceProperties object
); 
Animal.findAll().then(function(json){ ... })

In the above code, we pass 2 parameters to extend. The first parameter contains the static properties and methods, and in this case, this class does not need to have any instance property.

What does CanJS do with the instanceProperties object that we pass to the extend function?

These properties are added to the constructor's prototype.

var Animal = can.Construct.extend(
    {
        findAll: function() {
            return can.ajax({url: "/animals"});
        }
    },{
        init: function(name) {
            this.name = name;
        },
        sayHi: function() {
            console.log(this.name," says hai!");
        }
    }
);
var pony = new Animal("Gertrude");
pony.sayHi(); // "Gertrude says hai!"

How can a child class override static properties from the parent class through inheritance?

If you pass all three arguments to can.Construct, the second one will be attached directy to the constructor, allowing you to imitate static properties and functions. You can access these properties through the this.constructor property. Static properties can get overridden through inheritance just like instance properties.

var Animal = can.Construct.extend(
    {
        legs: 4
    }, {
        init: function(sound) {
            this.sound = sound;
        },
        speak: function() {
            console.log(this.sound);
        }
    }
);

var Snake = Animal.extend(
    {
        legs: 0
    }, {
        init: function() {
            this.sound = 'ssssss';
        },
        slither: function() {
            console.log('slithering...');
        }
    }
);

Animal.legs; // 4 -- Static properties can be access via the class' name without having to create an instance of the class.
Snake.legs; // 0
var dog = new Animal('woof');
var blackMamba = new Snake();
dog.speak(); // 'woof'
blackMamba.speak(); // 'ssssss'

In the example above, the child class redefine the static property 'legs', as well as the init function.

What is the purpose of fullName?

If you pass a name when creating a Construct, the fullName property will be set to the name you passed. The fullName consists of the namespace and the shortName.

can.Construct("MyApplication.MyConstructor",{},{});
MyApplication.MyConstructor.namespace // "MyApplication"
MyApplication.MyConstructor.shortName // "MyConstructor"
MyApplication.MyConstructor.fullName  // "MyApplication.MyConstructor"

What is the purpose of newInstance?

Returns an instance of can.Construct. This method can be overridden to return a cached instance. Creates a new instance of the constructor function. This method is useful for creating new instances with arbitrary parameters. Typically, however, you will simply want to call the constructor with the new operator.

// define and create the Person constructor
var Person = can.Construct.extend({
    init : function(first, middle, last) {
        this.first = first;
        this.middle = middle;
        this.last = last;
    }
});

// store a reference to the original newInstance function
var _newInstance = Person.newInstance;

// override Person's newInstance function
Person.newInstance = function() {

    // if cache does not exist make it an new object
    this.__cache = this.__cache || {};

    // id is a stingified version of the passed arguments
    var id = JSON.stringify(arguments);

    // look in the cache to see if the object already exists
    var cachedInst = this.__cache[id];
    if (cachedInst) {
        return cachedInst;
    }

    //otherwise call the original newInstance function and return a new instance of Person.
    var newInst = _newInstance.apply(this, arguments);
    this.__cache[id] = newInst;
    return newInst;
}

// create two instances with the same arguments
var justin = new Person('Justin', 'Barry', 'Meyer'),
brian = new Person('Justin', 'Barry', 'Meyer');
console.log(justin === brian); // true - both are references to the same instance

The above code creates a Person Construct and overrides newInstance to cache all instances of Person to prevent duplication. If the properties of a new Person match an existing one it will return a reference to the previously created object, otherwise it returns a new object entirely.

What is the purpose of the static setup method?

Perform initialization logic for a constructor function. The static setup method provides inheritable setup functionality for a Constructor function.

Group = can.Construct.extend(
    {
        setup: function(Construct, fullName, staticProps, protoProps){
            this.childGroups = [];
            if(Construct !== can.Construct){
                this.childGroups.push(Construct)
            }
            Construct.setup.apply(this, arguments)
        }
    },
    {
    }
)
var Flock = Group.extend(...)
Group.childGroups[0] //-> Flock

The above example creates a Group constructor function. Any constructor functions that inherit from Group will be added to Group.childGroups.

Parent = can.Construct.extend(
    {
        setup : function(base, fullName, staticProps, protoProps){
            this.base = base;
            // call base functionality
            can.Construct.setup.apply(this, arguments)
        }
    },
    {
    }
);

Parent.base; // can.Construct
Child = Parent({});
Child.base; // Parent

In the above code, the Parent class adds a reference to its base class to itself, and so do all the classes that inherit from it.

What are the parameters accepted by the static setup method?

(base, fullName, staticProps, protoProps)
  • base: The base constructor that is being inherited from.
  • fullName: The name of the new constructor.
  • staticProps: The static properties of the new constructor.
  • protoProps: The prototype properties of the new constructor.

The static setup method is called immediately after a constructor function is created and set to inherit from its base constructor. It is useful for setting up additional inheritance work.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License