Classes1 are essentially a blueprint for creating new instances of an object with predefined functions and variables. These instances can then store state related to that individual instance. Over the years, JavaScript has constantly come under attack for its lack of any real support for classes.
In Chapter 5, “Collections and Iterations,” we looked at objects in JavaScript. In each example we hand rolled a brand new object and gave that new object a set of functions and values. This works great for the occasional simple object, but what if we wanted to have a more complex data model? More importantly, what if we wanted to have more than one of those complex data models? That is typically where classes come in.
Fortunately, CoffeeScript steps up to the plate with full class support. So if JavaScript does not have any real support for classes, how does CoffeeScript solve the problem? The short answer is through the use of some clever scoping and the use of functions and objects. The long answer is, read the rest of this chapter.
Defining classes in CoffeeScript can be as simple and as straightforward as one line:
class Employee
(function() {
var Employee;
Employee = (function() {
function Employee() {}
return Employee;
})();
}).call(this);
With that, we have defined a simple new class called Employee
. We can instantiate new instances of that object like so:
class Employee
emp1 = new Employee()
emp2 = new Employee()
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee() {}
return Employee;
})();
emp1 = new Employee();
emp2 = new Employee();
}).call(this);
We call the new
keyword before the name of our class, and we get a brand-new instance of that object to do with as we like.
When creating new instances of objects with the new
keyword, it isn’t required to put the parentheses at the end, as we do in our examples, but I find it looks nice and makes the code a bit easier to read. Do whatever feels right to you.
When defining functions on our classes we follow the same rules, and syntax, that we would if we were defining a function on a simple object—because that is exactly what we are doing.
class Employee
dob: (year = 1976, month = 7, day = 24)->
new Date(year, month, day)
emp1 = new Employee()
console.log emp1.dob()
emp2 = new Employee()
console.log emp2.dob(1979, 3, 28)
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee() {}
Employee.prototype.dob = function(year, month, day) {
if (year == null) year = 1976;
if (month == null) month = 7;
if (day == null) day = 24;
return new Date(year, month, day);
};
return Employee;
})();
emp1 = new Employee();
console.log(emp1.dob());
emp2 = new Employee();
console.log(emp2.dob(1979, 3, 28));
}).call(this);
Tue, 24 Aug 1976 04:00:00 GMT
Sat, 28 Apr 1979 05:00:00 GMT
constructor
FunctionCoffeeScript lets us define a function called constructor
that will be called when we create a new instance of an object. The constructor
is like every other function we’ve talked about. The only thing special about the constructor
function is that it will be called automatically when we instantiate a new instance of an object without us having to call it explicitly.
I told a little white lie earlier when I said that the constructor
function gets called automatically when we create a new instance of an object. In reality, when we create a new instance of an object like this, new Employee()
, we are calling that constructor
function directly. It’s just renamed.
class Employee
constructor: ->
console.log "Instantiated a new Employee object"
dob: (year = 1976, month = 7, day = 24)->
new Date(year, month, day)
emp1 = new Employee()
console.log emp1.dob()
emp2 = new Employee()
console.log emp2.dob(1979, 3, 28)
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee() {
console.log("Instantiated a new Employee object");
}
Employee.prototype.dob = function(year, month, day) {
if (year == null) year = 1976;
if (month == null) month = 7;
if (day == null) day = 24;
return new Date(year, month, day);
};
return Employee;
})();
emp1 = new Employee();
console.log(emp1.dob());
emp2 = new Employee();
console.log(emp2.dob(1979, 3, 28));
}).call(this);
Instantiated a new Employee object
Tue, 24 Aug 1976 04:00:00 GMT
Instantiated a new Employee object
Sat, 28 Apr 1979 05:00:00 GMT
As you can see in our example, every time we create a new Employee
object, it will print a message out to the console to let us know that it’s been created.
As we’ll see in this chapter, the constructor
function can provide an easy way to quickly set up your new object with any custom data for that object.
At their heart, classes in CoffeeScript are just glorified objects that produce a lot of boilerplate JavaScript code to let them do the things they do. Because classes are just plain objects—granted, objects with a lot of window dressing—scope of variables, attributes, and functions behave the same as they do in regular objects.
Let’s investigate by taking our Employee
class again. Employees in real life have names, so let’s make sure our Employee
class can reflect it. When we create a new instance of the Employee
class, we want to have the name passed in and assigned to an attribute that is scoped to the instance of the new Employee
object.
class Employee
constructor: (name)->
@name = name
dob: (year = 1976, month = 7, day = 24)->
new Date(year, month, day)
emp1 = new Employee("Mark")
console.log emp1.name
console.log emp1.dob()
emp2 = new Employee("Rachel")
console.log emp2.name
console.log emp2.dob(1979, 3, 28)
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee(name) {
this.name = name;
}
Employee.prototype.dob = function(year, month, day) {
if (year == null) year = 1976;
if (month == null) month = 7;
if (day == null) day = 24;
return new Date(year, month, day);
};
return Employee;
})();
emp1 = new Employee("Mark");
console.log(emp1.name);
console.log(emp1.dob());
emp2 = new Employee("Rachel");
console.log(emp2.name);
console.log(emp2.dob(1979, 3, 28));
}).call(this);
Mark
Tue, 24 Aug 1976 04:00:00 GMT
Rachel
Sat, 28 Apr 1979 05:00:00 GMT
As we discussed earlier, the constructor
function is no different from any other function, so the same scope and definition rules apply. Knowing that we are now passing in an argument called name
, we set the name
argument onto an attribute on the object instance using @name = name
. Remember from Chapter 3, “Control Structures,” that the @
alias is equal to this.
With the attribute set on the object instance, we can then call it like any other attribute on the object.
There is an even easier and cleaner way to achieve the same goal, and it’s quite possibly one of my favorite features of CoffeeScript (and one I wish other languages would implement). We can trim our constructor
down by doing the following:
class Employee
constructor: (@name)->
dob: (year = 1976, month = 7, day = 24)->
new Date(year, month, day)
emp1 = new Employee("Mark")
console.log emp1.name
console.log emp1.dob()
emp2 = new Employee("Rachel")
console.log emp2.name
console.log emp2.dob(1979, 3, 28)
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee(name) {
this.name = name;
}
Employee.prototype.dob = function(year, month, day) {
if (year == null) year = 1976;
if (month == null) month = 7;
if (day == null) day = 24;
return new Date(year, month, day);
};
return Employee;
})();
emp1 = new Employee("Mark");
console.log(emp1.name);
console.log(emp1.dob());
emp2 = new Employee("Rachel");
console.log(emp2.name);
console.log(emp2.dob(1979, 3, 28));
}).call(this);
Mark
Tue, 24 Aug 1976 04:00:00 GMT
Rachel
Sat, 28 Apr 1979 05:00:00 GMT
By adding the @
operator in front of the definition of the name
argument, we tell CoffeeScript that we want it to generate JavaScript that will assign that argument to an attribute with the same name, in this case called name
.
We can also easily access that attribute in the other functions of the class. Let’s update the example again, this time to add a method that prints out the employee’s name and birth date:
class Employee
constructor: (@name)->
dob: (year = 1976, month = 7, day = 24)->
new Date(year, month, day)
printInfo: (year = 1976, month = 7, day = 24)->
console.log "Name: #{@name}"
console.log "DOB: #{@dob(year, month, day)}"
emp1 = new Employee("Mark")
emp1.printInfo(1976, 7, 24)
emp2 = new Employee("Rachel")
emp2.printInfo(1979, 3, 28)
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee(name) {
this.name = name;
}
Employee.prototype.dob = function(year, month, day) {
if (year == null) year = 1976;
if (month == null) month = 7;
if (day == null) day = 24;
return new Date(year, month, day);
};
Employee.prototype.printInfo = function(year, month, day) {
if (year == null) year = 1976;
if (month == null) month = 7;
if (day == null) day = 24;
console.log("Name: " + this.name);
return console.log("DOB: " + (this.dob(year, month, day)));
};
return Employee;
})();
emp1 = new Employee("Mark");
emp1.printInfo(1976, 7, 24);
emp2 = new Employee("Rachel");
emp2.printInfo(1979, 3, 28);
}).call(this);
Name: Mark
DOB: Tue Aug 24 1976 00:00:00 GMT-0400 (EDT)
Name: Rachel
DOB: Sat Apr 28 1979 00:00:00 GMT-0500 (EST)
I would be remiss if we left this section with the code looking like that. I don’t want to have to keep passing in the year, month, and day every time I want to print out the employee’s information. I want to pass a birth date into the constructor
function and then access it whenever I want. So let’s do that:
class Employee
constructor: (@name, @dob)->
printInfo: ->
console.log "Name: #{@name}"
console.log "DOB: #{@dob}"
emp1 = new Employee("Mark", new Date(1976, 7, 24))
emp1.printInfo()
emp2 = new Employee("Rachel", new Date(1979, 3, 28))
emp2.printInfo()
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee(name, dob) {
this.name = name;
this.dob = dob;
}
Employee.prototype.printInfo = function() {
console.log("Name: " + this.name);
return console.log("DOB: " + this.dob);
};
return Employee;
})();
emp1 = new Employee("Mark", new Date(1976, 7, 24));
emp1.printInfo();
emp2 = new Employee("Rachel", new Date(1979, 3, 28));
emp2.printInfo();
}).call(this);
Name: Mark
DOB: Tue Aug 24 1976 00:00:00 GMT-0400 (EDT)
Name: Rachel
DOB: Sat Apr 28 1979 00:00:00 GMT-0500 (EST)
That is certainly cleaner and a little more DRY.2 Therefore, when I see two arguments for a function I start asking the question, will there be more arguments? If the answer is yes, I reconsider how I’m defining my function; in this case I’m concerned about the constructor
function. What happens when we need to pass in other information, such as salary, department, manager, and so on? Let’s do a bit more refactoring:
class Employee
constructor: (@attributes)->
printInfo: ->
console.log "Name: #{@attributes.name}"
console.log "DOB: #{@attributes.dob}"
if @attributes.salary
console.log "Salary: #{@attributes.salary}"
else
console.log "Salary: Unknown"
emp1 = new Employee
name: "Mark"
dob: new Date(1976, 7, 24)
salary: "$1.00"
emp1.printInfo()
emp2 = new Employee
name: "Rachel"
dob: new Date(1979, 3, 28)
emp2.printInfo()
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee(attributes) {
this.attributes = attributes;
}
Employee.prototype.printInfo = function() {
console.log("Name: " + this.attributes.name);
console.log("DOB: " + this.attributes.dob);
if (this.attributes.salary) {
return console.log("Salary: " + this.attributes.salary);
} else {
return console.log("Salary: Unknown");
}
};
return Employee;
})();
emp1 = new Employee({
name: "Mark",
dob: new Date(1976, 7, 24),
salary: "$1.00"
});
emp1.printInfo();
emp2 = new Employee({
name: "Rachel",
dob: new Date(1979, 3, 28)
});
emp2.printInfo();
}).call(this);
Name: Mark
DOB: Tue Aug 24 1976 00:00:00 GMT-0400 (EDT)
Salary: $1.00
Name: Rachel
DOB: Sat Apr 28 1979 00:00:00 GMT-0500 (EST)
Salary: Unknown
Now we can pass any arguments we want into the Employee
class, and they’ll be available to us through the attributes
attribute on the object. That is definitely a lot nicer, and it certainly is more extensible down the line.
As you can see with the first employee, we passed in a third attribute, salary
, that we didn’t pass in for the second employee. We could pass in a hundred different attributes to our Employee
class now and our code doesn’t have to change.
One last thing before we leave this section. Some of you might have come up with what you think of as a great idea. What if you use the knowledge you gained in Chapter 5, “Collections and Iterations,” about looping through the attributes
argument and assigning each key/value pair directly to the object so you don’t have to keep calling @attributes
everywhere in the code?
Well, let’s see how you would do that, and we can also see why that could be a very bad idea:
class Employee
constructor: (@attributes)->
for key, value of @attributes
@[key] = value
printInfo: ->
console.log "Name: #{@name}"
console.log "DOB: #{@dob}"
if @salary
console.log "Salary: #{@salary}"
else
console.log "Salary: Unknown"
emp1 = new Employee
name: "Mark"
dob: new Date(1976, 7, 24)
salary: "$1.00"
emp1.printInfo()
emp2 = new Employee
name: "Rachel",
dob: new Date(1979, 3, 28)
printInfo: ->
console.log "I've hacked your code!"
emp2.printInfo()
(function() {
var Employee, emp1, emp2;
Employee = (function() {
function Employee(attributes) {
var key, value, _ref;
this.attributes = attributes;
_ref = this.attributes;
for (key in _ref) {
value = _ref[key];
this[key] = value;
}
}
Employee.prototype.printInfo = function() {
console.log("Name: " + this.name);
console.log("DOB: " + this.dob);
if (this.salary) {
return console.log("Salary: " + this.salary);
} else {
return console.log("Salary: Unknown");
}
};
return Employee;
})();
emp1 = new Employee({
name: "Mark",
dob: new Date(1976, 7, 24),
salary: "$1.00"
});
emp1.printInfo();
emp2 = new Employee({
name: "Rachel",
dob: new Date(1979, 3, 28),
printInfo: function() {
return console.log("I've hacked your code!");
}
});
emp2.printInfo();
}).call(this);
Name: Mark
DOB: Tue Aug 24 1976 00:00:00 GMT-0400 (EDT)
Salary: $1.00
I've hacked your code!
Uh-oh! We were easily able to override the printInfo
function when we passed in the list of attributes for our second employee. The code is definitely easier to read, but it’s also easier to hack, and who wants that? JavaScript is easy enough to modify on its own, so why should we make it even easier? With that said, let’s move on, pretending we didn’t just do that last refactor.
When writing object-oriented programs, developers often come across the need for inheritance.3 Inheritance lets us take a class, such as our Employee
class, and create a variant on that class.
In a business, everyone is an employee, but not everybody is a manager. So let’s define a Manager
class that inherits, or extends, from our Employee
class:
class Employee
constructor: (@attributes)->
printInfo: ->
console.log "Name: #{@attributes.name}"
console.log "DOB: #{@attributes.dob}"
console.log "Salary: #{@attributes.salary}"
class Manager extends Employee
employee = new Employee
name: "Mark"
dob: new Date(1976, 7, 24)
salary: 50000
employee.printInfo()
manager = new Manager
name: "Rachel"
dob: new Date(1979, 3, 28)
salary: 100000
manager.printInfo()
(function() {
var Employee, Manager, employee, manager,
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if
(__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() {
this.constructor = child; } ctor.prototype = parent.prototype; child.prototype =
new ctor; child.__super__ = parent.prototype; return child; };
Employee = (function() {
function Employee(attributes) {
this.attributes = attributes;
}
Employee.prototype.printInfo = function() {
console.log("Name: " + this.attributes.name);
console.log("DOB: " + this.attributes.dob);
return console.log("Salary: " + this.attributes.salary);
};
return Employee;
})();
Manager = (function(_super) {
__extends(Manager, _super);
function Manager() {
Manager.__super__.constructor.apply(this, arguments);
}
return Manager;
})(Employee);
employee = new Employee({
name: "Mark",
dob: new Date(1976, 7, 24),
salary: 50000
});
employee.printInfo();
manager = new Manager({
name: "Rachel",
dob: new Date(1979, 3, 28),
salary: 100000
});
manager.printInfo();
}).call(this);
Name: Mark
DOB: Tue Aug 24 1976 00:00:00 GMT-0400 (EDT)
Salary: 50000
Name: Rachel
DOB: Sat Apr 28 1979 00:00:00 GMT-0500 (EST)
Salary: 100000
In the real world we would probably use roles to define different types of employees, but for the sake of discussion let’s pretend that this is the best way to solve our problem of different types of employees.
The basic definition of our Manager
class was simple, class Manager
, but by using the keyword extends
and giving it the name of the class we wanted to extend, Employee
, we were able to gain all the functionality from the Employee
class in our Manager
class.
Let’s take it a step further and learn about how to override methods in subclasses. Let’s add a function, bonus
, to our Employee
class that returns 0
. Regular employees apparently don’t get bonuses. Managers, however, get a 10% bonus, so we want to make sure when we call the bonus
function on managers we get the right value.
class Employee
constructor: (@attributes)->
printInfo: ->
console.log "Name: #{@attributes.name}"
console.log "DOB: #{@attributes.dob}"
console.log "Salary: #{@attributes.salary}"
console.log "Bonus: #{@bonus()}"
bonus: ->
0
class Manager extends Employee
bonus: ->
@attributes.salary * .10
employee = new Employee
name: "Mark"
dob: new Date(1976, 7, 24)
salary: 50000
employee.printInfo()
manager = new Manager
name: "Rachel"
dob: new Date(1979, 3, 28)
salary: 100000
manager.printInfo()
(function() {
var Employee, Manager, employee, manager,
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if
(__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() {
this.constructor = child; } ctor.prototype = parent.prototype; child.prototype =
new ctor; child.__super__ = parent.prototype; return child; };
Employee = (function() {
function Employee(attributes) {
this.attributes = attributes;
}
Employee.prototype.printInfo = function() {
console.log("Name: " + this.attributes.name);
console.log("DOB: " + this.attributes.dob);
console.log("Salary: " + this.attributes.salary);
return console.log("Bonus: " + (this.bonus()));
};
Employee.prototype.bonus = function() {
return 0;
};
return Employee;
})();
Manager = (function(_super) {
__extends(Manager, _super);
function Manager() {
Manager.__super__.constructor.apply(this, arguments);
}
Manager.prototype.bonus = function() {
return this.attributes.salary * .10;
};
return Manager;
})(Employee);
employee = new Employee({
name: "Mark",
dob: new Date(1976, 7, 24),
salary: 50000
});
employee.printInfo();
manager = new Manager({
name: "Rachel",
dob: new Date(1979, 3, 28),
salary: 100000
});
manager.printInfo();
}).call(this);
Name: Mark
DOB: Tue Aug 24 1976 00:00:00 GMT-0400 (EDT)
Salary: 50000
Bonus: 0
Name: Rachel
DOB: Sat Apr 28 1979 00:00:00 GMT-0500 (EST)
Salary: 100000
Bonus: 10000
Overriding functions on the subclass is as simple as redefining the function again with new functionality. But what about if we want to call the original function and maybe add a little bit to it. Here’s an example.
Employees get really upset when they see their bonus amount as 0 in the printInfo
, so we’re going to take it out entirely, but we still want managers to see that information when we call printInfo
. We can do this using the super
keyword:
class Employee
constructor: (@attributes)->
printInfo: ->
console.log "Name: #{@attributes.name}"
console.log "DOB: #{@attributes.dob}"
console.log "Salary: #{@attributes.salary}"
bonus: ->
0
class Manager extends Employee
bonus: ->
@attributes.salary * .10
printInfo: ->
super
console.log "Bonus: #{@bonus()}"
employee = new Employee
name: "Mark"
dob: new Date(1976, 7, 24)
salary: 50000
employee.printInfo()
manager = new Manager
name: "Rachel"
dob: new Date(1979, 3, 28)
salary: 100000
manager.printInfo()
(function() {
var Employee, Manager, employee, manager,
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if
(__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() {
this.constructor = child; } ctor.prototype = parent.prototype; child.prototype =
new ctor; child.__super__ = parent.prototype; return child; };
Employee = (function() {
function Employee(attributes) {
this.attributes = attributes;
}
Employee.prototype.printInfo = function() {
console.log("Name: " + this.attributes.name);
console.log("DOB: " + this.attributes.dob);
return console.log("Salary: " + this.attributes.salary);
};
Employee.prototype.bonus = function() {
return 0;
};
return Employee;
})();
Manager = (function(_super) {
__extends(Manager, _super);
function Manager() {
Manager.__super__.constructor.apply(this, arguments);
}
Manager.prototype.bonus = function() {
return this.attributes.salary * .10;
};
Manager.prototype.printInfo = function() {
Manager.__super__.printInfo.apply(this, arguments);
return console.log("Bonus: " + (this.bonus()));
};
return Manager;
})(Employee);
employee = new Employee({
name: "Mark",
dob: new Date(1976, 7, 24),
salary: 50000
});
employee.printInfo();
manager = new Manager({
name: "Rachel",
dob: new Date(1979, 3, 28),
salary: 100000
});
manager.printInfo();
}).call(this);
Name: Mark
DOB: Tue Aug 24 1976 00:00:00 GMT-0400 (EDT)
Salary: 50000
Name: Rachel
DOB: Sat Apr 28 1979 00:00:00 GMT-0500 (EST)
Salary: 100000
Bonus: 10000
In the printInfo
function we defined in the Manager
class, we first called super
. When we call super
, that will call the original printInfo
function from the Employee
class with any arguments that might have come into the function. After we call super
we then print out the bonus information for the manager.
The call to super
can be called at any point in the function that is overriding it; it doesn’t have to be first (as in some languages). Subsequently, you don’t have to call the super
method at all.
When calling super
we do not need to explicitly pass in arguments. By default, any arguments that come into the function that is overriding super
will be passed to super
when called. You can, however, explicitly pass any arguments you want when calling super
. This can be useful if you want to augment or mutate the arguments before they go to the super
method.
Class-level functions are functions that don’t require an instance of the class in order to be called. These functions can be incredibly useful. One of the best uses is to provide a sort of namespacing for your functions. An example of this in JavaScript is Math.random()
. You don’t need to instantiate a new Math
object to get a random number. But by hanging the random
function off of the Math
class, you avoid the risk that there is another function called random
that might override your function.
In reality, Math
isn’t a class; it’s just a plain object that is used for namespacing. Occasionally I twist the truth a little bit to help get my point across.
You can also use class-level functions to do things that might affect multiple instances of a class, like search for them in a database.
We could write a couple of class-level functions on the Employee
class. Since we don’t have a full database at our disposal, we’ll just keep track of how many employee instances we create and report that number. To keep track of which employees we hire, we create a class-level function called hire
that will take the newly hired employees and add them to an array that will act as our impromptu database. We’ll also add a total
class-level function that will return the total number of employees we have in our faux database.
class Employee
constructor: ->
Employee.hire(@)
@hire: (employee) ->
@allEmployees ||= []
@allEmployees.push employee
@total: ->
console.log "There are #{@allEmployees.length} employees."
@allEmployees.length
new Employee()
new Employee()
new Employee()
Employee.total()
(function() {
var Employee;
Employee = (function() {
function Employee() {
Employee.hire(this);
}
Employee.hire = function(employee) {
this.allEmployees || (this.allEmployees = []);
return this.allEmployees.push(employee);
};
Employee.total = function() {
console.log("There are " + this.allEmployees.length + " employees.");
return this.allEmployees.length;
};
return Employee;
})();
new Employee();
new Employee();
new Employee();
Employee.total();
}).call(this);
There are 3 employees.
So how did we create those class-level functions? By prepending the function name with @
we are telling CoffeeScript that we want that function to be at the class level. In the JavaScript world this works because when we replace @
with this
. the this
context is that of the Employee
class, not an instance of that class.
The inside class-level functions scope is limited to other class-level functions and attributes.
I routinely create class definitions that are nothing but a bunch of class-level methods. This is great for building utility packages and to make sure that I have nicely scoped functions. And, if I ever want to, or need to, I can inherit from these classes and override certain functions for a particular need.
The use of super
is limited, however, when dealing with class-level functions and attributes. You can use super
when you want to call the original function that you have overridden in the subclass. However, and this is big, if the function you are calling using super
tries to make reference to any class-level attributes, you’ll get a big fat error.
Let’s take a look at what happens when we override the total
class-level function in the Manager
class and then try to call super
:
class Employee
constructor: ->
Employee.hire(@)
@hire: (employee) ->
@allEmployees ||= []
@allEmployees.push employee
@total: ->
console.log "There are #{@allEmployees.length} employees."
@allEmployees.length
class Manager extends Employee
@total: ->
console.log "There are 0 managers."
super
new Employee()
new Employee()
new Employee()
Manager.total()
(function() {
var Employee, Manager,
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if
(__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() {
this.constructor = child; } ctor.prototype = parent.prototype; child.prototype =
new ctor; child.__super__ = parent.prototype; return child; };
Employee = (function() {
function Employee() {
Employee.hire(this);
}
Employee.hire = function(employee) {
this.allEmployees || (this.allEmployees = []);
return this.allEmployees.push(employee);
};
Employee.total = function() {
console.log("There are " + this.allEmployees.length + " employees.");
return this.allEmployees.length;
};
return Employee;
})();
Manager = (function(_super) {
__extends(Manager, _super);
function Manager() {
Manager.__super__.constructor.apply(this, arguments);
}
Manager.total = function() {
console.log("There are 0 managers.");
return Manager.__super__.constructor.total.apply(this, arguments);
};
return Manager;
})(Employee);
new Employee();
new Employee();
new Employee();
Manager.total();
}).call(this);
There are 0 managers.
TypeError: Cannot read property 'length' of undefined
at Function.<anonymous> (.../classes/class_level_super.coffee:18:51)
at Function.total (.../classes/class_level_super.coffee:36:50)
at Object.<anonymous> (.../classes/class_level_super.coffee:49:11)
at Object.<anonymous> (.../classes/class_level_super.coffee:51:4)
at Module._compile (module.js:432:26)
at Object.run (/usr/local/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:68:25)
at /usr/local/lib/node_modules/coffee-script/lib/coffee-script/command.js:135:29
at /usr/local/lib/node_modules/coffee-script/lib/coffee-script/command.js:110:18
at [object Object].<anonymous> (fs.js:114:5)
at [object Object].emit (events.js:64:17)
As you can see, when we tried to call length
on the @allEmployees
attribute we got the following error:
TypeError: Cannot read property 'length' of undefined
The reason for this is straightforward, but it might take a minute for it to sink in. Because JavaScript doesn’t have true inheritance, CoffeeScript has to cheat and do some magic, as we talked about earlier, to give the illusion of classes and inheritance. Because of this, the subclass Manager
is a different object from the Employee
class, and because the attributes are being set on the Employee
class and not the Manager
class, the Manager
class doesn’t have access to them. I told you it was a bit to wrap your head around.
I find it best to try to avoid using super
at the class level. I also try to keep all my class-level functions self-contained so I don’t run into these sorts of issues.
In JavaScript, you can add functions and attributes to all instances of an object by adding those functions and attributes to the object’s prototype using the aptly named prototype
attribute.
In CoffeeScript we can do this using the ::
operator. Let’s add a size
function to all instances of array. We want the size
function to return the length of the array.
myArray = [1..10]
try
console.log myArray.size()
catch error
console.log error
Array::size = -> @length
console.log myArray.size()
myArray.push(11)
console.log myArray.size()
(function() {
var myArray;
myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
try {
console.log(myArray.size());
} catch (error) {
console.log(error);
}
Array.prototype.size = function() {
return this.length;
};
console.log(myArray.size());
myArray.push(11);
console.log(myArray.size());
}).call(this);
[TypeError: Object 1,2,3,4,5,6,7,8,9,10 has no method 'size']
10
11
The first time we try to call the size
function on our array we get an error because the function doesn’t exist. However, after we add the size
function to the Array
class’s prototype, it behaves just as we hoped it would the next couple of times we call it.
The ::
operator is there for convenience. You can still access the prototype
attribute directly, but I find that typing all those extra characters isn’t worth it.
JavaScript is an asynchronous4 or “evented” programming language. In non-asynchronous programming, each time a function is called execution of the rest of the program would be halted until the aforementioned function has returned. In JavaScript, this is not necessarily the case. It is quite common for the program to continue executing after calling a function, even though the function has yet to return. If it helps, think of this style of programming as “fire and forget;” the program fires off a call to a function and then forgets all about it. Let’s look at a quick example of how an asynchronous program runs.
fire = (msg, wait)->
setTimeout ->
console.log msg
, wait
fire("Hello", 3000)
fire("World", 1000)
(function() {
var fire;
fire = function(msg, wait) {
return setTimeout(function() {
return console.log(msg);
}, wait);
};
fire("Hello", 3000);
fire("World", 1000);
}).call(this);
World
Hello
As you can see, our program first said “World” and then “Hello.” In non-asynchronous programming we would have first seen the word “Hello” followed a few seconds later by the word “World.” Asynchronous programming can be incredibly powerful but can also be a bit cumbersome and confusing. Let’s look at how things can start to get a bit muddled up.
Let’s write a log
method. This method will log to the console that we are about to execute a callback function; then it will execute the callback; and finally, it will log to the console that we have executed the function.
class User
constructor: (@name) ->
sayHi: ->
console.log "Hello #{@name}"
bob = new User('bob')
mary = new User('mary')
log = (callback)->
console.log "about to execute callback..."
callback()
console.log "...executed callback"
log(bob.sayHi)
log(mary.sayHi)
(function() {
var User, bob, log, mary;
User = (function() {
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
return console.log("Hello " + this.name);
};
return User;
})();
bob = new User('bob'),
mary = new User('mary'),
log = function(callback) {
console.log("about to execute callback...");
callback();
return console.log("...executed callback");
};
log(bob.sayHi);
log(mary.sayHi);
}).call(this);
about to execute callback...
Hello undefined
...executed callback
about to execute callback...
Hello undefined
...executed callback
Well, I’m pretty sure that code was not supposed to say hello to undefined
. So what happened there? When the log
function called the callback function we passed in, that callback had lost the original context from which it came. The callback no longer had reference to the name
variable we set in our class. This type of problem is quite common in JavaScript, particularly when using libraries like jQuery when making AJAX requests or binding to events.
So how do we fix this? How do we give our callback back its context? The answer is to use =>
, also known as the fat arrow, instead of ->
when defining our sayHi
function in the User
class. Here is the same example, only this time I changed sayHi: ->
to sayHi: =>
. Let’s see what happens:
class User
constructor: (@name) ->
sayHi: =>
console.log "Hello #{@name}"
bob = new User('bob')
mary = new User('mary')
log = (callback)->
console.log "about to execute callback..."
callback()
console.log "...executed callback"
log(bob.sayHi)
log(mary.sayHi)
(function() {
var User, bob, log, mary,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
User = (function() {
function User(name) {
this.name = name;
this.sayHi = __bind(this.sayHi, this);
}
User.prototype.sayHi = function() {
return console.log("Hello " + this.name);
};
return User;
})();
bob = new User('bob'),
mary = new User('mary'),
log = function(callback) {
console.log("about to execute callback...");
callback();
return console.log("...executed callback");
};
log(bob.sayHi);
log(mary.sayHi);
}).call(this);
about to execute callback...
Hello bob
...executed callback
about to execute callback...
Hello mary
...executed callback
Amazing! One simple character did so much there. Let’s compare the JavaScript output of our unbound and bound examples to better understand what that one character did to our code:
(function() {
var User, bob, log, mary;
User = (function() {
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
return console.log("Hello " + this.name);
};
return User;
})();
bob = new User('bob'),
mary = new User('mary'),
log = function(callback) {
console.log("about to execute callback...");
callback();
return console.log("...executed callback");
};
log(bob.sayHi);
log(mary.sayHi);
}).call(this);
(function() {
var User, bob, log, mary,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
User = (function() {
function User(name) {
this.name = name;
this.sayHi = __bind(this.sayHi, this);
}
User.prototype.sayHi = function() {
return console.log("Hello " + this.name);
};
return User;
})();
bob = new User('bob'),
mary = new User('mary'),
log = function(callback) {
console.log("about to execute callback...");
callback();
return console.log("...executed callback");
};
log(bob.sayHi);
log(mary.sayHi);
}).call(this);
I will explain some of what is going on here, but if you don’t understand what the apply
function does, I suggest reading up on it in a JavaScript book. There are two differences between the two JavaScript files. The first is the existence of the __bind
function found in our =>
example. This function accepts two parameters, the first being the function that you would like to bind, and the second being the context to which you would like to bind the function. The __bind
will return a new function that will call the original function using apply
and passing in the context you provided.
The next difference is in the constructor for the User
class. We are redefining the sayHi
function by calling __bind
and passing in the original definition of sayHi
and the context of the class instance we are in.
If all that just confused you, don’t worry—you are not alone. Context and binding are very confusing subjects in JavaScript. If you don’t understand it, I recommend two things: First, get a good JavaScript book, and second, read “Understanding JavaScript Function Invocation and ‘this.’”5 Yehuda does a great job explaining it all in a couple-page blog post.
If you do understand the JavaScript that is going on here, you are probably smiling right now, knowing that you don’t have to deal with troublesome binding anymore. You can use =>
and your life will instantly be better. You’ll see this in action in Chapter 11, “Example: Todo List Part 2 (Client-side w/ jQuery).”
Well, wasn’t that fun? Classes in CoffeeScript are, for me at least, one of the biggest selling points of the language. I hope this chapter helped to sell you on them.
We covered a lot in this chapter. We looked at what a class is in CoffeeScript and how to define the most basic class possible.
Next, we looked at the “special” constructor
function and talked a lot about scope in our classes.
We talked about how to extend classes in CoffeeScript using the extends
keyword. We also learned how super
can help us extend the original functionality of a super
class’s function in a subclass.
After that, we talked in depth about class-level functions and prototype functions. We also learned about the trouble super
can cause at the class level if we’re not careful.
We ended the chapter by looking at the rather complex, but insanely powerful, concept of bound functions using =>
, also known as the fat arrow.
At this point you have learned pretty much all there is to learn about CoffeeScript. Congratulations! You are ready to leave the temple. This ends Part I of this book, “Core CoffeeScript.” Part II, “CoffeeScript in Practice,” will take all that we’ve learned from Part I and put it into practice using a few popular JavaScript libraries.
1. http://en.wikipedia.org/wiki/Class_(computer_programming)
2. http://en.wikipedia.org/wiki/DRY
3. http://en.wikipedia.org/wiki/Inheritance_(computer_science)
4. http://en.wikipedia.org/wiki/Asynchronous_I/O
5. http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/
3.145.35.194