This is not a full description of JavaScript. Rather, it is the most important things to know for the JavaScript we’ll use, assuming you know Java or C++ already.
We’ll run JavaScript as part of web pages. In that context, JavaScript’s primary way of communicating with the user is by modifying something called the DOM, a hierarchy of objects describing the visible contents of the webpage. We’ll use it for that, but during development we want some kind of more direct programmer-centric display.
That programmer-centric display can be found in the console
,
which is hidden by default in most browsers. There are two versions of
it too: one that just shows output and one that also allows you to
directly interact with the webpage through JavaScript.
The common keyboard shortcuts for opening the console include
It’s generally also available from a menu item under a name like
console
, JavaScript console
, browser console
,
developer tools
, or web inspector
. It might open in a view
with several tabs, only one of which is the console.
If you’re in the right console, you should be able to type an
expression like 2 + 3
and see the result
(5
).
If you type code in the console, you’ll see its result immediately. If you put code in an HTML or JavaScript file or inside a function, you won’t.
There is a global variable named console
that has
methods for putting information into the console. There are five such
methods, each with a different display mode.
Example invocation | Displays as |
---|---|
console.error(0) |
an error icon with redish background; also shows call stack |
console.warn(0) |
a caution icon with yellowish background; may show call stack, depends on browser |
console.log(0) |
no icon, no background |
console.info(0) |
an info icon or no icon, no background |
console.debug(0) |
no icon or background |
Consoles also let you pick which kind of messages you want to see, ranging from only errors to all five levels.
All of the console functions are variadic, accepting as many arguments as you wish; for example
console.log("text, number, array", 3+1+4+1+5, [3,1,4,1,5])
JavaScript uses a syntax inspired by Java, which in turn uses a syntax inspired by C++. It has a very different semantic model, though, so some parts like type names just get discarded entirely. It also has something that Java and C++ could have done but didn’t: automatic semicolon insertion.
Formally, JavaScript requires semicolons at the end of statements. Practically, it can almost always figure out where you needed one and add it for you—not by modifying your file, just by pretending you put a semicolon there.
Developers who come to JavaScript from semicolon-requiring languages like Java and Perl tend to include semicolons. Developers who come to JavaScript from non-semicolon languages like Python and LISP tend to omit semicolons. I regularly use both semicolon and non-semicolon languages, so my examples might be inconsistent in whether I add semicolons or not.
JavaScript is dynamically typed. That means that the type information is stored with the value, not the variable, and thus that the same variable can store multiple different types.
= 3 // x becomes the number `3`
x += 2 // x becomes the number `5`
x = "3" // x becomes the string `"3"`
x += 2 // x becomes the string `"32"` x
Every value is an object (in the object-oriented sense; JavaScript
also uses the word object
to mean something like a hash map),
even those that are primitives in other languages
2/3).toFixed(3) // the string `"0.667"`
(true.constructor // the function that creates Boolean values
Variables can be declared using either var
or
let
. let
creates a variable scoped to the current block (i.e., it will vanish at
the closing }
). var
creates a variable in scoped to the current function.
let a = null, b = null, c = null, d = null
function f() {
let a = 1
var b = 2
if (true) {
let c = 3
var d = 4
console.log(a,b,c,d) // shows `1 2 3 4`
}console.log(a,b,c,d) // shows `1 2 null 4`
}f()
console.log(a,b,c,d) // shows `null null null null`
Used outside of a function, var
declares a variable and
also puts them into the special
object. We
can access and modify global variables inside a function using window
window.variableName = value
.1 let
does not put its
variables in the window
object.
When you use a variable, JavaScript looks for it in the current
scope, then every containing scope, and then in the window
object, stopping when it finds it. If it does not find it, it might do
one of two things:
var
to declare it
instead.Strict mode can be entered by putting the exact statement
'use strict';
before any other statement in your
file or function. In most browsers, the console is never in strict
mode.
const
also exists and acts like let
except
it prevents subsequent assignments from changing the value stored in the
variable.
Functions can be defined in JavaScript in several ways:
function add(x,y) { return x + y }
– defines add
as a function in the same scope that
var add
would.add = function(x,y) { return x + y }
– defines a function like any other literal and assigns it to a
variable.add = (x,y) => { return x + y }
– defines a function like any other literal and assigns it to a
variable.add = (x,y) => x + y
– defines a function that only has one statement in its body like any
other literal and assigns it to a variable.Technically the functions defined with function
and
those defined with =>
have some differences, notably
when it comes to how they handle the this
keyword, but we
won’t need to know those differences for our purposes.
JavaScript has two kinds of nothing
: null
, which
you have to set explicitly, and undefined
,
which is used to indicate something was missing (e.g. window.qwertyuiop
is undefined
,
not null
).
There are two kinds of comparison in JavaScript. The ==
operator checks that values match, and the ===
operator
checks that values and types both match. Notably, numbers and strings
can be ==
each other,
3 == "3.0" // true
3 === "3.0" // false
undefined == null // true
undefined === null // false
JavaScript has a built-in dict/map type called objects
,
written with braces: {"one":1, "two":2}
.
Regardless of their type, keys are converted to strings before they are
used; values are left as-is. The usual bracket operator x[y]
also has a shorthand version
for compile-time-known string-valued keys: x["thing"]
and x.thing
are synonyms.
You can use []
and .
on any value type;
however, numbers, Booleans, and strings ignore any assignments using
these operators.
JavaScript has a built-in list type, written with brackets: [3,1,4,1,5]
.
Called arrays
, these are just a special type of object
,
meaning you can also treat them like dict/map types, though doing so
might result in some unexpected behaviors if they conflict with how the
array expects to use its fields.
There is much
more to know about objects, including prototypes,
constructors,
polymorphism, method resolution, the
this
keyword, calling
vs applying
methods, better
Map
objects, and so on. We won’t need any of that for
this course.
JavaScript wants to be responsive and have few errors. Threads tend
to result in programmer errors. Letting code wait for something like a
network or file system to return makes things unresponsive. JavaScript’s
solution is to have only one thread2 and have any code that
would normally wait instead return immediately with no results. These
return-before-they’re-really-done functions are called
asynchronous
.
Code that returns without a result begs the question, how do we get
the results? Unfortunately for us, the answer is it depends on when
the delayed-result function was added to JavaScript
.
JavaScript has a robust event system. Type a key, press a button or move the mouse and an event is created. You can request to be informed of events, providing a JavaScript function to be called on each one. Events are associated with a triggering object and an event name, so for example to be informed when the mouse is moved while pointing at a button you’d tell the button object to inform you of mousemove events.
Early asynchronous methods in JavaScript return an object that
generates its own events, such as the XMLHttpRequest’s
load
, error
, and readystatechange
events.
One we’ll use often is the window
object’s load
event which tells us that all the bits
and pieces of a webpage are ready for use and we can start hooking up
user input with display canvases without worrying that one or more of
the involved components is missing.
window.addEventListener('load', event => {
// get the gl object for our canvas, which we use to talk to the GPU
// initiate any setup that depends on it
})
Some functions don’t return a value; instead, we pass in to them other functions they should call on success or failure.
The main example of these we’ll use is the functions that request a delay, either for a fixed time window or until some event occurs. If I want to do something, then wait for 20 milliseconds and do it again, and so on forever you might naively expect to write code like
repeat forever:
do something
wait 20 milliseconds
but instead we’ll write
function repeatedly() {
something(...)
setTimeout(repeatedly, 20)
}
Here setTimeout
accepts the name of a function to be called and how long to wait before
calling it. Thus, the repeatedly
function invokes
something
and then requests that the
repeatedly
function be invoked again in 20ms.
Of particular interest to graphics is the requestAnimationFrame
function which will attempt to call its function at a frequency that
matches your monitor’s refresh rate. A common model for a continuously
changing animation will be
function updateDisplay(milliseconds) {
let secondsSincePageLoaded = milliseconds / 1000;
// compute new positions of objects
// clear the old content
// draw the new conent
requestAnimationFrame(updateDisplay)
}
If we instead only update the display in response to user input, we can save effort by only updating the display when needed with something like
function userAction(event) {
// compute new positions of objects
requestAnimationFrame(updateDisplay)
}function updateDisplay(milliseconds) {
// clear the old content
// draw the new conent
}
We don’t call updateDisplay
directly in
userAction
because user actions might arrive more often
than the screen refresh can draw them.
In 2017 JavaScript gained two new keywords: async
and
await
, and with them a new way of handling asynchronous
functions.
If a function is declared as aync function
it
returns an object called a Promise
before it is done executing.
There are two ways of getting at the final return value of the function:
The Promise
’s then
function accepts a
single-argument function which it calls with the result when ready.
fetch(url) // fetch is an async function
.then((result)=>{ // `()=>{}` creates an anonymous function
// ... // use the result here
})
Inside an async function
(and only there) you can
use await
to halt operation until a result is
available.
let result = await fetch(url) // fetch is an async function
// ... // use the result here
Using async function
and await
can lead to
much cleaner code than other methods of handling asynchronicity, but
await
can only be used in other
async function
s so if we’re not in an
async function
we’ll use then
callbacks
instead.
Web pages are usually written in Hyper-Text Markup Language (HTML). HTML is not a full programming language, but it is complicated enough to be larger than we want to explore in full in this course. It has also gone through various changes over its life, but in a mostly backwards-compatible way, meaning that there’s generally several ways to do anything you might wish to do.
Most text can be included in HTML as-is.
Angle brackets delimit tags. An opening tag has an identifier following the opening angle bracket, called the tag name. A closing tag has a slash and then the tag name. An opening and closing tag pair with the same tag name form an element; anything between the two is a child node of the element.
This is just text<p>we've now entered an element with tag name "p"</p>
and exited it again
Elements may have key-value pairs called attributes defined as part of their opening tag. These consist of an identifier, an equals sign, and a value in quotes with no intermediate spaces.
<script type="text/javascript" deferred="deferred">
// here we have a script element of the text/javascript type
// it also has a deferred attribute
</script>
Some attributes can be given with no value
<script type="text/javascript" deferred>
// same as above
</script>
and some types of elements are defined as never having children and hence not needing a closing tag
Some text<br>
Some more text, not part of a br element because br doesn't need closing
and some element types self-close when a new element type that can’t be its child is encountered, and some can self-close with a trailing slash in the opning tag, and … well, HTML has grown complicated over the years.
Every HTML file we write should have the following general structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The name this tab should have in a browser</title>
<script>let here = "we have in-line Javascript"</script>
<script src="other-file.js"></script>
</head>
<body>
<canvas id="mycanvas" width="500" height="500"></canvas>
</body>
</html>
<!DOCTYPE html>
lets browsers know we are writing
HTML5, not some other version of HTML, and increases the chances they’ll
display our code correctlyhtml
elementhtml
element should indicate the language any
user-visible text is in to assist in language-specific functionality
like hyphenation and screen reading.head
element must come before the body
element and should have
meta
element identifying the character set you write
the file in. The correct value here might depend on your text editor,
operating system, and locale settings. UTF-8 is recommended if you have
the option of changing it.title
element to give the webpage a name to show on
browser tabs, bookmarks, window title bars, etcscript
elements, either in-file or
referencing a separate filebody
element should have anything that is visible
to the user; we’ll most often use a canvas
element to
display the results of 3D renders.Note, most of these rules cane be bypassed most of the time; browsers work very hard to figure out what you meant if you don’t do what you were supposed to do. Hence, these rules are arguably all rules of politeness rather than rules of necessity.
Web browsers both want to let websites run code on your computer and want to protect you from the worst kinds of code that might be run. Servers want to provide some of their information to anyone who wants it, but some of it they want to limit to use on their own sites. Browsers want to be fast, only asking a server for information if they don’t already have that information on hand. The policies and practices inspire by these desires can sometimes make developing code in JavaScript more complicated than you’d expect.
HTML files can be viewed directly from your local disk; when so
viewed, they have a URL beginning file://
. However, files
viewed that way are less trusted by the browser than those served by a
web server with the http://
or https://
schemes, and operations like loading other files into the webpage’s
memory are unlikely to work.
Instead, you’ll need to run a local webserver to test your code with. This involves two parts:
Run a local webserver. You’ll need to do this from the command line from directory that contains your HTML file.
Pick one of the following:
With Python 3
python -m http.server
With Python 2
python -m SimpleHTTPServer
With PHP
php -S [::1]:8080
With Node.js
Install once with npm install -g httpserver
.
Once installed, run
httpserver 8000 localhost
There are many other webservers you could use too; I assume if you have one of those you already know how to use it.
Use the local server to view your files.
For example, if your file is named myfile.html
, in your
browser go to one of the following:
Depending on your OS and browser and which tool you used to start the server, it may be that only one of the above URLs works.
If you don’t do this, it is likely you’ll get an error message in the
console when using fetch
or loading a texture image that
includes the words CORS in it.
To avoid re-requesting files often, caching is used in many places on most systems, including in the browser, in the operating system, and in the various computers between you and the server. Unfortunately, caching can cause a webpage you’ve edited to not re-load, particularly if you have several files contributing to the page.
Because caching is handled in different ways be different tools, there’s no one-size-fits-all solution, but a combination of the following generally works:
Browsers have some kind of private window option: Google calls it Incognito, Microsoft calls it InPrivate, Mozilla and Apple call it Private. In this mode, caching is partially disabled, resulting in more reliable development.
In addition to the usual reload/refresh option, browsers provide
a hard refresh
by holding Ctrl (Windows/Linux) or Shift (MacX)
while pressing the refresh button or while typing the reload key
sequence.
I had one student who didn’t seem to have anything different in his setup than other students but who couldn’t make those options work. For him, the only way to reload multi-file locally-developed webpages was to exit the browser entirely, stop and restart the local web server, and then open the browser anew. Hopefully that won’t be your experience, but if it is that process does seem to work.
When loading images in JavaScript, some images won’t load, instead giving a CORS-related error in the console.
Servers can be configured to refuse javascript-based requests to load files. For example, the images on the https://illinois.edu homepage are set up this way; if you try to load them as a texture map of the like you’ll get a CORS error.
Because this is a configuration setting of the server, you can’t directly bypass it. Instead you’ll have to work around it, e.g. by loading the image in the HTML of the page, copying the image to a directory you control, or using a different image.