Foundational programming

Notes for a course at Linnaeus University about JavaScript and Node.js: https://coursepress.gitbook.io/1dv021/

The course features videos and the online book Eloquent JavaScript. The book JavaScript: The Good Parts is a recommended resource.

A few guides in setting up the environment with Git prepares for the exercises.

The course does not endorse semicolons however the Google JavaScript style guide says "Always use semicolons. Relying on implicit insertion can cause subtle, hard to debug problems."

A potential problem is illustrated by the following which tries to call c(d+e):
a = b + c
(d + e).print()

Introduction

Goals:

Chapters 1 to 12 of https://eloquentjavascript.net/ are used.

There are two examinations/parts with associated practice tasks.

Learning is not a spectator sport, if you don't actually try to solve problems you wont learn.

"Don't be afraid to ask questions. Don't be afraid to ask for help when you need it. I do that every day. Asking for help isn't a sign of weakness, it's a sign of strength. It shows you have the courage to admit when you don't know something, and then allows you to learn something new." --Barack Obama

Part 1:

Part 2:

Development environment:

Lecture 1

Keywords: history, datatypes, Number, String, Boolean, Object, typeof, arithmetic, logic, expressions, statements, variables, keywords, reserved words, functions.

JavaScript 1.0 was launched 1995 to validate forms before sending them to the server.

Node.js is an open source platform used to run JavaScript code on a server.

You should know why 00101010 is 42 (aka the answer to everything).

A binary number can be interpreted as various types.

Primitives: Undefined, Null, Boolean, Number, Symbol and String.

Every value must be a primitive or an Object.

typeof 42 // 'number'

Functions are objects.

4.712e7 // exponentials (4.712 * 10^7 = 47120000)

13%5 // remainder (3 since 2*5+3=13)

4+(1+2)*3 // first parentheses then multiplication then addition, 13

Special numbers: infinity, -infinity, NaN (Not a Number)

The course standard prohibits " for string, use '

Use \n for new lines: 'Line one\ntwo.'

Boolean operations: <, >, <=, >=, ==, !=, === (includes type), !== (includes type), && (AND), || (OR), ! (NOT)

Variables are declared with let or var, var is for backwards compatibility, let is almost always better since variables wont leak to other scopes.

Use descriptive variable names. Don't use odd symbols. Use camelCasing. A tip is to append your initials to your variables, even if code works perfectly in Firefox another browser or package can have defined some system variables that get overwritten if you use ordinary variable names.

Forbidden names:

Try console.log and Math.min(2,1,3)

Functions can be declared with 'let f=function(){return 1}' or 'function f(){return 1}'. If return is missing undefined is returned.

Lecture 2

Control structures: if (else)(else if), switch, a?1:0, while, do...while, for, recursion (a function calling itself is a likely apocalyptic cause)

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

Use break to exit a loop, continue to skip to next iteration.

Comments are made with // or, with caution, /* */. Commenting out blocks of code is not recommended for the long term, conditioning out with if (false) prevents code rot but still leaves confusing bloat.

Lecture 3

Some functions are built in: console.log('Hej hopp!'); let result = Math.floor(3.14)

Functions are required to not repeat code, DRY (Don't Repeat Yourself) coding is crucial. It is good to have at least one test for every function as this also serves as documentation, writing tests before the code has proven benefits such as increased modularity (see Test Driven Development).

Math.random() returns a value from 0 to 1.

const sayHello = function (name) { return 'Hello '+name }

Scopes are intricate, in short a variable declared inside a function is not accessible outside it.

For better and silent upstream toxin worse JavaScript simply ignores function calls with the wrong amount of arguments, resulting in weird undefined or NaN values showing up in unexpected places much later.

A function can be passed as an argument.

Closure: a function returns a nested function with access to the local scope.

Lecture 4

Arrays start with index 0 and while they can hold values of multiple types this should be avoided.

An array arr is defined simply as [1,2,3], the number of elements is stored in a.length, arr[1]==2.

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

Mozillas Array reference page.

An array variable only holds a reference to the array so if you set b=a and change b a will also be changed. To deepcopy an array with simple types (no functions) you can do b=JSON.parse(JSON.stringify(arr))

Push (append) a random number from 0 to 9 like so: arr.push(Math.floor(Math.random() * 10))

A function that takes an array and modifies it modifies the original array, it is necessary to clearly indicate which functions potentially modify their arguments to avoid silent data corruption.

To copy an array you can do c=arr.slice(). Note that unlike the JSON method above if arr contains nested arrays they will still be the same memory object in c.

With arr.sort() elements are sorted through string conversion, ie 10 comes before 2. To custom sort pass a function like so: arr.sort(function (a, b) { return a - b })

arr.forEach(function (value) { console.log(value) });

let sum = arr.reduce(function (a, b) { return a + b }, 0)

Remember to declare variables like sum with eg let or they will leak to global scope, strict mode prevents this.

arr.filter(function (a) { return a % 2 === 0 });

arr.map(function (a) { return a * 2 });

Lecture 5

Objects are declared like so: let obj = { a: 'some', b: 1 }

Object variables hold references like for arrays.

An empty object can be declared like {} (or new Object()) and fields added like so: obj.c='here'

Object.create(Object.prototype[, properties]) is another option.

Object properties are accessed with obj.a or obj['b']

for (let propertyName in obj) { console.log(propertyName + ': ' + obj[propertyName]) }

Object.keys(obj) gives an array with the property names.

Use throw to abort with error: if (!Array.isArray(arr)) { throw new TypeError(`arr doesn't refer to an array.`) }


try {
  bar(foo)
} catch (e) {
  console.error(e.message)
}

Lecture 6


const die = {
  faceValue: undefined,
  roll: function () {
    this.faceValue = Math.floor(Math.random() * 6) + 1
  }
}

The this keyword should be used sparingly as it can suddenly point to something unexpected, for example in callbacks. Check out the bind and call/apply functions.

Default argument values can be specified like so: function (greet = 'Hej!')

If you need many dices create a Factory: a function that creates a die object and then returns it; or a Constructor invoked with new.


function Die () {
  this.faceValue = undefined
  this.roll = function () {
    this.faceValue = Math.floor(Math.random() * 6) + 1
  }
}
const die = new Die()
console.log(Object.getOwnPropertyNames(die)) // OUTPUT: [ 'faceValue', 'roll' ]

Constructors accept parameters like ordinary functions.

To avoid duplicating the roll function in every Die object declare it as a prototype:
function Die () {
  this.faceValue = undefined
}
Die.prototype.roll = function () {
  this.faceValue = Math.floor(Math.random() * 6) + 1
}
const die = new Die()

The class syntax can be used since ECMAScript 2015:
class Die {
  constructor () {
    this.faceValue = undefined
  }
  roll () {
    this.faceValue = Math.floor(Math.random() * 6) + 1
  }
}
const die = new Die()

The designpattern OLOO (Object Linked to Other Objects) is an advanced alternative:
const Person = {
  talk: function () {
    console.log('Jag, ' + this.name + ', är ' + this.age + ' år.')
  }
}
const createPerson = function (name, age) {
  return Object.create(Person, {
    'name': {
      value: name,
      writable: true,
      enumerable: true,
      configurable: true
    },
    'age': {
      value: age,
      writable: true,
      enumerable: true,
      configurable: true
    }
  })
}

Lecture 7

Introducing Node.js modules.

Define a rectangle constructor function in a separate file and end with module.exports=Rectangle then:
const Rectangle = require('./src/Rectangle')
const rectangle = new Rectangle()

Object instances have a __proto__ reference to the constructors prototype.

Variables set with this are public whereas if you let set a value in the constructor it is private and only accessible through the functions (such as getters/setters).


function Die () {
  this._faceValue = undefined // semi-private property
}
Object.defineProperty(Die.prototype, 'faceValue', {
  get: function () {
    return this._faceValue
  }
})

Try adding a set: function to the above code that only allows certain numbers.

Private variables can have annoying side effects, sure it prevents users from making dumb mistakes but if you know what you are doing it is a pain to not be allowed to do it.


class Die {
  constructor () {
    this._faceValue = undefined
  }
  get faceValue () {
    return this._faceValue
  }
  roll () {
    this._faceValue = Math.floor(Math.random() * 6) + 1
  }
}

const _faceValue = new WeakMap()
class Die {
  constructor () {
    _faceValue.set(this, undefined)
  }
  get faceValue () {
    return _faceValue.get(this)
  }
  set faceValue (value) {
    const numberValue = Number(value)
    if (!Number.isInteger(numberValue) || numberValue <1 || numberValue> 6) {
      throw new Error('faceValue must be set to an integer between 1 and 6.')
    }
    _faceValue.set(this, value)
  }
  roll () {
    this.faceValue = Math.floor(Math.random() * 6) + 1
  }
}

class Die {
  #faceValue // private class field/class property, stage 3 proposal ES2019
  constructor () {
    this.roll()
  }
  get faceValue () {
    return this.#faceValue
  }
...

Inheritance prevents code repetition.


function Student (name, age, isCampus) {
  Person.call(this, name, age) // call the constructor for Person (defined elsewhere)
  this.isCampus = isCampus
}
Student.prototype = Object.create(Person.prototype) // inherits prototype from Person
Student.prototype.constructor =  Student // restores constructor to Student
Student.prototype.toString = function() { // overshadows the method in Person.prototype
  return 'Jag, ' + this.name + ', läser på ' +
    (this.isCampus ? 'campus' : 'distans') + ' och är ' +  this.age + ' år.'
}

class Student extends Person {
  constructor (name, age, isCampus) {
    super(name, age) // call constructor for Person
    this.isCampus = isCampus
  }
  toString() { // overshadows the method in Person
    return 'Jag, ' + this.name + ', läser på ' +
      (this.isCampus ? 'campus' : 'distans') + ' och är ' +  this.age + ' år.'
  }
}

Do obj instanceof Constructor to check if the constructor helped create the object.


const personBase = {
  toString: function () {
    return `Jag, ${this.name}, är ${this.age} år.`
  }
}
let createPerson = function (name, age) {
  return Object.create(personBase, {
    age: {
      value: age,
      writable: true, configurable: true, enumerable: true
    },
    name: {
      value: name,
      writable: true, configurable: true, enumerable: true
    }
  })
}
let createStudent = function (name, age, isCampus) {
  let obj = Object.create(createPerson(name, age), {
    isCampus: {
      value: isCampus,
      writable: true, configurable: true, enumerable: true
    }
  })
  obj.toString = function () {
...

Lecture 8

Code should be easy to read, easy to understand, easy to maintain and easy to collaborate on.

Modules contain objects that contain functions.

A module contains code for some task that may be needed in several places, easy reusability is therefore a key goal. A big tangled nest of stuff that does everything together may work but when something breaks you have to pick it all apart. Instead of mixing logic with UI we can have specific files, with only console code, that are easy to plug into different graphical frameworks. Robust test coverage can also be developed better.

Node.js handles modules with their own implementation of CommonJS. The clientside uses ECMAScript Modules (ES Modules).

Functions not assigned to module.exports are private within the module. Functions can be assigned there directly: module.exports.fun = function () {}

An object with the functions can be assigned to module.exports, or a constructor. For the module root to be a constructor upon import one can NOT assign module.exports.Constructor=Constructor, it has to be on the root: module.exports = Constructor

Import with relative path: const greetings = require('./src/greetings')

The import path has to start with ./ or ../ or / or be a core module or be in the node_modules catalogue.

If the path isn't a valid file Node.js tries adding .js then .json and finally .node.

Regular expressions are essential due to their ability to update code with advanced search and replace operations. Take time to get familiar with them, at first it will look extremely cryptic: /^[1-9]\d{2} ?\d{2}$/.test(value)

Examining new RegExp('^[1-9]\d{2} ?\d{2}$') we find ^ is start of string, [1-9] is any number except zero exactly once, \d{2} is any number twice, ? is nothing or a space and $ is end of string.

Squaring an array:
// imperative
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for (let i = 0; i <arr.length; i += 1) {
  arr[i] = Math.pow(arr[i], 2)
}
// declarative with anonymous function expression
let sqrArr = arr.map(function(num) {
  return Math.pow(num, 2)
})
// declarative with arrow function (lambda expression)
sqrArr = arr.map(num => Math.pow(num, 2))
Note that arrow functions cannot be used as prototypes (because of peculiarities with this).

Proposals for the ECMAScript standard.




Updated on 2020-08-07.