Transitioning from Java Classes to JavaScript Prototypes
JavaScript is a objected-oriented language with prototype-based inheritance. The lack of classes and language-level support for easily creating an inheritance chain causes a lot of fuss. Most programmers invest a great deal of time in first-year university trying to "get" object-oriented programming (OOP) in a class-based language like C++ or Java (I'll use Java as my class-based language example.) When they arrive to JavaScript their concepts of how an object-oriented language works no longer apply they learn that their big OOP investment was not complete. No one has ever agreed on what OOP is exactly so it should really be no surprise their education was incomplete. They need to invest more precious energy to "get" the JavaScript way of doing OOP. I think they feel ripped off that Java OOP doesn't "just work" in JavaScript and it seems anger is the most common reaction.
I too struggle(d) with learning how to do this business of prototype-based inheritance. There are very few books showing how to use prototype-based inheritance well. Prototype-based inheritance does appear in the famous Gang of Four Design Patterns book but it is not one of the more widely employed patterns in programs using languages with class-based inheritance.
First tactic: Avoidance
My very first post on this blog was about Class-Based Inheritance in JavaScript and I have made many posts since exploring various JavaScript techniques to achieve the kind of code reuse that is achieved by class-based inheritance in Java. In JavaScript, trying to manipulate the prototype chain of a constructor function is tricky. JavaScript just wants to have prototypes and objects based on those prototypes and it will hammer on you if you try to make deep inheritance chains.
Simulating single, class-based inheritance is possible but the very next thing most programmers want to simulate is super
(which ironically is a reserved word in JavaScript.) Simulating super
is a struggle that really can't be solved perfectly. Another disappointment.
When I started learning JavaScript, more experienced JavaScript programmers on Usenet's comp.lang.javascript repeatedly advised me to stop trying to simulate classes in JavaScript. "JavaScript doesn't have classes." "Stop thinking in terms of classes." "Don't do that." "You don't need classes." I didn't see how the last one was even possible. How could you not need classes? Every book on OOP talks about classes.
I struggled and struggled but simulating class-based inheritance in JavaScript really doesn't work and makes the code...well...very un-JavaScript-ish.
Second tactic: Acceptance (aka Submission)
I have given up trying to simulate classes and inheritance chains in JavaScript and my life as a JavaScript programmer has improved dramatically. I'll go as far to say that because JavaScript doesn't support Java's extends
-style inheritance and the resulting deep inheritance chains, JavaScript encourages a better style of of OOP. After all Extends is Evil. A quotation from the Extends is Evil article...
I once attended a Java user group meeting where James Gosling (Java's inventor) was the featured speaker. During the memorable Q&A session, someone asked him: "If you could do Java over again, what would you change?" "I'd leave out classes," he replied. After the laughter died down, he explained that the real problem wasn't classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.
The goal of Java's extends
, and inheritance in general, is code reuse (function reuse mainly.) We don't want to retype code we already typed once and we really don't want to maintain multiple copies of the same code. So code reuse is our main goal. It turns out that JavaScript is better equipped to reuse functions then most languages because JavaScript functions are first-class objects. You can grab a function-valued property from one object and attach it as a property of another object. If you grab all the properties of one object and attach them to another object you have, in essence, mixin-style multiple inheritance.
The remainder of this article shows an example using the observer pattern coded several ways in both Java and JavaScript to compare and contrast the various code-reuse strategies.
Feel free to cheat and scroll to the last three example of this article. The stuff from here to there is motivation for those final examples.
Example: Java inline implementation
As a base example, this code shows the observer pattern code directly inline. The Java inline implementation is clean. In this example, the code for the observer pattern cannot be shared with another model (eg. StockPriceModel.)
There is a little bulk for the type declarations and interfaces but that does enable the advantage of compile-time checks which is very important to some people especially those on large projects.
import java.util.ArrayList;
interface Subject {
public void addObserver(Observer observer);
public void notifyObservers();
}
interface Observer {
public void update();
}
// ---------------------------
class WeatherModel implements Subject {
private ArrayList<Observer> observers;
private double temperature;
public WeatherModel() {
observers = new ArrayList<Observer>();
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
public void setTemperature(double temp) {
temperature = temp;
notifyObservers();
}
public double getTemperature() {
return temperature;
}
}
// ---------------------------
class CurrentConditionsView implements Observer {
private WeatherModel model;
public CurrentConditionsView(WeatherModel m) {
model = m;
model.addObserver(this);
}
public void update() {
System.out.println(model.getTemperature());
}
}
// ---------------------------
public class ObservableOne {
public static void main(String[] args) {
WeatherModel victoriaWeather = new WeatherModel();
CurrentConditionsView victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
}
}
Example: JavaScript inline implementation
The JavaScript inline implementation is nice and clean also but the observer pattern code is not very easily shared with another model (eg. StockPriceModel.)
function WeatherModel() {
this.observers = [];
}
WeatherModel.prototype.addObserver = function(observer) {
this.observers.push(observer);
};
WeatherModel.prototype.notifyObservers = function() {
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
};
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
Example: Java implementation with extends
inheritance
In this example, we create a base class Observable
from which many model classes can inherit. This starts the classic extends
vs. implements
debate: if the WeatherModel
extends Observer
then WeatherModel
can't extend something else due to the limitation of single inheritance.
There is standard, feature-rich java.util.Observer
class that can be used as a base class like the Observer
is in this example. Even using the standard java.util.Observer
, the extends
vs. implements
debate remains a problem.
import java.util.ArrayList;
interface Subject {
public void addObserver(Observer observer);
public void notifyObservers();
}
interface Observer {
public void update();
}
// ---------------------------
class Observable implements Subject {
private ArrayList<Observer> observers;
public Observable() {
observers = new ArrayList<Observer>();
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// ---------------------------
class WeatherModel extends Observable {
private double temperature;
public WeatherModel() {}
public void setTemperature(double temp) {
temperature = temp;
notifyObservers();
}
public double getTemperature() {
return temperature;
}
}
// ---------------------------
class CurrentConditionsView implements Observer {
private WeatherModel model;
public CurrentConditionsView(WeatherModel m) {
model = m;
model.addObserver(this);
}
public void update() {
System.out.println(model.getTemperature());
}
}
// ---------------------------
public class ObservableTwo {
public static void main(String[] args) {
WeatherModel victoriaWeather = new WeatherModel();
CurrentConditionsView victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
}
}
Example: JavaScript class-based implementation with inheritance
If we simulate class-based inheritance in JavaScript, we can create a base class Observable
from which many model classes can inherit. The extends
vs. implements
problem remains.
function extends(sub, sup) {
function F() {}
F.prototype = sup.prototype;
sub.prototype = new F();
sub.prototype.constructor = sub;
sub.superConstructor = sup;
sub.superPrototype = sup.prototype;
};
// ---------------------------
function Observable() {
this.observers = [];
}
Observable.prototype.addObserver = function(observer) {
this.observers.push(observer);
};
Observable.prototype.notifyObservers = function() {
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
};
// ---------------------------
function WeatherModel() {
WeatherModel.superConstructor.call(this);
}
extends(WeatherModel, Observable);
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
alternate version without super
The above version shows just the beginning of what is ugly about trying to simulate super
in JavaScript. Here is a slightly different version that still uses simulated classes but doesn't require super
.
function extends(sub, sup) {
function F() {}
F.prototype = sup.prototype;
sub.prototype = new F();
sub.prototype.constructor = sub;
sub.superConstructor = sup;
sub.superPrototype = sup.prototype;
};
// ---------------------------
function Observable() {}
Observable.prototype.addObserver = function(observer) {
if (!this.observables) {
this.observers = [];
}
this.observers.push(observer);
};
Observable.prototype.notifyObservers = function() {
if (!this.observers) {
return;
}
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
};
// ---------------------------
function WeatherModel() {}
extends(WeatherModel, Observable);
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
Example: Java "has a" implementation
To switch to an implements
version of the Java code we can use a "has a" relationship where the WeatherModel
instances have an internal Observable
instance. The WeatherModel
instances then requires a couple very thin wrapper instance methods to implement the Subject
interface and provide access to the internal Observable
instance.
If more "has a" relationships are implemented as Subject
is in this example, the idea of "multiple inheritance" or "mixins" in Java becomes clear.
This solution is quite nice and it is conceptually clear. The only real problem is needing to write those thin wrapper instance methods. Suppose several interfaces are mixed in like this to many model classes. That could mean typing out many thin wrappers and we are trying to avoid code repetition.
import java.util.ArrayList;
interface Subject {
public void addObserver(Observer observer);
public void notifyObservers();
}
interface Observer {
public void update();
}
// ---------------------------
class Observable implements Subject {
private ArrayList<Observer> observers;
public Observable() {
observers = new ArrayList<Observer>();
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// ---------------------------
class WeatherModel implements Subject {
private double temperature;
private Observable observable;
public WeatherModel() {
// "has a" rather than "is a"
observable = new Observable();
}
// two thin wrappers for the Subject interface
public void addObserver(Observer observer) {
observable.addObserver(observer);
}
public void notifyObservers() {
observable.notifyObservers();
}
public void setTemperature(double temp) {
temperature = temp;
notifyObservers();
}
public double getTemperature() {
return temperature;
}
}
// ---------------------------
class CurrentConditionsView implements Observer {
private WeatherModel model;
public CurrentConditionsView(WeatherModel m) {
model = m;
model.addObserver(this);
}
public void update() {
System.out.println(model.getTemperature());
}
}
// ---------------------------
public class ObservableThree {
public static void main(String[] args) {
WeatherModel victoriaWeather = new WeatherModel();
CurrentConditionsView victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
}
}
Example: JavaScript "has a" implementation
This example follows the Java "has a" implementation above. It is very similar but, in my eyes, this JavaScript code looks absolutely horrible and exemplifies "not getting it" with regard to first-class functions and programming in JavaScript in general.
function Observable() {
this.observers = [];
}
Observable.prototype.addObserver = function(observer) {
this.observers.push(observer);
};
Observable.prototype.notifyObservers = function() {
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
};
// ---------------------------
function WeatherModel() {
this.observable = new Observable();
}
// two thin wrapper functions
WeatherModel.prototype.addObserver = function(observer) {
this.observable.addObserver(observer);
};
WeatherModel.prototype.notifyObservers = function() {
this.observable.notifyObservers();
};
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
Example: JavaScript explicit borrowing implementation
We want to avoid those thin wrapper functions of the previous example. In this example we simply borrow some functions from an observable
object. Although this example doesn't avoid the repetitive borrowing code, this is the key example of this article and indicates the idea of all following examples which make implementation easier.
In the Java "has a" example, the Observable
instance was wrapped inside the WeatherModel
and there was still a clear boundary around that wrapped object. This example shows what is more of a merging of some functions in one object into another object.
var observable = {
addObserver: function(observer) {
if (!this.observers) {
this.observers = [];
}
this.observers.push(observer);
},
notifyObservers: function() {
if (!this.observers) {
return;
}
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
}
};
// ---------------------------
function WeatherModel() {}
// borrow functions from the observable object
WeatherModel.prototype.addObserver = observable.addObserver;
WeatherModel.prototype.notifyObservers = observable.notifyObservers;
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
Example: JavaScript automated borrowing implementation
There are several ways to automate the borrowing in the previous example to avoid the repetitive code. This example is a passible way of implementing JavaScript mixins but following examples are even better.
var observable = {
addObserver: function(observer) {
if (!this.observers) {
this.observers = [];
}
this.observers.push(observer);
},
notifyObservers: function() {
if (!this.observers) {
return;
}
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
}
};
// ---------------------------
function WeatherModel() {}
// borrow all properties of the observable object
for (var prop in observable) {
WeatherModel.prototype[prop] = observable[prop];
}
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
Note that the above code uses a for-in loop to borrow all the properties from observable
. This process adds a prop
variable to the global scope. We can avoid this by using an automatically-evaluated, anonymous function around the borrowing.
(function() {
for (var prop in observable) {
WeatherModel.prototype[prop] = observable[prop];
}
})();
Example: JavaScript with a mixin
function
In the last example we are finally getting somewhere. We can borrow any number of function properties of one object and add those function to another object and we can do this with a couple lines of code. Now let's add a helper function for the borrowing.
function mixin(target, source) {
for (var p in source) {
target[p] = source[p];
}
}
// --------------------------
var observable = {
addObserver: function(observer) {
if (!this.observers) {
this.observers = [];
}
this.observers.push(observer);
},
notifyObservers: function() {
if (!this.observers) {
return;
}
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
}
};
// ---------------------------
function WeatherModel() {}
mixin(WeatherModel.prototype, observable);
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
The form of the mixin
function can become quite elaborate. For example, here is a mixin
function where the call specifies which functions are borrowed.
function mixin(target, source, props) {
if (props) {
if (props instanceof Array) {
for (var i=0; i<props.length; i++) {
target[props[i]] = source[props[i]];
}
}
else if (source[props]) {
target[props] = source[props];
}
}
else {
for (var prop in source) {
target[prop] = source[prop];
}
}
}
Example: JavaScript where module has the mixin
function
The previous example, with the global mixin
function offers great control and is a very good solution. For some reason the code in this example appeals more to me. In this case the module decides what it "exports".
var observable = {
addObserver: function(observer) {
if (!this.observers) {
this.observers = [];
}
this.observers.push(observer);
},
notifyObservers: function() {
if (!this.observers) {
return;
}
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
},
mixin: function(subject) {
for (var p in observable) {
if (p=='mixin') {
continue;
}
subject[p] = observable[p];
}
}
};
// ---------------------------
function WeatherModel() {}
observable.mixin(WeatherModel.prototype);
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
Example: My Favorite
The previous two examples are very good and useful solutions. Aesthetically this example is my favorite and thankfully the last one.
var observablize;
(function() {
var observable = {
addObserver: function(observer) {
if (!this.observers) {
this.observers = [];
}
this.observers.push(observer);
},
notifyObservers: function() {
if (!this.observers) {
return;
}
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
}
};
observablize = function (subject) {
for (var p in observable) {
subject[p] = observable[p];
}
}
})();
// ---------------------------
function WeatherModel() {}
observablize(WeatherModel.prototype);
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
this.notifyObservers();
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
model.addObserver(this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
Summary
If you made it all the way down here I'm pleasantly surprised. I hope this article helps at least a few people see how code reuse is actually easier in JavaScript than in class-based languages like Java.
Credits The idea for the WeatherModel and CurrentConditionsView examples came from Head First Design Patterns by Freeman, Freeman, Sierra and Bates.
Comments
Have something to write? Comment on this article.
Kris,
After investigating all sorts of JavaScript OOP libraries and techniques, I've been striving for a light, natural way to do OOP in JavaScript. The final examples in this post are just plain JavaScript as I think JavaScript prototypes were intended to be used. The function borrowing I'm using now, as shown in the last examples, doesn't necessarily even require any library or a single support function to accomplish the goal: a little for-in loop to borrow functions does the work. It looks like your type system falls on the heavier side of things. It's not for me but JavaScript's flexibility allows many approaches.
To allow the garbage collector to free the anonymous context containing the observable variable, the code could be rewritten like this:
var observablize =
(function(observable) {
return function (subject) {
for (var p in observable) {
subject[p] = observable[p];
}
}
})
({
addObserver: function(observer) {
if (!this.observers) {
this.observers = [];
}
this.observers.push(observer);
},
notifyObservers: function() {
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
}
});
Thank you for posting this, it a really good walk through of the class/prototype issue in Javascript. It's amazing what you can do when you start to really look into prototypical languages.
Great article. I don't know much about JavaScript but this article definitely open the gate for me to explore more. Thanks!
Using your functions i cannot borrow method implementations from more than one prototype, this works for me however
observablize = function (subject) {
for (var p in observable.prototype) {
subject.prototype[p] = observable.prototype[p];
}
}
Adam and Lou, thanks.
Ash, With borrowing you can borrow from as many objects as you want and move functions to any other object. The examples in the article are just examples. It is the idea of borrowing that is important. There are many possible permutation to discover.
The Prototype library has the cleanest "real" solution I've seen to the problem of method inheritance. For example, here's how MySubclass can extend and invoke aMethod in MySuperclass ...
// Create the superclass
MySuperclass = Class.create();
Class.extend(MySuperclass, {
aMethod: function (arg1, arg2) {
...
}
});
// Create the subclass
MySubclass = Class.create(MySuperclass)
Class.extend(MySubclass, {
// Override aMethod, with the super implementation passed
// as the first arg
aMethod: function ($super, arg1, arg2) {
... do some stuff ...
$super(arg1, arg2) // Invoke superclass
... do some more stuff
}
});
Thanks for this post. I'm in the process of crossing over from Java to Javascript. I am totally aware that I am writing complete sh** because I just haven't climbed up that learning curve yet. This post gave me a boost. Whoever inherits my code may yet be in luck! We'll see...
Thanks again, Ray
Great post, I've recently been working a lot with classes mainly working from behind a framework. This article explains alot of the behind work that I've been meaning to learn. Thanks!
Thanks for the article. It's a good explanation of prototype based inheritence.
A twist could be to use Function.prototype.call to bind the observable functions to the objects in question at runtime. I know this doesn't demonstrate prototypical inheritence, which is the point of the article, but hey...
var observable = {
addObserver: function(observer) {
if (!this.observers) {
this.observers = [];
}
this.observers.push(observer);
},
notifyObservers: function() {
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
}
};
// ---------------------------
function WeatherModel() {}
WeatherModel.prototype.setTemperature = function(temp) {
this.temp = temp;
observable.notifyObservers.call(this);
};
WeatherModel.prototype.getTemperature = function() {
return this.temp;
};
// ---------------------------
function CurrentConditionsView(model) {
this.model = model;
observable.addObserver.call(model, this);
}
CurrentConditionsView.prototype.update = function() {
alert(this.model.getTemperature());
};
// ---------------------------
var victoriaWeather = new WeatherModel();
var victoriaNews = new CurrentConditionsView(victoriaWeather);
victoriaWeather.setTemperature(15.3);
victoriaWeather.setTemperature(17.0);
victoriaWeather.setTemperature(14.7);
Cheers
Si
I've not kept up on the road map like I should but a recent post by Dustian Diaz led me to believe that Class definitions were headed this way in JavaScript 2.0.
http://www.dustindiaz.com/java-in-our-script/
So it may be wise to mention that these workarounds' shelf life is minimal and should be upgraded when/if browsers adopt the newer engine.
Until then this is as good as it gets. Thanks, Peter.
Lost,
Actually the point I was trying to make was these aren't workarounds but that JavaScript's OOP system is more capable than class-based systems!
I want to thank you for the nice "expanding textarea" hack you posted July 14th, 2006, but comments are closed on that. So I'm intruding here (though it turns out this looks like a fascinating post also). Anyhow, thanks!
Great article!
One comment is that rather than iterating over the members of observablize and copying them to the "class" prototype you could do something like:
observablize = function (subject) {
subject = new observable()
}
where subject is Class.prototype and observable is a function rather than the singleton.
Dave,
Thanks. I'm glad you enjoyed the article.
Your suggestion replaces the prototype object and so several mixins can't be used with such a system. I believe that your suggestion is more like a single inheritance scheme.
Great post.
Posts like this one are valuable to help people change their mindset - it demonstrates the thought process very clearly.
Great article Peter! We've been trying to get our heads around the idea that JavaScript should be written in a way that's best for JavaScript, not trying to make it look like Ruby.
Thanks!
-Ryan
A small addition:
notifyObservers: function() {
if (!this.observers) return;
for (var i=0; i<this.observers.length; i++) {
this.observers[i].update();
}
}
will make the code more robust.
Greetings, Leo
Leo,
Thanks for the comment that was an unfortunate omission. I've updated the article in the places where your comment apply.
Have something to write? Comment on this article.
I'm interested in your feedback on an alternate type system I've been brewing at home all year. I wrote a pretty dense article about it, but I tried to keep it amusing, and the code samples sparse.
http://cixar.com/~kris.kowal/blog/program/javascript/types.html
The code's in SVN, along with some other JavaScript padding.
https://cixar.com/tracs/javascript/browser/trunk/base.js