// Your code here
console.log(new Point(1, 2).plus(new Point(2, 1)))
// → Point{x: 3, y: 3}
Write a class called Point, which represents a
point in two-dimensional space. A point has x
and y properties, given as arguments to its
constructor.
It also has a single method plus, which takes
another point and returns the sum of the two points, that is, a
new point whose x is the sum of the x
properties of the two original points, and whose y is
the sum of their y properties.
function Speaker(name, verb) {
this.name = name
this.verb = verb || "says"
}
Speaker.prototype.speak = function(text) {
console.log(this.name + " " + this.verb + " '" + text + "'")
}
function Shouter(name) {
Speaker.call(this, name, "shouts")
}
Shouter.prototype = Object.create(Speaker.prototype)
Shouter.prototype.speak = function(text) {
Speaker.prototype.speak.call(this, text.toUpperCase())
}
new Shouter("Dr. Loudmouth").speak("hello there")
Rewrite these two object types to use the class
keyword, instead of direct prototype
manipulation. Speaker is a simple type that exposes
a speak method which, when called, logs the given
text along with the speaker's name. Shouter is a
subtype of Speaker which shouts its text and makes it
uppercase.
class Speaker {
constructor(name, verb) {
this.name = name
this.verb = verb || "says"
}
speak(text) {
console.log(this.name + " " + this.verb + " '" + text + "'")
}
}
class Shouter extends Speaker {
constructor(name) {
super(name, "shouts")
}
speak(text) {
super.speak(text.toUpperCase())
}
}
new Shouter("Dr. Loudmouth").speak("hello there")
This is the way the solution to the previous exercise might look.
The way the verb property is set per
instance rather than per class is kind of awkward. Refactor the
code to use a getter (get verb() { ...
}) instead of an instance property.
class Point {
constructor(x, y) {
this.x = x, this.y = y
}
add(other) {
return new Point(this.x + other.x, this.y + other.y)
}
}
var fakePoint = YOUR_CODE_HERE
console.log(fakePoint instanceof Point)
Use a single object literal to create an object that is
indistinguishable from a Point instance, without
calling the Point constructor.
function startNode(type, value, options) {
return YOUR_CODE_HERE
}
console.log(startNode("Identifier", "foo", {
sourceProperty: "src",
sourceValue: "bar.js"
}))
// → {type: "Identifier",
// value: "foo",
// src: "bar.js"}
Fill in the startNode function using a single
object literal. The function should return an object
with type and value properties
containing the value of the arguments by those names, and a third
property, named by the sourceProperty option, set to
the value of the sourceValue option.
var ids = {
next: 0
}
console.log(ids.get())
// → 0
console.log(ids.get())
// → 1
Add a get method to the ids object,
which returns the next id and increments its next
counter. Use the short method syntax.
var callbacks = []
for (var i = 0; i < 10; i++) {
callbacks.push(function() { console.log(i) })
}
callbacks[2]()
This is a typical mistake to make in JavaScript. We create a
number of functions in a loop, and refer to an outside variable
from these functions. All of them will end up referring to the
same variable, which ends up being incremented to 10.
Thus, callbacks[2] does not log 2. It
logs 10, as do all functions in the array.
Do you know the solution for such situations in ES5? Does ES6 provide a cleaner solution?
const account = {
username: "marijn",
password: "xyzzy"
}
account.password = "s3cret" // (*much* more secure)
console.log(account.password)
Does the fact that account is constant mean that
we can't update password? Why, or why not? And if
not, how could we make it so that we can't?
let s = new Something()
class Something {}
This code produces an error. So apparently, unlike functions, classes are not hoisted to the top of their scope. (They are block-scoped.)
What could be the reason for this?
const inventory = [
{type: "machine", value: 5000},
{type: "machine", value: 650},
{type: "duck", value: 10},
{type: "furniture", value: 1200},
{type: "machine", value: 77}
]
let totalMachineValue = YOUR_CODE_HERE
console.log(totalMachineValue)
Write an expression using higher-order array methods
(say, filter and reduce) to compute the
total value of the machines in the inventory
array.
function SortedArray(compare) {
this.compare = compare
this.content = []
}
SortedArray.prototype.findPos = function(elt) {
for (var i = 0; i < this.content.length; i++) {
if (this.compare(elt, this.content[i]) < 0) break
}
return i
}
SortedArray.prototype.insert = function(elt) {
this.content.splice(this.findPos(elt), 0, elt)
}
var sorted = new SortedArray(function(a, b) { return a - b })
sorted.insert(5)
sorted.insert(1)
sorted.insert(2)
sorted.insert(4)
sorted.insert(3)
console.log("array:", sorted.content)
The code for this exercise implements a wrapper for working
with sorted arrays. Its constructor takes a comparison function
that compares two elements and returns a number, negative if the
first is less than the second, zero when they are equal, and
positive otherwise (similar to what the sort method
on arrays expects).
Rewrite the code to use an ES6 class. Then, rewrite the loop to
use the ES6 array method findIndex, which is
like indexOf, but takes a function instead of an
element as argument, and returns the index of the first element
for which that function returns true (or returns -1
if no such element was found). For example [1, 2,
3].findIndex(x => x > 1) yields 1. Use arrow
functions for all function expressions.
console.log([1, 2, 3].reduce((x, y) => x + y, 0))
Where does an arrow function end? At a closing bracket or
semicolon, of course. But does a comma denote the end? Is the body
of the function in this example x + y, 0, or
just x + y?
Is there anything else that will end an arrow function body? Experiment.
function go(options) {
let {speed = 4,
enable: {hyperdrive = false,
frobnifier = true}} = options
console.log("speed=", speed,
"hyperdrive:", hyperdrive,
"frobnifier:", frobnifier)
}
go({speed: 5})
This function uses destructuring for argument parsing. But it
has a problem: it crashes when the caller passes an option object
without an enable property. Since all options have
defaults, we'd like to not crash in this case. Can you think of a
clean way to fix this problem?
If you also want to allow not passing in an option object at all, how would you solve that?
function lastIndexOf(arr, elt, start) {
for (let i = start - 1; i >= 0; i--)
if (arr[i] === elt) return i
return -1
}
console.log(lastIndexOf([1, 2, 1, 2], 2))
It would be nice to be able to call our `lastIndexOf` function without providing the third argument, and have it default to starting at the end of the array. It would be even nicer if we could express this with an ES6 default argument value. Can we?
In which scope are default arguments evaluated? (Experiment to find out.)
function detectCollision(objects, point) {
for (let i = 0; i < objects.length; i++) {
let object = objects[i]
if (point.x >= object.x && point.x <= object.x + object.width &&
point.y >= object.y && point.y <= object.y + object.height)
return object
}
}
const myObjects = [
{x: 10, y: 20, width: 30, height: 30},
{x: -40, y: 20, width: 30, height: 30},
{x: 0, y: 0, width: 10, height: 5}
]
console.log(detectCollision(myObjects, {x: 4, y: 2}))
The detectCollision function searches through an
array of rectangles and returns the first rectangle that the given
point is inside of.
Use destructuring and a higher-order function to make this code
cleaner. You might want to use the new array
method find, which takes a function as argument, and
returns the first element in the array (the element, not its
index) for which the function returns true.
function replace(array, from, to, elements) {
array.splice.apply(array, [from, to - from].concat(elements))
}
let testArray = [1, 2, 100, 100, 6]
replace(testArray, 2, 4, [3, 4, 5])
console.log(testArray)
function copyReplace(array, from, to, elements) {
return array.slice(0, from).concat(elements).concat(array.slice(to))
}
console.log(copyReplace([1, 2, 100, 200, 6], 2, 4, [3, 4, 5]))
let birdsSeen = []
function recordBirds(time) {
birdsSeen.push({time, birds: Array.prototype.slice.call(arguments, 1)})
}
recordBirds(new Date, "sparrow", "robin", "pterodactyl")
console.log(birdsSeen)
Simplify these three functions by using the spread syntax.
The first one, replace, replaces part of an array
with elements from another array.
The second one, copyReplace, does the same, but
creates a new array rather than modifying its argument.
The third one is used to record a birdwatcher's sightings. It
takes first a timestamp (say, a Date), and then any
number of strings naming birds. It then stores these in
the birdsSeen array.
const teamName = "tooling"
const people = [{name: "Jennie", role: "senior"},
{name: "Ronald", role: "junior"},
{name: "Martin", role: "senior"},
{name: "Anneli", role: "junior"}]
let message = YOUR_CODE_HERE
console.log(message)
Given the data in the starting code, use a template string to produce the following string. Make sure the numbers, names, and team name actually come from the data.
There are 4 people on the tooling team.
Their names are Jennie, Ronald, Martin, Anneli.
2 of them have a senior role.
function html(...) {
// Your code here
}
const mustEscape = '<>&"'
console.log(html`You should escape the ${mustEscape.length} characters “${mustEscape}” in HTML
`)
function escapeHTML(string) {
return String(string).replace(/"/g, """).replace(/</g, "<")
.replace(/>/g, ">").replace(/&/g, "&")
}
Write a function html that can be used as a
template string tag, and produces a string in which all the
interpolated pieces are escaped as HTML. Use the
supplied escapeHTML function to do the escaping.
Remember that a tag function gets an array of in-between strings as its first argument, and then the interpolated values as further arguments.
// Generate a random graph
const graph = []
for (let i = 0; i < 50; i++)
graph.push({value: Math.random(), edges: []})
for (let i = 0; i < 100; i++) {
let from = graph[Math.floor(Math.random() * graph.length)]
let to = graph[Math.floor(Math.random() * graph.length)]
if (from.edges.indexOf(to) != -1) continue
from.edges.push(to)
}
function connectedValue(node) {
// Your code here
}
console.log(connectedValue(graph[0]))
console.log(connectedValue(graph[24]))
console.log(connectedValue(graph[49]))
The code for this exercise generates a random graph—an array of nodes, each of which has a value and an array of edges to other nodes.
Your task is to implement the
function connectedValue which, starting at a node,
traces through the graph by following edges, summing up the total
value of all nodes connected to the start node (including the
start node itself).
The recursive exploring is the tricky part. You can use either
a worklist (an array of nodes that have to be explored) or a
recursive function. Use a Set object to track the
nodes that you've already visited (to avoid going around in
circles endlessly when the graph contains a cycle).
The Set methods you'll need for that
are .add(value) and .has(value).
class MyMap {
// Your code here
}
const names = new MyMap
names.set(Array, "the array constructor")
names.set(Math, "the math object")
console.log(names.get(Array))
// → "the array constructor"
console.log(names.size)
// → 2
names.delete(Array)
console.log(names.get(Array))
// → undefined
names.clear()
console.log(names.get(Math))
// → undefined
Implement your own version of Map, without
actually using Map. I know I said this did not exist,
but the catch is that you don't have to worry about algorithmic
complexity—looking up a value in a big map can be as slow as it
needs to be.
Implement at least these methods/getters:
set(key, value)get(key)delete(key)get size()clear()
function get(url) {
return new Promise((succeed, fail) => {
var req = new XMLHttpRequest()
req.open("GET", url, true)
req.addEventListener("load", () => {
if (req.status < 400)
succeed(req.responseText)
else
fail(new Error("Request failed: " + req.statusText))
})
req.addEventListener("error", () => {
fail(new Error("Network error"))
})
req.send(null)
})
}
There is one page on the domain http://marijnhaverbeke.nl that contains the word “Piranha”. It is linked from the domain's top page.
Given this Promise-returning implementation of an HTTP get request, write a simple promise-based “crawler” that first fetches the top page, then uses some crude regexp technique to find linked URLs in that page, and fetches all linked local pages, in parallel. It scans the content of those pages for the word “Piranha”, and logs the address of any matching pages to the console.
function all(promises) {
return new Promise(function(success, fail) {
// Your code here.
});
}
// Test code.
all([]).then(function(array) {
console.log("This should be []:", array);
});
function soon(val) {
return new Promise(function(success) {
setTimeout(function() { success(val); },
Math.random() * 500);
});
}
all([soon(1), soon(2), soon(3)]).then(function(array) {
console.log("This should be [1, 2, 3]:", array);
});
function fail() {
return new Promise(function(success, fail) {
fail(new Error("boom"));
});
}
all([soon(1), fail(), soon(3)]).then(function(array) {
console.log("We should not get here");
}, function(error) {
if (error.message != "boom")
console.log("Unexpected failure:", error);
});
The Promise constructor has a
method all that, given an array of promises, returns
a promise that waits for all of the promises in the array to
finish. It then succeeds, yielding an array of result values. If
any of the promises in the array fail, the promise returned by
all fails too (with the failure value from the
failing promise).
Implement something like this yourself as a regular function
called all.
Note that after a promise is resolved (has succeeded or failed), it can't succeed or fail again, and further calls to the functions that resolve it are ignored. This can simplify the way you handle failure of your promise.
// Your code here let roundedAbs = Math.round[compose](Math.abs) console.log(roundedAbs(-5.5)) // → 6
Define a symbol compose, and add it
to Function.prototype, with a method that composes
the this function with the method's argument,
returning a new function that applies both composed functions, one
after the other, to its argument, and returns the result.
class Queue {
constructor() {
this._content = []
}
put(elt) {
return this._content.push(elt)
}
take() {
return this._content.shift()
}
}
let q = new Queue
q.put(1)
q.put(2)
console.log(q.take())
console.log(q.take())
Change the Queue class to use a symbol instead of
an underscore-prefixed property name for the private content
property.
class List {
constructor(head, tail) {
this.head = head
this.tail = tail
}
map(f) {
return new List(f(this.head), this.tail && this.tail.map(f))
}
}
let list = new List("x", new List("y", new List("z", null)))
for (let elt of list) console.log(elt)
// → x
// y
// z
Make the List class (a linked list implementation)
iterable by adding a [Symbol.iterator] method.
(Remember: an iterator is an object with a next
method that returns {value, done} objects.)
function integers() {
let i = 0
return {next() { return {value: i++} },
[Symbol.iterator]() { return this }}
}
function take(n, iter) {
return {
next() {
// Your code here
},
[Symbol.iterator]() { return this }
}
}
for (let elt of take(3, integers()))
console.log(elt)
// → 0
// 1
// 2
The integers function creates an infinite
iterator, that keeps on producing integers forever.
Implement the take function, which wraps an
iterator in another iterator, but cutting it off
after n elements.
class List {
constructor(head, tail) {
this.head = head
this.tail = tail
}
map(f) {
return new List(f(this.head), this.tail && this.tail.map(f))
}
}
console.log(List.from([3, 2, 1]))
// → List{head: 3, tail: List{head: 2, tail: List{head: 1, tail: null}}}
Here is our List class again. In
ES6, Array has a static method from,
which does the thing that you'd now
use Array.prototype.slice.call for—turn an array-like
object (like arguments
or node.childNodes) into a real array. If its
argument it iterable, it'll take the values from the iterator, and
create an array from that. If not, it'll look for
a length property, and use integer indexes to fetch
element values.
Add a static from method to the List
class, which behaves the same way: It takes a single argument. If
that argument is iterable (has a Symbol.iterator
property), it iterates of it, and creates a list from its content.
If not, it loops over the object's indices
(using .length), and builds a list from its
content.
Note that lists are easiest to build from back to front. You might want to first convert an iterable to an array to make building your list easier. Alternatively, use mutation to build the list front-to-back.
class List {
constructor(head, tail) {
this.head = head
this.tail = tail
}
map(f) {
return new List(f(this.head), this.tail && this.tail.map(f))
}
}
let list = new List("x", new List("y", new List("z", null)))
for (let elt of list) console.log(elt)
// → x
// y
// z
Solve exercise Iterators.1 again, making
the List class iterable by adding
a [Symbol.iterator] method. But this time, use a
generator function.
(Remember: the syntax for declaring
a Symbol.iterator generator method in a class
is *[Symbol.iterator]().)
function* integers() {
for (let i = 0;; i++) yield i
}
function* take(n, iter) {
// Your code here
}
for (let elt of take(3, integers()))
console.log(elt)
// → 0
// 1
// 2
Redo exercise Iterators.2 using a generator.
drive(function*() {
// Your code here
})
function drive(generator) {
let iterator = generator()
function resume(result) {
if (result.done) return
result.value.then(
value => resume(iterator.next(value)),
error => resume(iterator.throw(error)))
}
resume(iterator.next())
}
function get(url) {
return new Promise((succeed, fail) => {
var req = new XMLHttpRequest()
req.open("GET", url, true)
req.addEventListener("load", () => {
if (req.status < 400)
succeed(req.responseText)
else
fail(new Error("Request failed: " + req.statusText))
})
req.addEventListener("error", () => {
fail(new Error("Network error"))
})
req.send(null)
})
}
In exercise Promises.1, we wrote a simple web crawler
using promises (get the content of http://marijnhaverbeke.nl,
fetch the content of all local links, search for the word
“Piranha”). Do this again, using the drive function
from the slides and a generator function. Don't worry about making
the requests in parallel this time.