A Brief Javascript Tutorial: Part 1
A BRIEF JavaScript TUTORIAL: PART 1
The “.js” in three.js stands for JavaScript, and as you might expect, you will need to know some JavaScript to be able to use three.js. However, these days there are a couple of flavors of the language, so we’ll spend a bit of time here defining exactly which features we’ll be using.
Let’s start by taking a quick look at the state of JS today and the position that puts us in, as three.js developers.
A Very Brief History of JavaScript
The JavaScript language has gone through something of a rebirth in recent years, during which time it went from being an ugly duckling to something that you might, with a little grace, call a disgruntled swan. Not quite beautiful yet, and still with its annoyances and problems, but much, much better than it used to be.
The committee that oversees the development of the language is called Ecma International and the technical standard that JavaScript follows is called ECMAScript.
JavaScript has gone through various iterations, from Version 1 way back in 1996, through to version 5 in 2009. It stagnated there for a couple of years, with a single minor update to 5.1 in 2011.
Finally, a much-needed Version 6 was released in 2015, dubbed ECMAScript 2015, commonly called ES6 for short. There was a lot of new syntax introduced here, but this release also marked a change in the way versions will be released going forward. From now on there will be one version per year, called ES2016, ES2017 etc. Collectively, these are sometimes referred to as ESNext.
At the same time, browsers have largely switched to an evergreen updating system. If you use Firefox, you’ll frequently see the “Updating Firefox…” message. Chrome, Edge, and Safari do the same, but silently. This means that your browser automatically supports a lot of the new syntax, with more being added all the time.
You can see a big and ugly support table for everything related to JavaScript, new and old, on kangax's site or a much nicer but less complete list on caniuse.com, both of which you can use to judge whether you should use a new JavaScript (or CSS) feature or not.
What About People Using Old Browsers?
Unfortunately, despite this automatic updating system, many people will still be using out of date browsers that don’t support the latest language features. For the purposes of this book, and to keep things simple, we’ll be assuming that you are using an up to date browsers.
Of course, in a production app that’s not an acceptable assumption to make, but fortunately, we can compile our ES6 code down to ES5 code that will run in old browsers using a tool such as Babel, and we’ll cover how to do that in Section Two.
JavaScript Used in “Section One: Getting Started”
Here’s everything that you need to know about JavaScript to follow through to the end of Section One.
All JavaScript that we write will go inside files with a
.js
extension. We can include JavaScript in our HTML using a <script>
tag, as described in the previous chapter.<script src="https://threejs.org/build/three.js"></script>
We can also write JavaScript directly in our HTML like this:
<script>
const x = 'welcome to JavaScript!';
</script>
However, in this book, we will always write our code in separate
.js
files, never write it directly in our HTML.Comments
Single line comments in JavaScript start with a double forward slash:
//
// This is a comment - you can write anything you like here it will get ignored
// This is the second line of the comment
Comments can also be defined in “multiline style”.
/*
This is a multiline comment.
Everything inside here gets ignored
*/
We’ll only ever use the single line comment style in this book.
Primitive Data Types
Until recently there were just 5 primitive data typed in JavaScript: Numbers, Strings, Booleans,
null
, and undefined
.
A new type called symbol was added recently, however, we will not make use of it in this book. Let’s take a look at the other 5 now.
Number
Unlike some other languages, JavaScript does not differentiate between an Integer such as -23, 0, or 100 and a floating point number such as 0.05, 23.0002, or 4.5. All numbers are treated equally and in general, are very easy to work with.
In addition to representing standard numbers as you might expect, the Number data type includes three special symbols:
Infinity
and -Infinity
and NaN
(Not a Number).Boolean
Booleans are the keywords
true
and false
. We can use them as flags to let us know whether something is switched on or not, or has been completed, or needs to be updated:const updateTheThing = false;
const lightIsOn = true;
const modelHasLoaded = true;
const animationIsPlaying = false;
They are also returned by comparison operators which we’ll look at below.
String
In Section One we’ll stick to ES5 style strings which can be defined using single quotes:
const a = 'Hi there!'
Or using double quotes:
const b = "I'm a bumblebee!"
As you can see, when using double quotes were able to write the word I’m which has a single quotation mark in it. To write this as a variable using single quotes, we would use a backtick escape character:
const b = 'I\'m a bumblebee';
We’ll always use single quotes throughout this book, and use “\” whenever it’s necessary to write words like
'We\'ll'
, 'Here\'s'
or 'I\'m'
.Template Strings
Later in the book, we’ll also use template strings which were introduced with ES6. These are defined using “backticks”, and allow us to do quite a few extra things. The feature we need is the ability to write multiline strings, and we’ll use template strings only when we need to do so:
const x = `
This is a multiline string
which can contain all kinds of
fancy punctuation and would've been
impossible to do up until a couple of years ago
^_^
`
We can also use template strings to include easily include variables in our strings. Previously, we would have to do this:
const a = 2;
const b = 3;
const answer = 'The sum of ' + a + ' plus ' + b + ' is: ' + ( a + b ) + '.'
Phew!
Fortunately, with template strings this gets much simpler, since we can just wrap a variable inside
${}
:const answer = `The sum of ${a} plus ${b} is ${(a + b)}.`
Much better!
Null and Undefined
The final two primitive data types,
null
and undefined
, are very similar in JavaScript. Both mean “nothing here”.
The main difference is that
undefined
get assigned automatically whenever some value is not found, whereas null
only happens when the programmer types in null
somewhere in the code.
So, if you ask JavaScript what is
x
? but you’ve never given any value to x
, then it will tell you x
is undefined
.
By contrast, you might set
x = null
somewhere in your code to let yourself know that x
has not been given a value yet.Objects
Objects in JavaScript are defined using curly braces:
{}
, and hold collections of data in [key, value]
pairs. Everything that is not one of the above primitive data types is an object, including functions and arrays.
Here, we are creating an object called
myObject
with two keys x
and y
which hold values 5
and 'hello'
respectively.const myObject = {
x: 5,
y: 'hello',
}
Keys are also referred to as property names, and values are also referred to as properties.
Property names are always strings, whereas properties can be any data type (numbers, strings, arrays, other objects, functions,
null
, undefined
, etc ).Accessing data in Objects: Dot Notation and Bracket Notation
The preferred way of accessing data in objects is using dot notation:
const n = myObject.x; // now variable n holds the value 5
Alternatively, we can use “bracket notation”:
const p = myObject[ 'y' ]; // now variable p holds the value 'hello'
We’ll mainly use bracket notation when we need to use a variable to access the data:
const myObj = {
prop: 5,
}
const y = 'prop';
const z = myObj[ y ]; // z now holds the value 5
Arrays
Arrays in JavaScript are defined using square brackets:
[]
and hold single values ( which can be any JavaScript values such as numbers, strings, other arrays, other objects, functions, etc. ).const myArray = [ 'hello', 'goodbye', 45, 23 ]
We can then access data from the array using an index, starting at zero - in the above array, index 0 holds the string
'hello'
and index 3 holds the number 23.
We can access the indices using square brackets:
const x = myArray[ 0 ]; // x now holds the string 'hello'
const y = myArray[ 2 ]; // y now holds the number 45
Arrays can contain other arrays, which we call nesting:
const arr = [ [ 1, 2 ], [ 3, 4 ] ];
Arrays have a number of convenience methods to make accessing or modifying the data they contain easier. Check out the Arrays page on MDN for a complete list.
In particular, we’ll use
push
quite a bit to add new items to the end of the array:const arr = []; // create an empty array
arr.push( 2 ); // arr = [ 2 ]
arr.push( 'bumblebee', 'butterfly' ); // arr = [ 2, 'bumblebee', 'butterfly' ]
We can also find the length of the array using
Array.length
:const myArray = [ 'hello', 'goodbye', 45, 23 ]
const length = myArray.length; // length = 4;
And we can access the last element in the array using
length - 1
as the index:const lastElem = myArray[ myArray.length -1 ]; // lastElem = 23
The
-1
is needed since arrays are indexed from zero, not one.
Variables: let
, const
, var
var
Old style JavaScript had one way of defining a variable, using the keyword
var
. So, we would write:var a = 'hello'; // a string
var b = 5; // a number
var e = [ 1, 2, 3, 4, 'f', 'g', 'h', 'i' ]; // an array containing numbers and strings
ES6 introduces
let
and const
, which are basically designed to replace var
. You can still use var
to keep things backward compatible, but you don’t need to anymore, and we’ll never use var
in this book.const
const
is used to assign data to a variable that cannot be changed later.const x = 5; // x must always equal 5 for the lifetime of your program
Attempting to change it later will cause an error:
x = 6; // Error! Can't update constant variable!
let
When using
let
to define a variable you are saying that the value will get changed sometime in the future:let y = 5;
Unlike the const example, doing this is fine:
y = 6; // no error, y now holds the number 6
The reason for this split, aside from making it easier for us humans to read the code, is so that the computer can optimize the code by knowing which things may need to be updated.
You should always define a variable using
const
unless you know it will need to be changed at sme point.Case Sensitive
Variable names in JavaScript are case sensitive:
const apples = 4;
const Apples = 5;
Here,
Apples
and Apples
are two different variables.CamelCase
We’ll always use CamelCase for variable names that consist of more than one word:
const myName = 'Lewy';
let modelHasLoaded = false;
Dynamic Typing
JavaScript is “loosely” or “dynamically” typed language. In short, this means that we don’t care what data type a variable holds. This means that we can do this:
let x = 5; // x holds a number
// sometime later
x = 'lemon'; // x now holds a string.
This makes the language very flexible, but it can also lead to confusion as it puts the responsibility on you, the programmer, to remember what kind of data a variable holds. One of the benefits of
const
is that you know a variable will always hold the initial data that you assigned to it.Function
Functions are defined using the
function
keyword and look like this:function () {
// do some stuff here
}
This is an anonymous function, meaning that it has no name, and therefore no way for us to refer to it later. While this is a common way of writing functions and we’ll use this in a couple of places, in general, it will be more useful for us to give our functions a name. We can do this in two ways. First, by assigning the function to a variable:
const myFunction = function () {
// do some stuff
}
Or, by directly naming the function:
function myFunction() {
// do some stuff
}
In either case, we can later call the function using either its name or the name of the variable that we assigned it to:
myFunction();
Functions can also be empty:
function placeHolderFunction() {};
It’s common practice for writing placeholder functions that help to keep your code organized.
Function can take parameters:
function add( a, b ) {
const sum = a + b;
}
Later, we can use, or call, this function to add two numbers:
add( 1, 2 );
Or two strings:
add( 'caterpillar', 'butterfly' );
Or two objects:
const x = {
a: 'hello',
}
const y = {
b: 'goodbye',
}
add( x, y );
Or even a string and a number:
add( 2, 'asparagus' );
JavaScript doesn’t care what type of variable you use. Just be warned that, while adding strings or numbers together will probably do what you expect it to, passing in random things will probably give crazy results.
The return
keyword
Of course, the
add()
function above is not very useful. It does add two things together, but it doesn’t give us any way to see the results! To get data out of a function, we need to use the return
keyword.function add( a, b ) {
return a + b;
}
const x = add( 1, 2 ); // x = 3
The function will immediately exit when it encounters the
return
keyword, and nothing else in the function will be processed:function useless( a, b ) {
return;
const sum = a + b; // your program will never reach this line.
}
Note in the above function, we are not returning anything. That’s fine, the result of the function will just be undefined:
const x = useless( 1, 2 ); // x = undefined
Arrow Functions
Arrow function syntax is a new way of defining functions that was introduced in ES6. They are similar to normal functions, however, they have a shorter syntax.
Here’s an anonymous arrow function:
() => {}
Let’s create a subtract function o compliment our add function, written in arrow style:
const subtract = ( a, b ) => {
return a - b;
}
Actually, we can go even shorter than that since leaving out the curly braces makes the
return
keyword implicit:const subtract = ( a, b ) => ( a - b );
There are important technical differences between arrow functions and normal functions, however, for our purposes, we’ll mainly be using whichever one makes our code clearer and easier to read.
Arithmetic operators
All the standard mathematical operators are in JavaScript and they look like this:
const sum = 1 + 1; // addition
const sub = 5 - 3; // subtraction
const div = 10 / 2; // division
const mult = 5 * 20; // multiplication
There is also unary negation, which means” take a variable and makes it negative”:
const a = 5;
const b = -a; // b = -5
Of course that means we must also have unary plus:
const a = 5;
const b = +a; // still 5
Its use is completely different though, as it turns out the fastest way to turn something, such as a string, into a number:
const a = '3'; // a string holding the character 3
const x = +a; // x now holds the number 3
It doesn’t work for everything though, and in most cases will return
NaN
(not a number).const x = 'irreverant';
const y = +x; // y = NaN
In addition to these, there are the increment and decrement operators:
let a = i;
a++; // return a and then add one to a
++a; // add one to a and then return it
a--; // return a and then subtract one from a
--a; // subtract one from a and then return it
The remainder operator gives the remainder when the left operator is divided by the right. It always takes the sign of the left operator:
101 % 10; // 1
-101 % 10; // -1
Finally, there is the exponent, or to the power of, operator:
const a = 2;
const b = a ** 2; // b = 2 * 2;
const c = a ** 3; // c = 2 * 2 * 2;
const d = a ** 4; // d = 2 * 2 * 2 * 2
Assignment operators
First of all is the basic assignment operator, which uses the equals sign. We’ve already used this quite a bit:
const x = 5;
const y = 'hello';
The rest of the assignment operators are shorthand for each of the arithmetic operators:
a += 5; // a = a + 5
a -= 5; // a = a - 5
a /= 5; // a = a / 5
a *= 5; // a = a * 5;
a **= 5 // a = a ** 5
a %= 5 // a = a % 5
Logical Operators: AND, OR, NOT
There are three logical operators in JavaScript: AND, OR and NOT, which are defined using
&&
, ||
and !
, respectively. When used to compare Boolean values (true
and false
), these work much as you’d expect:const x = true;
const y = true;
const z = false;
x && y; // true AND true -> true
x && z; // true AND false -> false
x || y; // true OR true -> true
x || z; // true || false -> true
!x; // NOT true -> false
!z; // NOT false -> true
However, more care needs to be taken when comparing other values (see the section on Truthy and Falsybelow), it’s worth spending some time studying the documentation so that you can learn to compare things intuitively.
Comparison operators
Comparison operators compare two values and return a Boolean value (
true
or false
). Figuring out what will evaluate as true and what will evaluate as false is sometimes tricky in JavaScript since you can compare anythingwith anything. For example, is the string “apples” less than the number 5?
To keep things simple, we’ll just use numbers and Booleans here. In your code you should always try to make sure that you are comparing sane values, since comparing
'apples'
with const myObject = { a: 1 };
is a common source of frustrating errors.
Getting into a discussion of this is a bit beyond us for now, so let’s just look at the comparison operators that we’ll use is this book. First up, “strict equality”, meaning “exactly the same”, which is denoted by three equals signs (
===
):'bumblebee' === 'butterfly';
5 === 5; // true
true === true; // true
There’s also non-strict equality defined by two equals signed:
==
, but unless you know what you’re doing, things are more likely to work as you expect if you always use the strict version ===
.
Next, strict inequality denoted by
!==
5 !== 4; // true
5 !== 4; // false
'apples' !== 'dragon fruit'; // true
There also the non-strict version:
!=
, but again, you should avoid using that.
The less than (
<
) and greater than (>
) operator are next:3 < 5; // true
6 > 23; // false
6 < 6; // false
Finally, we have the less than or equal (
<=
) and greater than or equal (>=
) operators:6 <= 6; // true
34 <= 2; // false
34 >= 2; // true;
Control Flow
Control Flow refers to various methods of controlling what your program does in a given situation. There are a number of ways of doing this in JavaScript, however, in this book we’ll exclusively use
if...else
statements to control the flow of our program.
If..else statements allow us to create branches in our code.
if( percentLoaded <= 70 ) {
const barColor = 'blue';
updateLoadingBar( percentLoaded, barColor );
} else if ( percentLoaded < 100 ) {
const barColor = 'red';
updateLoadingBar( percentLoaded, barColor );
} else {
hideLoadingBar();
startAnimation();
}
Truthy and Falsy
We can also directly compare check the value of an object, for example:
// set the initial model value to null
let model = null;
// call the loadModel funcion
loadModel( model );
// Since loading the model will take some time.
// we can periodically check if it's ready yet using
if( model ) {
startAnimation;
}
Doing this puts the variable
model
into a so-called Boolean Context, meaning that we must interpret it as either true
or false
. Every value in JavaScript can be interpreted as a Boolean in this way, and in general, the interpretation is what you would expect.
In fact, nearly everything is interpreted as
true
(truthy) in a Boolean context, while things like false
, 0
, -0
, null
, undefined
, ''
and ""
(empty strings) and NaN
(not a number) are interpreted as false
(falsy).Doing Similar Operations Many Times (Loops and Iteration)
One of the things that computers are best at is doing similar things many times, really, really fast, such as adding a million numbers together.
JavaScript has plenty of ways of doing this, but in general, we’ll stick with two:
For Loops
We can perform an operation multiple times using a for loop, which looks like this:
for ( let i = 0; i < 5 ; i ++ ) {
// do somethings 5 times
}
Of course, we rarely want to do exactly the same things many times in a row, so we can change something inside the loop:
let total = 0;
for ( let i = 0; i < 5 ; i ++ ) {
total = total + 1;
}
// now total = 5
Array.forEach
Another useful thing is to be able to iterate over the values in an array. The following will add together the elements in the array:
const arr = [ 1, 2, 3, 4 ];
let sum = 0;
arr.forEach( function( element ) {
sum += element;
} );
We can also use arrow functions for a cleaner syntax:
const arr = [ 1, 2, 3, 4 ];
let sum = 0;
arr.forEach( ( element ) => {
sum += element;
} );
// now sum = 10
Callback Functions
The function we just passed into the
arr.forEach
method has a special name - it’s called a “callback function”, meaning it gets passed into another function as an argument.
The function inside the
forEach
brackets is an anonymous callback function. Let’s rewrite the code using a named function to highlight this.const arr = [ 1, 2, 3, 4 ];
let sum = 0;
const callback = ( element ) => {
sum += element;
};
arr.forEach( callback );
// now sum = 10
Now you can see that we’re passing in the function called
callback
as an argument to Array.forEach()
.Synchronous Code
Everything that we’ve done so far has been Synchronous, meaning that it all happens in a immediate, linear fashion, and statements are executed one after another:
const x = 5;
const y = 100;
add( x, y );
The above code is executed line by line, reading from top to bottom. This is known as []synchronous code, and, in general, that’s just what we want. But what if one of the operations takes a long time?
const x = 5;
const y = 100;
const myModel = loadModel( 'path/to/model.file' );
addModelToScene( myModel );
add( x, y );
We’re talking about loading things over the internet here, and if your model is a few megabytes it might take some time to load. In the above example, we would have to wait until the model has finished loading to
add( x, y )
. Not good!
To overcome this, we need to switch to an asynchronous code style.
Asynchronous Code
When we come to loading a model in the final chapter, we’ll need to write some asynchronous code, and we’ll use callback functions to achieve this, since these are the simplest and easiest to understand way of writing asynchronous code, although they do have some major drawback which we’ll address in Section Two.
In the case of our
loadModel
example above, it might look like this:const x = 5;
const y = 100;
const onLoadCallback = ( myModel ) => {
addModelToScene( myModel );
};
loadModel( 'path/to/model.file', onLoadCallback );
add( x, y );
In this case, the
add( x, y )
proceeds immediately, without waiting for the model to finish loading, and once the model does finish loading, the onLoadCalback
fires off.
Note that we’re skipping over all of the details of how the
loadModel()
function is implemented here, for the sake of simplicity. That’s fine, since asynchronous code is complicated, but you don’t need to know how it works to use it.Recursion
A recursive function is simply a function that calls itself.
Here’s a function that prints the string
'Hello'
to the browser’s console 100 times.let count = 0;
const printHello = () => {
console.log( 'Hello', count );
count++;
// recursion just means that a function calls itself
if( count < 100 ) printHello();
}
The new
keyword and constructor functions
The convention in JavaScript is that when a function name starts with a capital letter, then it’s a constructor, which means that it’s intended to be used with the new keyword:
function Person ( name, age ) {
this.name = name;
this.age = age;
}
Then we can use the
Person
constructor to create lots of new people like so:const sally = new Person( 'Sally', 122 );
const esme = new Person( 'Esmerelda', 323 );
const elle = new Person( 'Eloise', 96 );
They will then have properties defined inside
Person
, which we can access in the normal way (for example, with dot notation):const x = elle.age; // x holds the value
Each person that was created using the
Person
constructor is known as an instance of the Person class.
In Section One, we won’t be writing any constructor functions, but we will be using quite a few of the ones that come with three.js. For example, to create a new box-shaped
Mesh
object we will do something similar to this:const geometry = new Geometry();
const material = new Material( 'red' );
const box = new Mesh( geometry, material );
Methods
When we define a function inside a constructor, we’ll call it a method. Let’s add a method to print a person’s name to
Person
.function Person ( name, age ) {
this.name = name;
this.age = age;
this.printName = () => {
console.log( name );
}
}
Now, we can create a person and use the
printName
method to print their name to the browser console.const fred = new Person( 'Frederico', 23 );
fred.printName(); // logs 'Frederico' to the browsers console.
Array.forEach()
, which we used above, is a method on the Array object.Scope
Scope is a very important concept in JavaScript, but often confusing for beginners (and experts!).
Simply put, it means “where can I access this variable from?”.
const x = 5;
function addToX( y ) {
return x + y;
}
const z = addToX( 3 ); // ?
Will the above code work? Yes, in this case the variable
x
can be accessed from inside the function, meaning that the function has access to the outer scope. However, the function also has it’s own scope, so we can overwrite the value of x
inside the function:const x = 5;
function addToX( y ) {
const x = 1;
return x + y;
}
const z = addToX( 3 ); // z = 4
This works, but it’s a bad idea since it can lead to a lot of confusion. In this simple example it’s obvious, but in a fully fledged application this could lead to a lot of confusion.
There’s a lot more to scope than this, and it’s worth taking some time to understand how scope works in JavaScript so that you can code intuitively, without having to double check your work. Here’s a good tutorial on scope if you want to explore this further.
Functional Style Programming
As a way of overcoming this problem, we’ll stick use some aspects of a programming style that is known as Functional Programming.
This means that we will try to avoid writing functions that rely on knowledge of the outer scope. We’ll always try to write our functions so that we pass in all the parameters they need to work with.
const x = 5;
function addToX( x, y ) {
return x + y;
}
const z = addToX( 3 ); // z = 8
And that’s it, literally all of the JavaScript that you need to know to follow through until the end of Section One. In the next chapter we’ll introduce the extra bits that we’ll need from Section Two onwards, but if you’re very new to coding then feel free to skip that for now and come back to it when you need to.
Comments
Post a Comment