If it takes more than one line to print "Hello World", JavaScript should not call itself a scripting language. In fact, it is the shortest "Hello World" program in this book (tied with Lisp, Ruby, Haskell, and Python):
js> "Hello World" Hello World
Of course, we are cheating, since we just use the shell to repeat the string value (so we did for the other languages). The real "Hello World" program looks like this:
js> print('Hello World') Hello World
This starts revealing some information about JavaScript. We can call functions (print being one of them) using the "standard" (mathematical) function notation, and strings literals can be defined with single quotes or double quotes (as in the first example).
The print function can take any number of arguments, and these arguments can be of any type.
js> print('result:', 4+5*3) result: 19
This also shows us that we can perform arithmetic, again using the standard (i.e., mathematical) syntax. As in Python, the printed values are separated by spaces.
JavaScript follows Java's expression syntax including update operators (+=, -=, and so forth), increment and decrement operators, as well as bit-wise operations (e.g., & for bit-wise "and" and << for "shift left".
Like in Java, numbers can be entered as integer, floating point (with or without exponent), hexadecimal (starting with 0x), or octal (starting with 0) literals.
js> 1.5e-2 0.015 js> 0x9d 157 js> 011 9
JavaScript knows only one number type (simply called "number") that is stored as a 64-bit floating point number (C's "double" type). Therefore, division does not distinguish between integers and real numbers, and the result of 3/2 is 1.5 and not 1.
js> typeof(55) number js> typeof(5.5) number js> 3 / 2 1.5 js> 5 % 2 1 js> 5.5 % 2 1.5 js> 5.5 % 2.5 0.5
Boolean expressions also use Java's syntax with the constants true and false and the C operators !, &&, and || for the three Boolean operations "not", "and", and "or". But in contrast to strongly typed languages such as Java, they can be applied to any expression. A value is considered false if it is the boolean constant false, an empty string, a numerical zero, or a null reference. Everything else (including empty lists) is considered true.
Semantically, the boolean "and" and "or" operators work as shortcut operators returning one of their operands (like in Python). The result of an "and" expression is the first operand that is false or, if all operands are true, the last that is true. Similarly, the result of an "or" expression is the first operand that is true or, if all operands are false, the last that is false.
js> "a" && "b" b js> 0 && "b" 0 js> "" || 0 0
This way, we can also simulate a functional "if-then-else" expression, but we do not have to resort to this trick, since JavaScript also support question mark operator.
js> 4 < 5 && "b" || "c" b js> 4 > 5 && "b" || "c" c js> 4 < 5 ? "b" : "c" b
JavaScript offers Java's conditional and loop statements.
js> i = 3; 3 js> while (i > 0) { print(i); i -= 1; } 3 2 1 0 js> do { print(i); i += 1; } while (i < 4) 0 1 2 3 4 js> for (i=0; i<3; i++) { print(i); } 0 1 2 js> if (i < 3) { print("less than three"); } else if (i > 3) { print("greater than three"); } else { print("exactly three"); } exactly three
We can also use a break statement to escape from the innermost loop or switch clause.
JavaScript's switch statement supports numbers and strings (like C#).
Let's see if we can detect more similarities to Python. Defining lists looks exactly the same.
js> x = [1, 2, "blah"] 1,2,blah js> x[0] 1 js> x[2] blah js> x.slice(1,2) 2 js> x.slice(0,2) 1,2
As we see, list literals and access to elements use the same syntax as Python, and as in any other language with some C heritage, indexing starts at zero (think pointers and offsets). JavaScript also allows us to extracts parts of a list using slices with the slice method. This is the first time we see a method call in JavaScript, and again, there is no surprise. Note that the slice indexes describe half-open intervals, that is, the left boundary is included and the right one is not.
As for maps, the main difference is that they are printed as objects rather than showing the keys and values (we will see shortly why).
js> m = {"blah": 55, "blub": 66} [object Object] js> m["blah"] 55 js> m["xxx"] js>
We also notice that a missing key does not raise an exception, but just returns nothing (which is called null in JavaScript).
JavaScript treats functions as first class objects, almost like a functional language. One way to define a function is to create an anonymous function and assign it to a variable. The three periods in the following examples denote the repeated function definition as printed by the interactive shell.
js> add = function(x, y) { return x + y; } ... js> add(1, 2) 3
The anonymous function is defined with the function keyword followed by the argument list and the code block defining the function's body. Since JavaScript is dynamically typed, there is no need to define the argument and return types. To return a value, we call the return operator followed by the expression we want to return (no implicit return like in functional languages).
You can also use the more conventional syntax with the function's name following the function keyword.
js> function add(x, y) { return x + y; } js> add(1,2) 3
The result is exactly the same. The second syntax is just a shortcut for the first one (saving as much as a single character, the assignment operator =).
Having defined a function using anonymous function objects and assignment, it is clear that we easily pass functions as arguments to other functions. In the following example, we define the function times that calls a call function a given number of times.
js> function times(n, f) { for (i=0; i<n; ++i) { f() } } ... js> times(5, function() { print("blah") }) blah blah blah blah blah
We can use the same mechanism to define higher order functions which return functions themselves, such as the composition of functions.
js> function compose(f, g) { return function(x) { return f(g(x)) } } js> function times2(x) { return 2*x } js> function plus10(x) { return x + 10; } js> compose(times2, plus10)(10) 40
The compose function defined above works only for single arguments. For the general case we need to apply a function to an argument list without knowning the actual number of arguments in advance. As a first observation, we can call a JavaScript function with more than the required number of arguments. We can, for example, pass three instead of two arguments to the add function defined above.
js> add(1, 2, 3) 3
The third argument is simply ignored. In the body of a function we have access to array of arguments under the special variable arguments. This allows us to generalize the add function to arbitrarily many arguments.
js> function sum() { var result = 0; for (var i=0; i<arguments.length; i++) { result += arguments[i]; } return result; } js> sum(1, 2, 3) 6
If we don't know the number of arguments in advance, we can call a function using its apply method which takes the object the function belongs to (more on this in the next section) as the first argument and the array of arguments as the second.
js> sum.apply(this, [1, 2, 3, 4]) 10
Putting all these pieces together, we can now define the general composition function.
js> function compose(f, g) { return function() { return f(g.apply(this, arguments)); } } js> compose(times2, sum)(1, 2, 3, 4) 20
Here, this is another implicit variable pointing to the object for which the function was called.
There is a third way to define a function which leads us directly to the object-oriented aspects of JavaScript. We can construct a function dynamically using strings for the argument names as well as the function's body by passing these strings to the Function constructor.
js> add = new Function("x", "y", "return x+y"); ... js> add(1, 2) 3
As you can imagine, this built-in code generation capability is extremely powerful (and dangerous).
Most of the chapters in this book explaining the object-oriented features of a language are called "Objects and Classes", just like object-oriented programming could by called "class-oriented" in most languages. JavaScript is one of the few exceptions. There are no classes. Its object-oriented features are based on prototypes rather than classes, an approach which dates back to the Self language.
To start with, let us revisit dictionaries (maps), now viewed as objects.
js> person = { "name": "Homer" } [object Object] js> person.name Homer js> person["name"] Homer js> person.age = 66 66 js> person["age"] 66 js> delete person.name true js> person.name
In JavaScript, objects and maps are basically the same thing. You can view the direct access to an object's properties (such as name and age) as syntactic sugar for the index operator. Obviously, this only works as long as the name of the property is a proper JavaScript identifier (starting with a letter, dollar sign, or underscore character).
js> person["1"] = 123 123 js> person["1"] 123 js> person.1 js: "<stdin>", line 52: uncaught JavaScript exception: SyntaxError: missing ; before statement (<stdin>; line 52) js: person.1 js: .......^
Like in Python, properties can be any kind of value including functions (in Python "callable objects"). A method or operation in other object-oriented languages corresponds to a function property in JavaScript.
js> person.hello = function() { print("Hello, I'm", this.name); } ... js> person.hello() Hello, I'm Homer
We obviously do not want to set the properties for each new object again and again. In JavaScript, we have two means to define the properties of all objects of a certain kind (I almost wrote "class"): constructors and prototypes.
A constructor is a function that sets the properties of a new object. It is called when constructing an object with the new operator.
js> function Person(name) { this.name = name; } js> person = new Person("Homer"); [object Object] js> person.name Homer js> person.constructor function Person(name) { this.name = name; }
We can ask an object for its constructor using the constructor attribute. In a sense, JavaScript's classes are the constructor functions. This becomes even more apparent when we look at prototypes. First, we should remark that functions are first class objects which can have properties themselves (again just like in Python).
js> function add(x, y) { return x + y; } js> add.description = "I'm adding two numbers" I'm adding two numbers js> add.description I'm adding two numbers
When constructing an object, it is linked to its constructor function, and each constructor function has the prototype property. When looking for a property of an object, JavaScript first checks the object itself. If the property is defined in the object, its value is returned. If it is not defined in the object itself, JavaScript checks the prototype of the object, that is, the prototype property of the object's constructor function. If the prototype object defined the requested property, its value is returned. Otherwise, the result is null. The following example demonstrates the different situations.
js> person.age js> Person.prototype.age = 55 55 js> person.age 55 js> person.age = 66 66 js> person.age 66 js> delete person.age true js> person.age 55
We first check the person's age property which turns out to be empty. We then set the age property of the constructor's prototype to 55. Now, the person object still does not define the age property itself, but JavaScript finds it in the prototype and prints 55. After setting the person's age property explicitly, we see the new property value. Finally, after deleting the property again, the prototype's value is retrieved.
The nice thing about this prototype approach is that it treats attributes and methods exactly the same way. All properties, whether they are normal values (attributes) or functions (method), follow the same lookup rules.
Static or class methods of other object-oriented languages become function properties of JavaScript's constructors.
js> function Person(name, age) { this.name = name; this.age = age; } js> Person.compareAge = function(a, b) { return a.age - b.age; } ... js> homer = new Person("Homer", 55); [object Object] js> bart = new Person("Bart", 11); [object Object] js> Person.compareAge(homer, bart) 44