Javascript notes¶
Language highlights¶
- Syntax and control structures superficially resemble Java/C/C++
- Dynamically typed
- Has primitive types and strings (which behave more like primitive types than objects) and reference types (objects, arrays, and functions).
- Numbers and arithmetic are floating-point and there is no integer type.
- Runs in an interpreter with automatic memory management (garbage collection). Modern JS engines do some fairly agressive optimizations so it’s reasonably fast.
- Objects are really just name-value pairs (associative arrays). There is a
class
keyword, but in fact there are no classes in the sense that Java has them, but can create class-like definitions using constructor functions and prototypes. - Arrays and functions are just objects with a few special properties
- Functions (or methods) are “first-class” objects and can be stored in variables or as object properties. Functions do not have to be associated with objects, but when they are we can call them methods.
- The scope of a function (method) is a distinct object that contains the local variables in scope at the point of the function’s declaration. The scope can outlast the function invocation, forming part of a closure. Capturing local variables in a closure turns out to be a useful programming technique.
- Implements inheritance via the prototype chain.
JavaScript is frequently run in a browser, though it does not have to be. This document uses a special setup (the Runestone tools from interactivepython.org) so that you can edit and run the code examples within the special text boxes below.
Output goes to a local window using the custom writeln()
function. More realistically, you would use console.log()
in your code to send diagnostic output to the JS console. To see the JS console, you’ll need to bring up the Developer Tools in your browser (Ctrl-Shift-I for Chrome or Firefox). (Unfortunately, using the console is not always a useful thing to do with this page, because of the sheer volume of diagnostic output produced by the Runestone infrastructure.)
The format of the for-loop is familiar, but note that the variable “i” is not declared with a type as you might expect. Any variable will happily store any type you put in it. You don’t actually even need the let
declaration, but you should always use a let
or var
declaration for variables: if you omit the declaration, JS will silently create the variable for you but it will have global scope and may silently hide some already existing global variable. The difference between let
and var
is that let
variables have block scope (like Java) while the scope of a var
variable is the entire function in which it is declared. JS programmers say that var
definitions are “hoisted”, since they act as though they are all moved up to the top of the function (or file) in which they occur.
There is a also a keyword const
that acts like let
except the initially assigned value can’t be changed.
Objects¶
You can create an object with the new
operator or by typing an empty set of braces. Objects can be given properties just by referring to them. You can use dot notation or associative-array notation, since an object is really just a collection of name-value pairs. You can also use JSON notation to set up a list of name-value pairs.
You can define a data type by creating a constructor, which is just a function (more later).
Arrays¶
JS also has arrays, which are just like objects, except values are associated with numbers instead of strings. Arrays don’t have a fixed predefined length, and don’t have to be contiguous (you can still loop through the indices).
null
and undefined
¶
As you can see the elements of an “array” don’t have to be the same type. This example begs the question: what happens if we look at arr[2]
? You might well ask. JS has two interesting values, null
and undefined
, which are not the same. The meaning of null
is similar to its meaning in Java: a special value you can assign to an object reference to specifically indicate that it does not refer to an actual object. On the other hand, undefined
is the value a variable has if it has never been initialized.
The operators ==
and ===
¶
As in Java there is a == operator for equality, but also a different operator for a stricter version of “sameness”, called the identity operator and denoted with a triple equals sign ===. For primitives and strings, == and === have the same effect. Primitives and strings are compared by value, so unlike Java, you can use the == operator to compare strings, or even the === operator. (Weird exception: the value NaN is never equal or identical to any other value, not even itself.) Objects, arrays, and functions are compared by reference and are equal or identical only if they refer to the same object.
null
and undefined
are the same according to ==, but not ===.
The == operator performs type conversion when comparing values of different types. For example, 42 == “42” is true. The === operator returns false for different types.
The corresponding negations are != and !==.
Functions¶
Functions, or methods, in JS are “first-class” objects, meaning that a function can be stored in a variable, passed as a parameter, compared with ==, etc., just like any other values. They can be named or anonymous. In the next example we create two functions, one named f that returns a value and an anonymous function that is stored as a property of an object, and does not return a value. Note the parameters are not typed and are not declared with let
or var
.
Variable scope and lifetime¶
In Java, a local variable comes into existence at the point where it is declared and its scope extends to the end of the block in which it is declared. When execution reaches the end of the block, or when the method returns, the variable effectively ceases to exist. The way we think of this is that locals are just “slots” within an activation frame on the call stack. Of course, a local variable could refer to an object on the heap, and a heap object can outlive any method call.
In JS, local variables have a different kind of existence. They are not just slots in an activation frame on the call stack. In fact, all JS variables are really properties of some object. Global variables are properties of the “global scope object” which, in the case of client-side JS code, is the Window. Local variables are properties of something called the “call object” or “scope object”. The scope object is created when a function is called and the locals defined within the function (including parameters) exist as properties of the scope object. The strange thing is that the scope object, and therefore local variables declared in a function, may continue to exist even after the function returns.
Function definitions can be nested inside other functions. Part of a function definition is a reference to the scope object containing the variables that are in scope at the point of the function’s declaration. For a function declared at the top level, this is just the global scope containing global variables.
A nested function body can refer to any variable declared in any enclosing function as well as to global variables. Specifically, the scope object for a nested function has a reference to the scope object for an enclosing function, which has a reference to the scope for its enclosing function, and so on up to the global object, forming what is called the “scope chain”.
Now, if the nested function is only used inside of the enclosing function, there is nothing special about any of this. But functions are first class values, and references to functions can be returned, stored in variables, or stored as properties of objects. So a reference to a nested function can persist even after the enclosing function returns. And as long as a reference to a function exists, its scope chain continues to exist.
Here is an example with a simple nested function. Note that the body of the nested function that is returned by create() refers on the local variable x.
In the scope chain for f1, x has value 1, and that value persists as long as we hold the reference f1, even though x was a local variable in function create(), which has returned. The second call to create() has its own scope object, in which x has value 5.
A function, together with a scope chain, is called a closure. Closures are frequently used in JS idioms and examples, especially when creating objects with non-public attributes or dynamically creating event handlers (as in Ajax), so it is important to understand them.
Here is a similar example. The inner function returned by mystery() retains variable v in its closure.
It is worth noticing that the call objects making up the scope chain for a given function depend only on where the function is declared, not on where it is called. JS functions are said to be “lexically scoped”.
Constructors¶
You define a data type by defining a constructor function for it. A constructor function is just a function that initializes some attributes of an object. It does not have a return statement. It is invoked using the keyword new
. JS programmers always capitalize the first letter of a constructor name.
Methods and pseudoclasses¶
Note that Point looks like the definition of a class. One of its attributes is a function, which can be invoked like a method in Java. However, the example above differs from a class specification in that there will be a different dist() function for each instance of point we create (which you can see from the output of the last two lines).
Every JS object has an internal reference to another object called the prototype object, and attributes of the prototype object are “inherited” by the object, in the sense that they appear to be attributes of the object. (Although you rarely need to do so, you can examine the prototype of an object directly using the __proto__
property.) When an object instance is created using the new
keyword and a constructor, the prototype for the instance is initialized to be the prototype
attribute of its constructor function. The prototype attribute defaults to be an empty object. So to create a class-like definition in which the methods are shared by all instances, you can define the methods as attributes of the constructor function’s prototype.
What happens is that when you try to invoke distance()
on a Point instance, the interpreter will discover that Point has no attribute named distance
, and will then look in its prototype.
It is important to note that ALL instances of Point will share the same prototype object. So the prototype is a good place to put methods, but not a good place to put attributes. There is a built-in asymmetry: when you try to read an attribute that isn’t defined for an object, the interpreter will look in its prototype and use the value there if it is present. If you try to write an attribute that isn’t defined for an object, the interpreter will simply define it for the object, and ignore the value in the prototype from then on. More on this a bit later.
Inheritance¶
We saw with the Point example that if an attribute is not found in the object, the interpreter will look in the prototype; if not in the prototype, it looks in the prototype’s prototype, and on up the chain to Object. This looks a lot like like the dynamic binding mechanism in an OO language, and in fact it is through the use of prototypes that you can get something like inheritance in JavaScript. There are really two distinct aspects to inheritance.
- Creating and initializing the attributes of an object that were initially defined in the supertype.
- Setting up the prototype chain so that methods defined in the prototypes will be found.
It should be emphasized that JavaScript is extremely flexible and there are several ways to achieve the same results, but this approach is popular. The class
keyword in later versions of JS is essentially a syntactic sugar for implementing this strategy.
The first step makes sense when you think about the fact that a constructor has a slightly different role in JavaScript than in Java or C#. In Java, the attributes (instance variables) are predefined in the class definition, and all the constructor has to do is initialize them correctly. In JavaScript, there’s no such thing as a class definition; every object is initially a kind of blank slate, and the attributes have to be created as well as initialized.
Here is an example.
The class
, extends
, and super
keywords¶
We can implement the above hierarchy more concisely using the class
keyword, introduced in ECMAScript 2015. This is nothing but a compact way to implement exactly the inheritance strategy shown in the previous example. There’s still no such thing as a class in JavaScript.