Web
Object-oriented JavaScript
##
Overview of Today's Class - Wat (Video) - Quiz about last week's lecture - Correction of last week's assignment - Prototype-oriented JavaScript - Object-oriented JavaScript - Manipulating DOM objects - Drawing in the HTML Canvas - Introduction of next week's assignment
##
Educational Objectives On completion of this part, students will be able to: - Create object using the class syntax - Use the `extends` keyword to create a class as the child of another class - Use the `super` keyword to access and call functions on an object's parent - Use the `static` keyword to create static methods and properties - Can manipulate the DOM using JavaScript - Can draw in the HTML Canvas using JavaScript - Can manipulate arrays and objects in JavaScript using the array methods
Quiz
Foundations of JavaScript
##
Question 1 Quelle ou quelles affirmations sont correctes à propos de JavaScript: - (A) JavaScript est un language compilé - (B) JavaScript est un language interpreté - (C) JavaScript est un language statique - (D) JavaScript est un language dynamique - (E) JavaScript est un language orienté objet - (F) JavaScript est un language orienté prototype - (G) Aucune affirmation correcte Notes:
- (B) JavaScript est un language interpreté - (D) JavaScript est un language dynamique - (F) JavaScript est un language orienté objet (mais prototype-based, par opposition à class-based)
#
Question 2 Quel est l'output du code suivant ? ```js[] function fun(value) { var v = value; } fun(42); console.log(v); ``` - (A) `42` - (B) `undefined` - (C) `Error` - (D) Aucune réponse correcte Notes:
(C) `Error` (v is not defined) `v` n'est défini que dans la fonction, bien que ce soit une `var`. `v` n'est donc pas défini dans le scope global, et l'appel à `console.log(v)` provoque une `ReferenceError`.
#
Question 3 Quel est l'output du code suivant ? ```js[] function fun(value) { if (value == 42) { var v = value; } return v; } console.log(fun(42)); ``` - (A) `42` - (B) `undefined` - (C) `Error` - (D) Aucune réponse correcte Notes:
(A) `42` `v` est défini dans un bloc de la fonction, son scope est donc **toute** la fonction. `v` est donc définie à la ligne 5, et la ligne 6 retourne `42`.
#
Question 4 Quel est l'output du code suivant ? ```js[] function fun(value) { if (value == 42) { let v = value; } return v; } console.log(fun(42)); ``` - (A) `42` - (B) `undefined` - (C) `Error` - (D) Aucune réponse correcte Notes:
(C) `Error` `v` est défini dans un bloc de la fonction, son scope est donc **le bloc**. `v` n'est donc pas définie à la ligne 5, et la ligne 6 retourne une `ReferenceError`.
#
Question 5 Quelle est la valeur imprimée par le programme suivant? ```js console.log("PI = ${Math.PI}"); ``` - (A) `"PI = 3.141592653589793"` - (B) `"PI = ${Math.PI}"` - (C) `"PI = 3.14"` - (D) `"PI = ${3.141592653589793}"` - (E) Aucune réponse correcte Notes:
(B) `"PI = ${Math.PI}"` Les *template literals* sont délimités par des backticks (\`). Ici, on utilise des guillemets (") qui ne sont pas interprétés comme des *template literals*. La chaîne de caractères est donc imprimée telle quelle.
#
Question 6 Quelle est la valeur imprimée par le programme suivant? ```js function fun(value) { let v = value; return [() => v++, () => v--]; } let [inc, dec] = fun(10); console.log(inc()); console.log(inc()); console.log(dec()); console.log(dec()); ``` - (A) `10`, `11`, `12`, `11` - (B) `11`, `12`, `11`, `10` - (C) `10`, `11`, `10`, `9` - (D) `11`, `12`, `9`, `8` - (E) Aucune réponse correcte Notes:
(B) `10`, `11`, `12`, `11` Tout d'abord, l'opérateur `++` est postfixé, donc `v++` retourne la valeur de `v` avant l'incrémentation, et similairement pour `v--`. Il ne peut donc pas s'agir de (B) ou (D). Ensuite, les deux fonctions retournées par `fun` sont des closures, elles capturent donc `v`, et toute modification à une variable capturée depuis une closure modifie la variable elle-même dans son environnement de départ. `inc` et `dec` modifient donc la même variable, et les changements de l'un sont visibles par l'autre.
#
Question 7 Quelle est la valeur imprimée par le programme suivant? ```js[] var x = "2"; if (x == 2) { var x = 3; } console.log(x); ``` - (A) `2` - (B) `"2"` - (C) `3` - (D) `"3"` - (E) `null` - (F) `undefined` - (G) Aucune réponse correcte Notes:
(C) `3` Deux choses ont lieu ici. Premièrement, bien que `x` soit une string comparée à un nombre, JavaScript parse la string en nombre pour effectuer la comparaison. On entre donc dans le `if`. Ensuite, `x` est redéfinie dans le bloc du `if` à la valeur `3`. Puisque `var` définit une variable dans le scope du contexte d'exécution par opposition au scope du bloc, `x` est redéfinie à 3 globalement, et donc imprimée à la ligne 5.
Corrections
##
Labo 1
##
Questions
Prototype-oriented JavaScript
##
Recall JavaScript's Types ECMAScript defines 7 **primitive** (Immutable) types for values: ```js undefined; // Undefined 3.14; // Number true; // Boolean "Heig-vd"; // String 9007199254740992n; // BigInt Symbol("Symbol") // Symbol null; // Null (Structural root primitive) ``` ECMAScript defines a special mutable type called **object** for collections of properties. ```js {prop: "value"}; // Object ``` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Data_types
##
Objects An object is a mutable unordered collection of properties. A property is a tuple of a key and a value. A property key is either a string or a symbol. A property value can be any ECMAScript language value. ```js let car = { make: 'Ford', model: 'Mustang', year: 1969 } ``` You can access properties using the **dot notation** for properties named `"^[a-z]+(_[a-z]+)+$"`: ```js let car = new Object(); car.make = 'Ford'; car.model = 'Mustang'; car.year = 1969; ``` Properties can also be accessed or set using the **bracket notation**: ```js let car = new Object(); car['make'] = 'Ford'; car['model'] = 'Mustang'; car['year'] = 1969; ``` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects
##
Methods When a function is stored as a property of an object, we call it a **method**. When a method is invoked, the value of `this` inside the method is the object the method is called on. ```js var apple = { color: 'red', toString: function() { return `This fruit is ${this.color}!`; } } console.log(apple.toString()); // This fruit is red! ```
##
Methods When a function which is not the property of an object is invoked, `this` is bound to the **global** object. This is an **error** in the design of the language as it prevents the definition of helper functions. ```js var color = 'blue'; var apple = { color: 'red', toString: function() { function helper() { return `This fruit is ${this.color}!`; } return helper(); } } // This fruit is blue! console.log(apple.toString()); ``` This issue can be addressed with: - The `apply(this, args)`, `call(this, arg, ...)` or `bind(this)` methods of a `Function` object that redefine `this`. - The arrow function expression that does not define its own `this` and takes the one present in its scope. Notes: - The `apply(this, args)` function is a method of `Function` instances, which allows to override the `this` object with one provided as argument. For example, given a `getName` method on a `Person` object, calling `getName.apply(animal, args)` will execute that `getName` function as if it had been called like `animal.getName(args)`. - The `call(this, ...)` function is identical, except that the arguments are given directly, instead of in the form of an array. - The `bind(this)` method of `Function` returns a new function whose `this` object is overridden with the provided one.
##
The prototype property Every object automatically has a prototype property (usually `__proto__`). By default this points to `Object.prototype`, whose own `__proto__` is `null`. ```js var obj = { city: "Madrid", greet() { console.log("Welcome to ${this.city}!") } } ```
When an object's prototype property is the prototype of another object, we say that the former *inherits* from the latter. Here, `obj` inherits from `Object`. Notes: All objects have a property holding a *prototype* object. That property is often named `__proto__`, but no standard enforces this. Note that this prototype *is an object*, so it also has a prototype property. This creates the *prototype chain*. When an object is created, its prototype property is automatically set to `Object.prototype`, the prototype of the `Object` type. Note that, `Object.prototype` being an object, it also has a prototype property itself, but it is `null` since it is the end of the prototype chain. Whenever a property or method is requested on an object, it is first searched for in that object, and if not found, it is searched in its prototype, and so on until it is found or the entire prototype chain has been traversed.
##
The prototype property Prototypes being objects, this forms a chain called the **prototype chain**.
When accessing an attribute of an object, if it cannot be found on that object, it is then searched along the prototype chain. ```js obj.toString(); // Uses Object.prototype.toString ``` We therefore usually put methods that are to be shared in the prototype rather than on the object.
##
Constructor Invocation Objects inherit the properties of their prototype, hence JavaScript is class-free. If a function is invoked with the `new` operator: - a new object that inherits from the function's prototype is created - the function is called and `this` is bound to the created object - if the function doesn't return something else, `this` is returned ```js function Fruit(color) { this.color = color; } Fruit.prototype.toString = function() { return `This fruit is ${this.color}!`; } var apple = new Fruit("red"); console.log(apple.toString()); // This fruit is red! ``` Notes: Functions define a `prototype` data property that is used when a function is used as a constructor with the `new` operator. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype For more information on the `new` operator, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
##
Prototype Inheritence With prototypes, inheritence can be achieved using the `Object.create(obj)` or `Object.assign(target, source)` functions. Here, the idea consists in using an existing object as the prototype of a newly created object. ```js function Fruit(color) { this.color = color; } Fruit.prototype.toString = function() { return `This fruit is ${this.color}!`; } function Apple(color, name) { Fruit.call(this, color); this.name = name; } Apple.prototype = Object.assign(Apple.prototype, Fruit.prototype); var apple = new Apple("red", "golden"); console.log(apple.toString()); // This fruit is red! ``` When a lookup fails on the apple object, it now falls back on the Fruit `prototype`. Notes: `Object.create(param)` creates a new object and sets its prototype (`__proto__`) to the provided `param`;
##
Consider the Array object The `Array` object is a global object that is used in the construction of arrays, which are high-level, list-like objects. ```js let fruits = ['Apple', 'Banana', 'Pear']; ``` Here, the `[]` notation is a **shorthand** for the `Array` constructor. ```js let fruits = new Array('Apple', 'Banana', 'Pear'); ``` Note that `Array` is a function and the `new` operator makes it be used as a constructor. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
##
Consider the Array object - indices Because arrays are objects, getting `myArray[42]` is equivalent to accessing a property named `"42"` on the object `myArray`. That's why the `for ... in` loop, which iterates over an object's properties, can be used to iterate over the indices of an array. ```js let fruits = ['Apple', 'Banana', 'Pear']; for (let index in fruits) { console.log(index); // 0, 1, 2 } ```
##
Overriding the prototype Changes to an object's prototype are seen by all instances of that object. That's why it's called a prototype. The properties and methods of prototypes can be **overridden** along the chain. ```js var fruits = ['apple', 'banana', 'pear']; console.log(fruits.toString()); // apple,banana,pear Array.prototype.toString = function() { return `Array of size ${this.length}`; } console.log(fruits.toString()); // Array of size 3! ```
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype
##
Putting it all together As objects inherit properties from their prototype, we can say that JavaScript is a **prototype-based** language and not a **class-based** one. While JavaScript does offer a class syntax, it is only **syntactic sugar** for the prototype-based inheritance system.
Object-oriented JavaScript
##
The Object-oriented Syntax Introduced in ECMAScript 2015, classes are syntactic sugar over JavaScript's prototype-based inheritance. It is **not** a new object-oriented inheritance model. ```js class Fruit { constructor(color) { this.color = color; } toString() { return `This fruit is ${this.color}!`; } } class Apple extends Fruit { constructor(color, name) { super(color); this.name = name; } toString() { return super.toString(); } } let apple = new Apple("red", "golden"); console.log(apple.toString()); // This fruit is red! ``` - The `extends` keyword is used in class declarations or class expressions to create a class as the child of another class. - The `constructor` method is a special method for creating and initializing an object described by a class. - The `super` keyword is used to access and call functions on an object's parent. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
##
Private Properties, Getter and Setter The class syntax gives the ability to define private properties (and methods) with `#` and to define property like getters and setters. ```js class Fruit { #color; get color() { return this.#color; } set color(color) { this.#color = color; } } let apple = new Fruit(); apple.color = 'red'; // calls the setter console.log(apple.color); // red (calls getter) console.log(apple.#color); // SyntaxError: Private field '#color' must be declared in an enclosing class ``` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
##
Static Properties and Methods The static keyword can also be used to define a static properties and methods. Static methods can be called without instantiating the class and they cannot be called from an instance. **Static methods** are often used to create utility functions. ```js class Point { constructor(x, y) { this.x = x; this.y = y; } static distance(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; return Math.hypot(dx, dy); } } let p1 = new Point(5, 5); let p2 = new Point(10, 10); console.log(Point.distance(p1, p2)); // 7.0710678118654755 ``` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes Notes: Static methods and properties are defined on the class itself, not on the prototype. They are thus not inherited by instances of the class.
##
Mix-ins Multiple inheritance is not supported in JavaScript, but it possible to simulate with mix-ins. ```js const canWalk = (Base) => class extends Base { walk() { console.log("Walking..."); } }; const canEat = (Base) => class extends Base { eat() { console.log("Eating..."); } }; class Person extends canWalk(canEat(Object)) { constructor(name) { super(); this.name = name; } } let person = new Person("John"); person.walk(); // Walking... person.eat(); // Eating... ``` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
Javascript Modules
##
Modules ECMAScript 6 introduced modules, a reusable piece of code that can be exported from one program and imported for use in another program. ### Exporting Any value can be exported (object, number, string, etc). ```js // Export upon declaring a value export function sum(a, b) { ... } export const ANSWER = 42 // Export a declared value export { val1, val2, ... } ``` An export can be made **default** by following `export` with `default`.
##
Modules ECMAScript 6 introduced modules, a reusable piece of code that can be exported from one program and imported for use in another program. ### Importing The imported script must be loaded as a module with the `type="module"` attribute. ```html ``` The `import` statement must always be at the top of the js file, before any other code. ```js import { export1, export2, ... } from "module-name"; // import specific named values import { export1 as alias1, ... } from "module-name"; // import value with alias (e.g. to solve name conflicts) import * as name from "module_name"; // import all into an object import name from "module-name"; // import the default export // equivalent to import { default as name } from "module-name"; import "module-name"; // imports for side-effects; runs it but imports nothing. ``` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules Notes: - `as` allows renaming the import and can be used to solve name conflicts - There can be only one `default` export per module.
##
Modules - Example ```js // inside fruit.js class Fruit {} export Fruit; ``` ```js // inside apple.js import Fruit from 'fruit.js'; class Apple extends Fruit {} export Apple; ``` ```js // inside index.js import Apple from 'apple.js'; console.log(new Apple()); ``` ```html ```
Manipulating DOM objects
##
What is the DOM? - DOM stands for **Document Object Model** - The DOM is a programming interface for HTML and XML - The DOM represents the structure of a document in memory - The DOM is an object-oriented representation of the web page - The DOM lets other programming languages manipulate the document https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
##
The DOM's content tree ```html
My Document
Header
Paragraph
``` When a browser such as Chrome or Firefox parses an HTML document, it builds a **content tree** and then uses it to **display** the document.
https://developer.mozilla.org/en-US/docs/Web/API/Document_object_model/Using_the_W3C_DOM_Level_1_Core
##
Accessing the Document In JavaScript, the `Document` interface represents any web page loaded in the browser and serves as an entry point into the web page's content, which is the DOM tree. Access the `document` object and its properties from the Chrome DevTools. ```js document; document.location; // Getter returns location object (info about current URL). document.location = "https://heig-vd.ch/"; // Setter to update displayed URL. document.designMode = "on"; // Lets user interactively modify page content. document.referrer; // URL of page that linked to this page. ``` https://developer.mozilla.org/en-US/docs/Web/API/Document
##
Accessing the Elements of the DOM The `Element` interface represents the HTML elements loaded in the DOM tree. ```js console.log(document.getElementById("id")); console.log(document.getElementsByClassName("slide")); console.log(document.getElementsByTagName("h1")); ``` CSS Selectors can also be used to query elements. ```js console.log(document.querySelector("ul > li")); // selects the first matching element console.log(document.querySelectorAll("ul > li")); // selects all matching elements ``` Elements can then be modified in JavaScript. ```js document.getElementsByClassName("slide") .forEach(el => el.style = "background-color: blue"); let element = document.getElementById("my-icon"); element.innerHTML = "
Hello, World!
"; element.setAttribute("href", "https://www.heig-vd.ch/"); element.className; // Value of `class` attribute element.classList; // List of classes element.children; // List of child elements ``` https://developer.mozilla.org/en-US/docs/Web/API/Element
##
Listening to DOM Events DOM Events are sent to notify code of interesting things that have taken place. Each event is represented by an object that inherits from the `Event` object, and may have additional custom fields and/or functions used to get additional information about what happened. ```js document.onkeydown = function(event) {console.log(event);} document.addEventListener('keydown', event => console.log(event)) ``` Important DOM events include `load`, `click`, `mouseenter`, etc. ```js element.addEventListener('mouseenter', event => doSomething()); ``` The propagation of an event in the DOM can be stopped programatically. ```js event.stopPropagation(); ``` Event handlers can also be registered from the HTML. ```html
``` https://developer.mozilla.org/en-US/docs/Web/Events Notes: For an example of event propagation, see: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Examples#example_5_event_propagation
##
DOM Manipulation Libraries Libraries such as [jQuery](https://jquery.com/) and [Zepto](https://jquery.com/) are intended at simplifying DOM manipulation by extending the DOM and providing helpers. ```html ``` ```js $(document).ready(function(){ $("p").click(function(){ $(this).hide(); }); }); ``` JQuery uses an **imperative style** that requires to specify the changes in the order they should happen. Modern frameworks use a **declarative style** (such as React, Angular or Vue).
##
Depending or not depending? The Peter Parker principle:
### Must Read - [Thou shalt not depend on me (NDSS 2017)](https://blog.acolyer.org/2017/03/07/thou-shalt-not-depend-on-me-analysing-the-use-of-outdated-JavaScript-libraries-on-the-web/) - [Small world with high risks (USENIX Security 2019)](https://blog.acolyer.org/2019/09/30/small-world-with-high-risks/) Notes: As an appetizer for these reads: the situation is bad. Each package indirectly depends on tens and tens of other packages on average, making more than a third of packages dependent on vulnerable code. This is due to old packages, poor dependency management, and poor awareness.
Drawing in the HTML Canvas
##
Initializing a Canvas The Canvas API provides a means for drawing graphics via JavaScript and the `canvas` element. ```html
``` ```js let canvas = document.getElementById("canvas"); const ctx = canvas.getContext('2d'); // setting the context properties ctx.strokeStyle = 'blue'; ctx.fillStyle = 'green'; // clearing the canvas ctx.clearRect(0, 0, 100, 100); ``` https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
##
Drawing on the Canvas Writing some text: ```js ctx.fillText("test", 30, 10); // fillText(text, x, y) ``` Filling a rectangle: ```js ctx.fillRect(10, 10, 150, 100); // fillRect(x, y, width, height) ``` Drawing an arc: ```js ctx.beginPath(); // draw half a circle ctx.arc(50, 50, 10, 0, Math.PI); // arc(x, y, radius, startAngle, endAngle, counterclockwise = false) ctx.stroke(); ``` Free drawing: ```js // draw line ctx.beginPath(); ctx.lineTo(20, 20); // lineTo(x, y) ctx.lineTo(50, 50); ctx.stroke(); ``` https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes
##
Transformations in the Canvas Transformations enables more powerful ways to translate the origin to a different position, rotate the grid and even scale it. Canvas states are stored on a stack: - When the `save()` method is called, the current drawing state is pushed onto the stack. - When the `restore()` method is called, the last saved state is popped off the stack and all saved settings are restored. When you perform transformations on the grid to draw an object you often want to restore a prior state to draw the next object. ```js ctx.fillStyle = 'rgb(0, 0, 255, 0.4)'; ctx.save(); angle = 0; while (angle < Math.PI/2) { ctx.translate(200, 200); //translate canvas origin so that the rotation is done around center of rectangle (and not (0,0)) ctx.rotate(Math.PI / 10); ctx.translate(-200, -200); //translate back canvas origin ctx.fillRect(170, 170, 60, 60); //draw at (x - width/2, y - height/2) so that rectangle is centered around (200,200) angle += Math.PI / 10; } ctx.restore(); ``` https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
## Rendering Loop and Game Loop The [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) method sets a timer which executes a function or specified piece of code once the timer expires. The [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) method, offered on the Window and Worker interfaces, repeatedly calls a function or executes a code snippet, with a fixed time delay between each call. The [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals