Runtime-Objects


JavaScript comes with a very simple form of object inheritance built right in called "prototype inheritance".  Although in many ways, prototype inheritance is well suited for the browser, it is also too primitive for most developers.  This is why SproutCore provides a higher-level form of inheritance similar to the class-based models you are probably familiar with from other languages.

 

Introducing SC.Object

 

In SproutCore, all classes inherit from a base class called SC.Object.  This class builds directly on JavaScript's prototype inheritance mechanism to allow you to define classes and subclasses, and to create instances of classes.  It also automatically includes the SC.Observable mixin, adding support for Key Value Observing and Key Value Coding, two of the most important features of the SproutCore Runtime.

 

Usually when you create objects in your SproutCore application, they will inherit directly or indirectly from SC.Object.  You can test this for yourself by using the built-in 'instanceof' method defined by JavaScript:

 

myObject instanceof SC.Object

-> true 

 

Most of the time you will use the methods defined by SC.Object to create new subclasses and to create new instances of objects.  You may also use some of its helper methods to verify inheritance.

 

Defining a Subclass

 

To create a subclass of SC.Object or any class that inherits of SC.Object, use the extend() method.  You can pass one or more property hashes to this method which will be copied, in order, onto the new subclass prototype object (the prototype object for a class defines the properties that will be available on instances of that class):

 

MyClass = SC.Object.extend({

  foo: "foo1",

 

  bar: function() {

    return "bar";

  }

});

 

MyClass.prototype.foo => "foo1"

 

You can pass both regular properties to extend as well as functions.  Any function you define in a hash will become a method on the object you define.  In the example above, bar() is a function while foo is a simple property value.

 

Defining Mixins

 

Since you can pass more than one hash to extend(), you can use this facility to easily define mixins - or common sets of properties - that will be shared among multiple classes.  To define a mixin, just place all the properties you want to define in the mixin into a hash.  Then pass that same hash as a parameter to extend() to multiple subclasses:

 

MyMixin = {

  foo: "mixin1"

};

 

MyClass1 = SC.Object.extend(MyMixin, {

  bar: function() { 

    return "bar";

  }

});

 

MyClass2 = SC.Object.extend(MyMixin, {

  bar: function() {

    return "bar2";

  }

});

 

MyClass1.prototype.foo => "mixin1"

MyClass2.prototype.foo => "mixin1"

 

Note that since properties are copied onto your subclass in order, you can also override properties defined on a mixin by simply defining the same property in a later hash:

 

MyClass3 = SC.Object.extend(MyMixin, {

  foo: "class3"

});

 

MyClass3.prototype.foo => "class3"

 

Overriding Methods

 

Sometimes when creating subclasses, you will want to override a method that was already defined with a new implementation.  JavaScript does not provide a straightforward way to do this.  Thankfully, with a little assist from the build tools, SproutCore makes overriding methods easy.

 

To override a method in a class, just define the method again in a subclass.  To invoke an old implementation in your new method, just call sc_super():

 

MyClassA = SC.Object.extend({

  myMethod: function() {

    console.log('myMethod1');

  }

});

 

MyClassB = MyClassA.extend({

  myMethod: function() {

    sc_super(); // call MyClassA.prototype.myMethod();

    console.log("myMethod2");

  }

});

 

MyClassB.create().myMethod();

=> myMethod1

=> myMethod2 

 

Note that sc_super() does not take any arguments.  It simply forwards any parameters passed to your parent method on.

 

Unlike most SproutCore code, sc_super() is not actually a JavaScript method.  When you use it in your code, it will actually be replaced with the following string:

 

arguments.callee.base.apply(this, arguments);

 

This "magic string" effectively looks up the current function (arguments.callee), gets the super implementation (base), then calls it.  You will see this magic string in your code when you debug it in the browser.  This is one of the few places where the build tools will alter your code for you.

 

If you are using Runtime or any other SproutCore framework without build tools, you cannot use sc_super().  Instead, you should use the magic string above directly to invoke super.

 

Creating Instances

 

Most class based languages have you define constructor functions in order to create objects.  The problem is that this can lead to a lot of code when you want to configure your objects before you use them.  Either you end up calling a bunch of different methods or you will end up creating a number of different constructors for an object.  Regardless, for JavaScript apps where code size matters, we want to be more direct.

 

Rather than have you create a bunch of constructor methods, SproutCore has you create new object instances the same way you create subclasses - by directly configuring the object.  To create a new object from an SC.Object class or subclass, just call create(), passing in one or more hashes with properties you want set on the new object:

 

MyClass = SC.Object.extend({

  foo: "foo1",

  bar: "bar1"

});

 

var obj = MyClass.create({

  bar: "bar2"

});

 

obj.get('foo') => "foo1"

obj.get('bar') => "bar2"

 

Notice that in the example code above, we are no longer looking at a "prototype" object.  This is the primary difference between extend() and create().  extend() creates a new subclass with the objects, ready to be initialized at any time.  create() actually creates a new object based on the class prototype and then initializes it.

 

Notice also that once you have an instance, you should always interact with its properties using the universal accessor methods get() and set() [explained later].  This applies to any property that does NOT begin with an underscore ('_').  Underscored properties in SproutCore are usually considered private properties and can be accessed directly.  Methods are always called directly; no accessor required.

 

Singletons

 

One of the coolest benefits of SproutCore's direct configuration method for creating objects is that you can easily build singleton objects with complex configurations without having to create a special subclass first.  Just like with extend() you can pass multiple hashes to create(), including mixins:

 

var obj = SC.Object.create(MyMixin, {

  bar: "bar"

});

 

obj.get('foo') => mixin1

obj.get('bar') => bar

 

Often times you will use this facility to create the controllers in an application, which are complex singletons.  This further simplifies your code.

 

Initializing Objects

 

As nice as this direct configuration API can be, sometimes you really do need to run some setup code when a new object is created.  You can do this easily by defining an init() method for your object.  Whenever a new object is created, init() is the first method called.  Normally init() will simply setup property observing and exit.  If you override it, your init code will be called as well:

 

MyClass = SC.Object.extend({

  init: function() {

    sc_super();

    console.log("inited!");

  }

});

 

MyClass.create();

=> "inited!"

 

Note that call to "sc_super()" included in init here.  This is how you invoke the parent implementation of a method when you override it in SC.Object.  It is very important that you always call sc_super() in your init method, when you define one.   If you don't, many important features will not function properly including get() and set().

 

Initializing Mixins

 

When you define a mixin, sometimes you will want to perform some standard setup when an object is created as well.  Since defining init() would simply be replaced by any init() defined later, SproutCore provides special support for the initMixin() method as well.  

 

initMixin() is what's called a concatenated property.  This means that the actual initMixin property is actually an array. When you define initMixin() on your mixin, instead of replacing any previous initMixin() methods, your method is simply added to the list.  

 

Whenever an object is instantiated, any initMixins() in this list will be called:

 

MyMixin1 = {

  initMixin: function() {

    console.log("mixin1");

  }

};

 

MyMixin2 = { 

  initMixin: function() {

    console.log("mixin2");

  }

};

 

MyClass = SC.Object.extend(MyMixin1, {

  init: function() {

    sc_super();

    console.log('init!');

  }

});

 

// create new object, adding mixin to instance

var obj = MyClass.create(MyMixin2);

=> init!

=> mixin1

=> mixin2;

 

Destroying Objects 

 

To destroy any object based on SC.Object, just call the destroy() method on it.  If you are designing a subclass and you need to do some cleanup when a object is destroyed, you can override this method as well.  Just be sure to call sc_super().

 

SC.Object's also have an "isDestroyed" property that you can use to determine if an object has been destroyed or is still active.  The default implementation of destroy() will set this property for you.

 

Note that since JavaScript is a garbage collected language, most developers do not regularly call destroy() on objects when they are finished with them.  You will probably not need to use this facility often, but always consult the documentation for classes to see if they need to be destroyed to do cleanup when finished.

 

Destroying Mixins

 

Just like you can initialize mixins with initMixin(), destroyMixin() will be called on your mixin whenever an object is destroyed as well.

 

Determining an Object Type

 

Once you have an object instance, you can always get the class for that instance by checking the "constructor" property:

 

MyClass = SC.Object.extend();

var obj = MyClass.create()

 

obj.constructor

=> MyClass

 

Runtime also provides a number of useful methods you can use to determine the object type:

 

 

Moving On

 

Learn about Copying, Freezing, and Comparing Objects »

or Back to Runtime Programming Guide »