Introduction
Here I want to collect main concepts extracted from the book 'Javascript: the Good Parts' by Douglas Crockford to have and to give to others, a quick reference. The book is really well written, simple and smooth, and offers plenty of great 'best practices'.
Object Literals
var flight = {
airline: "Oceanic",
number: 815,
departure: {
IATA: "SYD",
time: "2004-09-22 14:55",
city: "Sydney"
},
arrival: {
IATA: "LAX",
time: "2004-09-23 10:42",
city: "Los Angeles"
}
};
Retrieve Objects Values
Two ways to retrieve values: using []
or using the .
.
flight["number"]
flight.number
have the same result.
We can generate a default value using ||
.
var status = flight.status || "unknown";
Attempting to retrive a value from a nonexixting property generate an undefined
value, and attempting
to retrive a value from undefined
will generate a TypeError
exception.
flight.equipment //undefined
flight.equipment.model //TypeError
flight.equipment && flight.equipment.model //to avoid exception..
Updating Objects Values
Using assignment we can update a property's value. If the property already exists the value will be replaced, if the property doesn't exist the object will be augmented.
flight["number"] = 719; // update
flight.status = 'overdue'; // object augmented
References
Objects are passed around by reference. They are never copied. That's it.
Prototype
Every object is linked to a prototype object from which it can inherit properties: Object.prototype
.
When you make a new object, you can select the object that should be its prototype.
Let's add a create()
method to Object
. This method creates a new object that uses an old object as its prototype.
Object.prototype.create = function(o) {
if (typeof Object.create !== 'function') {
Object.create = function(o) {
var F = function() {};
F.prototype = o;
return new F();
};
}
};
var another_flight = Object.create(flight);
The prototype link has no effect on updating. The prototype link is used only in retrieval. If we add a property to a prototype, that property will be visible to every object based on that prototype.
flight.status = "cancelled";
another_flight.status // cancelled;
Reflection
typeof
operator can be very useful to know the type of a property:
typeof flight.number //'number'
typeof flight.status //'string'
typeof flight.arrival //'object'
typeof flight.manifest //'undefined'
Must be careful with values like:
typeof flight.toString //'function'
typeof flight.constructor //'function'
Because every property on the chain can produce a value we have to deal with it. Two approaches to deal with undesired values:
- 1. check and reject 'function' values
- 2. use the
hasOwnProperty
method
Delete
We can delete a property with the delete
keyword, without touching the prototype linkage.
On the other hand we can reveal property of the prototype linkage.
Function Objects
Function are objects. Objects are collections of name/value pairs having a hidden link to a prototype object.
Function objects are linked to Function.prototype
which is itself linked to Object.prototype
.
Functions can be stored in variables, objects, and arrays. Functions can be passed as arguments to
functions, and functions can be returned from functions. Also, since functions are
objects, functions can have methods. Function can be invoked.
Function Literals
Function objects are created with function literals:
// Create a variable called add and store a function
// in it that adds two numbers.
var add = function (a, b) {
return a + b;
};
A function literal can appear anywhere that an expression can appear. Functions can
be defined inside of other functions. An inner function of course has access to its
parameters and variables. An inner function also enjoys access to the parameters and
variables of the functions it is nested within. The function object created by a function
literal contains a link to that outer context (closure
).
With parameters every function receive two extra parameters:
- •
this
- •
arguments
The this
value is determined by the invocation pattern used. The patterns differ in how the bonus
parameter this
is initialized.
There are four different invocation patterns:
1. Method invocation pattern: a function is stored as a property of an object, we call it a method.
//Create myObject. It has a value and an increment //method. The increment method takes an optional //parameter. If the argument is not a number, then 1 //is used as the default. var myObject = { value: 0, increment: function (inc) { this.value += typeof inc === 'number' ? inc : 1; } }; myObject.increment( ); document.writeln(myObject.value); // 1 myObject.increment(2); document.writeln(myObject.value); // 3
The binding of this to the object happens at invocation time. This very late binding makes functions that use this highly reusable.
2. Function invocation pattern: When a function is not the property of an object, then it is invoked as a function:
var sum = add(3, 4); // sum is 7
When a function is invoked with this pattern,
this
is bound to the global object. This was a mistake in the design of the language. Workaround: If the method defines a variable and assigns it the value ofthis
, the inner function will have access to this through that variable. By convention, the name of that variable is that :// Augment myObject with a double method. myObject.double = function ( ) { var that = this; // Workaround. var helper = function ( ) { that.value = add(that.value, that.value); }; helper(); // Invoke helper as a function. }; // Invoke double as a method. myObject.double( ); document.writeln(myObject.getValue( )); // 6
3. Constructor invocation pattern: If a function is invoked with the new prefix, then a new object will be created with a hidden link to the value of the function’s
prototype
member, andthis
will be bound to that new object. The new prefix also changes the behavior of the return statement: if the return value is not an object, thenthis
(the new object) is returned instead.// Create a constructor function called Quo. // It makes an object with a status property. var Quo = function (string) { this.status = string; }; // Give all instances of Quo a public method // called get_status. Quo.prototype.get_status = function ( ) { return this.status; }; // Make an instance of Quo. var myQuo = new Quo("confused"); document.writeln(myQuo.get_status( )); // confused
Functions that are intended to be used with the new prefix are called
constructors
. If a constructor is called without the new prefix, very bad things can happen without a compile-time or runtime warning.
Use of this style of constructor functions is not recommended.4. Apply invocation pattern: The apply method lets us construct an array of arguments to use to invoke a function. It also lets us choose the value of
this
. The apply method takes two parameters. The first is the value that should be bound tothis
. The second is an array of parameters.// Make an array of 2 numbers and add them. var array = [3, 4]; var sum = add.apply(null, array); // sum is 7 // Make an object with a status member. var statusObject = { status: 'A-OK' }; //statusObject does not inherit from Quo.prototype, //but we can invoke the get_status method on //statusObject even though statusObject does not have //a getStatus method. var status = Quo.prototype.getStatus.apply(statusObject); //status is 'A-OK'
Augmenting Types
We can make a method available to all functions simply augmenting Function.prototype
.
//We no longer have to type the name of the prototype property
Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};
//Extract the integer part of a number
Number.method('integer', function ( ) {
return Math[this < 0 ? 'ceiling' : 'floor'](this);
});
document.writeln((-10 / 3).integer( )); // -3
String.method('trim', function ( ) {
return this.replace(/^\s+|\s+$/g, '');
});
document.writeln('"' + " neat ".trim( ) + '"');
Functions scope
JavaScript does not have block scope even though its block syntax suggests that it does. This confusion can be a source of errors. It is best to declare all of the variables used in a function at the top of the function body. JavaScript does have function scope.
Closure
The good news about scope is that inner functions get access to the parameters and
variables of the functions they are defined within. Exception made for this
and arguments
.
We previously made a myObject
that had a value and an increment method. Suppose we
wanted to protect the value from unauthorized changes.
Instead of initializing myObject
with an object literal, we will initialize myObject
by
calling a function that returns an object literal. That function defines a value variable.
That variable is always available to the increment
and getValue
methods, but
the function’s scope keeps it hidden from the rest of the program:
var myObject = function ( ) {
var value = 0;
return {
increment: function (inc) {
value += typeof inc === 'number' ? inc : 1;
},
getValue: function ( ) {
return value;
}
};
}();
We are not assigning a function to myObject
. We are assigning the result of invoking
that function. Notice the ()
on the last line. The function returns an object containing two
methods, and those methods continue to enjoy the privilege of access to the value variable.
Let’s define a function to be used without the new prefix:
// Create a maker function called quo. It makes an
// object with a get_status method and a private
// status property.
var quo = function (status) {
return {
get_status: function ( ) {
return status;
}
};
};
// Make an instance of quo.
var myQuo = quo("amazed");
document.writeln(myQuo.get_status( ));
When we call quo , it returns a new object containing a get_status
method. get_status
does not have access to a copy of the parameter; it has access to the
parameter itself. This is possible because the function has access to the context in
which it was created. This is called closure
.
Modules
A module is a function or object that presents an interface but that hides its state and implementation. By using functions to produce modules, we can almost completely eliminate our use of global variables, thereby mitigating one of JavaScript’s worst features. For example, suppose we want to augment String with a deentityify method. Its job is to look for HTML entities in a string and replace them with their equivalents. The ideal approach is to put it in a closure, and perhaps provide an extra method that can add additional entities:
String.method('deentityify', function ( ) {
// The entity table. It maps entity names to
// characters.
var entity = {
quot: '"',
lt: '<',
gt: '>'
};
// Return the deentityify method.
return function ( ) {
//This is the deentityify method. It calls the string
//replace method, looking for substrings that start
//with '&' and end with ';'. If the characters in
//between are in the entity table, then replace the
//entity with the character from the table. It uses
//a regular expression (Chapter 7).
return this.replace(/&([^&;]+);/g, function (a, b) {
var r = entity[b];
return typeof r === 'string' ? r : a;
});
};
}());
The general pattern of a module is a function that defines private variables and functions; creates privileged functions which, through closure, will have access to the private variables and functions; and that returns the privileged functions or stores them in an accessible place. It promotes information hiding and other good design practices.
Inheritance
JavaScript is a prototypal language, which means that objects inherit directly from other objects.
* Prototypal
You start by making a useful object. You can then make many more objects that are like that one.
var myMammal = {
name : 'Herb the Mammal',
get_name : function ( ) {
return this.name;
},
says : function ( ) {
return this.saying || '';
}
};
Once we have an object that we like, we can make more instances with the Object.create
method.
We can then customize the new instances:
var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
};
myCat.get_name = function ( ) {
return this.says( ) + ' ' + this.name + ' ' + this.says( );
};
This is differential inheritance
. The problem is there is no privacy
. Everything is public.
* Functional approach
We start by making a function that will produce objects.
The function contains four steps:
- 1. It creates a new object.
- 2. It optionally defines private instance variables and methods. These are just ordinary vars of the function.
- 3. It augments that new object with methods. Those methods will have privileged access to the parameters and the vars defined in the second step.
4. It returns that new object.
var constructor = function (spec, my) { var that, other private instance variables; my = my || {}; //Add shared variables and functions to my that = a new object; //Add privileged methods to that return that; };
The spec
object contains all of the information that the constructor needs to make an
instance. The contents of the spec could be copied into private variables or transformed
by other functions. Or the methods can access information from spec
as they
need it. (A simplification is to replace spec
with a single value. This is useful when
the object being constructed does not need a whole spec
object.)
The my
object is a container of secrets that are shared by the constructors in the
inheritance chain. The use of the my
object is optional. If a my
object is not passed in,
then a my
object is made.
Declare the private instance variables and private methods for the object.
The variables and inner functions of the constructor become the private members of the instance.
The inner functions have access to spec
and my
and that
and the private variables.
Add the shared secrets to the my object. This is done by assignment:
my.member = value;
It's time to make a new object and assign it to that
. How?
- • We can use an object literal.
- • We can use the
Object.create
method on a prototype object. - • We can call another functional constructor, passing it a
spec
object (possibly the samespec
object that was passed tothis
constructor) and themy
object.
We augment that
, adding the privileged methods that make up the object’s
interface. We can assign new functions to members of that
. Or, more securely, we
can define the functions first as private methods, and then assign them to that
:
var methodical = function ( ) {
...
};
that.methodical = methodical;
The advantage to defining methodical in two steps is that if other methods want to
call methodical , they can call methodical()
instead of that.methodical()
.
Finally we can return that
.