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 It is almost the case that window.x
and global-scope var x
are the same thing. The exception occurs in the no-redeclarations rule:
You can declare var x
at most once in the global scope, but it’s OK if that one time happens after window.x
already exists.You can’t have both let x
and var x
in the same scope, but you can have a let x
in the global scope and a separate window.x
. If you do, x
will resolve to the let x
, not the window.x
. 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 Web Workers allow more, but we won’t use them 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.
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.