JavaScript
Live Demo: https://www.youtube.com/watch?v=BrQKM_uaZKE
Github Repo: https://github.com/web-engineering-tuwien/recipe-search-live-demo
https://developer.mozilla.org/en-US/docs/Web/Tutorials#javascript-tutorials
https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures
https://eloquentjavascript.net/
JavaScript
interpreted scripting language.
Standardized as ECMAScript language in the ECMA-262
Other ECMAScript implementations: JScript, ActionScript, QtScript, …
In this course: EXMAScript 6
Embedding into HTML
External
linking to an external JavaScript file in <head>
defer
= asynchronous loading
It will defer (= delay) loading the JS until the page load is completed.
<script type="text/javascript" src="foo.js" defer> </script>
Internal
In <body> or <head>
type="module"
is
defer
by default
<script type="module">
statement;
statement;
...
</script>
<script type="text/javascript">
statement;
statement;
...
</script>
As event handlers
<a href="http://www.bar.com" onmouseover="alert('hi');">
Pseudo-URLs in links
<a href="javascript:alert('You clicked');">Click me</a>
Syntax
Key words for declaration, Scoping
Function-scope:
var
→ try to avoid- will only live in the function or the outer scope that it was called in
- Function scopes can nest with nested functions
- hoisting
: optimzing code before executing it within the virtual machine → During the hoisting process all
var
variables get declared in the beginning of every function without being initialized .Therefore accessing / reading an previously un-initialized variable with
var
won´t cause an issue and is accessible everywhere within the function. → This makes code prone to error.
Block-scope:
let
,const
- will only exist within its block (just like Java)
- const identifiers cannot change → improves memory efficiency. Useful for objects and functions that shouldn´t change.
Global Scope: no keyword
- Variables are global by default if no keyword is used
Variables, Data types
There are 8 basic data types in JavaScript.
number
for numbers of any kind: integer or floating-point
bigint
is for integer numbers of arbitrary length.
string
for strings. A string may have zero or more characters, there’s no separate single-character type.
boolean
fortrue
/false
.
null
for unknown values – a standalone type that has a single valuenull
.
undefined
for unassigned values – a standalone type that has a single valueundefined
.
object
for more complex data structures.
symbol
for unique identifiers.
The
typeof
operator allows us to see which type is stored in a variable.-
Two forms:
typeof x
ortypeof(x)
.
-
Returns a string with the name of the type, like
"string"
.
-
For
null
returns"object"
– this is an error in the language, it’s not actually an object.
Type conversions:
Strings, Template Literals
Strings: (“…”, ‘…’)
- Backslash escapes (\) String concatenation with + operator: "Dear " + s1.name + ", thank you for…”
JavaScript Template Literals (Replaces concatenation operations)
- Use backquote (`) as delimiter
- Can span multiple lines (new line included in the string)
-
Can have embedded expressions → ${expression} — computes expression and replaces it with string value
message = `Dear ${student.name}, Your GPA, ${gpa(student)}, is lower than the average: ${averageGPA}`
Operators
- Numerical operators: +,-,*,/,++,--,%
-
Comparison operators: >,>=,<,<=,==,!=,===,!==
Two kinds of comparison operators
console.log("1" == 1); // true - compares content console.log("1" === 1); // false - compares type + content
Functions, Callbacks
Declared with keyword
function
:-
Functions are first-class citizens, can be passed as parameters, can return other functions
Callbacks (= higher order functions)
Here is a quick example:
function greeting(name) { alert('Hello ' + name); }function processUserInput(callback) { var name = prompt('Please enter your name.'); callback(name); }processUserInput(greeting);
The above example is a synchronous callback, as it is executed immediately.
Note, however, that callbacks are often used to continue code execution after an asynchronous (In other words, actions that we initiate now, but they finish later) operation has completed — these are called asynchronous callbacks.
For instance, one such function is the setTimeout function.
Callback Hell / Pyramid of Doom
In a “callback-based” style of asynchronous programming. A function that does something asynchronously (at a later point in time) should provide a callback argument (usually an anonymous function) where we put the function to run after it’s complete.
As calls become more nested, the code becomes deeper and increasingly more difficult to manage. That’s sometimes called “callback hell” or “pyramid of doom”:
(Here the loadScript function executes the anonymous function that is its second parameter if script was loaded successfully → the goal is to load a series of scripts sequentially)
Callback Hell example
Take a look at the function
loadScript(src)
, that loads a script with the givensrc
:function loadScript(src) { // creates <script src="…"> with given src // this causes the script with given src to start loading and run when complete let script = document.createElement('script'); script.src = src; document.head.append(script); }
We can use this function like this:
loadScript('/my/script.js');
The script is executed “asynchronously”, as it starts loading now, but runs later, when the function has already finished. If there’s any code below
loadScript(src)
, it doesn’t wait until the script loading finishes.Let’s say we need to use the new script as soon as it loads. It declares new functions, and we want to run them.
Let’s add a
callback
function as a second argument toloadScript
that should execute when the script loads:function loadScript(src, callback) { let script = document.createElement('script'); script.src = src;script.onload = () => callback(script);document.head.append(script); }
Now: How can we load two scripts sequentially: the first one, and then the second one after it (with error handling)?
Here’s an improved version of
loadScript
that tracks loading errors:function loadScript(src, callback) { let script = document.createElement('script'); script.src = src;script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`));document.head.append(script); }
It calls
callback(null, script)
for successful load andcallback(error)
otherwise.To use the loadScript function:
loadScript('/my/script.js', function(error, script) { //anonymous function that takes 2 params if (error) { // handle error } else { // script loaded successfully } });
From the first look, it’s a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it looks fine.
But for multiple asynchronous actions that follow one after another we’ll have code like this:
loadScript('1.js', function(error, script) {if (error) { handleError(error); } else { // ... loadScript('2.js', function(error, script) { if (error) { handleError(error); } else { // ... loadScript('3.js', function(error, script) { if (error) { handleError(error); } else { // ...continue after all scripts are loaded (*)} });} }); } });
In the code above:
-
We load
1.js
, then if there’s no error.
-
We load
2.js
, then if there’s no error.
-
We load
3.js
, then if there’s no error – do something else(*)
.
That’s sometimes called “callback hell” or “pyramid of doom.”
There are some solutions but none of them really solve the problem.
Luckily, there are other ways to avoid such pyramids. One of the best ways is to use “promises”.
-
We load
- Function call only matched by name, not parameters → no overloading
- Missing parameters replaced by undefined, Additionally passed parameters are ignored
-
Since ES6:
Optional parameters
by specifying
default
function topGrade(student, threshold=1.5) { return gpa(student) <= threshold; }
Other function concepts:
Anonymous functions (= lambda expressions)
Named function:
function greet(name){ return "Hello"; }
Anonymous function: without a name,
var greet = function(name){ return "Hello";}
Arrow Functions
Their behavior are the same as of a function. They are anonymous functions with a special syntax. (syntactic sugar)
To define an arrow function, we use the
() => {}
structure as follows:const greet = (name) => { return "Hello " + name + "!"; } console.log(greet("Eric")); // prints out Hello Eric!
In this function, the
name
argument to thegreet
function is used inside the function to construct a new string and return it using thereturn
statement.In case that the function only receives one argument, we can omit the parenthesis:
const greet = name => { return "Hello " + name + "!"; } console.log(greet("Eric")); // prints out Hello Eric!
And, in case that we want to do a explicit return of the function and we have only one line of code, we can avoid the
return
statement and omit brackets too:const greet = name => "Hello " + name + "!"; console.log(greet("Eric")); // prints out Hello Eric!
Using an arrow as a callback compared to a normal function:
let numbers = [3, 5, 8, 9, 2];// Old way function multiplyByTwo(number){ return number * 2; } console.log(numbers.map(multiplyByTwo)); // prints out: 6, 10, 16, 18, 4// Using ES6 arrow functions const multiplyByTwo = number => number * 2; console.log(numbers.map(multiplyByTwo)); // prints out: 6, 10, 16, 18, 4
Also:
-
Arrow functions don’t have their own
this
orsuper
, so they transparently fit into the surrounding context. - That makes them very useful in some cases.
-
Functions are first-class citizens, can be passed as parameters, can return other functions
Objects
var student = new Object(); student.nr = 'e11912007'; student.name = 'A Student'; student.age = 19; student.hasSTEOP = true; student = { nr : 'e120...', name : 'A Student', age : 19, hasSTEOP : true };
Objects have no underlying class → (Almost) everything is an object!! Objects are associative arrays.
- key = property, value = value
- Added when value assigned
- Support dynamically adding/deleting values
JSON - JavaScript Object Notation
The JSON (JavaScript Object Notation) is a general format to represent values and objects. It is described as in RFC 4627 standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it’s easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/....
JavaScript provides methods:
JSON.stringify
to convert objects into JSON.
JSON.parse
to convert JSON back into an object.
Limitations: Cannot de/serialize JS functions → JSON can not transfer data that contains functions
Classes
operator "new"
The regular
{...}
syntax allows to create one object. But often we need to create many similar objects, like multiple users or menu items and so on.That can be done using constructor functions and the
"new"
operator.Constructor function
- They are named with capital letter first.
-
They should be executed only with
"new"
operator.function User(name) { this.name = name; this.isAdmin = false; }let user = new User("Jack");
When a function is executed with
new
, it does the following steps:-
A new empty object is created and assigned to
this
.
-
The function body executes. Usually it modifies
this
, adds new properties to it.
-
The value of
this
is returned.
In other words, new User(...) does something like:
function User(name) { // this = {}; (implicitly)this.name = name; this.isAdmin = false;// return this; (implicitly) }
-
A new empty object is created and assigned to
The more advanced OOP syntax
The basic class syntax looks like this:
class MyClass { prop = value; // propertyconstructor(...) { // constructor // ... }method(...) {} // methodget something(...) {} // getter method set something(...) {} // setter method[Symbol.iterator]() {} // method with computed name (symbol here) // ... }
MyClass
is technically a function ( the one that we provide asconstructor
), while methods, getters and setters are not. They are written to the.prototype
.The
constructor()
method is called automatically bynew
, so we can initialize the object there.
Arrays
Heterogenous Lists
- Add/remove elements on start/end with: push/pop/shift/unshift
- Sort array: sort/reverse
- can be concatenated with concat
- String conversion with join
Functions as Objects
Functions are objects too: they can have properties and methods
- Difference to pure objects: They can be called and can return a value
-
Definition assigned as properties (use
new
andthis
)function Student(nr, name, age, hasSteop) { this.nr = nr; this.name = name; this.age = age; this.hasSteop = hasSteop; this.finishSteop = function() { this.hasSteop = true; } } var jc = new Student('e0828…',‘Jurgen Cito', 29, false); jc.finishSteop();
ES6 Class is syntactic sugar for functions as objects:
class Student { constructor(nr, name, age, hasSteop) { this.nr = nr; this.name = name; this.age = age; this.hasSteop = hasSteop; } finishSteop() { this.hasSteop = true; } } var jc = new Student('e0828…', ‘Jurgen Cito', 29, false); jc.finishSteop(); //Student { nr: 'e0828112', name: 'Jurgen Cito', age: 29, hasSteop: true } //typeof(jc) === 'object'
Events
Pop-up Boxes
Confirm boxes will return "true" if ok is selected, and return "false" if cancel is selected. Alert boxes will not return anything. Prompt boxes will return whatever is in the text box. (Note: prompt boxes also have an optional second parameter, which is the text that will already be in the text box.)
confirm("Hi!"); prompt("Bye!"); alert("Hello");
Event callback attached to HTML elements.
<button onclick="alert('Test!')"> Test me! </button>
let button = document.getElementsByTagName(“button")[0] header.click(); //Execute predefined event header.onclick = function(){alert('Clicked!');} //Set event listener - only one listener supported let func = function() {alert('Clicked!');} header.addEventListener(“click", func) header.removeEventListener(“click", func)
Event types
Modules
https://www.samanthaming.com/tidbits/79-module-cheatsheet/
Establish namespace in separate module files: Ability to create named export or default exports
Rule: One module per file, one file per module
Export specific elements:
export function gpa(student) {…} //EITHER THIS WAY function gpa (student) {…}; export gpa; //OR THIS WAY
Import in other files:
import * as Student from ‘./student.js'; s = {…}; s.gpa = Student.gpa(s); //<- now gpa is in the Student namespaceimport { gpa } from ‘./student.js’; s = {…}; s.gpa = gpa(s); //<- now gpa can be accessed directly
Promises
Analogy:
Fans have subscribed the mailing subscription list of a singer so that they get instantly notified once the next album becomes becomes available or if something goes wrong.
Components:
- A “producing code” that does something and takes time. For instance, some code that loads the data over a network. That’s a “singer”.
- A “consuming code” that wants the result of the “producing code” once it’s ready. Many functions may need that result. These are the “fans”.
- A promise is a special JavaScript object that links the “producing code” and the “consuming code” together. The “producing code” takes whatever time it needs to produce the promised result, and the “promise” makes that result available to all of the subscribed code when it’s ready. This is the “subscription list”.
The constructor syntax for a promise object is:
let promise = new Promise(/*executor(resolve, reject)*/);
let promise = new Promise(function(resolve, reject) { // executor (the producing code) });
The executor runs automatically and attempts to perform a job. When it is finished with the attempt it calls
resolve
if it was successfulreject
if there was an error.The
promise
object returned by thenew Promise
has 2 internal properties:state
-
initially
"pending"
"fulfilled"
whenresolve
is called by the executor
"rejected"
whenreject
is called by the executor
-
initially
result
-
initially
undefined
value
whenresolve(value)
is called by the executor
error
whenreject(error)
is called by the executor
-
initially
Example: Successful
let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("done"), 1000); //"done" is the result });
After one second of “processing” the executor calls
resolve("done")
to produce the result. This changes the state of thepromise
object:Example: Unsuccessful
let promise = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error("Whoops!")), 1000); //"whoops" is the error statement });
But it is recommended to use Error objects (or objects that inherit from Error).
The call to reject(...) moves the promise object to "rejected" state:
Consumers: then, catch, finally
A Promise object serves as a link between the executor (the “producing code” or “singer”) and the consuming functions (the “fans”), which will receive the result or error.
Consuming functions can be registered (subscribed) using methods .then, .catch and .finally.
.then
promise.then( function(result) { ... }, //RESOLVE FUNCTION function(error) { ... } //REJECT FUNCTION );
Can also be called with only a single parameter (resolve function).
.catch
The call
.catch(f)
is a complete analog of.then(null, f)
, it’s just a shorthand..finally
The call
.finally(f)
is similar to.then(f, f)
in the sense that f always runs when the promise is settled: be it resolve or reject.
async/await
The async/await pattern: allows an asynchronous, non-blocking function to be structured in a way similar to an ordinary synchronous function.
await
can only be used from inside anasync
function.await
expressions make promise-returning functions behave as though they're synchronous by suspending execution until the returned promise is fulfilled or rejected.It literally suspends the function execution until the promise settles , and then resumes it with the promise result as the return value.
Here's an example:
resolveAfter2Seconds()
returns a promise but theresult
variable only stores the result of the promise:value
whenresolve(value)
error
whenreject(error)
function resolveAfter2Seconds() { return new Promise( resolve => { //executor function setTimeout(() => { resolve('resolved'); }, 2000); //resolve function }); }async function asyncCall() { console.log('calling'); const result = await resolveAfter2Seconds(); console.log(result); // expected output: "resolved" }asyncCall();
Async
functions always return a promise.If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.
For example, the following:
async function foo() { return 1 }
...is similar to:
function foo() { return Promise.resolve(1) }
Inheritance
Object Prototypes, Prototypal inheritance
In JavaScript, objects have a special hidden property
[[Prototype]]
, that is either null or references another object. That object is called “a prototype”.The property
[[Prototype]]
is internal and hidden, but there are many ways to set it.One of them is to use the special name
__proto__
, like this:Now if we read a property from
rabbit
, and it’s missing, JavaScript will automatically take it fromanimal
.For instance:
let animal = { eats: true }; let rabbit = { jumps: true };rabbit.__proto__ = animal; // we can find both properties in rabbit now: alert( rabbit.eats ); // true alert( rabbit.jumps );
All objects have a prototype
- All prototype are objects, any object can be a prototype
- Objects inherit properties and methods from prototype
-
Object.prototype is top of the prototype chain (its prototype is null) (similar to Object.class in Java)
var aStudent = { nr : 'e120...', ... }; // aStudent -> Object.prototype var students = [ aStudent, ... ]; // students -> Array.prototype -> Object.prototpye var jc = new Student('e08…', ...); // aStudent -> Student.prototype -> Object.prototype function print() { ... }; // print -> Function.prototype -> Object.prototype
Existing prototypes can be extended at any time: its called "monkey-patching" and should be avoided!
String.prototype.distance = function() { ... }; // monkey patching
Class inheritance
To extend a class:
class Child
extends
Parent
:That means
Child.prototype.__proto__
will beParent.prototype
, so methods are inherited.
Browser and Document APIs
Standard Library/Common APIs
Overview
Overview: There’s a root object called window.
APIs accessible through Javascript.
Browser Object Model BOM → interact with browser
Window
,Frames
,History
,Location
,Navigator
(browser type & version), ...
Storage (Local Storage, Cookies)
Document Object Model DOM → interact with HTML page
Properties:
document.forms
,document.links
, …
Methods:
document.createElement
,document.getElementsByTagName
, …
Browser Object Model BOM (window)
Allows access to browser objects. → Not standardized but very similar in most browsers.
Object Property and Methods
window Other global objects, open(), close(), moveTo(), resizeTo()
screen width, height, colorDepth, pixelDepth, …
location hostname, pathname, port, protocol, assign(), …
history back(), forward()
navigator userAgent, platform, systemLanguage, …
document body, forms, write(), close(), getElementById(), …
Popup Boxes alert(), confirm(), prompt()
Timing setInterval(func,time,p1,…), setTimeout(func,time)
Storage (Local Storage, Cookies)
Cookies
Cookies are sent with every single request.
String/value pairs, Semicolon separated.
document.cookie = "name=Jane Doe; nr=1234567; expires=" + date.toGMTString()
Web Storage
Data that the user is required to store (as key / value pairs)
Browser defines storage quota.
Both storage objects provide same methods and properties:
setItem(key, value)
– store key/value pair.
getItem(key)
– get the value by key.
removeItem(key)
– remove the key with its value.
clear()
– delete everything.
key(index)
– get the key on a given position.
length
– the number of stored items.
- Local Storage(
window.localStorage
)stored in users browser without any expiration date.
In comparison to Cookies: more secure, larger, not transferred (stays local)
- Session Storage(
window.sessionStorage
)Data is destroyed when tab/browser is closed.
Document Object Model DOM
For HTML, XHTML, XML
HTML elements as objects with properties, methods and events - DOM tree nodes.
The
document
object is the trees root.
Everything in the html file becomes a node:
text nodes (spaces and \n are stored as seperate nodes), comment nodes ...
Getting nodes
ID, tag name, class name, Document properties
let title = document.getElementById("title");
let links = document.getElementsByTagName("a");
let greens = document.getElementsByClassName("green");
let imgs = document.images;
let firstParaBox = document.querySelector(“p.box”);
let allBoxes = document.querySelectorAll(“p.box,div.box”);
Reading properties
<UL id="t1">
<LI>Item 1</LI>
</UL>
document.getElementById('t1').nodeName
// -> returns 'UL'
document.getElementById('t1').getAttribute('id')
// -> returns 't1'
document.getElementById('t1').innerHTML
// -> returns '<li>Item 1</li>'
document.getElementById('t1').children[0].nodeName
// -> returns 'LI'
document.getElementById('t1').children[0].innerText
// -> returns 'Item 1'
Manipulating nodes
- Content (innerHTML)
- Element attributes
- Element style
-
Element class (
className
,classList
)
title.innerHTML = "newTitle";
links[0].href = "http://...";
links[0].setAttribute("href",…)
greens[0].style.color = “red";
greens[0].className = “red"
greens[0].classList.add(“dangerzone”)
Adding nodes
Create, append, remove, …
let header = document.createElement("h2");
let text = document.createTextNode("SubTitle");
header.appendChild(text);
document.removeChild(title);
document.replaceChild(title, header);
Adding Event Handlers
let list = document.getElementById('t1');
list.addEventListener('click', (event) => {
alert(`Clicked: ${event.target.innerText}`);
});
Traversal of nodes
parentElement, nextElementSibling, previousElementSibling, childNodes
Accessibility
DOM Updates
Changing / updating the DOM frequently
can confuse screen readers
not be visible in high magnification
Updates may come too fast
Guidelines - Accessible Rich Internet Applications (ARIA)
If the content updates more than 5 seconds, provide ability to pause, stop, or hide them
Inform users of changes: Setting focus, Highlight, Alert, Live Region (ARIA Term)
Communicate to user that page is dynamic
Provide static HTML page alternatives
Event-Driven Programming
- Flow of the program is determined by responding to user actions called events
- Writing programs driven by user events
<img src="..." alt="..." onclick="alert('Hello');" />
let button = document.getElementsByTagName("button")[0]
header.click(); //Execute predefined event
header.onclick = function(){alert('Clicked!');} //Set event listenerlet func = function() {alert('Clicked!');}
header.addEventListener("click", func);
header.removeEventListener("click", func);
DOM Event Types
Event Description
load,unload User enters/leaves page
change Form input field changes
focus/blur User focuses/unfocuses an input field
Submit Form is submitted
mouseover, mouseout Mouse enters/leaves region
mousedown/mouseup/click Mouse click events
keydown/keyup/keypress Keyboard events
drag User drags an element
Asynchronous Programming
Asynchronous Programming ≠ Multithreading
Waiting for actions to finish is implicit in the synchronous model, while it is explicit, under our control, in the asynchronous one.
Sending Asynchronous Requests with Callbacks and Promises (like HTTP Fetch)