Javascript
The Language
Javascript is an object oriented language which started life as an embedded scripting language within a web browser. It was developed first by Netscape (and called LiveScript) for use in the Navigator browser, had it's name changed when Sun Microsystems became involved and was soon copied by Microsoft (as JScript). The language was later standardised by ECMA as ECMAScript which is perhaps the 'proper' name of the language but everyone calls it Javascript.
The name Javascript is confusing (perhaps by design) because the language is not related to Java and is in fact quite a different kind of language. The syntax is somewhat similar in that they both share the core syntax of C-like languages, but the design philosophy behind them is very different and they are useful for different tasks.
Javascript is a dynamically typed language, meaning that you don't declare the types of variables (just like Python). A variable can hold any type of data and it's up to the programmer to ensure that they know what the type of a variable is to avoid errors. Errors such as trying to add an array to an integer will be discovered only at run-time; whereas in a statically typed language like Java they would be found at compile time. Dynamically typed languages are generally thought to be easier for non-expert programmers to learn and lend themselves to quick scripts to solve specific problems. Writing larger applications and ensuring code is bug-free can be more difficult unless programmers are very careful.
Javascript is an object oriented language, but unlike Python or Java, objects in Javascript are not based on predefined class definitions. Instead they are built around the idea of prototypes (objects inherit from other prototype objects) and new fields and methods can be added at run-time as needed. Objects can be thought of as general purpose containers with slots which can hold either data of any type or functions which act like methods. This is a bit different to the way of thinking in Java and Python but has some interesting consequences that are exploited in the way that Javascript programs are written.
The most common environment to run Javascript code is the web browser. All modern web browsers come with a Javascript execution component that will run code embedded in web pages that are being viewed. However, in recent years, the use of Javascript as a server side language is becoming more common. The node.js project provides a runtime environment for Javascript that does not rely on a browser and can be used to write general purpose scripts including server-side web applications. The popularity of Javascript outside the browser can perhaps be attributed to the rise of the front-end developer who knows Javascript and therefore wants to use it to perform scripting and write server-side code. The node.js eco-system is now very well established and any practicing web developer needs to have some familiarity with Node and the NPM package manager that is used to distribute Javascript software.
When Javascript is running in the web browser, the main resource that a Javascript program has access to is the data structure corresponding to the HTML page currently being viewed. Javascript programs are able to interact with this data structure, querying it and updating it and so affect the content that is being viewed by the user. Javascript can also respond to events that occur in the browser, such as a user moving the mouse over a particular element, clicking on a link or entering text into a form. In this way, Javascript is used to enhance the user experience and build interactive applications within the web browser.
These chapter might one day expand to be a proper Javascript tutorial but at the moment the goal is just to give a flavour of the language. There are lots of resources on the net for learning about Javascript but if you are already a programmer, the best way is to try to write some code and use reference materials and code examples to work things out.
Running Javascript in the Browser
To associate JavaScript code with a web page, the code is embedded in the page or
linked to with <script></script>
tags.
We can embed the script directly:
<script>
// your script goes here
alert('Hello World')
</script>
This tag can go anywhere in the HTML page and there can be many script tags in one page. The code is written directly inside the tag so this is only really suitable for short fragments of code. In practice, you would almost never use this way of embedding Javascript in a real application. Instead you would refer to an external script:
<script src="/static/sample.js">
Either of these methods of including Javascript has the same effect, the
code is loaded and executed directly. A script tag can be placed at any
point in the page, either in the head or body. The code is able to modify
the current page up to that point via the DOM (see below) and it can
insert content into the current page with document.write
.
For this reason,
whenever a <script>
tag is seen, the rendering of the HTML page is halted
while the script is executed and then resumed when it is done. This can
slow down the loading of a page if there are many scripts or if running some
scripts takes a long time. For this reason it is common to put all
script tags that load external Javascript at the end of the body section of
the HTML page. In this way, the page is fully loaded before the Javascript is
downloaded and executed.
In modern browsers there is a solution to this problem in the defer
attribute
on the script
tag. For example:
<script src="/static/sample.js" defer>
In every modern browser, this will defer the execution of the Javascript code until
the page has completely loaded. This means that we can add the script
tags to load
Javascript to the page head
just as we do with CSS assets.
This page on HTML5Rocks discusses the range of issues around loading Javascript into the browser for those who want a deeper view.
Elements of Javascript
This text does not attempt to teach you everything about the Javascript language. The goal is to point out some important features and show you how to achieve some common tasks with Javascript. You should refer to other sources to learn the language itself. E.g. MDN.
Functions
Functions can be defined in Javascript much like any other procedural language:
function write_times(message, count) {
for (let j=0; j<count; j++) {
document.write(message)
}
}
write_times('hello', 10)
Note that the input arguments are not typed as they would be in Java or C. The final line shows the newly defined function in use.
This function also illustrates the use of the for
loop which has the same syntax
as in C++ or Java with the exception of the variable declaration using let instead
of a type specifier.
Functions in Javascript can also be anonymous - defined without a name. Also, since functions are just values in the program (in the same way as in Python) they can be assigned to variables. So we can achieve the same as the above with the following code:
let write_times = function (message, count) {
for (let j=0; j<count; j++) {
document.write(message);
}
}
This might seem a little odd but there are some common uses of this idiom in Javascript so it is important to understand it.
A very common example is to create an event handler. This is a function that will be called when some event happens. To do this we set a variable within the Document Object Model (representing the current page in the browser) to a function. For example, this function will be called when the page has finished loading:
window.onload = function() {
console.log("Page Has Loaded");
}
We also often pass functions as values to other functions. Here's an example from the
events chapter that adds a handler for the click event on an element. Notice
that the anonymous function is the second argument to the addEventListener
function:
let el = document.getElementById("outside")
el.addEventListener("click", function(event) {
alert("Event type: " + event.type );
})
Since this is such a common thing to do in Javascript, there is a new more compact syntax for functions called arrow functions. There are some subtle differences between these and regular functions which we'll point out later but the two examples above could be re-written more compactly as:
window.onload = () => {
console.log("Page Has Loaded");
}
let el = document.getElementById("outside")
el.addEventListener("click", (event) => { alert("Event type: " + event.type ); })
Debugging Output
Since your Javascript program is running in the web browser, we need a way to
log messages so that we can see them for debugging or other purposes. In Python
we would insert a print
statement in code. In Javascript we use console.log
and the output appears in the browser Javascript console which can be
found via the browser developer tools.
All modern browsers also have a very good interactive debugger that can be used to step through your code and place breakpoints. Get to know your developer tools!
Variables and Scope
Variables in Javascript should be introduced with one of the keywords var
, let
or const
.
This is optional when creating a variable - if it isn't present, Javascript will
look for an existing variable in the current scope (eg. within a
function) and if it does not find it, look in the next level until it
gets to global scope - if it never finds a variable of that name it will
be created in global scope. So, without one of these keywords you are
effectively using global variables inside your functions.
The difference between these keywords is subtle. Firstly const
is the simplest,
this defines a constant value - one that won't change. It can be global or local to
a block of code (curly braces).
Note that sometimes the values of const
variables can actually change if they are
mutable objects. So, if I have an variable holding an element from the DOM, I can
change the content of that element (e.g. using .innerHTML
) even if I hold the value
in a const
variable. For this reason, most of the variables that we use in Javascript
are actually const
. If the value might actually be a different number or string or
a different button element from the DOM, then you need to use let
or var
.
const button = document.getElementById("button1")
button.innerHTML = "Fire!"
button.onclick = () => { fire_button('loudly') }
On the other hand var
and let
seem quite similar;
both introduce a variable that can be changed and can be used in global or local scope.
The difference is that inside a function, var
declares a variable that will exist
everywhere in the function while let
declares a variable that will only exist within
a particular block. Some examples will help to illustrate this.
var gg = 12;
function simple() {
var hh = gg;
gg = hh + 1;
return hh;
}
console.log("SIMPLE: ", simple(), gg)
in this exmaple, the variable gg
is a global variable, the reference to it inside the function
will modify the global value. The variable hh
only exists within the function, so hh
will be
undefined after the function call. The output will be SIMPLE: 12 13
.
A variable declared with let
only exists within the containing block, while a var
variable
exists through the entire function. Here's an example:
function simple() {
// start a code block inside the function
{
var gg = 99;
let hh = gg + 1;
gg += hh;
}
// now hh doesn't exist but gg does
return gg;
}
console.log("SIMPLE: ", simple())
So, while you can use both var
and let
to define variables, you can really just use
let
all the time if you need a variable who's value changes.
There aren't many situations when var
is a better choice.
One place you might forget to declare a variable is in a for loop:
function counter() {
for(i = 0; i<10; i++) {
console.log(i);
}
}
While this code will work ok, it is silently creating or referring to a global
variable i
as the loop counter. If you were to call a function inside the loop that
also modified a variable i
without a declaration, it would cause odd behaviour. Here's an
example using a recursive call.
const one = (flag) => {
console.log("one", flag)
for(i = 0; i<3; i++) {
console.log('π ~ counter ~ i', i, flag)
if (flag) {
one(false)
}
}
}
one(true)
Here I have a function that calls itself recursively if the flag
argument is true. It
calls it with false
so that we only get one level of recursion. We might expect this
to run the loop three times, each time calling one(false)
recursively which would itself
run the loop three times. However, since the variable i
is global, it gets a value of 4 after
the first recursive call and we only get four iterations of the loop in total.
So, always use let
in writing your for loop:
const one = (flag) => {
console.log("one", flag)
for(let i = 0; i<3; i++) {
console.log('π ~ counter ~ i', i, flag)
if (flag) {
one(!flag)
}
}
}
one(true)
Since let
declares a block-scoped variable, the value of i
will be undefined outside
of the loop. This is what you would expect coming from another language like Java.
So, the general rule is to always declare variables before they are used. Use
const
for variables that won't change. Use let
for variables that will
change.
Arrays and Strings
Strings in Javascript are similar to those in Python. Both single and double quotes can be used (but Python's triple quote notation is not supported). They can contain unicode characters with special symbols if you wish.
const s1 = "Hello World!"
const s2 = 'Hello World!'
const s3 = 'Hola mΓ³n!'
const s4 = 'αααΊαΉααα¬αα«αααΉαα¬αα±α¬α'
Strings are objects and can be operated on with methods (just like Python) and then can be subscripted with the square bracket notation.
console.log(s1[3])
// methods do not modify the string
console.log(s1.toUpperCase())
console.log(s1)
// just say hello
console.log(s1.substr(0, 5))
// note that length is a property, not a function
console.log(s1.length)
Here is the definition of an array of strings in Javascript:
const months = ['January','February',
'March','April','May','June','July','August',
'September','October','November','December'];
Arrays in Javascript are just the same as those in most other languages; they can be indexed with a number starting from zero and they have various methods to manipulate the contents.
Arrays in Javascript (just like Python but not like Java) can contain different typed data. So I can have an array that contains both numbers and strings.
Note that arrays in Javascript are objects that have methods, just like in Python. So I can operate on an array using these methods:
// get the name of the month from a month number
function getMonth(N) {
return months[N+1]
}
// confuse things by reversing the array - note that this modifies it in place
months.reverse()
// and sort it in place too
months.sort()
// remove the last element
const last = months.pop()
// and the first
const first = months.shift()
// get the first three remaining months
const three = months.slice(0, 3)
Strings can be concatenated using the +
operator:
const newstring = s1 + 'How are you today!'
Javascript Gotchas
Javascript is an odd language, there are many things about it that don't seem to make sense if you are used to other languages and can be the source of bugs. There are some useful guides to a number of non-obvious features:
I'll go over a few of these here.
Equality Testing
The first issue relates to comparison operators. Javascript has both double and triple
equals operators to compare whether two values are the same but these behave quite
differently. The double equals operator ==
will try as hard as it can to make
the values be equal by converting the type of a value if necessary. So comparing
the string "1"
to the integer 1
will succeed:
const x = 1
if (x == "1") {
console.log("They are the same!")
}
This can be exactly what you want but it can also be a source of bugs when two things
you thought would be different end up being the same. If you use the triple equal
operator ===
the type coercion will not happen and the comparison will succeed only
if the values really are the same. So this code will print the message in the else clause:
const x = 1
if (x === "1") {
console.log("The are the same")
} else {
console.log("Not the same because of triple equals!")
}
Since the odd behaviour of double equals can be a source of bugs, best practice in Javascript is to always use triple equals when comparing values.
Two kinds of nothing
Javascript has two special values that sort of mean nothing. Python for example has
the special value None
and Java has null
, these are used to indicate when a
variable holds nothing or a function returns no value. Javascript has the special
value null
for this purpose and it has more or less the same meaning. However,
Javascript also has the special value undefined
which is the result of evaluating
a variable that is undefined. In other languages this would throw an error, but Javascript
just returns this special value.
Implicit conversion
Similar to the double equals comparison, there are other times when Javascript converts
the type of values without being asked. One of these is with boolean tests. The following
values will convert implicitly to false
:
undefined
ornull
false
- Numbers
+0
,-0
andNaN
(Not a Number - eg. Math.sqrt(-1)) - the empty string
''
everything else is considered to be true
. So I can use these values directly in an if
statement:
const x = ''
if (x) {
/* this will not run because '' is false */
}
Another case that can trip you up is the implicit conversion of types to strings. If I write the following:
const x = '5' /* a string */
console.log( x + 1 )
the output will be 51
rather than 6
because the number 1
was converted to a string and
we then get a string concatenation operation '5' + '1'
rather than an addition.
Semicolons
Javascript code can include semi-colons at the end of statements:
x = '5';
console.log( x + 1 );
however, the semi-colon is optional and can be left out in almost all cases. One place it is required is if you want to include two statements on one line:
x = '5'; console.log( x + 1 );
For the most part then you can write Javscript with no semi-colons and all will be well. There are though some obscure cases when a missing semi-colon might get you in trouble.
Modules
In most programming languages, we break up an application into more than one file. This helps with code management and also allows us to structure an application to put certain things into one module to make it self contained. In Javascript it has always been difficult to do this as it lacked a formal module mechanism. People reverted to weird tricks to write code that could be used in a modular fashion. Fortunately, the ES6 standard introduced module import/export and nearly all browsers now implement this feature (the main exception is the old Internet Explorer).
To write a Javascript module we make use of the export
keyword somewhere in the file. There are many options here
but I'll just cover the simplest case which is to list the symbols to be exported
at the top of the file. Here's an example, assume it is stored in the file namegen.js
:
export {maxNameLength, generateName};
const maxNameLength = 1024;
const unexportedVariable = "useless";
function generateName(length) {
if (length <= maxNameLength) {
length = maxNameLength;
}
return 'a'.repeat(length);
}
In this example, we define a variable and a function that are exported and a second variable that
is not. Another module that imports this module can get access to maxNameLength
and generateName
but not to unexportedVariable
which is only usable inside this module.
Importing is similarly simple, making use of the import
keyword as you might expect. The simplest
case is where we just name the symbols we want to import from another module. Here is an example:
import {generateName} from './namegen.js'
console.log(generateName(10));
console.log(maxNameLength); // will be undefined
Here we import only the generateName
function and we can make use of it as if it was defined in
this file. The variable maxNameLength
will not be imported and so trying to use it will give a
result of undefined
(that special Javascript value that means the variable doesn't exist).
In some cases you want to import everything from a module. In this case we need to give a prefix for the imports to prevent a symbol from the imported module clashing with one defined locally - this is more likely if you just say import everything since the list of exports might change at some point in the future. So we would write:
import * as namegen from './namegen.js'
console.log(namegen.generateName(10));
console.log(namegen.maxNameLength);
Note that we need to refer to the function and variables with the defined prefix. Trying to use them without the prefix won't work.
One thing to understand is what is the path name we should use after the from
keyword. This
is either a relative or absolute path to the file that we want to import. This is a URL, it
could begin with http and reference a file on another server (but see below for some caveats).
Most often though you will be referencing a module on the same server and will use either a
relative or absolute path.
My example above is a relative path, it starts with ./
because a single period is an alias
for the current directory (in Unix based systems, and this carries over to URL paths). A
relative path can't be just the file name. So './namegen.js'
says look in the current directory for the file namegen.js
. You could also refer to a subdirectory
like './modules/namegen.js'
if you want to keep all of your modules in a subdirectory - but note the
relative path still begins with a period.
The second alternative is an absolute path. For example, '/modules/namegen.js'
- the path begins
with a forward slash and the browser will resolve it as it would if it saw this in the
href
attribute of a link. It will make a request to the same server for this URL path.
It is possible to import a module from another server, something like:
import {example} from 'http://example.org/some/module.js'
The browser will try to load this URL to get the module contents. However, there are rules as to where you can load code from. By default, this will be blocked by a policy which says that a Javascript module can only load code from the same server: CORS or Cross Origin Resource Sharing policy. It is possible to configure your server to return HTTP headers that permit your Javascript to load modules from another site. This prevents malicious code infecting your Javascript and loading bad code from some random evil hacker server. So, in general you won't use a full URL to import a module.
Loading Modules into the Browser
Once you have converted your code to use import
statements your Javascript files become
Javascript modules. We are used to loading Javascript into the page with a <script>
tag:
<script src="script.js" defer>
(where defer
means we defer loading until the page has finished loading). If you use import
in
script.js
and try to do this you will get an error message in your console. Something like
"SyntaxError: import declarations may only appear at top level of a module"
. To fix this
we need to tell the browser that what we're importing is a module, which we do with the
type
attribute:
<script src="script.js" type=module defer>
This primes the browser to expect a module and it will then be happy with import statements. The big difference is that when a module is loaded, the code is executed as normal but all of the symbols defined in the module are not available in global scope in the browser. Using modules also enforces the CORS policy for more secure coding.
We've been discussing the browser loading these modules since that is where we are running our Javascript most of the time. However, there might be other systems loading these modules, in particular it might be running under the Node.js system which runs Javascript outside of the browser. Import/export statements can also be used by so-called bundlers which read your Javascript code and bundle it up into a compact form suitable for delivery to the browser - eg. it might combine all of your modules into a single file to avoid the browser having to make many HTTP requests.
Summary
There is a lot more to Javascript than this chapter has covered. You should refer to other sources to learn the details of the language. What I've tried to do here is to point out some highlights and points of difference with other languages that you might be familiar with. The goal of this text is to give one view of how Javascript is used in developing modern web applications. Most of the important detail is in how it is used and how the application works in the browser.