I wrote this trying to get closures and function properties straight in my head, and it was useful for that. I'm not sure if it has any value for showing how JS's OO system works, but it might.
Can you guess what this prints?
[function(){ console.log( this )}][0]();
All JavaScript data is typed, and you can find out the type using typeof
:
> typeof 5
'number'
> typeof true
'boolean'
All values that aren't simple scalars ("primitives") are objects, and have a
typeof 'object'
, with one special edge-case: functions are objects, but
respond to typeof
as function
:
> typeof []
'object'
> typeof new Date()
'object'
> typeof( () => {} )
'function'
But because a function in JavaScript is also an object a function can have properties:
// An object with properties
let obj = {};
obj['foo'] = 'bar';
console.log( obj.foo );
// A function with properties
let fun = () => {};
fun['foo'] = 'bar';
console.log( fun.foo );
Note that this is distinct concept from a closure:
function counter() {
let x = 0;
// this function closes over x
return function() { return ++x }
}
let foo = counter();
console.log(foo()); // 1
console.log(foo()); // 2 -- function has closed over x and remembers its val
let bar = counter();
console.log(bar()); // 1 -- each new call to counter() creates a new closure...
console.log(foo()); // 3 -- ... without affecting the old one
// but note that x is _NOT_ a property of either counter() or the function
// returned by invoking it:
console.log( counter.x ); // undefined
console.log( foo.x ); // undefined
// and we can even set a different x as a property of both that has no effect
// on the closed-over variable x:
counter.x = 'new';
foo.x = 'now';
console.log( counter.x ); // "new"
console.log( foo.x ); // "now"
console.log( foo() ) // 4
JavaScript's this
keyword -- called inside a non-class-constructor function
-- returns the object from which the function was called:
// there's some special-casing around Object [global] that's dull and fiddly
// but that strict-mode nicely side-steps
"use strict";
function cat() { console.log( "cat: ", this ) }
function dog() { console.log( "dog: ", this ) }
cat.dog = dog;
cat(); // cat: undefined -- wasn't called "from" anything
dog(); // dog: undefined -- wasn't called "from" anything
// was called from `cat`, so `this` is `cat`
cat.dog(); // dog: [Function: cat] { dog: [Function: dog] }
// was called from inside a newly created array
[dog][0](); // dog: [ [Function: dog] ]
// `apply` allows us to set `this` to something else
dog.apply([1,2,3]); // dog: [ 1, 2, 3 ]
We can use this to start to bolt together things that look a bit like traditional OO objects:
const goes = function() { console.log( this.sound ) }
// Normal object with `sound` and `goes` properties
const dog = { sound: "woof", goes: goes }
const cat = { sound: "miao", goes: goes }
dog.goes(); // woof -- goes() is called with `this` set to `dog`
cat.goes(); // miao
Manually assigning a goes
property for each object we want to create is a
hassle, but JavaScript allows us to specify that an object inherits properties
from a parent object that's a prototype for it:
const goes = function() { console.log( this.sound ) }
const animal = { goes, alive: true }; // bare `goes` = shorthand for goes:goes
// dog doesn't have its own goes property
const dog = { sound: "woof" }
// but we tell property-resolution where else it can look
dog.__proto__ = animal;
const cat = { sound: "miao" }
cat.__proto__ = animal;
dog.goes(); // woof
cat.goes(); // miao
// note there's no distinction between a property or a function that's a
// property, so we can look up simple properties in the same way we can look
// up the "methods"
console.log( dog.alive ); // true
... but again, we've got some pretty hassle-ish code as we're manually
assigning the scary-looking and very definitely private-looking __proto__
for
each of our new objects. Let's start by manually writing a little factory for
these objects:
const goes = function() { console.log( this.sound ) }
const animal = { goes, alive: true };
function factory( sound ) {
const self = {}; // Creates a blank, plain JavaScript object
self.sound = sound; // add the `sound` attribute
self.__proto__ = animal; // Link this object to the animal object
return self;
}
const dog = factory( 'woof' );
const cat = factory( 'miao' );
dog.goes(); // woof
cat.goes(); // miao
factory()
here looks like it could be made pretty generic, but it's still got
some animal-specific logic in it -- it would be better if we could tie
instantiation effects like self.sound = sound
to animal
itself, and then
have a completely generic factory
. Let's add a _setup
function:
const goes = function() { console.log( this.sound ) }
const setupAnimal = function(sound) { this.sound = sound };
const animal = { _setup: setupAnimal, goes, alive: true };
function factory( obj, arg1 ) {
const self = {}; // Creates a blank, plain JavaScript object
self.__proto__ = obj; // Link to the parent function
// Run _setup(), but set `this` to be `self` first
obj._setup.apply(self, [arg1]);
return self;
}
const dog = factory( animal, 'woof' );
const cat = factory( animal, 'miao' );
dog.goes(); // woof
cat.goes(); // miao
The "magic" here is the use of apply()
, which allows us to alias this
inside setupAnimal
to be the newly created object.
We have built at this point all the pieces we really need of an OO system, and
if we were to set factory()
in the global object where everything can access
it, we're pretty much job done now. However, if we really want to show off how
'leet we are, well, this code is far too readable, and it's far too obvious
that this is "bolted on" OO.
Given that a function itself is an object, we could have our setupAnimal()
itself be the object that we're using as the base, and do away with having
defined it separately:
const animal = function( sound ) {
this.sound = sound;
this.goes = function() { console.log( this.sound ) };
this.alive = true;
}
function factory( obj, arg1 ) {
const self = {}; // Creates a blank, plain JavaScript object
self.__proto__ = obj; // Link to the parent function
// Run the object itself as the setup, on the newly created self
obj.apply( self, [arg1] );
return self;
}
const dog = factory( animal, 'woof' );
const cat = factory( animal, 'miao' );
dog.goes(); // woof
cat.goes(); // miao
You've probably guessed by now exactly where this is headed, and realized that
our factory()
function is a reimplementation of the new
keyword. In fact we
can just call new
already on this animal()
function directly to get the
same effect:
const animal = function( sound ) {
this.sound = sound;
this.goes = function() { console.log( this.sound ) };
this.alive = true;
}
const dog = new animal( 'woof' );
const cat = new animal( 'miao' );
dog.goes(); // woof
cat.goes(); // miao
Side-note: our factory()
function doesn't correctly set the object's
constructor
property, as I didn't want to muddy the waters. You could easily
add that to the definition of factory()
as self.constructor = obj
. See:
Object.prototype.constructor
NB: JavaScript programmers prefer to name their functions that they'll use as
objects with a capital letter at the front, for example Animal
not animal
.
Arrow functions are awesome because they allow us to pretend to be real
programmers writing
lambdas
and because they save a jaw-dropping 5 characters of source code. However, they
also close over this
based on the scope the Arrow function is defined
within.
const normal = function() { console.log( this ) };
const arrow = () => console.log( this );
// `this` becomes the object from which the function was called for a normal
[normal][0](); // [ [Function: foo] ]
// `this` is whatever it was when `arrow` was defined, which means it's the
// same `this` we have in scope right now...
[arrow][0](); // {}
this.foo = 'bar';
[arrow][0](); // { foo: 'bar' }
/* In the browser, `this` outside a function will be the global object, in
Node.js it'll be set to the same value as whatever `exports` is */
JS now has real syntax to deal with classes
John Resig's precursor slide-show to his Ninja book is a fun read and covers many of the same themes as this does.