2. A Closer Look at JavaScript

Chances are, if you are reading this book, you have worked with JavaScript in the past. Perhaps you have worked on or written a web application that uses HTML, CSS, and JavaScript, and you have written some code to make the client more dynamic and interactive, either directly by manipulating the browser Document Object Model (DOM) or by using a framework such as jQuery or Prototype to hide some of the messier details for you. You might have even found working with JavaScript a reasonably frustrating experience and spent a lot of time fighting the different browsers’ incompatibilities, and it is also quite possible that you have never really studied the JavaScript language apart from the most basic of language features, looking things up on the fly as you go.

The good news is that modern web browsers are slowly driving a much-needed cleanup of the JavaScript language. Additionally, ECMAScript, which is the specification on which modern implementations are based, is also evolving. The Chrome V8 JavaScript engine itself is also improving and cleaning up many frustrating things about the language and adding important features that are missing or need updating.

So, even if you’ve already played around with JavaScript in the past, it is probably worth your while to read through this chapter fully and learn about some of the details you might have overlooked or about some of the new features or those changed by V8 and Node.js. Although most of the discussion in this chapter is applicable to standard JavaScript, sometimes I show you something new and improved in Google’s V8. For such nonstandard features, I mark them with (V8 JS).

Types

This section begins the review of JavaScript by looking at the types the language offers. For much of the discussion in this chapter, I use the Node.js Read-Eval-Print-Loop (REPL) to demonstrate how the code works. To help you out, I use bold to indicate things that you type into the interpreter.

client:LearningNode marcw$ node
>

Type Basics

Node.js has a few core types: number, boolean, string, and object. The two other types, function and array, are actually special kinds of objects, but because they have extra features in the language and runtime, some people refer to these three—object, function, and array—as complex types. The types null and undefined are also special kinds of objects and are also treated specially in JavaScript.

The value undefined means that a value has not been set yet or simply does not exist:

> var x;
undefined
> x = {};
{}
> x.not_valid;
undefined
>

null, on the other hand, is an explicit assertion that there “is no value”:

> var y;
undefined
> y
undefined
> y = null;
null
>

To see the type of anything in JavaScript, you use the typeof operator:

> typeof 10
'number'
> typeof "hello";
'string'
> typeof undefined
'undefined'
> typeof function () { var x = 20; }
'function'
>

Constants

While Node.js theoretically supports the const keyword extension that some modern JavaScript implementations have implemented, it’s still not widely used. For constants, the standard practice is still to just use uppercase letters in variable declarations:

> var SECONDS_PER_DAY = 86400;
undefined
> console.log(SECONDS_PER_DAY);
86400
undefined
>

Numbers

All numbers in JavaScript are 64-bit IEEE 754 double-precision floating-point numbers. For all positive and negative integers that can be expressed in 253 bits accurately, the number type in JavaScript behaves much like integer data types in other languages:

> 1024 * 1024
1048576
> 1048576
1048576
> 32437893250 + 3824598359235235
3824630797128485
> -38423538295 + 35892583295
-2530955000
>

The tricky part of using the number type, however, is that for many numeric values, it is an approximation of the actual number. For example:

> 0.1 + 0.2
0.30000000000000004
>

When performing floating-point mathematical operations, you cannot just manipulate arbitrary real numbers and expect an exact value:

> 1 - 0.3 + 0.1 == 0.8
false
>

For these cases, you instead need to check if the value is in some sort of approximate range, the size of which is defined by the magnitude of the values you are comparing. (Search the website stackoverflow.com for articles and questions on comparing floating-point numbers for good strategies on this.)

For those situations in which you absolutely need to represent 64-bit integer values in JavaScript without any chance of approximation errors, you are either stuck using the string type and manipulating these numbers by hand, or you can use one of the available modules for manipulating big integer values. (We’ll learn about modules in Chapter 5, “Modules.”)

JavaScript is a bit different from other languages in that dividing a number by zero returns the value Infinity or -Infinity instead of generating a runtime exception:

> 5 / 0
Infinity
> -5 / 0
-Infinity
>

Infinity and -Infinity are valid values that you can compare against in JavaScript:

> var x = 10, y = 0;
undefined
> x / y == Infinity
true
>

You can use the functions parseInt and parseFloat to convert strings to numbers:

> parseInt("32523523626263");
32523523626263
> parseFloat("82959.248945895");
82959.248945895
> parseInt("234.43634");
234
> parseFloat("10");
10
>

If you provide these functions with something that cannot be parsed, they return the special value NaN (not-a-number):

> parseInt("cat");
NaN
> parseFloat("Wankel rotary engine");
NaN
>

To test for NaN, you must use the isNaN function:

> isNaN(parseInt("cat"));
true
>

Finally, to test whether a given number is a valid finite number (that is, it is not Infinity, -Infinity, or NaN), use the isFinite function:

> isFinite(10/5);
true
> isFinite(10/0);
false
> isFinite(parseFloat("banana"));
false
>

Booleans

The boolean type in JavaScript is both simple and simple to use. Values can either be true or false, and although you technically can convert values to boolean with the Boolean function, you almost never need it because the language converts everything to boolean when needed, according to the following rules:

1. false, 0, empty strings (""), NaN, null, and undefined all evaluate to false.

2. All other values evaluate to true.

Strings

Strings in JavaScript are sequences of Unicode characters (represented internally in a 16-bit UCS-2 format) that can represent a vast majority of the characters in the world, including those used in most Asian languages. There is no separate char or character data type in the language; you just use a string of length 1 to represent these. For most of the network applications you’ll be writing with Node.js, you will interact with the outside world in UTF-8, and Node will handle all the details of conversion for you. Except when you are manipulating binary data, your experience with strings and character sets will largely be worry-free.

Strings can be wrapped in single or double quotation marks. They are functionally equivalent, and you are free to use whatever ones you want. To include a single quotation mark inside a single-quoted string, you can use ', and similarly for double quotation marks inside double-quoted strings, you can use ":

> 'Marc's hat is new.'
'Marc's hat is new.'
> ""Hey, nice hat!", she said."
'"Hey, nice hat!", she said.'
>

To get the length of a string in JavaScript, just use the length property:

> var x = "cat";
undefined
> x.length;
3
> "cat".length;
3

Attempting to get the length of a null or undefined string throws an error in JavaScript (which helpfully includes a stack trace that can be used to find where exactly the error occurred):

> x = null;
null
> x.length;
TypeError: Cannot read property 'length' of null
    at repl:1:2
    at REPLServer.self.eval (repl.js:109:21)
    at rli.on.self.bufferedCmd (repl.js:258:20)
    at REPLServer.self.eval (repl.js:116:5)
    at Interface.<anonymous> (repl.js:248:12)
    at Interface.EventEmitter.emit (events.js:96:17)
    at Interface._onLine (readline.js:200:10)
    at Interface._line (readline.js:518:8)
    at Interface._ttyWrite (readline.js:736:14)
    at ReadStream.onkeypress (readline.js:97:10)

To add two strings together, you can use the + operator:

> "cats" + " go " + "meow";
'cats go meow'
>

If you start throwing other types into the mix, JavaScript converts them as best it can:

> var distance = 25;
undefined
> "I ran " + distance + " kilometers today";
'I ran 25 kilometers today'
>

Note that this can provide some interesting results if you start mixing expressions a bit too much:

> 5 + 3 + " is my favorite number";
'8 is my favorite number'
>

If you really want “53” to be your favorite number, you can just prefix it all with an empty string to force the conversion earlier:

> "" + 5 + 3 + " is my favorite number";
'53 is my favorite number'
>

Many people worry that the concatenation operator + has terrible performance when working with strings. The good news is that almost all modern browser implementations of JavaScript—including Chrome’s V8 that you use in Node.js—have optimized this scenario heavily, and performance is now quite good.

String Functions

Many interesting functions are available to manipulate strings in JavaScript. To find the location of a string with another string, use the indexOf function:

> "Wishy washy winter".indexOf("wash");
6
>

To extract a substring from a string, use the substr or splice function. (The former takes the starting index and length of the string to extract; the latter takes the starting index and ending index.)

> "No, they're saying Booo-urns.".substr(19, 3);
'Boo'
> "No, they're saying Booo-urns.".slice(19, 22);
'Boo'
>

If you have a string with some sort of separator character in it, you can split that up into component strings by using the split function and get an array as the result:

> "a|b|c|d|e|f|g|h".split("|");
[ 'a',
  'b',
  'c',
  'd',
  'e',
  'f',
  'g',
  'h' ]
>

Finally, the trim function (V8 JS) does exactly what you would expect—removes whitespace from the beginning and end of a string:

> '       cat       '. trim();
'cat'
>

Regular Expressions

JavaScript has powerful regular expression support, the full details of which are beyond the scope of this book, but I briefly show how and where you can use them. A certain number of string functions can take arguments that are regular expressions to perform their work. These regular expressions can be entered either in literal format (indicated by putting the regular expression between two forward slash [/] characters) or as a call to the constructor of a RegExp object:

/[aA]{2,}/
new RegExp("[Aa]{2,}")

Both of these are regular expressions that match against a sequence of two or more instances of the letter a (uppercase or lowercase).

To replace all sequences of two or more a’s with the letter b on string objects, you can use the replace function and write either of the following:

> "aaoo".replace(new RegExp("[Aa]{2,}"), "b");
'boo'
> "aaoo".replace(/[Aa]{2,}/, "b");
'boo'
>

Similar to the indexOf function, the search function takes a regular expression and returns the index of the first match against it or -1 if no such match exists:

> "aaoo".search(/[Aa]{2,}/);
0
> "aoo".search(/[Aa]{2,}/);
-1
>

Objects

Objects are one of the core workhorses of the JavaScript language, and something you will use all the time. They are an extremely dynamic and flexible data type, and you can add and remove things from them with ease. To create an object, you can use either of the following, although the latter, known as object literal syntax, is almost always preferred nowadays:

> var o1 = new Object();
undefined
> var o2 = {};
undefined
>

You can also specify the contents of objects using object literal syntax, where you can specify member names and values at initialization time:

var user = {
    first_name: "marc",
    last_name: "wandschneider",
    age: Infinity,
    citizenship: "man of the world"
};

You can add a new property to your user object by using any of the following methods:

> user.hair_colour = "brown";
'brown'
> user["hair_colour"] = "brown";
'brown'
> var attribute = 'hair_colour';
undefined
> user[attribute] = "brown";
'brown'
> user
{ first_name: 'marc',
  last_name: 'wandschneider',
  age: Infinity,
  citizenship: 'man of the world',
  hair_colour: 'brown' }
>

If you try to access a property that does not exist, you do not receive an error but instead just get back undefined:

> user.car_make
undefined
>

To remove a property from an object, you can use the delete keyword:

> delete user.hair_colour;
true
> user
{ first_name: 'marc',
  last_name: 'wandschneider',
  age: Infinity,
  citizenship: 'man of the world' }
>

The flexibility of objects in JavaScript makes them quite similar to various associative arrays, hash maps, or dictionaries seen in other languages, but there is an interesting difference: getting the size of an object-as-associative-array in JavaScript is a bit tricky. There are no size or length properties or methods on Object. To get around this, you can write the following:

> Object.keys(user).length;
4

Note that this uses a nonstandard extension to JavaScript Object.keys; although V8 and most modern browsers already support it (older versions of Internet Explorer typically do not).

Arrays

The array type in JavaScript is actually a special casing of the object type, with a number of additional features that make arrays useful and powerful. To create arrays, you can use either traditional notation or array literal syntax:

> var arr1 = new Array();
undefined
> arr1
[]
> var arr2 = [];
undefined
> arr2
[]
>

As with objects, I almost always prefer the literal syntax version and rarely use the former.

If you use the typeof operator on arrays, you get a surprising result:

> typeof arr2
'object'
>

Because arrays are actually objects, the typeof operator just returns that, which is very frequently not what you want! Fortunately, V8 has a language extension to let you test determinatively whether or not something is an array: the Array.isArray function:

> Array.isArray(arr2);
true
> Array.isArray({});
false
>

One of the key features of the array type in JavaScript is the length property, used as follows:

> arr2.length
0
> var arr3 = [ 'cat', 'rat', 'bat' ];
undefined
> arr3.length;
3
>

By default, arrays in JavaScript are numerically indexed:

// this:
for (var i = 0; i < arr3.length; i++) {
    console.log(arr3[i]);
}
// will print out this:
cat
rat
bat

To add an item to the end of an array, you can do one of two things:

> arr3.push("mat");
4
> arr3
[ 'cat',  'rat',  'bat',  'mat' ]
> arr3[arr3.length] = "fat";
'fat'
> arr3
[ 'cat',  'rat',  'bat',  'mat',  'fat' ]
>

You can specify the index of the element where you want to insert a new element. If this element is past the last element, the elements in between are created and initialized with the value undefined:

> arr3[20] = "splat";
'splat'
> arr3
[ 'cat', 'rat', 'bat', 'mat', 'fat', , , , , , , , , , , , , , , , 'splat' ]
>

To remove elements from an array, you might try to use the delete keyword again, but the results may surprise you:

> delete arr3[2];
true
> arr3
[ 'cat', 'rat', , 'mat', 'fat', , , , , , , , , , , , , , , , 'splat' ]
>

You see that the value at index 2 still “exists” and has just been set to undefined.

To truly delete an item from an array, you probably should use the splice function, which takes an index and the number of items to delete. What it returns is an array with the extracted items, and the original array is modified such that they no longer exist there:

> arr3.splice(2, 2);
[  , 'mat' ]
> arr3
[ 'cat', 'rat', 'fat', , , , , , , , , , , , , , , , 'splat' ]
> arr3.length
19

Useful Functions

There are a few key functions you frequently use with arrays. The push and pop functions let you add and remove items to the end of an array, respectively:

> var nums = [ 1, 1, 2, 3, 5, 8 ];
undefined
> nums.push(13);
7
> nums
[ 1,  1,  2,  3,  5,  8,  13 ]
> nums.pop();
13
> nums
[ 1,  1,  2,  3,  5,  8 ]
>

To insert or delete items from the front of an array, use unshift or shift, respectively:

> var nums = [ 1, 2, 3, 5, 8 ];
undefined
> nums.unshift(1);
6
> nums
[ 1,  1,  2,  3,  5,  8 ]
> nums.shift();
1
> nums
[ 1, 2, 3, 5, 8 ]
>

The opposite of the string function split seen previously is the array function join, which returns a string:

> var nums = [ 1, 1, 2, 3, 5, 8 ];
undefined
> nums.join(", ");
'1, 1, 2, 3, 5, 8'
>

You can sort arrays using the built-in sort function, which can be used with the built-in sorting function:

> var jumble_nums = [ 3, 1, 8, 5, 2, 1];
undefined
> jumble_nums.sort();
[ 1,  1,  2,  3,  5,  8 ]
>

For those cases where the built-in function for it doesn’t quite do what you want, you can provide your own sorting function as a parameter:

> var names = [ 'marc', 'Maria', 'John', 'jerry', 'alfred', 'Moonbeam'];
undefined
> names.sort();
[ 'John',  'Maria',  'Moonbeam',  'alfred',  'jerry',  'marc' ]
> names.sort(function (a, b) {
        var a1 = a.toLowerCase(), b1 = b.toLowerCase();
        if (a1 < b1) return -1;
        if (a1 > b1) return 1;
        return 0;
    });
[ 'alfred',  'jerry',  'John',  'marc',  'Maria',  'Moonbeam' ]
>

To iterate over items in arrays, you have a number of options, including the for loop shown previously, or you can use the forEach function, as follows:

[ 'marc', 'Maria', 'John', 'jerry', 'alfred', 'Moonbeam'].forEach(function (value) {
    console.log(value);
});
marc
Maria
John
jerry
alfred
Moonbeam

The forEach function is an extension to the JavaScript language that most modern browsers support fully.

Type Comparisons and Conversions

As alluded to previously, for the most part, types in JavaScript behave as you would expect them and as you have seen in other programming languages. JavaScript has both the equality operator == (do the two operands have the same value?) and the precise equality operator === (do the two operands have the same value and are of the same type?):

> 234 == '234'
true
> 234 === '234'
false
> 234234.235235 == 'cat'
false
> "cat" == "CAT"
false
> "cat".toUpperCase() == "CAT";
true

You also saw that a number of different things evaluate to false, despite being quite different:

> '' == false == null == undefined == 0
true
> null === undefined
false
>

This saves you some time when doing tasks such as checking arguments to functions:

function this_works(param) {
    if (param == null || param == undefined || param == '')
       throw new Error("Invalid Argument");
}

function this_is_better(param) {
   if (!param) throw new Error("Invalid Argument");
}

One case where type comparisons can be tricky is if you use object constructors for values instead of just using primitives:

> var x = 234;
undefined
> var x1 = new Number(234);
undefined
> typeof x1
'object'
> typeof x
'number'
> x1 == x
true
> x1 === x
false
>

The object constructors are functionally equivalent to the primitive types; all the same operations, operators, and functions produce the same results, but the precise equality operator === and typeof operator produce different results. For this reason, it is recommended to just use the primitive types whenever possible.

Functions

Although it does not look like it at first glance (the name doesn’t help either), JavaScript is a functional programming language, wherein functions are fully typed objects that can be manipulated, extended, and passed around as data. Node.js takes advantage of this capability, and you will use it extensively in your network and web applications.

Basics

The simplest kind of function is exactly as you would expect:

> function hello(name) {
      console.log("hello " + name);
}
undefined
> hello("marc");
hello marc
undefined
>

To declare parameters for a function in JavaScript, you simply list them in the parentheses. There is, however, absolutely no checking of these parameters at runtime:

> hello();
hello undefined
undefined
> hello("marc", "dog", "cat", 48295);
hello marc
undefined
>

If too few parameters are passed into a function call, the resulting variables are assigned the value undefined. If too many are passed in, the extras are simply unused.

All functions have a predefined object in the body called arguments (that looks and behaves much like an array, but is not really an array—it’s actually an object that also has a length property). It has all the values that were passed in to this particular function call and gives you greater flexibility on passing in parameters, allowing you can also to do extra checking on the parameter list.

function add_two(a, b) {
    return a + b;
}
function add_n() {
    var sum = 0;
    for (var i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;
}

Indeed, you can go a step further and use it to make your functions more powerful and flexible. Suppose you want to initialize a caching subsystem you wrote. To do this, the function takes a size to create the cache with and uses default values for other things such as cache location, expiration algorithm, maximum cache item size, and storage type. You could write the function as follows:

function init_cache(size_mb, location, algorithm, item_size, storage_type) {
    ...
}

init_cache(100, null, null, null, null);

However, it would be even cooler if you could have the function be “smart” enough to give you a couple of different ways to call it:

function init_cache() {
    var init_data = {
        cache_size: 10,
        location: '/tmp',
        algorithm: 'lru',
        item_size: 1024,
        storage_type: 'btree'
    };

    var a = arguments;
    for (var i = 0; i < a.length; i++) {
        if (typeof a[i] == 'object') {
            init_data = a[i];
            break;
        } else if (typeof a[i] == 'number') {
            init_data.cache_size = a[i];
            break;
        } else {
            throw new Error("bad cache init param");
        }
    }

    // etc
}

Now you have a number of different ways you can call this function:

init_cache();
init_cache(200);
init_cache({ cache_size: 100,
             location: '/exports/dfs/tmp',
             algorithm: 'lruext',
             item_size: 1024,
             storage_type: 'btree'} );

Functions in JavaScript do not even need to have names:

> var x = function (a, b) {
      return a + b;
}
undefined
> x(10, 20);
30

These nameless functions are typically called anonymous functions. There is one drawback to fully anonymous functions and that comes in debugging:

> var x = function () {
      throw new Error("whoopsie");
}
undefined
> x();
Error: whoopsie
    at x (repl:2:7)
    at repl:1:1
    at REPLServer.self.eval (repl.js:109:21)
    at rli.on.self.bufferedCmd (repl.js:258:20)

Anonymous functions do not permit the language engine to tell you what the name of the function is when an exception is thrown. This can make your life a bit more difficult when debugging.

A simple solution and modern extension to the JavaScript language is to simply name the anonymous functions:

> var x = function bad_apple() {
      throw new Error("whoopsie");
}
undefined
> x();
Error: whoopsie
    at bad_apple (repl:2:7)
    at repl:1:1
    at REPLServer.self.eval (repl.js:109:21)
    at rli.on.self.bufferedCmd (repl.js:258:20)

In complicated programs, having an exact pointer to the location of an error can be a real lifesaver. For this reason, some people choose to name all their anonymous functions.

You have already seen an example of anonymous functions in the earlier section “Arrays” when you called the sort function with an anonymous function to do case-insensitive string comparisons. You will use many more of them throughout this book.

Function Scope

Every time a function is called, a new variable scope is created. Variables declared in the parent scope are available to that function, but variables declared within the new scope are not available when the function exits. Consider the following code:

var pet = 'cat';

function play_with_pets() {
    var pet = 'dog';
    console.log(pet);
}

play_with_pets();
console.log(pet);

It outputs the following:

dog
cat

Combining this scoping with anonymous functions can be a good way to do some quick or private work with private variables that will disappear when the anonymous function exits. Here’s a contrived example to compute the volume of a cone:

var height = 5;
var radius = 3;
var volume;

// declare and immediately call anon function to create scope
(function () {
    var pir2 = Math.PI * radius * radius;   // temp var
    volume = (pir2 * height) / 3;
})();

console.log(volume);

You will see a number of other common patterns involving functions when you move into Chapter 3, “Asynchronous Programming.”

Arrow Functions

One new feature in ECMAScript that has made it into Node.js is a bit of syntactic sugar to make writing anonymous functions a bit easier. Before version 4.4.5 of Node.js, to write an anonymous function, you had to write it as follows:

var x = function (a, b) { return a + b; }

With the addition of arrow functions, however, you can now write anonymous functions in one of two forms:

(param1, ..., param n) => { statement list }
(param1, ..., param n) => expression

The second of these is actually just a special case of the first and could also be written as:

(param1, ..., param n) => { return expression; }

Thus, our example expression above could be written as:

> var x = (a, b) => a + b;
undefined
> x(4, 5)
9
>

Arrow functions also solve a very important problem with object identity (the this value), which we will cover in Chapter 3, “Asynchronous Programming,” so you’ll see us using them quite a lot in our code.

Language Constructs

JavaScript contains nearly all the language operators and constructs you would expect, including most logical and mathematical operators.

The ternary operator is supported:

var pet = animal_meows ? "cat" : "dog";

Even though numbers are implemented as double-precision floating-point numbers, bitwise operations are supported in JavaScript: The & (and), | (or), ~ (inverse), and ^ (xor) operators all work by:

1. First converting the operands into 32-bit integers,

2. Performing the bitwise operation, and

3. Finally, converting the resulting number back to a JavaScript number.

In addition to the standard while, do...while, and for loops, JavaScript also supports a new language extension to the for loop called the for...in loop. This loop is used to get the names of all the keys on an object:

> var user = {
      first_name: "marc",
      last_name: "wandschneider",
      age: Infinity,
      occupation: "writer"
};
undefined
> for (key in user) {
       console.log(key);
}
first_name
last_name
age
occupation
undefined
>

If you use for...in loops on arrays, you’ll get the indexes of the elements in the array:

> var x = [ 1, 2, 3, 4 ];
undefined
> for (var idx in x) {
      console.log(x[idx]);
}
1
2
3
4
undefined

Node.js also supports another recent language extension to JavaScript called the for...of loop. This loop is useful for iterating over values of objects that are marked as being iterable. Arrays (recall these are special types of objects) are all iterable, as are a few other objects that implement the iterable interfaces. While we won’t go into iterable objects in detail in this book, you’ll run across them in a number of places in Node.js, most notably the arguments list we mentioned above for functions:

function add_n_iterable() {
    var sum = 0;
    for (x of arguments) {
        sum += x;
    }
    return sum;
}
add_n_iterable(1, 2, 3, 4, 5, 6, 7, 8, 9);
45

Classes, Prototypes, and Inheritance

Object-oriented programming in JavaScript is a bit different from other languages in that there is no explicit class keyword or type. Indeed, classes are all declared as functions:

function Shape () {
    this.X = 0;
    this.Y = 0;

    this.move = function (x, y) {
        this.X = x;
        this.Y = y;
    }
    this.distance_from_origin = function () {
        return Math.sqrt(this.X*this.X + this.Y*this.Y);
    }
}

var s = new Shape();
s.move(10, 10);
console.log(s.distance_from_origin());

Note that when we declare member methods or functions on an object, we will use the standard function keyword syntax, not arrow functions. We’ll explain more about why in the next chapter.

The preceding program generates the following output:

14.142135623730951

You can add as many properties and methods to your classes as you like, at any time:

var s = new Shape(15, 35);
s.FillColour = "red";

The function that declares the class also serves as its constructor!

There are two problems with this scheme for creating classes, however. First, it seems a bit inefficient to have to carry around the method implementations with every single object (every time you create a new instance of Shape, you are creating the move and distance_from_origin functions from scratch). Second, you might like to extend this class to create circles and squares and have them inherit the methods and properties from the base class without your having to do any extra work.

Prototypes and Inheritance

By default, all objects in JavaScript have a prototype object, which is the mechanism through which they inherit properties and methods. Prototypes have been the source of much confusion in JavaScript over the years, often because different browsers use different nomenclature for it and subtly different implementations. Because it is relevant to the interests of this chapter, I demonstrate the model that V8 (and thus Node) uses and that other modern JavaScript implementations seem to be moving toward.

Change the Shape class created earlier so that all inheriting objects also get the X and Y properties, as well as the methods you have declared on it:

function Shape () {
}

Shape.prototype.X = 0;
Shape.prototype.Y = 0;

Shape.prototype.move = function (x, y) {
    this.X = x;
    this.Y = y;
}
Shape.prototype.distance_from_origin = function () {
    return Math.sqrt(this.X*this.X + this.Y*this.Y);
}
Shape.prototype.area = function () {
    throw new Error("I don't have a form yet");
}
var s = new Shape();
s.move(10, 10);
console.log(s.distance_from_origin());

Run this script and you get the same output as the previous one. Indeed, functionally, apart from potentially being slightly more memory efficient (if you created lots of instances, they would all share the implementations of move and distance_from_origin instead of carrying around their own), it is not that different. You have added a method area that all shapes will have. On the base class, it does not make much sense, so you just have it throw an error.

More importantly, you have set yourself up to extend it quite easily:

function Square() {
}

Square.prototype = new Shape();
Square.prototype.__proto__ = Shape.prototype;
Square.prototype.Width = 0;

Square.prototype.area = function () {
    return this.Width * this.Width;
}

var sq = new Square();
sq.move(--5, -5);
sq.Width = 5;
console.log(sq.area());
console.log(sq.distance_from_origin());

The code for this new Square class makes use of a new JavaScript language feature seen in V8 and a few other implementations: the __proto__ property. It lets you tell JavaScript that a new class you are declaring should have the base prototype of the specified type, and then you can extend it from there.

You can further extend things with a new class called Rectangle, inheriting from the Square class:

function Rectangle () {
}

Rectangle.prototype = new Square();
Rectangle.prototype.__proto__ = Square.prototype;
Rectangle.prototype.Height = 0;

Rectangle.prototype.area = function () {
    return this.Width * this.Height;
}

var re = new Rectangle();
re.move(25, 25);
re.Width = 10;
re.Height = 5;
console.log(re.area());
console.log(re.distance_from_origin());

To convince yourself that things are going smoothly, you can use an operator you have not seen before, instanceof:

console.log(sq instanceof Square);      // true
console.log(sq instanceof Shape);       // true
console.log(sq instanceof Rectangle);   // false
console.log(re instanceof Rectangle);   // true
console.log(sq instanceof Square);      // true
console.log(sq instanceof Shape);       // true
console.log(sq instanceof Date);        // false

Errors and Exceptions

In JavaScript, you traditionally signal errors using an Error object and a message. You throw this error to signal the error condition:

> function uhoh () {
     throw new Error("Something bad happened!");
}
undefined
> uhoh());
Error: Something bad happened!
    at uhoh (repl:2:7)
    at repl:1:1
    at REPLServer.self.eval (repl.js:109:21)
    at rli.on.self.bufferedCmd (repl.js:258:20)

You can catch this error condition with a try / catch block as seen in other languages:

function uhoh () {
    throw new Error("Something bad happened!");
}

try {
    uhoh();
} catch (e) {
    console.log("I caught an error: " + e.message);
}

console.log("program is still running");

The output of this program is:

I caught an error: Something bad happened!
program is still running

As you see in the next chapter, however, this method of error handling does create some problems with the asynchronous model of programming you will be using in Node.js and thus we won’t use it as often as you might expect.

Some Important Node.js Globals

Node.js has a few key global variables that are always available to you.

global

When you write JavaScript in web browsers, you have the window object, which acts as a global variable. Any variables or members you attach to it are available anywhere in your application.

Node.js has something similar, called the global object. Anything attached to it is available anywhere in your node application:

function printit(var_name) {
    console.log(global[var_name]);
}

global.fish = "swordfish";
global.pet = "cat";

printit("fish");  // prints swordfish
printit("pet");    // prints cat
printit("fruit"); // prints undefined

console

You have already seen the global variable console in Node, as you frequently use the console.log function. There are a few other interesting functions on the console object, however:

Image warn(msg)—This function is similar to log, but it prints to stderr (the standard error output, often given special treatment in shells and scripts).

Image time(label) and timeEnd(label)—The first marks a time stamp, and when you call the second, it prints out the elapsed time since the time function was called.

Image assert(cond, message)—If cond evaluates to false, throw an AssertionFailure exception with the message provided.

process

The other key global in Node is the process global variable, which contains a lot of information and methods that you will see as you work through this book. The exit method is one way to terminate your Node.js programs, the env function returns an object with current user environment variables, and cwd returns the current working directory of the app.

Summary

You took a quick look at the JavaScript language in this chapter, and I hope your knowledge of the language has at least been sharpened a little bit, with some confusing or unknown areas clarified a bit. With this basic knowledge under your belt, you can now begin to look at how Node.js uses the language so effectively to create powerful and speedy applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset