Much of SproutCore is built on top of a few core technologies that comprise the "runtime environment". You can think of the runtime environment as the foundation that you will build your application on top of. The success story for SproutCore is that its foundation is so well designed, which makes it possible to build extremely large and complex applications while still achieving exceptional performance. Therefore, being able to understand the fundamentals of the runtime environment and how to use it properly is the key to any well-written SproutCore application and in this chapter we will attempt to do just that.
In this chapter we will cover the following:
As you are likely aware, JavaScript uses prototypes and there are no classes in the "class-based object-oriented programming" sense within JavaScript. But in SproutCore, we often refer to classes and objects (that is instances of a class) and the reason for this jargon is in part due to the familiarity of those terms to most developers and in part due to the manner in which we use SproutCore's root class, SC.Object
, to organize and enhance our code. Please don't get hung up on the semantics of a prototype versus a class, it really doesn't matter for our purposes.
What you should pay attention to though, is that SC.Object
is the base of every class within SproutCore and provides the class-like features, such as inheritance, instantiation, mixin support, key-value coding, binding and observing, computed properties, and other conveniences that we will use.
It's probably easier to explain these features through example, so let's first create a subclass of SC.Object
. For instance if we had an app, which we'll simply call MyApp, that contained different types of vehicles, we may wish to define a reusable class containing properties shared by all of the vehicles. To do so we simply subclass SC.Object
using the extend
method. For example:
MyApp.Vehicle = SC.Object.extend();
To define common properties for the MyApp.Vehicle
class, we pass a JavaScript object to the extend
method. Here's a nice professional example defining the MyApp.Vehicle
class along with its default properties:
/** @class A vehicle. */ MyApp.Vehicle = SC.Object.extend({ /** The make of the vehicle. @type String @default 'unknown' */ make: 'unknown', /** The model of the vehicle. @type String @default 'unknown' */ model: 'unknown', /** The type of the vehicle. @type String @default null */ type: null });
Once the MyApp.Vehicle
class is defined, we can further subclass it as we like. For example:
MyApp.Car = MyApp.Vehicle.extend({ /** The top speed of the car in miles per hour. @type Number @default 0 */ topSpeed: 0, /** MyApp.Vehicle override */ type: 'car', /** Helper method to convert the given speed in miles per hour to kilometers per hour. @param {Number} speedMph The speed in mph. @returns {Number} The speed in kph. */ calculateSpeedKph: function (speedMph) { return speedMph * 1.609344; } });
Once we've defined the classes we need for our application, we are able to create the instances of these classes for use. To create a new instance of SC.Object
or any of its subclasses, we use the create
method. For example, we could create an SC.Object
instance as shown:
var myObject = SC.Object.create();
We would create instances of SC.Object
subclasses in the same way as shown:
var aVehicle = MyApp.Vehicle.create(); var aCar = MyApp.Car.create(); var aView = SC.View.create();
As you would expect, instances inherit the properties of their parent classes and are unique from one another. For example:
aVehicle.get('type'), // returns null aCar.get('type'), // returns 'car' aVehicle.calculateSpeedKph; // returns undefined aCar.speedKph(60); // returns 96.56064
And just like with extend
, we can initialize and further configure our object on creation by passing in one or more JavaScript objects. For example:
var myObject = SC.Object.create({ a: 'foo', b: 'bar' }); myObject.get('a'), // returns 'foo' var fastCar = MyApp.Car.create({ topSpeed: 195 }); fastCar.get('topSpeed'), // returns 195
Did you notice in the previous example that we created an instance, myObject
, based directly off of SC.Object
? While we could have made a subclass and used it to create an instance, it was simpler just to pass the custom properties and methods to the create
method. Doing this is a useful way to create singleton objects that can exist for the lifetime of the running application, which is more efficient then defining a subclass and only ever instantiating one object from it.
Before we look at more features of SC.Object
, there are two important methods defined within SC.Object
that are used when dealing with the object's lifecycle. These two methods are init
and destroy
. As you might expect, the purpose of the methods is to set up and teardown an object. Therefore if you would like to perform extra initialization on your object as it is created, you would override the init
method and likewise, to perform additional cleanup on destruction, you would override the destroy
method.
For example:
GolfApp.Game = SC.Object.create({ /** Array of scores for each hole in order. */ scores: null, /** @private */ init: function () { // Always call the superclass method! sc_super(); // Any extra initialization can go here. For example, // Initialize the scores. this.scores = []; }, /** @private */ destroy: function () { // Always call the superclass method! sc_super(); // Any extra cleanup can go here. } });
There are two important things to recognize about the previous example. The first is that you need to be careful when defining non-primitive values for properties (that is Arrays and JavaScript objects). Notice that the value of the scores
property is defined initially as null
and later set to the array object in init
? If we had rather defined the property as an array, like scores: [], …
, every instance of GolfApp.Game
would share the same array object in memory. While the bad players might enjoy sharing their scores with the good players, it's not what we want to happen. Instead, when each instance is created we use init
to give that instance a unique scores array.
The second important thing to recognize is that we are able to override the SC.Object
class's original init
and destroy
methods and still call them using sc_super
. This is a special class-like behavior added by SproutCore that is not normally easy to accomplish in JavaScript, but with SC.Object
it's trivial.
The sc_super()
function is not actually a JavaScript function, but a preprocessor command for the build tools. This is one of the few places where the build tools will modify your code and replace sc_super()
with arguments.callee.base.apply(this, arguments)
, which looks up the current function on the super class and calls it using the arguments given.
Now that we've learned how to build a simple class hierarchy using SC.Object
, let's look at further approaches to defining properties that makes it easier to prevent code duplication and keep our hierarchy lightweight.
18.226.88.110