Javascript Cheatsheet: Musa Al-Hassy
Javascript Cheatsheet: Musa Al-Hassy
Javascript Cheatsheet: Musa Al-Hassy
s://github.com/alhassy/JavaScriptCheatSheet March 12, 2020 In the same way, for the same purpose, we may use var but it has undesirable
properties; e.g., its declarations are in the global scope and no error is raised using
JavaScript CheatSheet var x = · · · if x is already declared.
In the same way, we may use const to introduce names that are constant: Any
JavaScript is what everyone calls the language, but that name is trademarked (by Oracle, attempt to change their values crashes the program.
which inherited the trademark from Sun). Therefore, the official name of JavaScript is A binding name may include dollar signs ($) or underscores (_) but no other
ECMAScript. The “ECMA” in “ECMAScript” comes from the organisation that hosts punctuation or special characters.
the primary standard, the European Computer Manufacturers Association.
As the programming language of browsers, it is remarkably error-tolerant. It simply “fails
silently” by giving error values such as undefined when things are not there or 0 / 0 ≈ Scope and Statements
NaN for nonsensical numeric expressions.
Each binding has a scope, which is the part of the program in which the binding is
By accident, there are two (mostly) interchangeable values null and undefined that de- visible. For bindings defined outside of any function or block, the scope is the whole
note the absence of a meaningful value. Many operations that don’t produce meaningful program—you can refer to such bindings wherever you want. These are called global.
values yield undefined simply because they have to yield some value. Here is a neat
story about null. let x = 10; global bindings are defined outside of any
block and can be referenced anywhere.
Types { // new local scope
let y = 20; local bindings are defined within a block
JavaScript considers types only when actually running the program, and even there var z = 30; and can only be referenced in it.
often tries to implicitly convert values to the type it expects. console.log(x + y + z); // ⇒ 60
typeof gives a string value naming the type of its argument. } let, const declare local bindings; var
always makes global ones!
The functions Number, String, Boolean try to convert values into those types.
// y is not visible here
console.log(typeof 4.5, typeof ’4.5’, typeof true) // console.log(y)
// ⇒ number string boolean
// But z is!
console.log(8 * null // Multiplication needs numbers so null 7→ 0 console.log(x + z); // ⇒ 40
, ’five’ * 2 // ’five’ is not a number, so ’five’ 7→ NaN
, ’5’ - 1 // Subtraction needs numbers so ’5’ 7→ 5
Besides the assignment statement, we also have the following statements:
, ’5’ + 1) // The first is a string,
// so “+” denotes catenation, so 1 7→ ’1’ Conditionals: if (condition) A else B
Blocks: If Si are statements, then {S0 ; ...; Sn ;} is a statement.
console.log(Number(’2.3’) // ⇒ 2.3 The for/of syntax applies to arrays, strings, and other iterable structures —we
,Number(’five’) // ⇒ NaN will define our own later.
,Boolean(’five’) // ⇒ true
,Boolean(’’) // ⇒ false // Print all the elements in the given list.
,String(NaN) // ⇒ ’NaN’ for (let x of [’a’, 1, 2.3]) {
,String(null)) // ⇒ ’null’ console.log(‘x ≈ ${x}‘);
}
Variable Bindings
JavaScript is whitespace insensitive.
let x0 = v0 , ..., xn = vn ; introduces n-new names xi each having value vi .
Arithmetic
The vi are optional, defaulting to undefined.
The program crashes if any xi is already declared. In addition to the standard arithmetic operations, we have Math.max(x0 , ..., xn )
Later we use xi = wi ; to update the name xi to refer to a new value wi . that takes any number of numbers and gives the largest; likewise Math.min(· · · ).
Other common functions include Math.sqrt, Math.ceil, Math.round, Math.abs, and
◦ Augmented updates: x ⊕= y ≡ x = x ⊕ y
Math.random() which returns a random number between 0 and 1. Also, use % for remain-
◦ Increment: x-- ≡ x += 1 der after division.
◦ Decrement: y-- ≡ x -= 1
// Scientific notation: xey ≈ x × 10y
let x, y = 1, z; console.log(1, 2.998e8, 100 + 4 * 11)
console.log(x, y, z); // ⇒ undefined 1 undefined
1
// Special numbers so that division “never crashes”. Strings
console.log(1/0, -1/0, Infinity - 10) // ⇒ Infinity -Infinity Infinity
console.log(Infinity - Infinity, 0/0) // ⇒ NaN NaN Any pair of matching single-quotes, backticks, or double-quotes will produce a string
literal. However, backticks come with extra support: They can span multiple lines and
// Random number in range min...Max produce formatted strings, where an expression can be evaluated if it is enclosed in ${· · · }.
Math.floor(Math.random() * (max - min) + min)
console.log(‘half of 100 is ${100 / 2}‘) // ⇒ half of 100 is 50
NaN stands for “not a number”, it is what you get when a numeric expression has no
meaningful value. s.repeat(n) ≈ Get a new string by gluing n-copies of the string s.
Any NaN in an arithmetic expressions swallows the whole expression into a NaN. Trim removes spaces, newlines, tabs, and other whitespace from the start and end
of a string.
Number.isNaN(x) is true iff x is NaN.
Everything is equal to itself, except NaN. Why? NaN denotes the result of nonsensical console.log(" okay \n ".trim()); // ⇒ okay
computations, and so is not equal to the result of any other nonsensical computation. s.toUpperCase() and s.toLowerCase() to change case.
console.log(NaN == NaN) // ⇒ false s.padStart(l , p) ≈ Ensure s is of length ≥ l by padding it with p at the start.
2
Arrays // Print the elements of the given array
[‘a‘, ‘b‘, ‘c‘].forEach(l => console.log(l));
Array indexing, arr[i], yields the i-th element from the start; i.e., the number of items
to skip; whence arr[0] is the first element. // ∃/∀
console.log([1, 2, 3].some(e => e == 2)) // true
let numbers = []; // ⇒ undefined console.log([1, 2, 3].every(e => e == 2)) // false
console.log(numbers[2]);
numbers.push(2); // Sum the elements of an array
numbers.push(5); // Is an element in the array? No. console.log([1, 2, 3, 4].reduce((soFar, current) => soFar + current)); // ⇒ 10
numbers.push(7); console.log(numbers.includes(7))
// flatten an array of arrays
// or numbers = numbers.concat(’ola’) let flatten = (xss) => xss.reduce((sofar, xs) => sofar.concat(xs), [])
numbers = [2, 5, 7] console.log(numbers)
// ⇒ [ 2, 5, ’ola’ ] let arrays = [[1, 2, 3], [4, 5], [6]];
console.log(numbers[2]); // ⇒ 7 console.log(flatten(arrays)) // ⇒ [ 1, 2, 3, 4, 5, 6 ]
let last = numbers.pop() // ⇒ 7 console.log(numbers.reverse())
console.log(numbers) // ⇒ [ 2, 5 ] // ⇒ [ ’ola’, 5, 2 ] Higher-order functions start to shine when you need to compose operations.
(Stack) The push method adds values to the end of an array, and the pop method does Functions
the opposite, removing the last value in the array and returning it. (Queue) The cor-
responding methods for adding and removing things at the start of an array are called Function values can do all the things that other values can do; i.e., they can be used in
unshift and shift, respectively. arbitrary expressions; e.g., a binding that holds a function is still just a regular binding
and can, if not constant, be assigned a new value.
Arrays have the following methods, which behave similar to the string ones from earlier.
A function definition is a regular binding where the value of the binding is a
length concat includes indexOf lastIndexOf slice function.
One difference is that unlike string’s indexOf, which searches for substrings, array’s
Functions declared using the top-level function keyword may be used before their
indexOf searches for a specific value, a single element.
declarations.
console.log([1, 2, 3, 2, 1].indexOf(2)); // ⇒ 1
console.log([1, 2, 3, 2, 1].lastIndexOf(2)); // ⇒ 3 const square = function(x) { // Shorter way to define functions
return x * x; console.log(square2(12));
The concat method can be used to glue arrays together to create a new array, similar to }; function square2(x) {
what the + operator does for strings. return x * x;
If you pass concat an argument that is not an array, that value will be added to console.log(square(12)); // ⇒ 144 }
the new array as if it were a one-element array. This is a push.
A return keyword without an expression after it will cause the function to return
Array(n).fill(x) ≈ Get a new array of n-copies of element x. undefined.
Functions that don’t have a return statement at all, similarly return undefined.
xs.forEach(a) to loop over the elements in an array and perform action a.
xs.filter(p) returns a new array containing only the elements that pass the Declaring function f (· · · ) {· · · } will not raise a warning if the name f is already
predicate p. in use —similar to var.
xs.map(f) transforms an array by putting each element through the function f. One may also define functions using “arrow” notation: (x0 , ..., xn ) => · · · .
xs.reduce(f, e) combines all the elements in an array into a single value. ◦ When there is only one parameter name, you can omit the parentheses
◦ We can omit the starting value e if the array xs is non-empty, in which case around the parameter list.
e is taken to be the first element xs[0]. ◦ If the body is a single expression, rather than a (multi-line) block in braces,
xs.some(p) tests whether any element matches a given predicate function p. that expression will be returned from the function.
◦ xs.every(p) tests if every element of xs satisfies p.
So, these two definitions of square do the same thing:
xs.findIndex(p) finds the position of the first element that matches the predicate
p. const square1 = (x) => { return x * x; };
With the exception of forEach, the above functions do not modify the array they are const square2 = x => x * x;
given. As will be seen, arrow functions are not exactly the same as declared functions.
3
JavaScript is extremely fault-tolerant: If we give a function more arguments than it We can even write functions that provide new types of control flow.
needs, the extra arguments are just ignored. If we give it too few arguments, the missing
arguments are assigned undefined. function unless(test, then) { let n = 8;
if (!test) then(); unless(n % 2 == 1, () => {
// Extra arguments are ignored } console.log(n, "is even");
console.log(square(4, true, "hedgehog")); // ⇒ 16 });
// ⇒ 8 is even
// No longer a function!
square = ’g’ Destructuring and the “spread” Operator
(Default Values) If you write an = operator after a parameter, followed by an expres-
sion, the value of that expression will replace the argument when it is not given. If you know the value you are binding is an array/object, you can use []/{} brackets
to “look inside” of the value, binding its contents.
let square = (x = 1) => x * x;
One of the reasons the doit function below is awkward to read is that we have a binding
console.log(square(3)); // ⇒ 9
pointing at our array, but we’d much prefer to have bindings for the elements of the array,
console.log(square()); // ⇒ 1
whence the second definition of doit.
(Rest Parameters) It can be useful for a function to accept any number of arguments. let xs = [9, 11, 22, 666, 999];
For example, Math.max computes the maximum of all the arguments it is given. To write
such a function, you put three dots before the function’s last parameter, which is called // The following are the same.
“the rest parameter” and it is treated as an array containing all further arguments. function doit(xs){ return xs[0] + xs[1] + xs[2]; }
function doit([x, y, z]) {return x + y + z; }
function max(...numbers) { You can use a similar three-dot notation to //
let result = -Infinity; call a function with an array of arguments. // Only first three items accessed in “doit”; extra args are ignored as usual.
for (let number of numbers) { console.log(doit(xs))
if (number > result)
result = number; let numbers = [5, 1, 7]; // Destructuring to get first three elements and remaining
} console.log(max(...numbers)); let x = xs[0], y = xs[1], z = xs[2], ws = xs.slice(3);
return result; // ⇒ 7 console.log(x, y, z, ws) // ⇒ 9 11 22 [ 666, 999 ]
} // Nice! Same thing.
let [a, b, c, ...ds] = xs
console.log(max(4, 1, 9, -2)); // ⇒ 9 console.log(a, b, c, ds) // ⇒ 9 11 22 [ 666, 999 ]
4
Note that if you try to destructure null or undefined, you get an error, much as you let languages = [’js’, ’python’, ’lisp’]
would if you directly try to access a property of those values. let person = { name: ’musa’
, age: 27
let {x0 , ..., xn , ...w} = v , ’favourite number’: 1
≡ let x0 = v.x0 , ..., xn = v.xn ; w = v; delete w.x0 , ..., delete w.xn , languages // Shorthand for “languages: [’js’, ’python’, ’lisp’]”
, age: 29 // Later bindings override earlier ones.
As usual, in arrow functions, we may destructure according to the shape of the elements // Two ways to attach methods; the second is a shorthand.
of the array; e.g., if they are lists of at least length 2 we use (soFar, [x, y]) => · · · . , speak: () => ‘Salamun Alaykum! Hello!‘
This may be useful in higher order functions such as map, filter, reduce. , info () { return ‘${this.name} is ${this.age} years old!‘; }
};
Objects console.log(person.age) // ⇒ 29
Objects and arrays (which are a specific kind of object) provide ways to group several // Trying to access non-existent properties
values into a single value. Conceptually, this allows us to put a bunch of related things // Reading a property that doesn’t exist will give you the value undefined.
in a bag and run around with the bag, instead of wrapping our arms around all of the console.log(person.height) // ⇒ undefined
individual things and trying to hold on to them separately. These “things” are called
properties. // Is the property “name” in object “person”?
console.log(’name’ in person); // ⇒ true
Arrays are just a kind of object specialised for storing sequences of things.
// Updating a (computed) property
Values of the type object are arbitrary collections of properties. One way to create an let prop = ’favourite’ + ’ ’ + ’number’
object is by using braces as an expression that lists properties as “name:value” pairs. person[’favourite number’] = 1792
console.log(person[prop]) // ⇒ 1792
1. Almost all JavaScript values have properties. The exceptions are null and
undefined. If you try to access a property on one of these nonvalues, you get // Dynamically adding a new property
an error. Properties are accessed using value.prop or value["prop"]. person.vest = ’purple’
2. Whereas value.x fetches the property of value named x, value[e] tries to eval- console.log(person.vest) // ⇒ purple
uate the expression e and uses the result, converted to a string, as the property
name. // Discard a property
delete person[’favourite number’]
3. The dot notation only works with properties whose names look like valid (variable)
binding names. So if you want to access a property named 2 or John Doe, you // Get the list of property names that an object *currently* has.
must use square brackets: value[2] or value["John Doe"]. console.log(Object.keys(person)) // ⇒ [ ’name’, ’age’, ’languages’, ’vest’ ]
4. Unless value contains a property x, we have value.x ≈ undefined.
// Variables can contribute to object definitions, but are otherwise unrelated.
Hence, out of bounds indexing results in undefined. languages = [’C#’, ’Ruby’, ’Prolog’]
console.log(person.languages) // ⇒ [ ’js’, ’python’, ’lisp’ ]
5. Notice that the this keyword allows us to refer to other parts of this object literal.
Above, info used the person object’s information, whereas speak did not. The
// Calling an object’s methods
“this” keyword is covered in more detail below.
console.log(person.speak()) // ⇒ Salamun Alaykum! Hello!
6. Variables names in an object literal, like languages, denote a shorthand for a console.log(person.info()) // ⇒ musa is 29 years old!
property with the same and value, but otherwise is no longer related to that bind-
ing. You can define getters and setters to secretly call methods every time an object’s property
is accessed. E.g., below num lets you read and write value as any number, but internally
This is useful if we want multiple objects to have the same binding; e.g., with the getter method is called which only shows you the value’s remainder after division by
let x = · · · , a = {name: ’a’, x}, b = {name: ’b’, x}, both objects have the modulus property.
a x property: a.x and b.x. let num = { modulus: 10
, get value() { return this._secret % this.modulus; }
7. We cannot dynamically attach new properties to the atomic types String, Number, , set value(val) { this._secret = val; } }
Boolean; e.g., let x = 2; x.vest = ’purple’; console.log(x.vest); prints
undefined. We can write it, but they “don’t stick”. num.value = 99 console.log(num.value) // ⇒ 9
8. Below, we could have begun with the empty object then added properties dynam- console.log(num._secret) // ⇒ 99 num.modulus = 12;
ically: let person = {}; person.name = ‘musa‘; person.age = 29; .... console.log(num.value) // ⇒ 3
5
Exercise: Make an object num such that num.value varies, returning a random Object-Oriented Programming
number less than 100, each time it’s accessed.
In English, prototype means a preliminary model of something from which other forms
Using get, set is a way to furnish prototypes with well-behaved properties that are
are developed or copied. As such, a prototypical object is an object denoting the original
readable or writable, or both.
or typical form of something.
An object can also be used as a “key:value” dictionary: When we ‘look-up’ a key, we find
a particular value. E.g., with ages = {mark: 12, james: 23, larry: 42} we use In addition to their properties, JavaScript objects also have prototype —i.e., another
ages.mark to find Mark’s age. object that is used as a source of additional properties. When an object gets a request
for a property that it does not have, its prototype will be searched for the property, then
Similarly, objects can be used to simulate keyword arguments in function calls. the prototype’s prototype, and so on.
Object.getPrototypeOf(x) returns the prototype of an object x.
The this Keyword
For example, arrays are derived from Array.prototype which is derived from
Usually a method needs to do something with the object it was called on. When a Object.prototype —which is the great ancestral prototype, the entity behind almost
function is called as a method — looked up as a property and immediately called, as all object. Object.prototype provides a few methods that show up in all objects, such
in object.method() —– the binding called this in its body automatically points at the as toString, which converts an object to a string representation.
object that it was called on.
We can use the Object.getOwnPropertyNames(x) to get all the property names
function speak(line) { linked to object x.
console.log(‘The ${this.type} rabbit says ’${line}’‘);
} It is occasionally useful to know whether an object was derived from a specific class. For
let whiteRabbit = {type: "white", speak}; this, JavaScript provides a binary operator called instanceof. Almost every object is an
let hungryRabbit = {type: "hungry", speak}; instance of Object.
x instanceof y ≈ Object.getPrototypeOf(x) == y.prototype
whiteRabbit.speak("Hola!"); // ⇒ The white rabbit says ’Hola!’
hungryRabbit.speak("Hey!") // ⇒ The hungry rabbit says ’Hey!’ // “Object” includes “toString”, and some other technical utilities.
console.log(Object.getOwnPropertyNames(Object.prototype))
You can think of this as an extra parameter that is passed in a different way. If you
want to pass it explicitly, you can use a function’s call method, which takes the this // Some true facts
value as its first argument and treats further arguments as normal parameters. console.log( {} instanceof Object
speak.call(hungryRabbit, "Burp!"); , [] instanceof Array
// ⇒ The hungry rabbit says ’Burp!’ , Math.max instanceof Function
, Math.max instanceof Object) // Since Function derives from Object
With call, an object can use a method belonging to another object. E.g., below we use
whiteRabbit’s speaking method with its this keywords referring to exoticRabbit. // “Object” has no parent prototype.
console.log(Object.getPrototypeOf(Object.prototype)); // ⇒ null
let exoticRabbit = {type: ’exotic’}
(Extension Methods / Open Classes) To attach a new property to a ‘kind’ of object,
whiteRabbit.speak.call(exoticRabbit, ‘Jambo!‘) we simply need to attach it to the prototype —since all those ‘kinds’ of objects use the
// ⇒ The exotic rabbit says ’Jambo!’ prototype’s properties. Let’s attach a new method that can be used with any array.
Since each function has its own this binding, whose value depends on the way it is called, Array.prototype.max = function () {
you cannot refer to the this of the wrapping scope in a regular function defined with the console.log(’ola’); return Math.max(...this)
function keyword. }
Arrow functions are different —they do not bind their own this but can see the this console.log([3,1,5].max()); // ⇒ Prints “ola”, returns 5
binding of the scope around them. Thus, you can do something like the following code,
which references this from inside a local function: console.log(Object.getOwnPropertyNames(Array.prototype))
// ⇒ Includes length, slice, ..., and our new “max” from above
function normalise() {
console.log(this.coords.map(n => n / this.length)); When you call the String function (which converts a value to a string) on an object, it
} will call the toString method on that object to try to create a meaningful string from
normalise.call({coords: [0, 2, 3], length: 5}); // ⇒ [0, 0.4, 0.6] it.
If we had written the argument to map using the function keyword, the code wouldn’t Array.prototype.toString = function() { return this.join(’ and ’); };
work. console.log(String([1, 2, 3])) // ⇒ 1 and 2 and 3
6
(Overriding) When you add a property to an object, whether it is present in the pro-
totype or not, the property is added to the object itself. If there was already a property Classes are prototypes along with constructor functions!
with the same name in the prototype, this property will no longer affect the object, as it
is now hidden behind the object’s own property. A class defines the shape of a kind of object; i.e., what properties it has; e.g., a Person can
speak, as all people can, but should have its own name property to speak of. This idea is
Array.prototype.colour = ’purple’ xs.colour = ’green’ realised as a prototype along with a constructor function that ensures an instance object
console.log(xs.colour) // ⇒ green not only derives from the proper prototype but also ensures it, itself, has the properties
let xs = [1, 2, 3] that instances of the class are supposed to have.
console.log(xs.colour) // ⇒ purple console.log(Array.prototype.colour)
// ⇒ purple let prototypicalPerson = {};
prototypicalPerson._world = 0;
You can use Object.create to create an object with a specific prototype. The de- prototypicalPerson.speak = function () {
fault prototype is Object.prototype. For the most part, Object.create(someObject) console.log(‘I am ${this.name}, a ${this.job}, in a world of ‘
≈ { ...someObject }; i.e., we copy the properties of someObject into an empty object, + ‘${prototypicalPerson._world} people.‘) }
thereby treating someObject as a prototype from which we will build more sophisticated
objects. function makePerson(name, job = ‘farmer‘) {
let person = Object.create(prototypicalPerson);
Unlike other object-oriented languages where Object sits as the ancestor of all objects, person.name = name;
in JavaScript it is possible to create objects with no prototype parent! person.job = job;
// Empty object that *does* derive from “Object” prototypicalPerson._world++;
let basic = {} return person;
console.log( basic instanceof Object // ⇒ true }
, "toString" in basic) // ⇒ true
// Example use
// Empty object that does not derive from “Object” let jasim = makePerson(‘jasim‘);
let maximal = Object.create(null); jasim.speak() // I am jasim, a farmer, in a world of 1 people.
console.log( maximal instanceof Object // ⇒ false makePerson(‘kathy‘).speak() // I am kathy, a farmer, in a world of 2 people.
, "toString" in maximal) // ⇒ false We can fuse these under one name by making the prototype a part of the constructor.
Prototypes let us define properties that are the same for all instances, but properties that By convention, the names of constructors are capitalised so that they can easily
differ per instance are stored directly in the objects themselves. E.g., the prototypical be distinguished from other functions.
person acts as a container for the properties that are shared by all people. An individual
person object, like kathy below, contains properties that apply only to itself, such as its function Person(name, job = ‘farmer‘) {
name, and derives shared properties from its prototype. this.name = name;
this.job = job;
// An example object prototype Person.prototype._world++;
let prototypicalPerson = {}; }
prototypicalPerson._world = 0;
prototypicalPerson.speak = function () { Person.prototype._world = 0;
console.log(‘I am ${this.name}, a ${this.job}, in a world of ‘ Person.prototype.speak = function () {
+ ‘${prototypicalPerson._world} people.‘) } console.log(‘I am ${this.name}, a ${this.job}, in a world of ‘
prototypicalPerson.job = ‘farmer‘; + ‘${Person.prototype._world} people.‘) }
// Example use: Manually ensure the necessary properties are setup // Example use
// and then manually increment the number of people in the world. let jasim = Object.create(Person.prototype)
let person = Object.create(prototypicalPerson); Person.call(jasim, ‘jasim‘)
person.name = ‘jasim‘; jasim.speak() // ⇒ I am jasim, a farmer, in a world of 1 people.
prototypicalPerson._world++;
person.speak() // ⇒ I am jasim, a farmer, in a world of 1 people. // Example using shorthand
let kasim = new Person (‘kathy‘)
// Another person requires just as much setup kasim.speak() // ⇒ I am kathy, a farmer, in a world of 2 people.
let kathy = { ...prototypicalPerson }; // Same as “Object.create(· · · )”
kathy.name = ‘kathy‘; If you put the keyword new in front of a function call, the function is treated as a con-
prototypicalPerson._world++; structor. This means that an object with the right prototype is automatically created,
kathy.speak() // ⇒ I am kathy, a farmer, in a world of 2 people. bound to this in the function, and returned at the end of the function.
7
new f(args) The Iterator Interface
≈ (_ => let THIS = Object.create(f.prototype);
f.call(THIS, args); return THIS;) () The object given to a for/of loop is expected to be iterable. This means it has a
method named Symbol.iterator. When called, that method should return an object
All functions automatically get a property named prototype, which by default holds a that provides a second interface, the iterator. This is the actual thing that iterates. It
plain, empty object that derives from Object.prototype. You can overwrite it with a has a next method that returns the next result. That result should be an object with a
new object if you want. Or you can add properties to the existing object, as the example value property that provides the next value, if there is one, and a done property, which
does. should be true when there are no more results and false otherwise.
Notice that the Person object derives from Function.prototype, but also has a property
Let’s make an iterable to traverse expression trees.
named prototype which is used for instances created through it.
console.log( Object.getPrototypeOf(Person) == Function.prototype class Expr { // [0] Our type of expression trees
, Person instanceof Function static Constant(x) {
, jasim instanceof Person let e = new Expr();
, Object.getPrototypeOf(jasim) == Person.prototype) e.tag = ’constant’, e.constant = x;
return e;
Hence, we can update our motto: }
Classes are constructor functions with a prototype property!
static Plus(l, r) {
Rather than declaring a constructor, then attaching properties to its prototype, we may let e = new Expr();
perform both steps together using class notation shorthand. e.tag = ’plus’, e.left = l, e.right = r;
return e;
class Person { }
static #world = 0 }
constructor(name, job = ‘farmer‘) {
this.name = name; // [1] The class tracks the progress of iterating over an expression tree
this.job = job; class ExprIterator {
Person.#world++; constructor(expr) { this.expr = expr; this.unvisited = [{expr, depth: 0}]; }
} next () {
speak() { if(this.unvisited.length == 0) return {done: true};
console.log(‘I am ${this.name}, a ${this.job}, in a world of ‘ let {expr , depth} = this.unvisited.pop();
+ ‘${Person.#world} people.‘) if (expr.tag == ’constant’) return {value: {num: expr.constant, depth}}
} if (expr.tag == ’plus’) {
} // pre-order traversal
this.unvisited.push({expr: expr.right, depth: depth + 1})
// Example use this.unvisited.push({expr: expr.left, depth: depth + 1})
}
let jasim = new Person(‘jasim‘) return this.next()
jasim.speak() }
// ⇒ I am jasim, a farmer, in a world of 1 people. }
new Person(‘kathy‘).speak() // [2] We can add the iterator after-the-fact rather than within the Expr class.
// ⇒ I am kathy, a farmer, in a world of 2 people. Expr.prototype[Symbol.iterator] = function () { return new ExprIterator(this) }
Notice that there is a special function named constructor which is bound to the class
name, Person, outside the class. The remainder of the class declarations are bound // [3] Here’s some helpers and an example.
to the constructor’s prototype. Thus, the earlier class declaration is equivalent to the let num = (i) => Expr.Constant(i)
constructor definition from the previous section. It just looks nicer. let sum = (l, r) => Expr.Plus(l, r)
// test ≈ 1 + (2 + (3 + 4))
Actually, this is even better: The static #world = 0 declaration makes the prop- let test = sum( num(1), sum( num(2), sum(num(3), num(4))))
erty world private, completely inaccessible from the outside the class. The static // console.log(test) // ⇒ Nice looking tree ^_^
keyword attaches the name not to particular instances (this) but rather to the
constructor/class name (Person). // [4] We now loop over an expression with for/of
Indeed, in the previous examples we could have accidentally messed-up our world for (let {num, depth} of test)
count. Now, we get an error if we write Person.#world outside of the class. console.log(‘${num} is ${depth} deep in the expression‘)
8
Recall that inside a class declaration, methods that have static written before their Reads
name are stored on the constructor. It appears that static properties are shared by all
instances, because the constructor object has these as properties rather than particular
instance objects. https://eloquentjavascript.net/
JavaScript and the Browser This is a book about JavaScript, programming, and the wonders of the digital.
Browsers run JavaScript programs, which may be dangerous and so browsers limit Many of the examples in this cheatsheet were taken from this excellent read!
what a program may do —e.g., it cannot look at your files.
https://exploringjs.com/index.html
An HTML document is a nested sequence of tagged items, which may be interpreted as
a living data-structure —with the screen reflecting any modifications. Exploring JS: Free JavaScript books for programmers —E.g., “JavaScript for im-
patient programmers”
The most important HTML tag is <script>. This tag allows us to include a piece
of JavaScript in a document. https://www.w3schools.com/js/
The data-structure is called the Document Object Model, or DOM, and it is accessed This tutorial will teach you JavaScript from basic to advanced.
with the variable document.
The DOM interface wasn’t designed for just JavaScript; e.g., it can be used with Other bite-sized lessions can be found at: https://masteringjs.io/
XML. fundamentals
https://learnxinyminutes.com/docs/javascript/
Call the following snippet test.html, then open it in your favourite browser.
<title> Ola! </title> Take a whirlwind tour of your next favorite language. Community-driven!
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference
<h3 id="myHeader"></h3>
The JavaScript reference serves as a repository of facts about the JavaScript lan-
<script> guage. The entire language is described here in detail.
alert(‘Welcome to my webapge!‘);
https://github.com/you-dont-need/You-Dont-Need-Loops
let count = 0; Avoid The One-off Problem, Infinite Loops, Statefulness and Hidden intent.
function modifyThePage(){
document.title = ‘New zany title ~ ${Math.random()}‘;
myHeader.innerText = ‘New zany heading ~ ${count}‘; Some Fun Stuff ˆ_ˆ
count += Math.floor(Math.random() * 10);
}
</script>
// A “quine” is a program that prints itself, such as this one:
<button onclick="modifyThePage();">Change the title and header </button> f = _ => console.log(‘f = ${f};f()‘); f()
// Prints:
Such a script will run as soon as its <script> tag is encountered while the browser reads // f = _ => console.log(‘f = ${f};f()‘);f()
the HTML. This page will pop up a dialog when opened to show a message.
Some attributes can also contain a JavaScript program. The <button> tag shows up as // Range of numbers. Including start, excluding end.
a button and has an onclick attribute whose value (function) will be run whenever the let range = (start, end) => [...Array(end - start).keys()].map(x => x + start)
button is clicked. console.log(range(3, 8)) // ⇒ [ 3, 4, 5, 6, 7 ]
Notice that by providing ID’s to tags, we may refer to them in our JavaScript code. // Flatten an array
let xss = [[1, 2, 3], [4, 5, 6]]
Including large programs directly in HTML documents is often impractical. The let flatten = xss => [].concat(...xss)
<script> tag can be given a src attribute to fetch a script file (a text file containing a console.log(flatten(xss)) // ⇒ [ 1, 2, 3, 4, 5, 6 ]
JavaScript program) from a URL.
<h1>Testing alert</h1> // Randomise the elements of an array
<script src="code/hello.js"></script> let shuffle = (arr) => arr.slice().sort(() => Math.random() - 0.5)
let xs = [1, 2, 3, 4, 5, 6]
The code/hello.js file included here contains the simple program alert("hello!"). console.log(shuffle(xs)) // ⇒ [ 5, 1, 4, 6, 2, 3 ]