1 of 24

Lecture plan JavaScript 20-10-26 kl 13

13:15-13:20 Intro (admin)

13:20-13:40 Session 1

13:40-13:50 Answers (spela in)

13:50-14:05 Session 2 link (end session with a break)

14:05-14:20 break

14:20-14:30 Answers (spela in)

14:30-14:50 Session 3

14:50-15:00 Answers (spela in)

2 of 24

JavaScript for Python and Java programmers

Cristian Bogdan

3 of 24

const, let, for(;;), if()… But do we need for and if??

const arr= [1,2,3,5];

// sum up the odd elements plus one:

// Procedural: �let sum= 0;�for(let i=0; i<arr.length; i++){� if(arr[i] %2===1) {� sum+= arr[i] +1;� }�}

// Functional:�// keeping only the odd ones�arr.filter(function(x){return x%2===1;})

// or:�arr.filter(x=>x%2)

// adding 1�arr.map(x=>x+1)

// sum up�arr.reduce((acc, x)=> acc+x, 0)

// filter, map, reduce do NOT change the array

// they return a new array (immutable)

// The new array also has the filter, map, reduce methods

// So the functional form is

arr.filter(TODO).map(TODO).reduce(TODO)

4 of 24

Variables and scopes

let a = 42; // global scope, explicit declaration

function f1(param1) { // parameters visible in local scope let b = 12; // local scope, explicit declaration� neverUse = 27; // global scope, implicit declaration, NEVER use!!!� const d="the d string"; // local scope, constant declarationlet a= 10; // local a shadowing the global a

function f2(param2){� let e= new Date(); // only visible in f2, not f1� let f= a+b+param1+param2; // f2 has access to all f1 variables and parameters � let a= 12; // shadowing a yet again

}

}

5 of 24

From past years

function f(){

for(i=0; i< someMax;i++) { call to g() }

}

// same technique used in g() and all other functions for all loops

What’s the problem?

What if you call a monetized API from these loops?

Therefore you will call the lab API through a proxy that counts how many calls per second each lab group makes

6 of 24

Which data types are new to you?

let myInt = 7;

let myBool = true;

let myString = "ABC";

let myQuoteMixString= 'AB"C"D';

let myRegex= /ab+c/ // regular expression

let myTemplateString= `first line

second line`;

let myTemplateFormatString= `first ${myInt} line

second line`; // ^^^^ will expand ${myInt} to 7

let anObject={ a:3, b:true }; // see Objects later

let myArray = [1, 2, false, [4, 2], "x"]; // see Arrays later

const aFunction= function(param){ console.log(param);} // functions are just like any other variable

const arrowFunction= (param) => console.log(param) ; // arrow function

7 of 24

Operators

Assignment and Comparison operators are the same as in C, Java, Python

=, +=, -= …

Logical operators are like in Java and C so they differ from Python

|| or && and ! not

C/Java Conditional operator �condition?ifTrue:ifFalse Python >=2.5: ifTrue if condition else ifFalse

Assignment is an expression, returns a value (like in C, Java, unlike in Python) but do not use that!

(a=1)+2 → 3

let a=0;

if(a=1)console.log(“was true”)�????

8 of 24

Automatic conversion

Happens often in Javascript so the code is briefer… �From basic types to Objects (autoboxing)

(1).toString() the integer 1 is converted automatically to a Number object, which has a toString() method. See also �(1).constructor �(/ab+c/).constructor

Conversion between basic types: string + number converts the number to string automatically. number+ boolean also works (boolean converted to 0 or 1)

Comparison operators try to convert first!

1=="1" → true Why in the world do they have this feature?�1==="1" → false. Try also 1!=="1" 1!=="0"

9 of 24

Objects and methods

Very easy to create:

let rect={height:10, width:20} ;

Adding a property (aka field in C, Java)

rect.color="blue";

Methods are just function properties:

rect.hello=function(){ return "I am a rectangle";} ;

rect.hello= () => "I am a rectangle" ;

How to add a surface() method? �Hint: use this�Hint: do not use arrow functions

You can skip the end-of-statement ; (semicolon) if it comes at the end of the line. ��Especially useful at the Console

You may see it skipped in lecture materials or slides when it’s meant to experiment at the console

10 of 24

this in arrow functions

It may be attractive to use

rect.surface=() => this.height* this.width

But it will not work!

In traditional function(), this has a very complicated definition. In arrow functions, the JS designers simplified: they do not have an own this, but inherit it from the enclosing traditional function, if any.

In short: in Classes and Objects, use traditional, not arrow, functions as methods. Within methods, you can use arrow functions, they will inherit the method scope and therefore the “right” this (the object).

Read more at MDN. Look for “No separate this” in the page

Another resource.

arrow functions don't bind their own scope, but inherit it from the parent scope”

const obj={ // form used a lot by Vue.js� someProp: 13,� method(param){ � const f= x=> x+this.someProp;� return f(param); � },� lastProp: 42�};

class MyClass{� constructor(){ this.someProp=13; } method(param){ � const f= x=> x+this.someProp;� return f(param); � }�};

11 of 24

for(;;) for( in ) for ( of )

for (;;) is like in the traditional C/Java. You can declare the variable on the spot using let

for(let i=0; i< someMax; i++) {... }

for(... in …) iterates through properties of an object

for(const x in rect) {console.log(x); } // will print a line for each of: height, width, surface

for(...of …) iterates through elements of an Iterable object (string, array, ...)

// as in C or Java, you can skip the curly braces {} if there’s just one statementfor(const x of "some string") console.log(x); // will print a line for each character in the string

Which one do we recommend? None!

Use array.map(func) or array.reduce(func, initialAccumulatorValue). �Sometimes array.forEach(func) will do

12 of 24

Objects keys, properties

let o={} // equivalent with:�o= new Object() // especially python ppl: always use new!

A key is a property name. obj[“key”] is the same as obj.key but much more general:

rect.height → 10 �rect["height"] → 10

rect["height"]=12 �let field="height"�rect[field] → 12 // Compare with java Core Reflection...

for(const f in rect) console.log(rect[f]) � // will print the values of height and width, and the surface function

delete rect.surfacetrue // delete was successful�delete rect[0]true // delete is successful even for non-existent keys...

for(const x in rect) console.log(x) // for ... in can be rewritten using for ... of and Object.keys�Object.keys(rect)["height", "width", "surface"]

for(const x of Object.keys(rect)) console.log(x) // print same as for … in above!

Keys in object initializers can be variable

let k="height":�let rect={[k]:10, width:20}

13 of 24

Arrays as objects with integer keys

let arr=[7,2,9] // arrays are objects with integer keys and a special prototype. Same as new Array(7,2,9)�arr.length → 3 arr.something="bla" // can be mixed with ordinary keys �arr.length → 3 �arr[0] → 7�arr[2] → 9 �arr["2"] → 9 // 2 above was actually converted automatically to "2"!!! See Object.keys() call below�arr["something"]"bla" // the integer and non-integer keys are accessed the same way!!!�arr["length"] → 3 // length is always the max integer key + 1. something is a non-integer key�Object.keys(arr) → ["0", "1", "2", "something"] , length is hidden… Object.keys returns an array of keys for any objectarr[7]=11 // array grows automatically�arr.length 8 // max integer key is 7; 7 +1 = 8�arr[5] → undefinedObject.keys(arr) → ["0", "1", "2", “7”, "something"] �arr → [7, 2, 9, empty × 4, 11, something: "bla"]��arr[6]={someProp:5} // arrays can contain objects�arr[5]=[1,3,5] // arrays can contain arrays, a consequence of the above�

14 of 24

Higher-order functions. Functional programming

Array.prototype.filter, map, sort, etc. return an array so they can be chained

[3,1,2].sort() → [1,2,3]let plusOne= x=>x+1 // same as function plusOne(x){ return x+1; }�[0,7,5].map(plusOne) → [1,8,6][2,0,7].map(plusOne).sort() → [1,3,8]�[2,0,7].map(plusOne).sort().map(plusOne) → [2,4,9]

let isEven= x=>x%2==0 �[2,0,7].filter(isEven) → [2,0]�[2,0,7].filter(isEven).map(plusOne) → [3,1]�[2,0,7].filter(isEven).map(plusOne).filter(isEven) → [] of course :))�[2,0,7].filter(isEven).map(plusOne).sort() → [1,3]

reduce passes an accumulator together with each array element to a given function, to calculate the new accumulator. The result is the final accumulator.

let addUp= (acc, x) => acc+x�[2,0,7].reduce(addUp, 0) → 9 // calculates sum of all array elements�[2,0,7].reduce(addUp, "")"207" // same function, different initial accumulator

15 of 24

Any for() can be replaced with functional code

Array functions can easily replace any for loop, which are a known source of bugs

for(;;), for( of ) just use map(), reduce() or forEach()

for( in )

for(const x in rect) {console.log(x); } // This is procedural… functional version:�Object.keys(rect).forEach(x=>console.log(x)) // or shorter:�Object.keys(rect).forEach(console.log)

When to use map()?

  • To transform the array into a more useful one
  • Used a lot in interaction programming to generate list UIs array.map(x=> HTML for x)

When to use reduce()? When you want to sum array elements, concatenate elements (arrays, strings), collect object properties, etc

When to use forEach() ? When you are not interested in the result of any iteration

16 of 24

More on functional programming

Functional programming usually leads to safer code. This compensates for JavaScript’s lack of type safety, private members, etc.

Even if a function is passed to them, higher-order functions, are different from Alice/Bob subscription/notification

  • In higher-order functions, the function passed is executed (repeatedly) immediately, i.e. synchronously with the code that calls map, filter, etc.
  • if e.g. arr.map(func) has lots of work to do, it will take a long time, calling func in the process
  • while Bob.subscribe(func) returns immediately, and func will be called by Bob later.

17 of 24

Callbacks and asynchronous execution

// Alice: my code, subscribing to: when 7 seconds passed Bob: the browser, subscription method: setTimeout

setTimeout(()=> console.log("tick"), 7000) // when will this finish? in 7 seconds or immediately?

Subscriptions finish immediately (be it setTimeout, addEventListener, etc)! The callback executes at a point in time independent from Alice’s code (at the time of Bob’s choosing). That is, execution is asynchronous

let tick= ()=> console.log("tick")setTimeout(tick, 2000)

// Frequent mistake: Alice should never call its callback

setTimeout(tick(), 2000)

18 of 24

Shorthand: from variables to object properties

let height=5; let width=10;�let rectangle={ height, width } // shorthand object initialiser equivalent with:

let rectangle={height:height, width:width} // and with�rectangle={"height":height, "width":width}

let height="alpha"; // copied by value not by reference�let rectangle={ height, width };�height="beta";�rectangle {height: "alpha", width: 10}

19 of 24

Spread syntax ...

Combining arrays, elements�[...arr1, sep, ...arr2] makes a new array with the elements of arr1, followed by a “separator” element (sep), followed by the elements of arr2. Very easy to concatenate arrays or to insert elements at beginning or end

Making a true Array from an iterable�[...node.children] �HTML collections like node.children, selectElement.options are not of the class Array, so they don’t have map(), forEach() etc. If you want to do functional programming with them, you need to convert them to array first, using the spread.

Variable number of parameters�function(param1, param2, ...restOfParams) �We know for sure that we need two parameters but from parameter 3 on, we leave it flexible. restOfParams is available to the programmer as an array, can have zero or more elements. E.g. console.log takes any number of parameters

Passing each array element as a parameter to a function with variable parameter number�func(p1, p2, ...arr) Example:�console.log([1,2,3]) sees one parameter, prints the array BUTconsole.log(0, ...[1,2,3]) sees 4 parameters, prints them separated by spaces

Instead of�arr.push(x); // statement, changes the array�obj[k]=v; // statement, changes the object

Much better for functional programming:�[...arr, x] // expression, creates new array

Spread syntax works also with objects lately

{...obj, ...obj2, key:value}

{... obj, [key1]:val, ...obj2} // variable key name

20 of 24

From object properties to variables: destructuring

let {height, width} = rect;

Destructuring is especially useful in function parameters. Used by React functional components, and any hyperscript/JSX functional component

function someFunction({height, width}) { console.log(height, width); } // equivalent with:� function someFunction(obj) { console.log(obj.height, obj.width); }

// or the thick arrow versions:�const someFunction= ({height, width}) => console.log(height, width); // equivalent with:� const someFunction= obj => console.log(obj.height, obj.width);

// Test all with �someFunction({height:10, width:20})

Destructuring works also with arrays. Used by React hooks like e.g. useState()�const [a, b]= someArray;

21 of 24

If you come from Java

The syntax is the same (comes from C: if(){}else{}, for(;;){}, ==, !=, +=, -=), with a few additions

  • JavaScript is interpreted, no bytecode
  • Objects can be created without a class let rect={width:10, height:20}
  • any object property can be read and written (more like Java Map/Dictionary/Hashtable)
  • Arrays can be created and changed/grown freely, more like Java Vector/List/ArrayList
  • Functions are a basic type (like numbers, strings, dates), you do not need to create a class + method to define procedural behaviour.

Functions are represented as strings in JavaScript, they are interpreted when they are called (with optimization). You can always retrieve the source code of a function, just call func.toString()

22 of 24

If you come from Python

you have to learn the C/Java syntax if(){}else{}, for(;;){}, ==, !=, +=, -=

  • But you may be more familiar with functions as variables than people who come from a Java background
  • JavaScript is also an interpreted language, with compiling optimizations
  • JavaScript is also dynamically typed, but with much fewer type constraints.�1+"two" "1two" (illegal in Python)

In principle JavaScript is designed to accept as much as possible without complaining. It is a scripting language!

23 of 24

JavaScript closures: event listener

We use closures naturally in JS but let’s examine them closer. �Consider a DOM event listener on each element of an array. �h("div", {}, dishes.map(dish=>� h("div", {}� , h("span", {}, dish.title)� , h("button", {onClick: e=> model.delete(dish) }, "delete")� )� ) // end of map()�);

In a traditional programming language, the value of the dish local variable/parameter will disappear from memory (stack space) at the end of the function dish=> h("div", …). However, in JavaScript such local variables are kept around for as long as they are needed!

dish will be needed at every button click! Will be kept until the click listener is garbage collected (usually together with the button). �

Closures are typically used in connection to Bob-Alice notifications like event listeners. Typically the callback uses a local variable from its enclosing function.�

Declaring function: dish=>h(...)�Variable: dishCallback: e=>delete(dish)�Alice: hyperscript (renderer)�Bob: button�Subscription: onClick

24 of 24

JavaScript closures: cancelling the previous render

Consider a “render search result” function invoked at each keystroke. The render performs a query fetch() and renders the result. However if a new key was typed, the query string changed so there’s no point to render!class SearchView{� renderSearchResults(query){� if(this.cancelRender)� this.cancelRender();� let cancelled= false;� fetch(ENDPOINT+query).then(r=>r.json())� .then(data=> !cancelled && h("div", {}, data));� this.cancelRender= ()=> { cancelled=true; } ;� }�}�At the end, the function creates another function which it stores as this.cancelRender. The function will set to true the local variable ‘cancelled’

Declaring function: renderSearchResults()�Variable: cancelledCallback: data=> !cancelled && h() �Alice: hyperscript (renderer)�Bob: Promise�Subscription: then()

renderSearchResults(query1= "p")this.cancelRender: undefined�cancelled1: false�this.cancelRender: ()=>{cancelled1=true}

renderSearchResults(query2= "pi")this.cancelRender() cancelled1 becomes true

promise1.then(data=>!true && ) //no render!

cancelled2: false�this.cancelRender: ()=>{cancelled2=true}�

A more advanced version: abort the fetch(). Closure variable is the fetch controllerconst controller = new AbortController();fetch(url, {signal:controller.signal}).then(r=>r.json())� .then(d=>h("div",{}, d)).catch(err=>/*ignore on abort */); �this.cancelRender=()=>controller.abort() ; // catch() -> no render