Press "Enter" to skip to content

Ember Classes and Inheritance

admin

rocketborder3rev

An excerpt from Developing an Ember.js Edge.

JavaScript is an object-oriented language, and features inheritance of properties and methods. Unlike Java, C#, Python, or Ruby, which all employ “classical” inheritance, JavaScript implements “prototypal” inheritance. The difference lies in where objects look to find property and method definitions.

When a method is called on an object with classical inheritance, that object looks to a class for the method’s definition. If the class does not provide the method, the object will look through a chain of superclasses for a fitting method. In prototypal systems, classes do not exist. Instead, an object will look for a method defined on itself, and if it cannot be found, look to its attached prototype. The prototype is just another object, which can in turn have its own prototype. Thus, a chain of object instances is traversed when looking for a method instead of classes.

In JavaScript, an object’s prototype is set as a property on it’s constructor. This constructor must be called with the new keyword to properly initialize the object:

0001:var ParentObjectConstructor = function(){};
0002:ParentObjectConstructor.prototype = {
0003:  foo: function() { return 'bar'; }
0004:};
0005:
0006:var MyObjectConstructor = function(){};
0007:MyConstructor.prototype = new ParentObjectConstructor();
0008:
0009:var myObject = new MyObjectConstructor();
0010:myObject.foo()  // => 'bar'

JavaScript’s prototypal inheritance has some limitations. There is no concept of calling “super”: MyObjectConstructor cannot have its own definition of foo that delegates to the parent. There are no mixins, only parent objects. For each parent, an object must be instantiated.

Ember.Object provides a more classical and flexible JavaScript object:

  • Ember.Object and its descendants behave like classes.
  • The init property becomes the object constructor.
  • Subclasses can be created by calling extend on the parent class.
  • Instances can be created with new MyClass() if you wish to pass arguments to init, or with MyClass.create({ newProp: 'foo' }); if you want to add properties at initialization time.
  • Available methods can be added or overridden on an existing class (aka “monkey patching”) by calling MyClass.reopen({ addedProp: 'foo' }).
  • Class methods can be added with MyClass.reopenClass({ classMethod: function(){} });.

Let’s explore these features. First, open Chrome. Then, navigate to the starter kit and open the JavaScript console.

Create an instance of an Ember.Object:

var myInstance = new Ember.Object();

Confirm that the variable is an Ember.Object:

myInstance instanceof Ember.Object;  // => true

Set a property on an Ember object:

myInstance.set('name', 'Tom Dale');

Get a property on an Ember object:

myInstance.get('name');  // => 'Tom Dale'

You can also define properties of the object upon creation:

0001:var fido = Ember.Object.create({ animal: 'dog' });
0002:fido.get('animal');  // => 'dog'

Now, define a class that inherits from Ember.Object: When calling extend, the properties you want to present on instances of that class (instance properties and methods) are passed as an object. Let’s try it out:

0001:var Animal = Ember.Object.extend();
0002:var Dog = Animal.extend({ sound: 'woof' });
0003:
0004:var spot = new Dog();
0005:spot.get('sound');  // => 'woof'
0006:
0007:var Beagle = Dog.extend({ sound: 'howl' });
0008:var snoopy = new Beagle();
0009:snoopy.get('sound');  // => 'howl'

Reopen a class and add a property. What do you expect to happen?

Beagle.reopen({ sound: 'hoooooowl' });

Existing instances are not affected:

snoopy.get('sound');  // => 'howl'

But new instances are affected:

0001:var rover = new Beagle();
0002:rover.get('sound');  // => 'hoooooowl'

The object of properties passed to extend is really just a mixin. You can create named mixins and pass them to extend in the same manner as an object.

0001:var Adoptable = Ember.Mixin.create({
0002:  hasOwner: function() {
0003:    return !!this.get('owner');
0004:  }.property('owner')
0005:});
0006:
0007:var GoldenRetriever = Dog.extend(Adoptable, {
0008:  sound: 'ruff'
0009:});
0010:
0011:var dog = new GoldenRetriever();
0012:dog.get('hasOwner');  // => false
0013:dog.set('owner', 'Troy');
0014:dog.get('hasOwner');  // => true
0015:dog.set('sound');  // => 'ruff'

Mixins are a familiar pattern to many developers using object-oriented programming. They patch a class with methods or properties without providing a class themselves. A mixin cannot be instantiated; it can only extend a class or instance. Applied mixin methods can call methods applied before themselves with _super, and in fact the object passed to extend( is treated just like a mixin.

Ember comes packed with some powerful OO programming features. The Ruby programming language is an obvious influence, but Ember is far from a being a clone of Ruby’s functionality. In ambitious applications and large codebases, you gain a quick appreciation for how Ember fundamentals help you compose objects and interfaces.

rocketborder.bottom