Wow -- javascript is really not Object Oriented

home

Javascript is so flexible that it doesn't have classes and doesn't need them. But lots of people really, really want to write classes. Javascript humors them, or tries to. It's pretty cool. It's easy enough for Java to fake a class with predefined elements. It's a little more work to hack in these things having actual types. And then it does backflips to add working member functions. All for idiots who can't learn a different programming style. It's pretty cool.

javascript is already objects

As we know, Javascript's main datatype is a generic object, with completely arbitrary member variables (which it calls properties). We often create object and properties all at once, Map-style:

var p1 = {"x":6, "y":9};
p1.x // 6
p1.y // 9

But this is only a shortcut. We can add more properties on-the-fly, merely by assigning to them. This adds an animal property to p1:

p1.animal="frog";
// we just created a new animal property for p1

Because of this, it's common to "construct" an object by starting with an empty and adding the properties we want, 1 at a time:

// this is the same as var p2={"x":7, "y":4}
var p2 = {}; // empty object
p2.x=7; // creates new property x
p2.y=4; // ditto

The rules for properties are so loose that attempting to use a non-existant one isn't even considered an error. var x=p1.notMadeYet; assigns undefined to x.

Properties are so whacky that we can even change the types, or remove them:

p2.x=8
p2.x="seven"; // changed p2.x from int to string

delete p2["x"]; // now p2.x is back to "undefined"

The craziest feature, to an OOP programmer, is that an object can so lose track of its fields that we need a way to find them. Object.keys(p2) gives the names of p2's fields as a list: ["x","y"].

Moving on to member functions, as we know, javascript variables can easily point to existing functions or anonymous ones, i.e: var f1=function(x) { return x*2; }. Javascript properties can do the same, somewhat faking member functions:

// p as 2 "member functions":
var p={};
p.chooseOne = Math.min;
p.changeNum = function(var n) { return n+1; }

p.chooseOne(5,8) // 5
p.changeNum(9) // 10

These are normal functions. p happens to find them through its properties, but they're not member functions. Even so, they look like them and later, with some work and new special rules, we can make them act that way.

Pretend classes

Javascript's first step towards pretending to have classes is a convention of "return an object" functions. This Point function is a quick way to make and fill an "x,y,z-having object":

function Point(x,y,z) {
  var p = {};
  p.x=x; p.y=y; p.z=z;
  return p;
}

var p1 = Point(3,8,1);

Point is a perfectly ordinary function that returns objects. But it feels like a class constructor, which is what we were going for.

The new hack

To class it up (pun intended), javascript added a new keyword. Clearly, it has nothing to do with allocating dynamic memory. Instead, adding new before a function call invisibly creates an empty object, locally assigned to this, and returns it for free. First a non-useful example using an ordinary function:

function honk(n) { console.log("HONK"+n); }

// normal call:
var h1 = honk(3);  // HONK3
// h1 is "undefined" since honk has no return value

// calling it with "new":
var h2 = new honk(6); // HONK6
// h2 is {}, an empty string, created and returned by "new"

Calling it with it without new, honk() runs as normal. Adding new created and auto-returned an empty object (which was accessable inside using this).

The intended use of new is to slightly simplify our old fake constructor functions. Here's a Dog-making function meant to be called with new:

// written to work with "new":
function Dog(nm, old) {
  // we get this={} for free. Fill it in:
  this.name=nm;
  if(old>=0) this.age=old;
  else this.age=0;
  // "return this" is automatic
}

var d1 = new Dog("Rex",4);  // runs the Dog function
d1.name  // Rex
d1.age  // 4

Now that really feels like a proper constructor! It turns out it also sort-of makes a Dog object since new does one more thing. It adds the property "constructor" to the object with the name of the function (Dog) which was called (actually "constructor:Dog" is added several levels deep -- Javascript objects have a whole secret tree of built-in properties). More-or-less, we know d1 is a Dog since it gets a property saying it's one.

Backing up, regular old var d2={"name":"Rex", "age":4, "cName":"Dog"} would have done almost the same thing. Also, new still creates regular objects: they can still add properties -- d1.cost=34 or remove them -- delete p1["name"].

A fun fact about new-wanting functions is that they can be run without it. They will uselessly create globals:

var d3 = Dog("Sparky",7);
// no "new", so no auto-return. d3 is undefined

// Ooops! globals name and age were created and assigned:
print(name); // "Sparky"
print(age);  // 70

We'll see why this happens, later.

"Member" functions, Part-I

Our first attempt at adding true member functions is expeanding the special this rule. Whenever a function is called like p.f1(5) javascript automatically sets local variable this to the caller. In practice that means we can write almost real member functions if we always use this.name and so on. Here we add an isPuppy function to our Dogs:

// Dog creation function:
function Dog(nm, old) {
  this.name=nm; this.age=old;
  
  this.isPuppy = function() { return this.age<=2; }
}

var d1=new Dog("Yeller", 4);

if( d1.isPuppy() ) {...}; // this will work (it's false)

isPuppy is still a normal, non-member function. But inside of it this was set to d1 and this.age got the correct age. It's basically a real member function.

To double-check, let's try it without using this:

function Dog(nm, old) {
  ...
  // just "age" instead of "this.age":
  this.isPuppy = function() { return age<=2; }
}

var d1=Dog("X",9999); // d1 is very old, not a puppy
var age=1; // isPuppy uses this age instead of the dog's age
d1.isPuppy() // true!! 1<=2

What happened was that, as usual, isPuppy run as a normal function. When it got to age<=2 it looked at the global age variable.

One more bit of weirdness is that we can have general-use member functions, shared by several classes. Here normal function ageString assumes it will have a this variable. Weasel and Owl are able to use it as a member function:

function ageString() {
  if(this.age<=3) return "young";
  else if(this.age<=12) return "adult";
  return "old"; 
}

function Weasel(nm, old) {
  this.name=nm; this.age=old;
  this.wordForAge=ageString; // link to global function
}

function Owl(theBreed, old) {
  this.breed=theBreed; this.age=old;
  this.ageDescript=ageString; // link to global function
}

For fun each class uses a different name, but that doesn't matter. w1.wordForAge() works for weasels and o1.ageDescript() works for owls. The ageString function doesn't care what type this is set to, as long as it has an age.

Something funny we saw earlier, calling simply ageString() is legal. It attempts to read from the global age. The special rule for this is that it's always set to something. Either the caller, or the global space if there was no caller. This is why calling our fake constructor -- d1=Dog("Rex",8) -- sets global name and age; this was set to global. I can't think of a good use for the "set to global" rule.

Member functions, part-II

So far we've been attaching member functions to objects -- each copy of Dog has it's own isPuppy member function. That's not a good way to do it and not how a real class does it. For real we want to store member functions with the class. Javascript has a system to do it that way. It has an unfixable problem, which we'll see later, but it's still cool to see the trick.

First some background: as we know all javascript objects can also have properties, including named functions. Our Dog "constructor" can be assigned properties such as Dog.n=5;. That's a seriously weird place to put a 5, but you can do it.

We'll use that trick to attach class member functions to the constructor. Roughly, we'll set Dog.isPuppy=function.... The actual rule is to first create a property named prototype and put all of your member funtions there.

A run-through of calling these new types of member functions: first you call d1.isPuppy(). There's no property with that name, so the system checks d1 for the magic constructor property. In d1's case it has one (added when we called new Dog()). The rules say we then jump to the function Dog and check for Dog.prototype.isPuppy. We then call that with this, as usual, set to d1. That's roughly how real members functions work, so pretty neat.

There are no special rules to create a new-style member function. Simply assign them:

// all Dogs get a "bark" mem-func:
Dog.prototype.bark = function() { console.log("arf"); }

// and one more using a member variable:
Dog.prototype.isGoodName =
  function() { return this.name.length<=3; };
  // the best dog names are 3 or fewer letters

d1.bark(); // arf
d1.isGoodName()  // false (Spot is 1 letter too long)

Notice how, once again this is required. These remain normal functions using the special this local to find the caller.

To be complete, prototype can also hold static variables. It's nothing special -- we've seen that any function can have properties -- but in our minds Dog represents a class, so size here feels like a class variable:

Dog.prototype.size=37;

d1.size // 37
Dog.size // undefined. Too bad
Dog.prototype.size // 37

It's seriously weird how a class static can only be referenced through an object, and not the class name like it should be. Oh, well.

this is a hack

We should be excited that we've got something that feels and works like a class. Except there's a serious bug: these member functions don't work if we call them through references; this won't be set correctly. An example:

var isYoung1 = d1.isPuppy; // pointer to our "member" function

if( isYoung1() ) {} // calling it through the pointer
// ERROR -- no such global variable "age"

It fails since this is set at the moment of the call. When we assign var isYoung=d1.isPuppy; we completely loose all knowledge of d1. The call isYoung() is a normal function call and reverts to setting this to "globals".

That problem may seem contrived, but other languages do this all the time. In javascript all sorts of frameworks might want you to pass in pointers to functions, often member functions. So we have a serious problem. And again, it's because this is a hack. Javascript doesn't support member functions which truely operate in the environment of the object which called them.

A very strange hack

This won't help us but it's fun to play with. Javascript alows you to assign this manually, using the pre-made call function. The input is whatever object you want for this (followed by the regular inputs). In this silly example we use d1 to find a function, but then call it on d2:

d1.isPuppy.call(d2);
// calls d1.isPuppy, but with d2 as "this"
// tells us whether d2 is a puppy

var isYoung=d1.isPuppy;
isYoung.call(d1)  // this works! Manually set "this" to d1

For fun (and as an example of sending additional inputs through call) we can mimic the new command using call:

// Abusing a "constructor" function:
var p={}; // an empty
Point.call(p, 3,8,10);  // assigns 3,8,10 into this=p

Recall that Point has lines like this.x=x. We hand-set this to p, so it works! Notice how we didn't use new here. It would be an error since Point.call isn't a function, much less a constructor.

The point of this section is that this is merely another input to the function, passed in a non-standard way. It has none of the specialness it does in a true member function.

Solution 1: locking in the initial object

We'll need a quick review for this one. Like most fancy modern languages, Javascript can "lock in" context variables into a created function. Here makeAdder is a standard curried "create a 1-input function to add locked-in x" function:

// a function which creates and returns another function:
function makeAdder(addMe) { return function(n) { return n+addMe; } }

var a5=partialAdd(5); // a5 is a 1-input function that adds 5
var n1=a5(3); // 8

var a34 = partialAdd(34); // a34 is also a function, adding 34
a5(50)  // 55  both have their own copy of addMe
a34(50) // 84

Two things need to happen for this to work: we need to return a freshly created function (which makeAdder does), and it needs to use a temporary "outside" variable (addMe in this case). When those happen, the new function "captures" the current value of addMe. It's locked in as 5 in a5 and as 34 in a34. This isn't something javascript added for classes -- it's a standard advanced language feature.

Our member functions will use this to lock-in the value of this. Our constructors will declare a local equal to this and then use it in every member function:

function Dog(nm, old) {
  var self=this; // captures "this"
  this.name=nm; this.age=old;

  // using "self" instead of "this":
  this.isPuppy = function() { return self.age<=2; }
}

self isn't defined anywhere in isPuppy -- it's an outside variable, which means it's locked to the currect Dog in this copy. When we create another Dog we'll get a new copy is isPuppy locked to the new Dog.

To be sure we've got it, Let's make one that does more and has an input. Instead of self we'll use myDog:

function Dog {
  var myDog=this;
  ...

  this.ageMe = function(years) { myDog.age+=years; }
}

d1.ageMe(2); // d1 age increased by 2
var dogOp = d1.ageMe;

dogOp(-1);  // d1 gets 1 year younger

This trick won't work with the prototype method. It relies on each object having a personal copy of the function, each with its own personal locked-in value for self.

Solution #2: bind

This method also locks in this, but using a new command: bind. It's essentially a shorter way of doing the previous trick. An example:

function Cat(nm, old) {
  this.name=nm;
  this.age=old;

  var tempOlderThan = function(c2) { return this.age>c2.age; }

  // this is it:
  this.olderThan = tempOlderThan.bind(this);
}

The last line gives us a new function, running tempOlderThan with this locked to the currect Cat.

We can also do it without an intermediary:

  this.olderThan = (function(c2) { return this.age>c2.age; }).bind(this);

The convention is to double-assign. No reason except people think it looks cool:

  // we will override this one:
  this.olderThan f1=function(c2) { return this.age>c2.age; }

  this.olderThan = this.olderThan.bind(this);
  //        begins and ends with "this"!

bind is sort of a silly command. Javascript users already fixed things using the self trick, then javascript central decided to add bind, which does the same thing in fewer keystrokes. Even so, bind is now the official way of creating working member functions.

For fun, bind can also lock-in parameters, from the left. We can us it to make our "add-N" functions:

function add(a,b) { return a+b; }
// lock-in and remove input "a" with 5 then 34:
var add5=add.bind(null,5); // null is a dummy value for "this"
var add34=add.bind(null,34)

var n=add5(8); // 12
add34(4) // 38

The result is a function with the first few parameters removed. add5 and add34 take only b as their input, with a locked-in by the bind.

The class shortcut

Javascript now allows you to use a class syntax to create the stuff we've seen before. "constructor" creates the same old construction function from before. Notice how we still need to bind this:

class Goat {
  constructor(nm, old) {
    this.name=nm;
    this.age=old;
    this.sayName = this.sayName.bind(this); 
  }

  sayName() { console.log(this.name); }
}

var g1 = new Goat("Billy",7);

I don't even know what that funny syntax is for the base sayName -- something they made up just for this. It appears to only be usable to assign to in the constructor.

Writing it this way has 1 bonus: leaving out new (as in var g1=Goat();) is now an error (in the function-style method it's still junk, but not an error).

I suppose this is for people who won't be happy until they can declare a class using familiar syntax. As of this writing, javascript is adding more hacks to make it look more class-like.

End

To sum up, we've got 2 similar ways of making a pretend class: a "constructor" function, or class-style. You'll see a mix. In fact, the React.js framework has you use both, using "class" as a special signal.

A funny thing is how the first this-setting trick (where d1.myFunc() sets this) is outdated. Our new tricks manually fix it in place. Attempts to make javascript feel like it has classes were so frenzied they step on each other.

I have to go with my first impression here. Python gets by just fine with purely informal interfaces and malleable objects. But a bunch of idiots who learned Classulla-2 in Jr. High got the ear of the javascript board and made them crap it up with this stuff.

 

 

 

Comments. or email adminATtaxesforcatses.com