Introduction to JavaScript (ETech 2006 Tutorial)


This morning I'm in the A (Re-)Introduction to JavaScript tutorial taught be Simon Willison.

Simon recommends Javascript: The Definative Guide by David Flanagan as one of the few Javascript references that's worthwhile. He hasn't found a good reference on the Web.

Brendan Eich invented JavaScript in 1995. The ECMA standard went through only 3 revisions with the last one in 1998. Good news: the language is stable. Bad news: there's lots of codified warts.

Javascript avoids I/O, so it's not quite a general purpose language: it has to be embedded in a host environment that supplies this. In the case of the browser, this is the DOM.

Some advice:

  • Use the semicolon even though it's not requires.
  • Declare variables religiously
  • Avoid global variables.

There's no such thing as an integer in JS. Only floating point (double precisions 64-bit IEEE 754 values). There's a ton of Math functions. The parseInt function will guess the base. You can specify it in an optional second argument and you should to avoid guessing.

Strings are sequences of 16-bit unicode characters. Characters are just strings of length 1. Again, there are a ton of string functions that can be used to manipulate strings, including regexp functions. String concatenation overloads the + operators. Type coercion is automatic, so if you add a string to a number, you get a string. If you add a number to a string, you get a number.

null means no value and undefined means no value yet. Booleans are true and false, but there are truthy and falsy values as well. i, NaN, null, and undefined are falsy. Everything else is truthy.

Variables are declared. If you don't use var, the language will automatically define a global variable. This can cause all sorts of hard to debug situations. So, never do this!

== and != do type coercion, so 1 == true. To avoid this, use === and !==.

typeof will give you the type of the object. Arrays, say their objects, which they are, but don't give you the specific answer.

The logical operators (&&, and ||) short circuit and an undefined object is "false" so o && o.getName() will test for existence before applying the method.

Exceptions were borrowed directly from java. So, the have the try...catch...finally... syntax.

Objects in JavaScript are very different than objects in other languages. Objects are collections of name-value paris. The name part is a string, the value can be anything. Objects are created with the new operator, but you can also use a literal representation:

var o = {
 name: "Carrot",
 "for:", "Max",
 details: {
  color: "orange",
  size: 23
  }
};
obj.name = "Simon";
obj["name"] = "Simon";

In the final line, we could have dynamically created the string "name" so this is more flexible. An alternative form of the for-loop let's you access the attributes in the object.

Arrays are the other primary data aggregator. The keys have to be numbers, but they use the [] syntax, just like objects. You can use literal notation to create and initialize an array in one line. Arrays will add items past the end. To append to the end, use a[a.length] = 3 (a.push(3) also works).

If you do a for-loop with arrays, it's sensible to cache the array length in the initialization so avoid calculating the length each time (looking up a property in an object).

for (var i = 0; j = a.length; i < j; i++) {
...
}

Functions are pretty simple. If nothing is explicitly returned, then the return value is undefined. Parameters are more like guidelines. Missing parameters are treated as undefined. You can also pass in more arguments than the function is expecting. The extra arguments are ignored. There is a special array called arguments that contains all of the arguments. So, you can create variable arity functions. The arguments array is almost an array. You can't use all the array operators on it.

Functions are objects with methods. For example, apply is defined on functions.

You can create anonymous functions using the function operator. var avg = function() {...} is the same as function avg () {...}. This gives you the usual benefits: local scoping, closures, etc. Simon spends a bit of time showing how you can use anonymous functions to simulate the behavior of a let (see Lisp). Of course, it's pretty ugly since there are no macros.

The arguments array has a property called callee that points to the function that got called (similar to self, but for functions) so that you can create anonymous recursive functions. The callee property allows saving state between invocations, so you can, for example, define a function that remembers how many times it's been called and puts that in arguments.callee.count Cool.

You can use the property representation of objects to create literal objects that include methods. You can do this using the this keyword to refer to the current object. If you use the method without using the dot notation of method invocation, then this refers to the global object.

The problem with literals created in this way is that each object contains the function code. Using the new operator avoids this. new creates a new empty object and calls the specified function with this referencing the current object. Functions can be defined on the object using the prototype method.

function Person(first, last) {...}
Person.prototype.fullname = function () { ... }

This is a potentially confusing point because JavaScript is a prototype-based object-oriented language, not a class based language. Inheritance in prototype based languages happens by creating copies of prototypes.

A nice side effect of prototypes is that you can add new methods to objects at runtime. Existing instances of the object will look at the prototype to find methods and find the new methods. This applies to the core objects of the language as well, so you can add a reversed method to the built-in string object. This method would be seen even by string literals. Be careful, you can redefine built-ins doing this. Simon tells the story of using this feature to add a specific functionality to JavaScript on Safari that wasn't properly implemented prior to version 2.0.

Prototypes can be nested, creating chains. Deep nesting combined with dynamic prototype creation can make chains hard to debug. All chains terminate at Object.protoype. Object includes a toString method, for example. So, overriding it gives good error messages, etc. because you inherit properties (like read-only or don't-enumerate) defined in Object

JavaScript is a prototype-based language that pretends to be class-based, so it doesn't do either very well. You can use this idiom to reuse an object:

function Geek() {
 Person.apply)this, arguments);
 this.geeklevel = 5;
}
Geek.prototype = new Person();
Geek.prototype.setLevel = function(lvl) {
 this.geekLevel = lvl;
}
Geek.protoype.getLevel = function() {
 return this.geekLevel;
}

> s = new Geek("Simon", "Willison")

The prototype chain, in this instance is Geek --> Person --> Object. This is a little weird because we're creating an instance of Person to serve as the prototype (rather than a prototype). This makes constructors lame because you can't do anything useful in the constructor. If you google "javascript inheritance" on the Web, you'll see there are dozens of proposed solutions (workarounds) to this problem.

Functions in JavaScript are first class objects. A function is just another object. You can store it in a variable, pass it around, return it from a function, and so on. Simon starts with an example of defining arrayMap, a function that maps a function over an array, creating a new array. Next he shows how to define a function, salesTaxFactory that takes a tax rate and returns a function that calculates tax for that tax rate (i.e. he defines a closure).

Simon shows off the shell bookmark (that only works in Firefox). The book mark opens a JavaScript shell that you can use to test JavaScript.

A closure is a function that has captured the scope in which it was created. Functions in JavaScript have a scope chain that reference scopes that function is defined in. This is similar to other block structured languages that allow anonymous functions. This can cause problems when you refer to a loop variable, for example, since every closure created in the loop will refer to the same iteration variable, which has the final value, not he value it had when it was created.

Simon introduces the singleton pattern. This avoids namespace collisions. JavaScript has no sense of modules or packages. Any JavaScript running in the browser can access any other names. The less code that affects the global namespace, the better.

The singleton pattern uses closures to hide information. Here's an example:

var simon = (function () {
  var myVar = 5;
  function init(x) {
  ... // can access myVar and doPrivate
  }
  function doPrivate(x) {
  ... // invisiable to the outside world
  }
  function doSomething (x, y) {
  ... // can access myVar and doPrivate
  }
  return {
    'init': init, 'doSomething': doSomething
  }
})();

This is exactly what you'd do in Scheme or Lisp to create a self-defined object. Ironic for JavaScript since you're essentially using closures to create objects in an object-oriented language. Again, with macros, the ugliness could be hidden, but alas...

One of thorniest problems in JavaScript is memory leaks. JavaScript is a garbage collected language. If you're using large amount of memory, you need to make sure you cancel references and let the garbage collector do it job. Internet Explorer handles garbage collection differently for JavaScript and the DOM and can't handle circular references, creating ugly memory leaks. Call this function enough times in IE and it will crash:

function leak() {
  var div = document.getElementById('d');
  div.obj = {
    'leak': div
  }
}

This pattern can show up when you create a slider widget, for example. Apparently, this same bug showed up in the 1.5 alpha release of Firefox. It's been fixed now???

There are more sneaky examples.

function sneakyLeak() {
  var div = document.getElementById)'d);
  div.onclick = function() {
   alert("hi!");
  }
}

This is a common idiom. This can be hard to detect. The problem is that closures maintain access to the environment they were created in, regardless of whether they access anything in that environment or not. One way to avoid the problem is to assign null to div at the end of the function. This throws the reference away and lets the garbage collector pick it up.

Simon mentions that these are tricky problems and he'd programmed JavaScript for three years before he really understood them. Interestingly, anyone who's had CS330 would be equipped to understand these problems quickly. Another reason 330 is important.

Simon mentions that using most popular JavaScript libraries have systems for attaching and detaching events. Many of these can automatically free event references when the page is unloaded. These can be used to solve circular reference problems. Using libraries can solve a big chunk of the problem, but be aware.

Everytime you do complex lookups (using dot notation), dereferencing them can increase performance. This is especially important inside loops. Here's an example of dereferencing (actually it seems more like referencing to me):

var s = document.getElementById('d').style;
s.width = '100%';
// and so on

You see this in drag and drop operations, for example.

Simon recommends some JavaScript libraries:

  • dojotoolkit.org
  • developer.yahoo.net/yui - Yahoo! User Interface libraries. Similar to others. Good drag and drop, animation, etc. facilities. Designed to play well with each other and give good performance on high traffic Web sites.
  • mochikit.com - borrows a ton of ideas from Python and functional programming
  • prototype.conio.net - most famous library (included in Rails). Extends the language and has it's own style. Need to understand the style to use it. Comes with a set of tools for DOM manipulation and AJAX stuff.
  • script.aculo.us - Extension of prototype and adds lots of visual effects.

Things that every JavaScript programmer should know:

  • Everything in JavaScript is an object, even functions.
  • Every object is always mutable
  • The dot operator is equivalent to de-referencing by hash
  • The new keyword creates an object that class constructors run inside of, thereby imprinting them
  • Functions are always closures
  • The this keyword is relative to the execution context, not the declaration context (dynamically scoped)
  • The prototype property is mutable. This is the basis of inheritance in JavaScript.

Someone asks a question about macros and Simon starts talking about eval. His basic advice: don't use it. Slow, dangerous, etc. Much of what he saus is right for JavaScript, but not for languages with real macros like Lisp and Scheme. It's interesting that people who don't know about a particular langage feature just can't see how it could be used. This isn't a dig on Simon--he could be completely right with respect the JavaScript. Yet, I wonder...

Simon did a good job with this tutorial. He introduced JavaScript in a way that didn't lose people and at the same time brought out the gotchas that more experienced programmers would want to know. It's not easy keeping a language tutorial interesting, but Simon did alright.


Please leave comments using the Hypothes.is sidebar.

Last modified: Thu Oct 10 12:47:18 2019.