Scopes
Were going to start off really getting to know Scopes and Closures before diving into the more complex ideas of the parameter 'this' and prototypal inheritance. Finally we'll look at a number of different ways of writing classes and sub-classes.
Lexical Scope
This refers to regions in your source code where you can refer to by variable name without getting access errors.
Since functions create scope in JS, a simple programme with no functions has only one scope: the global scope.
Variable Access
A new Lexical Scope is created every time you create a Function definition. The function definition spans from the letter 'f' in functions all the way to the end curly brace '{'.
Between the two curly braces {}, different rules for access to variables apply - this is a new lexical scope.
Variable delared inside a function definition can only be accessed from inside that definition or from inside child scopes. They cannot be accessed from the parent scoope. So basically you can access a variable from inside the current scope and upwards into parent scopes, but not downwards into child scopes.
Scoping Limitations
JS allows you to declare a variable without using the var keyword. This actually creates the variable in the global scope, which is never what you would want inside a function's scope.
Note that not all {} in JS create scopes. Blocks like loops and conditionals use curly braces, but are not functions and therefore do not create new scopes.
Intro: Execution Contexts
When a program runs, it builds up a storage system for storing the variables and their values. These in-memory scope structures are called 'Execution Contexts'.
Execution Contexts vs. Lexical Scopes
Execution Scopes are built as the code runs, not as it is typed like lexical scopes.
In-memory scopes and in-memory objects
The in-memory execution context and in-memory scopes do not work in the same way and are in no way related and ne'er the twain shall meet, as they are kept completely seperate by the interpreter.
NB: The parentheses ()
after a function()
mean 'Invoke This Function Now'.
Closures
Any function should have access to all the varibles, from all the scopes, that surround it.
A Closure is any function, that somehow remains available, after all the outer surrounding scopes have returned.
Retaining Access to Functions
- Pass the function to setTimeout
return
the function to an outer function- assign the function to a global variable
Prediction Closure Output
Over the course of these videos a nice example of pushing functions onto an array is used, I will have to come back to this if I need to use Closures. Still, can't figure out what commone problem I have that this solves (of course, since I don't actually USE JavaScript for anything yet, that would explain it!)
Here's a python visualiser of the lesson, but it doesn't really show the execution contexts very well.
He says that when you are calling a function a lot and the parameters you are calling with are almost always the same, then that is probably a likely candidate for setting up access from outside the scope.
'this' Keyword
this
is NOT referring to:
- The function that it appears in.
var fn = function () { return this; };
- A new instance of the function it appears in.
- An object that happens to have that function as a property.
var obj = { fn: function () { return; } };
- An object literal that is the function's parent.
this
IS referring to:
The object to the left of the dot when it is being invoked.
obj.fn(a,b);That is, the '
obj
' in the above line (notice the function is being invoked).
Note that sometimes there won't be a dot because the object's method might be invoked with square bracket notation. e.g.
obj[fn](a,b);
WARNING: without an object to the left of the dot, this
will become global
and will usually be seen as window
in the chome debugger.
The dot is the method by which we pass in the binding for this
.
We can overwrite the dot binding to this by using the .call()
method and passing the object to be bound to in as the first parameter.
fn.call(obj, a, b);
In summary: the keyword 'this' let's us create one function object and use it as a method on a bunch of different objects. That is, you can write a method once and use on on loads of different objects. If you're really clever you can make sure that method only exists once in memory too...
Prototype Chains
A method for delegating lookups through a chain of objects. The prototype method lets you define an object with a built-in lookup to another object, instead of duplicating a bunch of methods in the second object.
Side note: they always go way deep into this idea of inheritance and prototyping, but it is something that has always been really obvious to me. What I do need to learn are the patterns and categories for the different types of prototyping.
Object.create()
, is how we specify an object's prototype, i.e. where to direct failed look-ups. e.g.
var obj1 = {color: "red"}; var obj2 = Object.create(obj1); obj2.color; // will evaluate to "red"
All objects eventually delegate to the Object Prototype. The Object Prototype provides the shared methods we can use on all objects like .toString()
, for example. Note that obj.toString()
has 'obj' to the left of the dot, this means it is passing 'obj' to be bound to the 'this' parameter of the Object Prototype.
Some objects, which are 'children' of the Object Prototype (like Array), will have their own methods with the same name as the Object Prototype. These methods will provide enhanced and more specific functionality to the parent's methods. To test for this, use the .hasOwnProperty()
method, which will return a Boolean value.
The object.constructor()
property is used to find out the function used to create the calling object, but usually this was is not defined and will look and find the top-level object-prototype's .constructor.
Object decorator pattern
Talked a lot about the refactoring code, when you recognise you are repeating code. The benefit's of code re-use etc. This seems obvious to me, but I understand why he needs to explain it. This all lead to refactoring code into higher level functions that will be more abstract, but more generalised and reusable.
A decorator function takes and object in as a parameter, does something to it (like add of set some properties) and returns the modified objects.
var animallike = (obj, legs) { obj.legs = legs; return obj; }
It is common to use adjectives when naming decorator functions. They will ofter be an objects name suffixed with 'like', as seen above with animallike.
When using the decorator pattern, you won't require the keyword 'this' when adding methods. He says it will be more efficient to refer to directly to the objects passed in... must look into this more if I find myself needing to use the decorator pattern. Speaking of which...
You'll find yourself using the decorator pattern when you want to add some functionality to an object.
Functional Classes
A class is a catergory of things that you'd like to build. Like Animals of Vehicles etc. The class name is, by convention, usually written Capitalised (ClassNamesLikeThis). A Class function differs from the decorator pattern in that instead of taking an object, enhancing it, and returning - it creates the class and returns it. A class function is also known as a Constructor.
var Car = (loc) { var obj = {}; obj.loc = loc; obj.move = function() { loc++; }; return obj; }
The class-type objects created by a invoking a constructor function are called Instances of the Class. Invoking a Class constructor is called Instantiating.
Creating plain Instances with the function class pattern will result in duplicate functions in memory, because each time an Instance is created it will create duplicates of any of the class's methods (obj.move
above).
To fix this, we will move the method outside the Class again.
var Car = (loc) { var obj = {}; obj.loc = loc; obj.move = move; return obj; } var move = function() { this.loc++; };Notice that we now have to use the keyword 'this' again as we are outside the Car function's scope. This is know as The Functional Shared Pattern.
There are still some areas that could benefit from optimsation in the pattern shown above, for one thing all of the methods will be named in two places: once in the Class definition (obj.move = move
) and once again in the function name (var move ...
). When there are multiple methods in a Class this will mean maintaining two lists, which is duplication. Secondly, the move
function is also a global variable again, which is never ideal.
It might be neater to store all of Car's methods in an object and encapsulate them into the Car class...
Car.methods = { move: function() { this.loc++ } };
Side Note: remember there are no classes in JavaScript, the patterns we are learning here are really Class emulations, but thinking of them as Classes has more value for now.
Prototypal Classes
To finish off the Functional Shared Pattern above and make it fully prototypal, we need to delgate failed look-ups for methods in the Car
object to the Car.method
's object.
var Car = (loc) { var obj = Object.create(Car.methods); obj.loc = loc; return obj; } Car.methods = { this.move = function() { loc++; } };This is not to be confused with the Psuedoclassical Pattern which is coming up shortly.
Since this pattern of creating an object to strore all of an objects's methods in an object is so common, JavaScript creates this object for us with every new object. But instead of calling it Object.methods
, they called it Object.prototype
. The refactore code now looks like:
var Car = (loc) { var obj = Object.create(Car.prototype); obj.loc = loc; return obj; } Car.prototype.move = function() { loc++; };The object literal that created the methods object can now be removed as it already exists for us, and we can add methods directly.
There's a final side note about prototype ambiguity that I noted as 'revisit' the first time, and I'm going to do the same now.
.prototype.constructor
All .prototype
objects that are attached to all objects, also come with one more property: constructor. This holds a referernce to the function that was used to create the Instance. So, Car.prototype.constructor
is Car
.
Psuedoclassical Pattern
Notice that the bolded lines would be expected to be repeated in every single Prototypal Class:
var Car = (loc) { var obj = Object.create(Car.prototype); obj.loc = loc; return obj; }So, JavaScript has a way that of doing these for us so we don't have to keep repeating them.
To alleviate repeating those lines, JavaScript will implicitly emulate them if we create the Instance of the Class using the special keyword 'new'. e.g.
var toyota = new Car();This is called invoking the function in Constructor Mode.
However, in Constructor Mode the actual lines that get added implicity, look more like these bolded below:
var Car = (loc) { this = Object.create(Car.prototype); obj.loc = loc; return this; }So the final psuedoclassical class would look like this:
var Car = (loc) { this.loc = loc; } Car.prototype.move = function() { loc++; };When instances are being created in Constructor mode with the keyword 'new'.
There is very little difference between the prototypal pattern and the psuedoclassical patter other than some sythactical variations, however: the some JavaScript engines have made further optimisations that suit the psuedoclassical pattern.
When deciding what properties will belong to a Class constructor function, it is good to ask "Which properties will define the instances as unique from each other?"
When deciding which properies belong to the prototype object, it is good to ask "Which properties and methods will be common to all instances?"
Superclass and subclasses (functional pattern)
Not much to see here really. Return if it ever comes up.
Superclasses, psuedoclassical style
To set unique properties from parameters on a psuedocalssical subclass and maintain the Superclasses assignment rules, we need to set it up like this:
var Car = function(loc) { this.loc = loc; }; var Van = function(loc) { Car.call(this, loc); };If we were to simply assign a new
this.loc = loc
in Van
, then that property would be an extra loc property that had nothing to do with its parent class. And changes to the parent class wouldn't filter down to the child classes. So we need to send the loc property to Car, and also send the Van's context ('this').
To set similar or shared methods from a child class to delegate failed lookup to the parent, we need to set it's prototype object to be created from the parent prototype.
Van.prototype = Object.create(Car.prototype);Although this is the recommended way of delegating failed method lookups to the parent, it is a fairly new language feature and most documentation that one will come across on the internet will show using the old and wrong method like this:
Van.prototype = new Car();
Because we overwrote the Van's prototype object with Object.create(Car.prototype), we also blitzed the prototype's .constructor property, which will now fall through to Car, which is wrong. To fix this, we can simple reassign it like so:
Van.prototype.constructor = Van;
And that's it!