





Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Community
Ask the community for help and clear up your study doubts
Discover the best universities in your country according to Docsity users
Free resources
Download our free guides on studying techniques, anxiety management strategies, and thesis advice from Docsity tutors
In this cheat sheet we have essential notions and detailed explanation of JavaScript language
Typology: Cheat Sheet
1 / 9
This page cannot be seen from the preview
Don't miss anything!
Musa Al-hassy https://github.com/alhassy/JavaScriptCheatSheet March 12, 2020
JavaScript is what everyone calls the language, but that name is trademarked (by Oracle, which inherited the trademark from Sun). Therefore, the official name of JavaScript is ECMAScript. The “ECMA” in “ECMAScript” comes from the organisation that hosts 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 ≈ NaN for nonsensical numeric expressions.
By accident, there are two (mostly) interchangeable values null and undefined that de- note the absence of a meaningful value. Many operations that don’t produce meaningful values yield undefined simply because they have to yield some value. Here is a neat story about null.
Types
JavaScript considers types only when actually running the program, and even there often tries to implicitly convert values to the type it expects.
typeof gives a string value naming the type of its argument. The functions Number, String, Boolean try to convert values into those types.
console.log(typeof 4.5, typeof ’4.5’, typeof true) // ⇒ number string boolean
console.log(8 * null // Multiplication needs numbers so null 7 → 0 , ’five’ * 2 // ’five’ is not a number, so ’five’ 7 → NaN , ’5’ - 1 // Subtraction needs numbers so ’5’ 7 → 5 , ’5’ + 1) // The first is a string, // so “+” denotes catenation, so 1 7 → ’1’
console.log(Number(’2.3’) // ⇒ 2. ,Number(’five’) // ⇒ NaN ,Boolean(’five’) // ⇒ true ,Boolean(’’) // ⇒ false ,String(NaN) // ⇒ ’NaN’ ,String(null)) // ⇒ ’null’
Variable Bindings
let x 0 = v 0 , ..., xn = vn; introduces n-new names xi each having value vi. The vi are optional, defaulting to undefined. The program crashes if any xi is already declared. Later we use xi = wi; to update the name xi to refer to a new value wi. ◦ Augmented updates: x ⊕= y ≡ x = x ⊕ y ◦ Increment: x-- ≡ x += 1 ◦ Decrement: y-- ≡ x -= 1
let x, y = 1, z; console.log(x, y, z); // ⇒ undefined 1 undefined
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 var x = · · · if x is already declared. In the same way, we may use const to introduce names that are constant: Any attempt to change their values crashes the program. A binding name may include dollar signs ($) or underscores (_) but no other punctuation or special characters.
Scope and Statements
Each binding has a scope, which is the part of the program in which the binding is visible. For bindings defined outside of any function or block, the scope is the whole program—you can refer to such bindings wherever you want. These are called global.
let x = 10;
{ // new local scope let y = 20; var z = 30; console.log(x + y + z); // ⇒ 60 }
// y is not visible here // console.log(y)
// But z is! console.log(x + z); // ⇒ 40
global bindings are defined outside of any block and can be referenced anywhere.
local bindings are defined within a block and can only be referenced in it.
let, const declare local bindings; var always makes global ones!
Besides the assignment statement, we also have the following statements: Conditionals: if (condition) A else B Blocks: If Si are statements, then {S 0 ; ...; Sn;} is a statement. The for/of syntax applies to arrays, strings, and other iterable structures —we will define our own later.
// Print all the elements in the given list. for (let x of [’a’, 1, 2.3]) { console.log(‘x ≈ ${x}‘); }
JavaScript is whitespace insensitive.
Arithmetic
In addition to the standard arithmetic operations, we have Math.max(x 0 , ..., xn) 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 Math.random() which returns a random number between 0 and 1. Also, use % for remain- der after division.
// Scientific notation: xey ≈ x × 10 y console.log(1, 2.998e8, 100 + 4 * 11)
// Special numbers so that division “never crashes”. console.log(1/0, -1/0, Infinity - 10) // ⇒ Infinity -Infinity Infinity console.log(Infinity - Infinity, 0/0) // ⇒ NaN NaN
// Random number in range min...Max Math.floor(Math.random() * (max - min) + min)
NaN stands for “not a number”, it is what you get when a numeric expression has no meaningful value.
Any NaN in an arithmetic expressions swallows the whole expression into a NaN. Number.isNaN(x) is true iff x is NaN.
Everything is equal to itself, except NaN. Why? NaN denotes the result of nonsensical computations, and so is not equal to the result of any other nonsensical computation.
console.log(NaN == NaN) // ⇒ false
Booleans
The empty string ”, list [], and 0, NaN are falsey —all else is truthy. Note: (p < q < r) ≈ (p < q) < r, it is not conjunctive!
console.log(true, false, 3 > 2, 1 < 2, 1 != 2, 4 <= 2 < 3)
// Upper case letters come first, then lower case ones. console.log(’abc’ < ’def’, ’Z’ < ’a’)
// Equality with coercions, and without. console.log(1.23 == ’1.23’, 1.23 === ’1.23’)
Precise Equality === is equality with no type coercions. Applying the “not”! operator will convert a value to Boolean type before negating it. Precedence: Relationals like == and > are first, then “and” &&, then “or” ||. The ternary operator: condition? if_true : if_false
console.log(null == undefined) // ⇒ true
Only the empty values are coerced into being equal, no other value is equal to an empty value. As such, x != null means that x is not an empty value, and is in fact a real meaningful value.
Since && and || are lazy, x || y means return x if x != false and otherwise return y; i.e., give me x if it’s non-empty, else y.
Likewise, x && y means give me y, if x is nonempty, else give me the particular empty value x.
console.log( 4 == 3 && 4 // 3 is truthy ,’’ == ’’ && 4 // ’’ is falsey ,’H’ == ’H’ && 4 // ’H’ is truthy , 0 == 0 && 4 // 0 is falsey , 4 == 0 || 4 // 0 is falsey )
Strings
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 produce formatted strings, where an expression can be evaluated if it is enclosed in ${· · · }.
console.log(‘half of 100 is ${100 / 2}‘) // ⇒ half of 100 is 50 s.repeat(n) ≈ Get a new string by gluing n-copies of the string s. Trim removes spaces, newlines, tabs, and other whitespace from the start and end of a string.
console.log(" okay \n ".trim()); // ⇒ okay s.toUpperCase() and s.toLowerCase() to change case. s.padStart(l , p) ≈ Ensure s is of length ≥ l by padding it with p at the start.
console.log(String(6).padStart(3, "0")); // ⇒ 006 s.replace(/./g, c => p(c)? f(c) : ”) ≈ Keep only the characters that satisfy predicate p, then transform them via f.
let s = ’abcde’.replace(/./g, c => ’ace’.includes(c)? c.toUpperCase() : ’’) console.log(s); // ⇒ ACE
The following methods also apply to arrays. s.length ⇒ Length of string s[i] ⇒ Get the i-th character from the start ◦ Unless 0 ≤ i < s.length, we have s[i] = undefined. s.concat(t) ⇒ Glue together two strings into one longer string; i.e., s + t.
console.log((’cat’ + ’enation’).toUpperCase()) // ⇒ CATENATION s.includes(t) ⇒ Does s contain t as a substring? s.indexOf(t) ⇒ Where does substring t start in s, or -1 if it’s not in s. ◦ To search from the end instead of the start, use lastIndexOf. s.slice(m,n) ⇒ Get the substring between indices m (inclusive) and n (exclu- sive). ◦ n is optional, defaulting to s.length. ◦ If n is negative, it means start from the end: s.slice(-n) ≈ s.slice(s.length - n). ◦ s.slice() ⇒ Gives a copy of s. There is no character type, instead characters are just strings of length 1. You can “split” a string on every occurrence of another string to get a list of words, and which you can “join” to get a new sentence. s.split(d ).join(d ) ≈ s. To treat a string as an array of characters, so we can apply array only methods such as f = reverse, we can use split and join: s.split(”).f().join(”) Keeping certain characters is best done with regular expressions.
JavaScript is extremely fault-tolerant: If we give a function more arguments than it needs, the extra arguments are just ignored. If we give it too few arguments, the missing arguments are assigned undefined.
// Extra arguments are ignored console.log(square(4, true, "hedgehog")); // ⇒ 16
// No longer a function! square = ’g’
(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.
let square = (x = 1) => x * x; console.log(square(3)); // ⇒ 9 console.log(square()); // ⇒ 1
(Rest Parameters) It can be useful for a function to accept any number of arguments. 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 rest parameter” and it is treated as an array containing all further arguments.
function max(...numbers) { let result = -Infinity; for (let number of numbers) { if (number > result) result = number; } return result; }
console.log(max(4, 1, 9, -2)); // ⇒ 9
You can use a similar three-dot notation to call a function with an array of arguments.
let numbers = [5, 1, 7]; console.log(max(...numbers)); // ⇒ 7
This “spreads” out the array into the function call, passing its elements as separate ar- guments. It is possible to include an array like that along with other arguments, as in max(9, ...numbers, 2).
Higher-order functions allow us to abstract over actions, not just values. They come in several forms.
For example, we can have functions that create new functions.
let greaterThan = n => (m => m > n); let greaterThan10 = greaterThan(10); console.log(greaterThan10(11)); // ⇒ true
And we can have functions that change other functions. (Decorators)
function noisy(f) { return (...args) => { let result = f(...args); console.log(‘Called: ${f.name}(${args}) ≈ ${result}‘); return result; }; } noisy(Math.min)(3, 2, 1); // Called: min(3,2,1) ≈ 1
We can even write functions that provide new types of control flow.
function unless(test, then) { if (!test) then(); }
let n = 8; unless(n % 2 == 1, () => { console.log(n, "is even"); }); // ⇒ 8 is even
Destructuring and the “spread” Operator
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. One of the reasons the doit function below is awkward to read is that we have a binding pointing at our array, but we’d much prefer to have bindings for the elements of the array, whence the second definition of doit. let xs = [9, 11, 22, 666, 999];
// The following are the same. function doit(xs){ return xs[0] + xs[1] + xs[2]; } function doit([x, y, z]) {return x + y + z; } // // Only first three items accessed in “doit”; extra args are ignored as usual. console.log(doit(xs))
// Destructuring to get first three elements and remaining let x = xs[0], y = xs[1], z = xs[2], ws = xs.slice(3); console.log(x, y, z, ws) // ⇒ 9 11 22 [ 666, 999 ] // Nice! Same thing. let [a, b, c, ...ds] = xs console.log(a, b, c, ds) // ⇒ 9 11 22 [ 666, 999 ]
// Destructuring to get first and remaining elements let [head, ...tail] = xs console.log(head, tail) // ⇒ 9 [ 11, 22, 666, 999 ]
// Destructuring on an object to get two properties and the remaining subobject let {name, age, ...more} = {name: "Musa", age: 72, x: 1, y: 2} console.log(name, age, more) // ⇒ Musa 72 { x: 1, y: 2 }
// Destructuring: Simultaneous assignment! var p = 1, q = 2 // ⇒ 1, 2 var [p, q] = [q, p] // swap them console.log(p, q) // ⇒ 2, 1
// Unpacking: f(...[x 0 , ..., xn]) ≈ f(x 0 , ..., xn) console.log(Math.min(...xs)) // ⇒ 9
// Unpacking: Merging arrays/objects let ys = [1, ...xs, 2, 3] // ⇒ 1, 9, 11, 22, 666, 999, 2, 3 let zs = {w: 0, ...more, z: 3} // ⇒ { w: 0, x: 1, y: 2, z: 3 }
// Updating a property, a key-value pair zs = {...zs, w: -1} // ⇒ { w: -1, x: 1, y: 2, z: 3 }
Note that if you try to destructure null or undefined, you get an error, much as you would if you directly try to access a property of those values.
let {x 0 , ..., xn, ...w} = v ≡ let x 0 = v.x 0 , ..., xn = v.xn; w = v; delete w.x 0 , ..., delete w.xn
As usual, in arrow functions, we may destructure according to the shape of the elements of the array; e.g., if they are lists of at least length 2 we use (soFar, [x, y]) => · · ·. This may be useful in higher order functions such as map, filter, reduce.
Objects
Objects and arrays (which are a specific kind of object) provide ways to group several values into a single value. Conceptually, this allows us to put a bunch of related things in a bag and run around with the bag, instead of wrapping our arms around all of the individual things and trying to hold on to them separately. These “things” are called properties.
Arrays are just a kind of object specialised for storing sequences of things.
Values of the type object are arbitrary collections of properties. One way to create an object is by using braces as an expression that lists properties as “name:value” pairs.
This is useful if we want multiple objects to have the same binding; e.g., with let x = · · · , a = {name: ’a’, x}, b = {name: ’b’, x}, both objects have a x property: a.x and b.x.
let languages = [’js’, ’python’, ’lisp’] let person = { name: ’musa’ , age: 27 , ’favourite number’: 1 , languages // Shorthand for “languages: [’js’, ’python’, ’lisp’]” , age: 29 // Later bindings override earlier ones. // Two ways to attach methods; the second is a shorthand. , speak: () => ‘Salamun Alaykum! Hello!‘ , info () { return ‘${this.name} is ${this.age} years old!‘; } };
console.log(person.age) // ⇒ 29
// Trying to access non-existent properties // Reading a property that doesn’t exist will give you the value undefined. console.log(person.height) // ⇒ undefined
// Is the property “name” in object “person”? console.log(’name’ in person); // ⇒ true
// Updating a (computed) property let prop = ’favourite’ + ’ ’ + ’number’ person[’favourite number’] = 1792 console.log(person[prop]) // ⇒ 1792
// Dynamically adding a new property person.vest = ’purple’ console.log(person.vest) // ⇒ purple
// Discard a property delete person[’favourite number’]
// Get the list of property names that an object currently has. console.log(Object.keys(person)) // ⇒ [ ’name’, ’age’, ’languages’, ’vest’ ]
// Variables can contribute to object definitions, but are otherwise unrelated. languages = [’C#’, ’Ruby’, ’Prolog’] console.log(person.languages) // ⇒ [ ’js’, ’python’, ’lisp’ ]
// Calling an object’s methods console.log(person.speak()) // ⇒ Salamun Alaykum! Hello! console.log(person.info()) // ⇒ musa is 29 years old!
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 the getter method is called which only shows you the value’s remainder after division by the modulus property. let num = { modulus: 10 , get value() { return this._secret % this.modulus; } , set value(val) { this._secret = val; } }
num.value = 99 console.log(num._secret) // ⇒ 99
console.log(num.value) // ⇒ 9 num.modulus = 12; console.log(num.value) // ⇒ 3
(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 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.
Array.prototype.colour = ’purple’
let xs = [1, 2, 3] console.log(xs.colour) // ⇒ purple
xs.colour = ’green’ console.log(xs.colour) // ⇒ green
console.log(Array.prototype.colour) // ⇒ purple
You can use Object.create to create an object with a specific prototype. The de- fault prototype is Object.prototype. For the most part, Object.create(someObject) ≈ { ...someObject }; i.e., we copy the properties of someObject into an empty object, thereby treating someObject as a prototype from which we will build more sophisticated objects.
Unlike other object-oriented languages where Object sits as the ancestor of all objects, in JavaScript it is possible to create objects with no prototype parent!
// Empty object that does derive from “Object” let basic = {} console.log( basic instanceof Object // ⇒ true , "toString" in basic) // ⇒ true
// Empty object that does not derive from “Object” let maximal = Object.create(null); console.log( maximal instanceof Object // ⇒ false , "toString" in maximal) // ⇒ false
Prototypes let us define properties that are the same for all instances, but properties that differ per instance are stored directly in the objects themselves. E.g., the prototypical 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 name, and derives shared properties from its prototype.
// An example object prototype let prototypicalPerson = {}; prototypicalPerson._world = 0; prototypicalPerson.speak = function () { console.log(‘I am ${this.name}, a ${this.job}, in a world of ‘
// Example use: Manually ensure the necessary properties are setup // and then manually increment the number of people in the world. let person = Object.create(prototypicalPerson); person.name = ‘jasim‘; prototypicalPerson._world++; person.speak() // ⇒ I am jasim, a farmer, in a world of 1 people.
// Another person requires just as much setup let kathy = { ...prototypicalPerson }; // Same as “Object.create(· · · )” kathy.name = ‘kathy‘; prototypicalPerson._world++; kathy.speak() // ⇒ I am kathy, a farmer, in a world of 2 people.
Classes are prototypes along with constructor functions!
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 realised as a prototype along with a constructor function that ensures an instance object not only derives from the proper prototype but also ensures it, itself, has the properties that instances of the class are supposed to have.
let prototypicalPerson = {}; prototypicalPerson._world = 0; prototypicalPerson.speak = function () { console.log(‘I am ${this.name}, a ${this.job}, in a world of ‘
function makePerson(name, job = ‘farmer‘) { let person = Object.create(prototypicalPerson); person.name = name; person.job = job; prototypicalPerson._world++; return person; }
// Example use let jasim = makePerson(‘jasim‘); jasim.speak() // I am jasim, a farmer, in a world of 1 people. makePerson(‘kathy‘).speak() // I am kathy, a farmer, in a world of 2 people.
We can fuse these under one name by making the prototype a part of the constructor. By convention, the names of constructors are capitalised so that they can easily be distinguished from other functions.
function Person(name, job = ‘farmer‘) { this.name = name; this.job = job; Person.prototype._world++; }
Person.prototype._world = 0; Person.prototype.speak = function () { console.log(‘I am ${this.name}, a ${this.job}, in a world of ‘
// Example use let jasim = Object.create(Person.prototype) Person.call(jasim, ‘jasim‘) jasim.speak() // ⇒ I am jasim, a farmer, in a world of 1 people.
// Example using shorthand let kasim = new Person (‘kathy‘) kasim.speak() // ⇒ I am kathy, a farmer, in a world of 2 people.
If you put the keyword new in front of a function call, the function is treated as a con- structor. This means that an object with the right prototype is automatically created, bound to this in the function, and returned at the end of the function.
new f(args) ≈ (_ => let THIS = Object.create(f.prototype); f.call(THIS, args); return THIS;) ()
All functions automatically get a property named prototype, which by default holds a plain, empty object that derives from Object.prototype. You can overwrite it with a new object if you want. Or you can add properties to the existing object, as the example does.
Notice that the Person object derives from Function.prototype, but also has a property named prototype which is used for instances created through it.
console.log( Object.getPrototypeOf(Person) == Function.prototype , Person instanceof Function , jasim instanceof Person , Object.getPrototypeOf(jasim) == Person.prototype)
Hence, we can update our motto:
Classes are constructor functions with a prototype property!
Rather than declaring a constructor, then attaching properties to its prototype, we may perform both steps together using class notation shorthand.
class Person { static #world = 0 constructor(name, job = ‘farmer‘) { this.name = name; this.job = job; Person.#world++; } speak() { console.log(‘I am ${this.name}, a ${this.job}, in a world of ‘
// Example use
let jasim = new Person(‘jasim‘) jasim.speak() // ⇒ I am jasim, a farmer, in a world of 1 people.
new Person(‘kathy‘).speak() // ⇒ I am kathy, a farmer, in a world of 2 people.
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 to the constructor’s prototype. Thus, the earlier class declaration is equivalent to the constructor definition from the previous section. It just looks nicer.
Actually, this is even better: The static #world = 0 declaration makes the prop- erty world private, completely inaccessible from the outside the class. The static keyword attaches the name not to particular instances (this) but rather to the constructor/class name (Person). Indeed, in the previous examples we could have accidentally messed-up our world count. Now, we get an error if we write Person.#world outside of the class.
The Iterator Interface
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 that provides a second interface, the iterator. This is the actual thing that iterates. It has a next method that returns the next result. That result should be an object with a value property that provides the next value, if there is one, and a done property, which should be true when there are no more results and false otherwise.
Let’s make an iterable to traverse expression trees.
class Expr { // [0] Our type of expression trees static Constant(x) { let e = new Expr(); e.tag = ’constant’, e.constant = x; return e; }
static Plus(l, r) { let e = new Expr(); e.tag = ’plus’, e.left = l, e.right = r; return e; } }
// [1] The class tracks the progress of iterating over an expression tree class ExprIterator { constructor(expr) { this.expr = expr; this.unvisited = [{expr, depth: 0}]; } next () { if(this.unvisited.length == 0) return {done: true}; let {expr , depth} = this.unvisited.pop(); 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}) this.unvisited.push({expr: expr.left, depth: depth + 1}) } return this.next() } }
// [2] We can add the iterator after-the-fact rather than within the Expr class. Expr.prototype[Symbol.iterator] = function () { return new ExprIterator(this) }
// [3] Here’s some helpers and an example. let num = (i) => Expr.Constant(i) let sum = (l, r) => Expr.Plus(l, r) // test ≈ 1 + (2 + (3 + 4)) let test = sum( num(1), sum( num(2), sum(num(3), num(4)))) // console.log(test) // ⇒ Nice looking tree ^_^
// [4] We now loop over an expression with for/of for (let {num, depth} of test) console.log(‘${num} is ${depth} deep in the expression‘)