Cheatsheet:

// Single-line comments start with two slashes.
/* Multiline comments start with slash-star,
   and end with star-slash */

// Statements can be terminated by ;
doStuff();

// ... but they don't have to be, as semicolons are automatically inserted
// wherever there's a newline, except in certain cases.
doStuff()

// Because those cases can cause unexpected results, we'll keep on using
// semicolons in this guide.

///////////////////////////////////
// 1. Numbers, Strings and Operators

// JavaScript has one number type (which is a 64-bit IEEE 754 double).
// Doubles have a 52-bit mantissa, which is enough to store integers
// up to about 9✕10¹⁵ precisely.
3; // = 3
1.5; // = 1.5

// Some basic arithmetic works as you'd expect.
1 + 1; // = 2
0.1 + 0.2; // = 0.30000000000000004
8 - 1; // = 7
10 * 2; // = 20
35 / 5; // = 7

// Including uneven division.
5 / 2; // = 2.5

// And modulo division.
10 % 2; // = 0
30 % 4; // = 2
18.5 % 7; // = 4.5

// Bitwise operations also work; when you perform a bitwise operation your float
// is converted to a signed int *up to* 32 bits.
1 << 2; // = 4

// Precedence is enforced with parentheses.
(1 + 3) * 2; // = 8

// There are three special not-a-real-number values:
Infinity; // result of e.g. 1/0
-Infinity; // result of e.g. -1/0
NaN; // result of e.g. 0/0, stands for 'Not a Number'

// There's also a boolean type.
true;
false;

// Strings are created with ' or ".
'abc';
"Hello, world";

// Negation uses the ! symbol
!true; // = false
!false; // = true

// Equality is ===
1 === 1; // = true
2 === 1; // = false

// Inequality is !==
1 !== 1; // = false
2 !== 1; // = true

// More comparisons
1 < 10; // = true
1 > 10; // = false
2 <= 2; // = true
2 >= 2; // = true

// Strings are concatenated with +
"Hello " + "world!"; // = "Hello world!"

// ... which works with more than just strings
"1, 2, " + 3; // = "1, 2, 3"
"Hello " + ["world", "!"]; // = "Hello world,!"

// and are compared with < and >
"a" < "b"; // = true

// Type coercion is performed for comparisons with double equals...
"5" == 5; // = true
null == undefined; // = true

// ...unless you use ===
"5" === 5; // = false
null === undefined; // = false

// ...which can result in some weird behaviour...
13 + !0; // 14
"13" + !0; // '13true'

// You can access characters in a string with `charAt`
"This is a string".charAt(0);  // = 'T'

// ...or use `substring` to get larger pieces.
"Hello world".substring(0, 5); // = "Hello"

// `length` is a property, so don't use ().
"Hello".length; // = 5

// There's also `null` and `undefined`.
null;      // used to indicate a deliberate non-value
undefined; // used to indicate a value is not currently present (although
           // `undefined` is actually a value itself)

// false, null, undefined, NaN, 0 and "" are falsy; everything else is truthy.
// Note that 0 is falsy and "0" is truthy, even though 0 == "0".

///////////////////////////////////
// 2. Variables, Arrays and Objects

// Variables are declared with the `var` keyword. JavaScript is dynamically
// typed, so you don't need to specify type. Assignment uses a single `=`
// character.
var someVar = 5;

// If you leave the var keyword off, you won't get an error...
someOtherVar = 10;

// ...but your variable will be created in the global scope, not in the scope
// you defined it in.

// Variables declared without being assigned to are set to undefined.
var someThirdVar; // = undefined

// If you want to declare a couple of variables, then you could use a comma
// separator
var someFourthVar = 2, someFifthVar = 4;

// There's shorthand for performing math operations on variables:
someVar += 5; // equivalent to someVar = someVar + 5; someVar is 10 now
someVar *= 10; // now someVar is 100

// and an even-shorter-hand for adding or subtracting 1
someVar++; // now someVar is 101
someVar--; // back to 100

// Arrays are ordered lists of values, of any type.
var myArray = ["Hello", 45, true];

// Their members can be accessed using the square-brackets subscript syntax.
// Array indices start at zero.
myArray[1]; // = 45

// Arrays are mutable and of variable length.
myArray.push("World");
myArray.length; // = 4

// Add/Modify at specific index
myArray[3] = "Hello";

// Add and remove element from front or back end of an array
myArray.unshift(3); // Add as the first element
someVar = myArray.shift(); // Remove first element and return it
myArray.push(3); // Add as the last element
someVar = myArray.pop(); // Remove last element and return it

// Join all elements of an array with semicolon
var myArray0 = [32,false,"js",12,56,90];
myArray0.join(";") // = "32;false;js;12;56;90"

// Get subarray of elements from index 1 (include) to 4 (exclude)
myArray0.slice(1,4); // = [false,"js",12]

// Remove 4 elements starting from index 2, and insert there strings
// "hi","wr" and "ld"; return removed subarray
myArray0.splice(2,4,"hi","wr","ld"); // = ["js",12,56,90]
// myArray0 === [32,false,"hi","wr","ld"]

// JavaScript's objects are equivalent to "dictionaries" or "maps" in other
// languages: an unordered collection of key-value pairs.
var myObj = {key1: "Hello", key2: "World"};

// Keys are strings, but quotes aren't required if they're a valid
// JavaScript identifier. Values can be any type.
var myObj = {myKey: "myValue", "my other key": 4};

// Object attributes can also be accessed using the subscript syntax,
myObj["my other key"]; // = 4

// ... or using the dot syntax, provided the key is a valid identifier.
myObj.myKey; // = "myValue"

// Objects are mutable; values can be changed and new keys added.
myObj.myThirdKey = true;

// If you try to access a value that's not yet set, you'll get undefined.
myObj.myFourthKey; // = undefined

///////////////////////////////////
// 3. Logic and Control Structures

// The `if` structure works as you'd expect.
var count = 1;
if (count == 3){
    // evaluated if count is 3
} else if (count == 4){
    // evaluated if count is 4
} else {
    // evaluated if it's not either 3 or 4
}

// As does `while`.
while (true){
    // An infinite loop!
}

// Do-while loops are like while loops, except they always run at least once.
var input;
do {
    input = getInput();
} while (!isValid(input));

// The `for` loop is the same as C and Java:
// initialization; continue condition; iteration.
for (var i = 0; i < 5; i++){
    // will run 5 times
}

// Breaking out of labeled loops is similar to Java
outer:
for (var i = 0; i < 10; i++) {
    for (var j = 0; j < 10; j++) {
        if (i == 5 && j ==5) {
            break outer;
            // breaks out of outer loop instead of only the inner one
        }
    }
}

// The for/in statement allows iteration over properties of an object.
var description = "";
var person = {fname:"Paul", lname:"Ken", age:18};
for (var x in person){
    description += person[x] + " ";
} // description = 'Paul Ken 18 '

// The for/of statement allows iteration over iterable objects (including the built-in String,
// Array, e.g. the Array-like arguments or NodeList objects, TypedArray, Map and Set,
// and user-defined iterables).
var myPets = "";
var pets = ["cat", "dog", "hamster", "hedgehog"];
for (var pet of pets){
    myPets += pet + " ";
} // myPets = 'cat dog hamster hedgehog '

// && is logical and, || is logical or
if (house.size == "big" && house.colour == "blue"){
    house.contains = "bear";
}
if (colour == "red" || colour == "blue"){
    // colour is either red or blue
}

// && and || "short circuit", which is useful for setting default values.
var name = otherName || "default";

// The `switch` statement checks for equality with `===`.
// Use 'break' after each case
// or the cases after the correct one will be executed too.
grade = 'B';
switch (grade) {
  case 'A':
    console.log("Great job");
    break;
  case 'B':
    console.log("OK job");
    break;
  case 'C':
    console.log("You can do better");
    break;
  default:
    console.log("Oy vey");
    break;
}


///////////////////////////////////
// 4. Functions, Scope and Closures

// JavaScript functions are declared with the `function` keyword.
function myFunction(thing){
    return thing.toUpperCase();
}
myFunction("foo"); // = "FOO"

// Note that the value to be returned must start on the same line as the
// `return` keyword, otherwise you'll always return `undefined` due to
// automatic semicolon insertion. Watch out for this when using Allman style.
function myFunction(){
    return // <- semicolon automatically inserted here
    {thisIsAn: 'object literal'};
}
myFunction(); // = undefined

// JavaScript functions are first class objects, so they can be reassigned to
// different variable names and passed to other functions as arguments - for
// example, when supplying an event handler:
function myFunction(){
    // this code will be called in 5 seconds' time
}
setTimeout(myFunction, 5000);
// Note: setTimeout isn't part of the JS language, but is provided by browsers
// and Node.js.

// Another function provided by browsers is setInterval
function myFunction(){
    // this code will be called every 5 seconds
}
setInterval(myFunction, 5000);

// Function objects don't even have to be declared with a name - you can write
// an anonymous function definition directly into the arguments of another.
setTimeout(function(){
    // this code will be called in 5 seconds' time
}, 5000);

// JavaScript has function scope; functions get their own scope but other blocks
// do not.
if (true){
    var i = 5;
}
i; // = 5 - not undefined as you'd expect in a block-scoped language

// This has led to a common pattern of "immediately-executing anonymous
// functions", which prevent temporary variables from leaking into the global
// scope.
(function(){
    var temporary = 5;
    // We can access the global scope by assigning to the "global object", which
    // in a web browser is always `window`. The global object may have a
    // different name in non-browser environments such as Node.js.
    window.permanent = 10;
})();
temporary; // raises ReferenceError
permanent; // = 10

// One of JavaScript's most powerful features is closures. If a function is
// defined inside another function, the inner function has access to all the
// outer function's variables, even after the outer function exits.
function sayHelloInFiveSeconds(name){
    var prompt = "Hello, " + name + "!";
    // Inner functions are put in the local scope by default, as if they were
    // declared with `var`.
    function inner(){
        alert(prompt);
    }
    setTimeout(inner, 5000);
    // setTimeout is asynchronous, so the sayHelloInFiveSeconds function will
    // exit immediately, and setTimeout will call inner afterwards. However,
    // because inner is "closed over" sayHelloInFiveSeconds, inner still has
    // access to the `prompt` variable when it is finally called.
}
sayHelloInFiveSeconds("Adam"); // will open a popup with "Hello, Adam!" in 5s

///////////////////////////////////
// 5. More about Objects; Constructors and Prototypes

// Objects can contain functions.
var myObj = {
    myFunc: function(){
        return "Hello world!";
    }
};
myObj.myFunc(); // = "Hello world!"

// When functions attached to an object are called, they can access the object
// they're attached to using the `this` keyword.
myObj = {
    myString: "Hello world!",
    myFunc: function(){
        return this.myString;
    }
};
myObj.myFunc(); // = "Hello world!"

// What this is set to has to do with how the function is called, not where
// it's defined. So, our function doesn't work if it isn't called in the
// context of the object.
var myFunc = myObj.myFunc;
myFunc(); // = undefined

// Inversely, a function can be assigned to the object and gain access to it
// through `this`, even if it wasn't attached when it was defined.
var myOtherFunc = function(){
    return this.myString.toUpperCase();
};
myObj.myOtherFunc = myOtherFunc;
myObj.myOtherFunc(); // = "HELLO WORLD!"

// We can also specify a context for a function to execute in when we invoke it
// using `call` or `apply`.

var anotherFunc = function(s){
    return this.myString + s;
};
anotherFunc.call(myObj, " And Hello Moon!"); // = "Hello World! And Hello Moon!"

// The `apply` function is nearly identical, but takes an array for an argument
// list.

anotherFunc.apply(myObj, [" And Hello Sun!"]); // = "Hello World! And Hello Sun!"

// This is useful when working with a function that accepts a sequence of
// arguments and you want to pass an array.

Math.min(42, 6, 27); // = 6
Math.min([42, 6, 27]); // = NaN (uh-oh!)
Math.min.apply(Math, [42, 6, 27]); // = 6

// But, `call` and `apply` are only temporary. When we want it to stick, we can
// use `bind`.

var boundFunc = anotherFunc.bind(myObj);
boundFunc(" And Hello Saturn!"); // = "Hello World! And Hello Saturn!"

// `bind` can also be used to partially apply (curry) a function.

var product = function(a, b){ return a * b; };
var doubler = product.bind(this, 2);
doubler(8); // = 16

// When you call a function with the `new` keyword, a new object is created, and
// made available to the function via the `this` keyword. Functions designed to be
// called like that are called constructors.

var MyConstructor = function(){
    this.myNumber = 5;
};
myNewObj = new MyConstructor(); // = {myNumber: 5}
myNewObj.myNumber; // = 5

// Unlike most other popular object-oriented languages, JavaScript has no
// concept of 'instances' created from 'class' blueprints; instead, JavaScript
// combines instantiation and inheritance into a single concept: a 'prototype'.

// Every JavaScript object has a 'prototype'. When you go to access a property
// on an object that doesn't exist on the actual object, the interpreter will
// look at its prototype.

// Some JS implementations let you access an object's prototype on the magic
// property `__proto__`. While this is useful for explaining prototypes it's not
// part of the standard; we'll get to standard ways of using prototypes later.
var myObj = {
    myString: "Hello world!"
};
var myPrototype = {
    meaningOfLife: 42,
    myFunc: function(){
        return this.myString.toLowerCase();
    }
};

myObj.__proto__ = myPrototype;
myObj.meaningOfLife; // = 42

// This works for functions, too.
myObj.myFunc(); // = "hello world!"

// Of course, if your property isn't on your prototype, the prototype's
// prototype is searched, and so on.
myPrototype.__proto__ = {
    myBoolean: true
};
myObj.myBoolean; // = true

// There's no copying involved here; each object stores a reference to its
// prototype. This means we can alter the prototype and our changes will be
// reflected everywhere.
myPrototype.meaningOfLife = 43;
myObj.meaningOfLife; // = 43

// The for/in statement allows iteration over properties of an object,
// walking up the prototype chain until it sees a null prototype.
for (var x in myObj){
    console.log(myObj[x]);
}
///prints:
// Hello world!
// 43
// [Function: myFunc]
// true

// To only consider properties attached to the object itself
// and not its prototypes, use the `hasOwnProperty()` check.
for (var x in myObj){
    if (myObj.hasOwnProperty(x)){
        console.log(myObj[x]);
    }
}
///prints:
// Hello world!

// We mentioned that `__proto__` was non-standard, and there's no standard way to
// change the prototype of an existing object. However, there are two ways to
// create a new object with a given prototype.

// The first is Object.create, which is a recent addition to JS, and therefore
// not available in all implementations yet.
var myObj = Object.create(myPrototype);
myObj.meaningOfLife; // = 43

// The second way, which works anywhere, has to do with constructors.
// Constructors have a property called prototype. This is *not* the prototype of
// the constructor function itself; instead, it's the prototype that new objects
// are given when they're created with that constructor and the new keyword.
MyConstructor.prototype = {
    myNumber: 5,
    getMyNumber: function(){
        return this.myNumber;
    }
};
var myNewObj2 = new MyConstructor();
myNewObj2.getMyNumber(); // = 5
myNewObj2.myNumber = 6;
myNewObj2.getMyNumber(); // = 6

// Built-in types like strings and numbers also have constructors that create
// equivalent wrapper objects.
var myNumber = 12;
var myNumberObj = new Number(12);
myNumber == myNumberObj; // = true

// Except, they aren't exactly equivalent.
typeof myNumber; // = 'number'
typeof myNumberObj; // = 'object'
myNumber === myNumberObj; // = false
if (0){
    // This code won't execute, because 0 is falsy.
}
if (new Number(0)){
   // This code will execute, because wrapped numbers are objects, and objects
   // are always truthy.
}

// However, the wrapper objects and the regular builtins share a prototype, so
// you can actually add functionality to a string, for instance.
String.prototype.firstCharacter = function(){
    return this.charAt(0);
};
"abc".firstCharacter(); // = "a"

// This fact is often used in "polyfilling", which is implementing newer
// features of JavaScript in an older subset of JavaScript, so that they can be
// used in older environments such as outdated browsers.

// For instance, we mentioned that Object.create isn't yet available in all
// implementations, but we can still use it with this polyfill:
if (Object.create === undefined){ // don't overwrite it if it exists
    Object.create = function(proto){
        // make a temporary constructor with the right prototype
        var Constructor = function(){};
        Constructor.prototype = proto;
        // then use it to create a new, appropriately-prototyped object
        return new Constructor();
    };
}



/* *******************************************************************************************
 * GLOBAL OBJECTS > OBJECT
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
 * ******************************************************************************************* */

// Global object: properties
Object.length                                        // length is a property of a function object, and indicates how many arguments the function expects, i.e. the number of formal parameters. This number does not include the rest parameter. Has a value of 1.
Object.prototype                                     // Represents the Object prototype object and allows to add new properties and methods to all objects of type Object.

// Methods of the Object constructor
Object.assign(target, ...sources)                    // Copies the values of all enumerable own properties from one or more source objects to a target object. method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object
Object.create(MyObject)                              // Creates a new object with the specified prototype object and properties. The object which should be the prototype of the newly-created object.
Object.defineProperty(obj, prop, descriptor)         // Adds the named property described by a given descriptor to an object.
Object.defineProperties(obj, props)                  // Adds the named properties described by the given descriptors to an object.
Object.entries(obj)                                  // Returns an array containing all of the [key, value] pairs of a given object's own enumerable string properties.
Object.freeze(obj)                                   // Freezes an object: other code can't delete or change any properties.
Object.getOwnPropertyDescriptor(obj, prop)           // Returns a property descriptor for a named property on an object.
Object.getOwnPropertyDescriptors(obj)                // Returns an object containing all own property descriptors for an object.
Object.getOwnPropertyNames(obj)                      // Returns an array containing the names of all of the given object's own enumerable and non-enumerable properties.
Object.getOwnPropertySymbols(obj)                    // Returns an array of all symbol properties found directly upon a given object.
Object.getPrototypeOf(obj)                           // Returns the prototype of the specified object.
Object.is(value1, value2);                           // Compares if two values are the same value. Equates all NaN values (which differs from both Abstract Equality Comparison and Strict Equality Comparison).
Object.isExtensible(obj)                             // Determines if extending of an object is allowed.
Object.isFrozen(obj)                                 // Determines if an object was frozen.
Object.isSealed(obj)                                 // Determines if an object is sealed.
Object.keys(obj)                                     // Returns an array containing the names of all of the given object's own enumerable string properties.
Object.preventExtensions(obj)                        // Prevents any extensions of an object.
Object.seal(obj)                                     // Prevents other code from deleting properties of an object.
Object.setPrototypeOf(obj, prototype)                // Sets the prototype (i.e., the internal [[Prototype]] property).
Object.values(obj)                                   // Returns an array containing the values that correspond to all of a given object's own enumerable string properties.

// Object instances and Object prototype object (Object.prototype.property or Object.prototype.method())
// Properties
obj.constructor                                      // Specifies the function that creates an object's prototype.
obj.__proto__                                        // Points to the object which was used as prototype when the object was instantiated.

// Methods
obj.hasOwnProperty(prop)                             // Returns a boolean indicating whether an object contains the specified property as a direct property of that object and not inherited through the prototype chain.
prototypeObj.isPrototypeOf(object)                   // Returns a boolean indicating whether the object this method is called upon is in the prototype chain of the specified object.
obj.propertyIsEnumerable(prop)                       // Returns a boolean indicating if the internal ECMAScript [[Enumerable]] attribute is set.
obj.toLocaleString()                                 // Calls toString().
obj.toString()                                       // Returns a string representation of the object.
object.valueOf()                                     // Returns the primitive value of the specified object.

/* *******************************************************************************************
 * GLOBAL OBJECTS > ARRAY
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
 * ******************************************************************************************* */

// Global object: properties
Array.length                                         // Reflects the number of elements in an array.
Array.prototype                                      // Represents the prototype for the Array constructor and allows to add new properties and methods to all Array objects.

// Global object: methods
Array.from(arrayLike[, mapFn[, thisArg]])            // Creates a new Array instance from an array-like or iterable object.
Array.isArray(obj)                                   // Returns true if a variable is an array, if not false.
Array.of(element0[, element1[, ...[, elementN]]])    // Creates a new Array instance with a variable number of arguments, regardless of number or type of the arguments.

// Instance: properties
arr.length                                           // Reflects the number of elements in an array.

// Instance: mutator methods
arr.copyWithin(target, start, end)                   // Copies a sequence of array elements within the array.
arr.fill(value, start, end)                          // Fills all the elements of an array from a start index to an end index with a static value.
arr.pop()                                            // Removes the last element from an array and returns that element.
arr.push([element1[, ...[, elementN]]])              // Adds one or more elements to the end of an array and returns the new length of the array.
arr.reverse()                                        // Reverses the order of the elements of an array in place — the first becomes the last, and the last becomes the first.
arr.shift()                                          // Removes the first element from an array and returns that element.
arr.sort()                                           // Sorts the elements of an array in place and returns the array.
array.splice(start, deleteCount, item1, item2, ...)  // Adds and/or removes elements from an array.
arr.unshift([element1[, ...[, elementN]]])           // Adds one or more elements to the front of an array and returns the new length of the array.

// Instance: accessor methods
arr.concat(value1[, value2[, ...[, valueN]]])        // Returns a new array comprised of this array joined with other array(s) and/or value(s).
arr.includes(searchElement, fromIndex)               // Determines whether an array contains a certain element, returning true or false as appropriate.
arr.indexOf(searchElement[, fromIndex])              // Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.
arr.join(separator)                                  // Joins all elements of an array into a string.
arr.lastIndexOf(searchElement, fromIndex)            // Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
arr.slice(begin, end)                                // Extracts a section of an array and returns a new array.
arr.toString()                                       // Returns a string representing the array and its elements. Overrides the Object.prototype.toString() method.
arr.toLocaleString(locales, options)                 // Returns a localized string representing the array and its elements. Overrides the Object.prototype.toLocaleString() method.

// Instance: iteration methods
arr.entries()                                        // Returns a new Array Iterator object that contains the key/value pairs for each index in the array.
arr.every(callback[, thisArg])                       // Returns true if every element in this array satisfies the provided testing function.
arr.filter(callback[, thisArg])                      // Creates a new array with all of the elements of this array for which the provided filtering function returns true.
arr.find(callback[, thisArg])                        // Returns the found value in the array, if an element in the array satisfies the provided testing function or undefined if not found.
arr.findIndex(callback[, thisArg])                   // Returns the found index in the array, if an element in the array satisfies the provided testing function or -1 if not found.
arr.forEach(callback[, thisArg])                     // Calls a function for each element in the array.
arr.keys()                                           // Returns a new Array Iterator that contains the keys for each index in the array.
arr.map(callback[, initialValue])                    // Creates a new array with the results of calling a provided function on every element in this array.
arr.reduce(callback[, initialValue])                 // Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
arr.reduceRight(callback[, initialValue])            // Apply a function against an accumulator and each value of the array (from right-to-left) as to reduce it to a single value.
arr.some(callback[, initialValue])                   // Returns true if at least one element in this array satisfies the provided testing function.
arr.values()                                         // Returns a new Array Iterator object that contains the values for each index in the array.

Fundamental Javascript Concepts You Should Understand

Plain Old JS Object Lesson Concepts


Fundamental Javascript Concepts You Should Understand

Plain Old JS Object Lesson Concepts

- Label variables as either Primitive vs. Reference - primitives: strings, booleans, numbers, null and undefined - primitives are immutable - refereces: objects (including arrays) - references are mutable - Identify when to use `.` vs `[]` when accessing values of an object - dot syntax `object.key` - easier to read - easier to write - cannot use variables as keys - keys cannot begin with a number - bracket notation `object["key]` - allows variables as keys - strings that start with numbers can be use as keys - Write an object literal with a variable key using interpolation

put it in brackets to access the value of the variable, rather than just make the value that string

let a = "b";
    let obj = {
        a: "letter_a",
        [a]: "letter b"
    }
  • Use the obj[key] !== undefined pattern to check if a given variable that contains a key exists in an object

  • can also use (key in object) syntax interchangeably (returns a boolean)

  • Utilize Object.keys and Object.values in a function

  • Object.keys(obj) returns an array of all the keys in obj

  • Object.values(obj) returns an array of the values in obj

Iterate through an object using a for in loop

let printValues = function(obj) {
      for (let key in obj) {
          let value = obj[key];
          console.log(value);
      }
  }

Define a function that utilizes ...rest syntax to accept an arbitrary number of arguments

  • ...rest syntax will store all additional arguments in an array

  • array will be empty if there are no additional arguments

let myFunction = function(str, ...strs) {
        console.log("The first string is " + str);
        console.log("The rest of the strings are:");
        strs.forEach(function(str) {
            console.log(str);
        })
    }

Use ...spread syntax for Object literals and Array literals

let arr1 = ["a", "b", "c"];
  let longer = [...arr1, "d", "e"]; // ["a", "b", "c", "d", "e"]
  // without spread syntax, this would give you a nested array
  let withoutRest = [arr1, "d", "e"] // [["a", "b", "c"], "d", "e"]
  • Destructure an array to reference specific elements

let array = [35, 9];

let [firstEl, secondEl] = array;

console.log(firstEl); // => 35

console.log(secondEl); // => 9

// can also destructure using … syntax let array = [35, 9, 14]; let [head, …tail] = array; console.log(head); // => 35 console.log(tail); // => [9, 14]

-Destructure an object to reference specific values
    -
    if you want to use variable names that don 't match the keys, you can use aliasing -
    `let { oldkeyname: newkeyname } = object` -
    rule of thumb— only destructure values from objects that are two levels deep ``
`javascript
let obj = {
   name: "Wilfred",
   appearance: ["short", "mustache"],
   favorites: {
      color: "mauve",
      food: "spaghetti squash",
      number: 3
   }
}
// with variable names that match keys
let { name, appearance } = obj;
console.log(name); // "Wilfred"
console.log(appearance); // ["short", "mustache"]

// with new variable names (aliasing)
let {name: myName, appearance: myAppearance} = obj;

console.log(myName); // "Wilfred"
console.log(myAppearance); // ["short", "mustache"]

// in a function call
let sayHello = function({name}) {
console.log("Hello, " + name); // "Hello Wilfred"
}

// nested objects + aliasing
let { favorites: {color, food: vegetable} } = obj;
console.log(color, vegetable); //=> mauve spaghetti squash

Write a function that accepts a array as an argument and returns an object representing the count of each character in the array

//
  let elementCounts = function(array) {
      let obj = {};
      array.forEach(function(el) {
          if (el in obj) obj[el] += 1;
          else obj[el] = 1;
      })
      return obj;
  }
  console.log(elementCounts(["e", "f", "g", "f"])); // => Object {e: 1, f: 2, g: 1}

Callbacks Lesson Concepts

  • Given multiple plausible reasons, identify why functions are called “First Class Objects” in JavaScript.

  • they can be stored in variables, passed as arguments to other functions, and serve as return value for a function

  • supports same basic operations as other types (strings, bools, numbers)

  • higher-order functions take functions as arguments or return functions as values

  • Given a code snippet containing an anonymous callback, a named callback, and multiple console.logs, predict what will be printed

  • what is this referring to?

  • Write a function that takes in a value and two callbacks. The function should return the result of the callback that is greater.

let greaterCB = function(val, callback1, callback2) {
    if (callback1(val) > callback2(val)) {
        return callback1(val);
    }
    return callback2(val);
}

let greaterCB = function(val, callback1, callback2) {
    if (callback1(val) > callback2(val)) {
        return callback1(val);
    }
    return callback2(val);
}

// shorter version let greaterCB = function(val, callback1, callback2) { return Math.max(callback1(val), callback2(val)); } // even shorter, cause why not let greaterCB = (val, cb1, cb2) => Math.max(cb1(val), cb2(val));

-Write a
function, myMap, that takes in an array and a callback as arguments.The
function should mimic the behavior of `Array#map`.
``
`javascript
let myMap = function(array, callback) {
   let newArr = [];
   for (let i = 0; i < array.length; i ++) {
      mapped = callback(array[i], i, array);
      newArr.push(mapped);
   }
   return newArr;
}
console.log( myMap([16,25,36], Math.sqrt)); // => [4, 5, 6];

let myMapArrow = (array, callback) => {
   let newArr = [];
   array.forEach( (ele, ind, array) => {
      newArr.push(callback(ele, ind, array));
   })
   return newArr;
}
console.log(myMapArrow([16,25,36], Math.sqrt)); // => [4, 5, 6];

Write a function, myFilter, that takes in an array and a callback as arguments. The function should mimic the behavior of Array#filter.

let myFilter = function(array, callback) {
      let filtered = [];
      for (let i = 0; i < array.length; i++) {
          if (callback(array[i])) {
              filtered.push(array[i], i, array);
          }
      }
  }

Write a function, myEvery, that takes in an array and a callback as arguments. The function should mimic the behavior of Array#every.

let myEvery = function(array, callback) {
      for (let i = 0; i < array.length; i++) {
          if (!callback(array[i], i, array)) {
              return false
          }
      }
      return true;
  }
  // with arrow function syntax
  let myEvery = (array, callback) => {
      for (let i = 0; i < array.length; i++) {
          if (!callback(array[i])) {
              return false
          }
      }
      return true;
  }

Scope Lesson Concepts

  • Identify the difference between const, let, and var declarations

  • const - cannot reassign variable, scoped to block

  • let - can reassign variable, scoped to block

  • var - outdated, may or may not be reassigned, scoped to function. can be not just reassigned, but also redeclared!

  • a variable will always evaluate to the value it contains regardless of how it was declared

  • Explain the difference between const, let, and var declarations

  • var is function scoped—so if you declare it anywhere in a function, the declaration (but not assignment) is "hoisted"

  • so it will exist in memory as “undefined” which is bad and unpredictable

  • var will also allow you to redeclare a variable, while let or const will raise a syntax error. you shouldn't be able to do that!

  • const won't let you reassign a variable, but if it points to a mutable object, you will still be able to change the value by mutating the object

  • block-scoped variables allow new variables with the same name in new scopes

  • block-scoped still performs hoisting of all variables within the block, but it doesn’t initialize to the value of undefined like var does, so it throws a specific reference error if you try to access the value before it has been declared

  • if you do not use var or let or const when initializing, it will be declared as global—THIS IS BAD

  • if you assign a value without a declaration, it exists in the global scope (so then it would be accessible by all outer scopes, so bad). however, there’s no hoisting, so it doesn’t exist in the scope until after the line is run

  • Predict the evaluation of code that utilizes function scope, block scope, lexical scope, and scope chaining

  • scope of a program means the set of variables that are available for use within the program

  • global scope is represented by the window object in the browser and the global object in Node.js

  • global variables are available everywhere, and so increase the risk of name collisions

  • local scope is the set of variables available for use within the function

  • when we enter a function, we enter a new scope

  • includes functions arguments, local variables declared inside function, and any variables that were already declared when the function is defined (hmm about that last one)

  • for blocks (denoted by curly braces {}, as in conditionals or for loops), variables can be block scoped

  • inner scope does not have access to variables in the outer scope

  • scope chaining — if a given variable is not found in immediate scope, javascript will search all accessible outer scopes until variable is found

  • so an inner scope can access outer scope variables

  • but an outer scope can never access inner scope variables

Define an arrow function

let arrowFunction = (param1, param2) => {

let sum = param1 + param2;

return sum;

}

// with 1 param you can remove parens around parameters let arrowFunction = param =>

// if your return statement is one line, you can use implied return let arrowFunction = param => param + 1;

// you don’t have to assign to variable, can be anonymous // if you never need to use it again param => param + 1;

Given an arrow function, deduce the value of this without executing the code

  • arrow functions are automatically bound to the context they were declared in.

  • unlike regular function which use the context they are invoked in (unless they have been bound using Function#bind).

  • if you implement an arrow function as a method in an object the context it will be bound to is NOT the object itself, but the global context.

  • so you can’t use an arrow function to define a method directly

let obj = {
name: “my object”,
unboundFunc: function () {

return this.name;

// this function will be able to be called on different objects

},
boundToGlobal: () => { return this.name; // this function, no matter how you call it, will be called // on the global object, and it cannot be rebound // this is because it was defined using arrow syntax },

makeFuncBoundToObj: function() {
        return () => {
            return this.name;
        }
        // this function will return a function that will be bound
        // to the object where we call the outer method
        // because the arrow syntax is nested inside one of this
        // function's methods, it cannot be rebound
    },

    makeUnboundFunc: function() {
        return function() {
            return this.name;
        }
        //this function will return a function that will still be unbound
    },

    immediatelyInvokedFunc: function() {
        return this.name;
    }(), // this property will be set to the return value of this anonymous function,
    // which is invoked during the object definition;
    // basically, it's a way to check the context inside of an object, at this moment

    innerObj: {
        name: "inner object",
        innerArrowFunc: () => {
            return this.name;
        } // the context inside a nested object is not the parent, it's still
        // the global object. entering an object definition doesn't change the context
    },

    let otherObj = {
        name: "my other object"
    }
// call unboundFunc on obj, we get "my object" console.log("unboundFunc: ", obj.unboundFunc()); // => "my object" // assign unboundFunc to a variable and call it let newFunc = obj.unboundFunc; // this newFunc will default to being called on global object console.log("newFunc: ",newFunc()); // => undefined // but you could bind it directly to a different object if you wanted console.log("newFunc: ", newFunc.bind(otherObj)()); // "my other object"
// meanwhile, obj.boundToGlobal will only ever be called on global object console.log("boundToGlobal: ", obj.boundToGlobal()); //=> undefined let newBoundFunc = obj.boundToGlobal; console.log("newBoundFunc: ", newBoundFunc()); // => undefined // even if you try to directly bind to another object, it won't work! console.log("newBoundFunc: ", newBoundFunc.bind(otherObj)()); // => undefined
// let's make a new function that will always be bound to the context // where we call our function maker let boundFunc = obj.makeFuncBoundToObj();// note that we're invoking, not just assigning console.log("boundFunc: ", boundFunc()); // => "my object" // we can't rebind this function console.log("boundFunc: ", boundFunc.bind(otherObj)()) // =>"my object"
// but if I call makeFuncBoundToObj on another context // the new bound function is stuck with that other context let boundToOther = obj.makeFuncBoundToObj.bind(otherObj)(); console.log("boundToOther: ", boundToOther()); // => "my other object" console.log("boundToOther: ", boundToOther.bind(obj)()) // "my other object"
// the return value of my immediately invoked function // shows that the context inside of the object is the // global object, not the object itself // context only changes inside a function that is called // on an object console.log("immediatelyInvokedFunc: ", obj.immediatelyInvokedFunc); // => undefined
// even though we're inside a nested object, the context is // still the same as it was outside the outer object // in this case, the global object console.log("innerArrowFunc: ", obj.innerObj.innerArrowFunc()); // => undefined

}

-Implement a closure and explain how the closure effects scope
    -
    a closure is "the combination of a function and the lexical environment within which that function was declared" -
    alternatively, "when an inner function uses or changes variables in an outer function" -
    closures have access to any variables within their own scope + scope of outer functions + global scope— the set of all these available variables is "lexical environemnt" -
    closure keeps reference to all variables ** even
if the outer
function has returned **
    -each
function has a private mutable state that cannot be accessed externally
    -
    the inner
function will maintain a reference to the scope in which it was declared.so it has access to variables that were initialized in any outer scope— even
if that scope
    -
    if a variable exists in the scope of what could have been accessed by a
function(e.g.global scope, outer
    function, etc), does that variable wind up in the closure even
if it never got accessed ?
    -
    if you change the value of a variable(e.g.i++) you will change the value of that variable in the scope that it was declared in

    ``
`javascript
function createCounter() {
   // this function starts a counter at 0, then returns a
   // new function that can access and change that counter
   //
   // each new counter you create will have a single internal
   // state, that can be changed only by calling the function.
   // you can't access that state from outside of the function,
   // even though the count variable in question is initialized
   // by the outer function, and it remains accessible to the
   // inner function after the outer function returns.
   let count = 0;
   return function() {
      count ++;
      return count;
   }
}

let counter = createCounter();
console.log(counter()); //=> 1
console.log(counter()); //=> 2
// so the closure here comes into play because
// an inner function is accessing and changing
// a variable from an outer function

// the closure is the combination of the counter
// function and the all the variables that existed
// in the scope that it was declared in. because
// inner blocks/functions have access to outer
// scopes, that includes the scope of the outer
// function.

// so counter variable is a closure, in that
// it contains the inner count value that was
// initialized by the outer createCounter() function
// count has been captured or closed over

// this state is private, so if i run createCounter again
// i get a totally separate count that doesn't interact
// with the previous one and each of the new functions
// will have their own internal state based on the
// initial declaration in the now-closed outer function

let counter2 = createCounter();
console.log(counter2()); // => 1

// if i set a new function equal to my existing counter
// the internal state is shared with the new function
let counter3 = counter2;
console.log(counter3());

Define a method that references this on an object literal

  • when we use this in a method it refers to the object that the method is invoked on

  • it will let you access other pieces of information from within that object, or even other methods

  • method style invocation — object.method(args) (e.g. built in examples like Array#push, or String#toUpperCase)

  • context is set every time we invoke a function

  • function style invocation sets the context to the global object no matter what

  • being inside an object does not make the context that object! you still have to use method-style invocation

  • Utilize the built in Function#bind on a callback to maintain the context of this

  • when we call bind on a function, we get an exotic function back — so the context will always be the same for that new function

let cat = {
  purr: function () {
  console.log("meow");
  },
  purrMore: function () {
  this.purr();
  },
  };
  let sayMeow = cat.purrMore; console.log(sayMeow()); // TypeError: this.purr is not a function

  // we can use the built in Function.bind to ensure our context, our this, // is the cat object let boundCat = sayMeow.bind(cat);
  boundCat(); // prints "meow"

-`bind`
   can also work with arguments, so you can have a version of a
   function with particular arguments and a particular context.the first arg will be the context aka the `this`
   you want it to use.the next arguments will be the functions arguments that you are binding -
       if you just want to bind it to those arguments in particular, you can use `null`
   as the first argument, so the context won 't be bound, just the arguments -
       Given a code snippet, identify what `this`
   refers to
       -
       important to recognize the difference between scope and context -
       scope works like a dictionary that has all the variables that are available within a given block, plus a pointer back the next outer scope(which itself has pointers to new scopes until you reach the global scope.so you can think about a whole given block 's scope as a kind of linked list of dictionaries) (also, this is not to say that scope is actually implemented in this way, that is just the schema that i can use to understand it) -
           context refers to the value of the `this`
           keyword -
           the keyword `this`
           exists in every
           function and it evaluates to the object that is currently invoking that
           function -so the context is fairly straightforward when we talk about methods being called on specific objects -
           you could, however, call an object 's method on something other than that object, and then this would refer to the context where/how it was called, e.g.
           ``
           `javascript
let dog = {
   name: "Bowser",
   changeName: function () {
      this.name = "Layla";
  },
};

// note this is **not invoked** - we are assigning the function itself
let change = dog.changeName;
console.log(change()); // undefined

// our dog still has the same name
console.log(dog); // { name: 'Bowser', changeName: [Function: changeName] }

// instead of changing the dog we changed the global name!!!
console.log(this); // Object [global] {etc, etc, etc,  name: 'Layla'}

CALLING SOMETHING IN THE WRONG CONTEXT CAN MESS YOU UP!

  • could throw an error if it expects this to have some other method or whatever that doesn’t exist

  • you could also overwrite values or assign values to exist in a space where they should not exist

  • if you call a function as a callback, it will set this to be the outer function itself, even if the function you were calling is a method that was called on a particular object

let cat = {
  purr: function () {
  console.log("meow");
  },
  purrMore: function () {
  this.purr();
  },
  };
  global.setTimeout(cat.purrMore, 5000); // 5 seconds later: TypeError: this.purr is not a function

we can use strict mode with "use strict"; this will prevent you from accessing the global object with this in functions, so if you try to call this in the global context and change a value, you will get a type error, and the things you try to access will be undefined

let sayMeow = cat.purrMore; console.log(sayMeow()); // TypeError: this.purr is not a function

// we can use the built in Function.bind to ensure our context, our this , // is the cat object let boundCat = sayMeow.bind(cat);

boundCat(); // prints “meow”

-`bind`
   can also work with arguments, so you can have a version of a
   function with particular arguments and a particular context.the first arg will be the context aka the `this`
   you want it to use.the next arguments will be the functions arguments that you are binding -
       if you just want to bind it to those arguments in particular, you can use `null`
   as the first argument, so the context won 't be bound, just the arguments -
       Given a code snippet, identify what `this`
   refers to
       -
       important to recognize the difference between scope and context -
       scope works like a dictionary that has all the variables that are available within a given block, plus a pointer back the next outer scope(which itself has pointers to new scopes until you reach the global scope.so you can think about a whole given block 's scope as a kind of linked list of dictionaries) (also, this is not to say that scope is actually implemented in this way, that is just the schema that i can use to understand it) -
           context refers to the value of the `this`
           keyword -
           the keyword `this`
           exists in every
           function and it evaluates to the object that is currently invoking that
           function -so the context is fairly straightforward when we talk about methods being called on specific objects -
           you could, however, call an object 's method on something other than that object, and then this would refer to the context where/how it was called, e.g.
           ``
           `javascript
let dog = {
   name: "Bowser",
   changeName: function () {
      this.name = "Layla";
  },
};

// note this is **not invoked** - we are assigning the function itself
let change = dog.changeName;
console.log(change()); // undefined

// our dog still has the same name
console.log(dog); // { name: 'Bowser', changeName: [Function: changeName] }

// instead of changing the dog we changed the global name!!!
console.log(this); // Object [global] {etc, etc, etc,  name: 'Layla'}
  • CALLING SOMETHING IN THE WRONG CONTEXT CAN MESS YOU UP!

  • could throw an error if it expects this to have some other method or whatever that doesn’t exist

  • you could also overwrite values or assign values to exist in a space where they should not exist

  • if you call a function as a callback, it will set this to be the outer function itself, even if the function you were calling is a method that was called on a particular object

we can use strict mode with "use strict"; this will prevent you from accessing the global object with this in functions, so if you try to call this in the global context and change a value, you will get a type error, and the things you try to access will be undefined

POJOs

1. Label variables as either Primitive vs. Reference

Javascript considers most data types to be ‘primitive’, these data types are immutable, and are passed by value. The more complex data types: Array and Object are mutable, are considered ‘reference’ data types, and are passed by reference.

  • Boolean — Primitive

  • Null — Primitive

  • Undefined — Primitive

  • Number — Primitive

  • String — Primitive

  • Array — Reference

  • Object — Reference

  • Function — Reference

2. Identify when to use . vs [] when accessing values of an object

let obj = {
    "one": 1,
    "two": 2
};

// Choose the square brackets property accessor when the property name is determined at
// runtime, or if the property name is not a valid identifier
let myKey = "one";
console.log(obj[myKey]);

// Choose the dot property accessor when the property name is known ahead of time.
console.log(obj.two);

3. Write an object literal with a variable key using interpolation

let keyName = "two";

// If the key is not known, you can use an alternative `[]` syntax for
// object initialization only
let obj2 = {
    [keyName]: 2
}
console.log(obj2);

4. Use the obj[key] !== undefined pattern to check if a given variable that contains a key exists in an object

function doesKeyExist(obj, key) {
    // obj[key] !== undefined
    // or:
    return key in obj;
}

let course = {
    bootcamp: 'Lambda',
    course: 'Bootcamp Prep'
}
console.log(doesKeyExist(course, 'course')); // => true
console.log(doesKeyExist(course, 'name')); // => false

5. Utilize Object.keys and Object.values in a function

function printKeys(object) {
    return Object.keys(object);
}

function printValues(object) {
    return Object.values(object);
}

console.log(printKeys({
    dog: "Strelka",
    dog2: "Belka"
}));
console.log(printValues({
    dog: "Strelka",
    dog2: "Belka"
}));

6. Iterate through an object using a for in loop

let player = {
    name: "Sergey",
    skill: "hockey"
};

for (let key in player) {
    console.log(key, player[key]);
}

console.log(Object.entries(player));

7. Define a function that utilizes …rest syntax to accept an arbitrary number of arguments

function restSum(...otherNums) {
    let sum = 0;
    console.log(otherNums);
    otherNums.forEach(function(num) {
        sum += num;
    });

    return sum;
}

console.log(restSum(3, 5, 6)); // => 14
console.log(restSum(1, 2, 3, 4, 5, 6, 7, 8, 9)); // => 45
console.log(restSum(0)); // => 0

8. Use …spread syntax for Object literals and Array literals

let numArray = [1, 2, 3];
let moreNums = [...numArray, 4, 5, 6]

console.log(moreNums);

let shoe = {
    color: "red",
    size: 10
};
let newShoe = {
    ...shoe,
    brand: "Nike",
    size: 12
};
console.log(newShoe);
newShoe.color = "black";
console.log(newShoe);

console.log(shoe);

9. Destructure an array to reference specific elements

let arr = ['one', 'two', 'three'];

let [first] = arr;
console.log(first);

10. Destructure an object to reference specific values

let me = {
    name: "Ian",
    instruments: ['bass', 'synth', 'guitar'],
    siblings: {
        brothers: ['Alistair'],
        sisters: ['Meghan']
    }
}

let {
    name,
    instruments: musical_instruments,
    siblings: {
        sisters
    }
} = me;

console.log(name);
console.log(musical_instruments);
console.log(sisters);

11. Write a function that accepts a string as an argument and returns an object representing the count of each character in the array

function charCount(inputString) {

    let res = inputString.split("").reduce(function(accum, el) {
        if (el in accum) {
            accum[el] = accum[el] + 1;
        } else {
            accum[el] = 1;
        }
        return accum;
    }, {})
    return res;

}

console.log(charCount('aaabbbeebbcdkjfalksdfjlkasdfasdfiiidkkdingds'));

Review of Concepts

1. Identify the difference between const, let, and var declarations

2. Explain the difference between const, let, and var declarations

var a = "a";
  • var is the historical keyword used for variable declaration.

  • var declares variables in function scope, or global scope if not inside a function.

  • We consider var to be deprecated and it is never used in this course.

let b = "b";
  • let is the keyword we use most often for variable declaration.

  • let declares variables in block scope.

  • variables declared with let are re-assignable.

const c = "c";
  • const is a specialized form of let that can only be used to initialize a variable.

  • Except when it is declared, you cannot assign to a const variable.

  • const scopes variables the same way that let does.

3. Predict the evaluation of code that utilizes function scope, block scope, lexical scope, and scope chaining

Consider this run function, inside which foo and bar have function scope . i and baz are scoped to the block expression.

// function and block scope in this example
function run() {
    var foo = "Foo";
    let bar = "Bar";

    console.log(foo, bar);

    {
        console.log(foo);
        let baz = "Bazz";
        console.log(baz);
    }

    console.log(baz); // ReferenceError
}

run();

Notice that referencing baz from outside it's block results in JavaScript throwing a ReferenceError.

Consider this run function, inside of which foo has function scope .

function run() {
    console.log(foo); // undefined
    var foo = "Foo";
    console.log(foo); // Foo
}

run();

Consider this func1 function and it's nested scopes.

// global scope
function func1(arg1) {
    // func1 scope

    return function func2(arg2) {
        // func2 scope

        return function func3(arg3) {
            // func3 scope

            console.log(arg1, arg2, arg3);
        }
    }
}

6. Implement a closure and explain how the closure effects scope

const adder = (arg1) => {
    return (arg2) => {
        return arg1 + arg2;
    }
};

const func2 = adder(2);
const result = func2(2);
console.log(result); // => 4;

4. Define an arrow function

const returnValue = (val) => val;

This simple construct will create a function that accepts val as a parameter, and returns val immediately. We do not need to type return val , because this is a single-line function.

Identically, we could write

const returnValue = (val) => {
    return val;
};

5. Given an arrow function, deduce the value of this without executing the code

function fDAdder(arr) {
    console.log(this);

    return arr.reduce((acc, ele) => {
        return acc + ele;
    });
};

fDAdder([1, 2, 4, 6]);

If we use a function declaration style function, the this variable is set to the global object (i.e. Object [global] in Node. JS and Window in your browser).

const adder = (arr) => {
    console.log(this);
    arr.reduce((acc, ele) => sum += ele);
};
adder([1, 2, 4, 6]);

In this example, we use a fat arrow style function. Note that when we declare a functions like this this becomes

7. Define a method that references this on an object literal

const pokemon = {
    firstname: 'Pika',
    lastname: 'Chu',
    getPokeName: function() {
        const fullname = `${this.firstname} ${this.lastname}`;
        return fullname;
    }
};

console.log(pokemon.getPokeName());

8. Utilize the built in Function#bind on a callback to maintain the context of this

const pokemon = {
    firstname: 'Pika',
    lastname: 'Chu',
    getPokeName: function() {
        const fullname = `${this.firstname} ${this.lastname}`;
        return fullname;
    }
};

const logPokemon = pokemon.getPokename.bind(pokemon);

logPokemon('sushi', 'algorithms'); // Pika Chu loves sushi and algorithms

9. Given a code snippet, identify what this refers to

function Person(name) {
    // this.name = name;
    // let that = this;

    setTimeout(function() {
        // console.log(this); // => Window
        // console.log(that); // => [Function] => Person
        // this.sayName(); // => no method error
        that.sayName();
    }, 1000);
}

Person.prototype.sayName = function() {
    console.log(this.name);
};

const jane = new Person("Jane");

The Complete JavaScript Reference Guide


The Complete JavaScript Reference Guide

### How to learn effectively

Learning: The acquisition of skills and the ability to apply them in the future.

What makes an Effective learner?

  • They are active listeners.

  • They are engaged with the material.

  • They are receptive of feedback.

  • They are open to difficulty.

Why do active learning techniques feel difficult?

  • It feels difficult because you are constantly receiving feedback, and so you are constantly adapting and perfecting the material.

Desirable Difficulty

  • The skills we wish to obtain is often a difficult one.

  • We want challenging but possible lessons based on current level of skill.

Effective learners space their practice

  • Consistent effort > cramming => for durable knowledge


Getting visual feedback in your programs

The first command we'll learn in JavaScript is console.log. This command is used to print something onto the screen. As we write our first lines of code, we'll be using console.log frequently as a way to visually see the output of our programs. Let's write our first program:

console.log("hello world");
console.log("how are you?");

Executing the program above would print out the following:

hello world
how are you?

Nothing too ground breaking here, but pay close attention to the exact way we wrote the program. In particular, notice how we lay out the periods, parentheses, and quotation marks. We'll also terminate lines with semicolons (;).

Depending on how you structure your code, sometimes you'll be able to omit semicolons at the end of lines. For now, you'll want to include them just as we do.

Syntax

We refer to the exact arrangement of the symbols, characters, and keywords as syntax. These details matter — your computer will only be able to “understand” proper JavaScript syntax. A programming language is similar to a spoken language. A spoken language like English has grammar rules that we should follow in order to be understood by fellow speakers. In the same way, a programming language like JavaScript has syntax rules that we ought to follow!

As you write your first lines of code in this new language, you may make many syntax errors. Don't get frustrated! This is normal — all new programmers go through this phase. Every time we recognize an error in our code, we have an opportunity to reinforce your understanding of correct syntax. Adopt a growth mindset and learn from your mistakes.

Additionally, one of the best things about programming is that we can get such immediate feedback from our creations. There is no penalty for making a mistake when programming. Write some code, run the code, read the errors, fix the errors, rinse and repeat!

Code comments

Occasionally we'll want to leave comments or notes in our code. Commented lines will be ignored by our computer. This means that we can use comments to write plain english or temporarily avoid execution of some JavaScript lines. The proper syntax for writing a comment is to begin the line with double forward slashes (//):

// let's write another program!!!
console.log("hello world");

// console.log("how are you?");

console.log("goodbye moon");

The program above would only print:

hello world
goodbye moon

Comments are useful when annotating pieces of code to offer an explanation of how the code works. We'll want to strive to write straightforward code that is self-explanatory when possible, but we can also use comments to add additional clarity. The real art of programming is to write code so elegantly that it is easy to follow.

The Number Data Type

The number data type in JS is used to represent any numerical values, including integers and decimal numbers.

Basic Arithmetic Operators

Operators are the symbols that perform particular operations.

  • + (addition)

  • - (subtraction)

  • asterisk (multiplication)

  • / (division)

  • % (modulo)

JS evaluates more complex expressions using the general math order of operations aka PEMDAS.

  • PEMDAS : Parentheses, Exponents, Multiplication, Division, Modulo, Addition, Subtraction.

  • To force a specific order of operation, use the group operator ( ) around a part of the expression.

Modulo : Very useful operation to check divisibility of numbers, check for even & odd, whether a number is prime, and much more! (Discrete Math concept, circular problems can be solved with modulo)

  • Whenever you have a smaller number % a larger number, the answer will just be the initial small number. console.log(7 % 10) // => 7;

The String Data Type

The string data type is a primitive data type that used to represent textual data.

  • can be wrapped by either single or double quotation marks, best to choose one and stick with it for consistency.

  • If your string contains quotation marks inside, can layer single or double quotation marks to allow it to work. “That's a great string”; (valid) 'Shakespeare wrote, “To be or not to be”'; (valid) 'That's a bad string'; (invalid)

  • Alt. way to add other quotes within strings is to use template literals. `This is a temp'l'ate literal ${function}` // use ${} to invoke functions within.

  • .length : property that can be appended to data to return the length.

  • empty strings have a length of zero.

  • indices : indexes of data that begin at 0, can call upon index by using the bracket notation [ ]. console.log(“bootcamp”[0]); // => “b” console.log(“bootcamp”[10]); // => “undefined” console.log(“boots”[1 * 2]); // => “o” console.log(“boots”[“boot”.length-1]); // => “t”

  • we can pass expressions through the brackets as well since JS always evaluates expressions first.

  • The index of the last character of a string is always one less than it's length.

  • indexOf() : method used to find the first index of a given character within a string. console.log(“bagel”.indexOf(“b”)); // => 0 console.log(“bagel”.indexOf(“z”)); // => -1

  • if the character inside the indexOf() search does not exist in the string, the output will be -1.

  • the indexOf() search will return the first instanced index of the the char in the string.

  • concatenate : word to describe joining strings together into a single string.

The Boolean Data Type

The Boolean data type is the simplest data type since there are only two values: true and false.

  • Logical Operators (Boolean Operators) are used to establish logic in our code.

  • ! (not) : reverses a Boolean value. console.log(!true); // => false console.log(!!false); // => false

  • Logical Order of Operations : JS will evaluate !, then &&, then ||.

  • Short-Circuit Evaluation : Because JS evalutes from left to right, expressions can “short-circuit”. For example if we have true on the left of an || logical comparison, it will stop evaluating and yield true instead of wasting resources on processing the rest of the statement. console.log(true || !false) // => stops after it sees “true ||”

Comparison Operators

All comparison operators will result in a boolean output.

The relative comparators

  • > (greater than)

  • < (less than)

  • >= (greater than or equal to)

  • <= (less than or equal to)

  • === (equal to)

  • !== (not equal to)

Fun Fact: “a” < “b” is considered valid JS Code because string comparisons are compared lexicographically (meaning dictionary order), so “a” is less than “b” because it appears earlier!

If there is ever a standstill comparison of two string lexicographically (i.e. app vs apple) the comparison will deem the shorter string lesser.

Difference between == and ===

===

Strict Equality, will only return true if the two comparisons are entirely the same.

==

Loose Equality, will return true even if the values are of a different type, due to coercion. (Avoid using this)

Variables

Variables are used to store information to be referenced and manipulated in a program.

  • We initialize a variable by using the let keyword and a = single equals sign (assignment operator). let bootcamp = “App Academy”; console.log(bootcamp); // “App Academy”

  • JS variable names can contain any alphanumeric characters, underscores, or dollar signs (cannot being with a number).

  • If you do not declare a value for a variable, undefined is automatically set. let bootcamp; console.log(bootcamp); // undefined

  • We can change the value of a previously declared variable (let, not const) by re-assigning it another value.

  • let is the updated version of var; there are some differences in terms of hoisting and global/block scope

Assignment Shorthand

let num = 0; num += 10; // same as num = num + 10 num -= 2; // same as num = num — 2 num /= 4; // same as num = num / 4 num *= 7; // same as num = num * 7

  • In general, any nonsensical arithmetic will result in NaN ; usually operations that include undefined.

Functions

A function is a procedure of code that will run when called. Functions are used so that we do not have to rewrite code to do the same thing over and over. (Think of them as 'subprograms')

  • Function Declaration : Process when we first initially write our function.

  • Includes three things:

  • Name of the function.

  • A list of parameters ()

  • The code to execute {}

  • Function Calls : We can call upon our function whenever and wherever* we want. (*wherever is only after the initial declaration)

  • JS evaluates code top down, left to right.

  • When we execute a declared function later on in our program we refer to this as invoking our function.

  • Every function in JS returns undefined unless otherwise specified.

  • When we hit a return statement in a function we immediately exit the function and return to where we called the function.

  • When naming functions in JS always use camelCase and name it something appropriate.

Greate code reads like English and almost explains itself. Think: Elegant, readable, and maintainable!

Parameters and Arguments

  • Parameters : Comma seperated variables specified as part of a function's declaration.

  • Arguments : Values passed to the function when it is invoked.

  • If the number of arguments passed during a function invocation is different than the number of paramters listed, it will still work.

  • However, is there are not enough arguments provided for parameters our function will likely yield Nan.

Including Comments

Comments are important because they help other people understand what is going on in your code or remind you if you forgot something yourself. Keep in mind that they have to be marked properly so the browser won't try to execute them.

In JavaScript you have two different options:

  • Single-line comments — To include a comment that is limited to a single line, precede it with //

  • Multi-line comments — In case you want to write longer comments between several lines, wrap it in /* and */ to avoid it from being executed

Variables in JavaScript

Variables are stand-in values that you can use to perform operations. You should be familiar with them from math class.

var, const, let

You have three different possibilities for declaring a variable in JavaScript, each with their own specialties:

  • var — The most common variable. It can be reassigned but only accessed within a function. Variables defined with var move to the top when the code is executed.

  • const — Can not be reassigned and not accessible before they appear within the code.

  • let — Similar to const, the let variable can be reassigned but not re-declared.

Data Types

Variables can contain different types of values and data types. You use = to assign them:

  • Numbers — var age = 23

  • Variables — var x

  • Text (strings) — var a = "init"

  • Operations — var b = 1 + 2 + 3

  • True or false statements — var c = true

  • Constant numbers — const PI = 3.14

  • Objects — var name = {firstName:"John", lastName:"Doe"}

There are more possibilities. Note that variables are case sensitive. That means lastname and lastName will be handled as two different variables.

Objects

Objects are certain kinds of variables. They are variables that can have their own values and methods. The latter are actions that you can perform on objects.

var person = {

firstName:”John”,

lastName:”Doe”,

age:20,

nationality:”German”

};

The Next Level: Arrays

Next up in our JavaScript cheat sheet are arrays. Arrays are part of many different programming languages. They are a way of organizing variables and properties into groups. Here's how to create one in JavaScript:

var fruit = [“Banana”, “Apple”, “Pear”];

Now you have an array called fruit which contains three items that you can use for future operations.

Array Methods

Once you have created arrays, there are a few things you can do with them:

  • concat() — Join several arrays into one

  • indexOf() — Returns the first position at which a given element appears in an array

  • join() — Combine elements of an array into a single string and return the string

  • lastIndexOf() — Gives the last position at which a given element appears in an array

  • pop() — Removes the last element of an array

  • push() — Add a new element at the end

  • reverse() — Sort elements in a descending order

  • shift() — Remove the first element of an array

  • slice() — Pulls a copy of a portion of an array into a new array

  • sort() — Sorts elements alphabetically

  • splice() — Adds elements in a specified way and position

  • toString() — Converts elements to strings

  • unshift() —Adds a new element to the beginning

  • valueOf() — Returns the primitive value of the specified object

Operators

If you have variables, you can use them to perform different kinds of operations. To do so, you need operators.

Basic Operators

  • + — Addition

  • - — Subtraction

  • * — Multiplication

  • / — Division

  • (...) — Grouping operator, operations within brackets are executed earlier than those outside

  • % — Modulus (remainder )

  • ++ — Increment numbers

  • -- — Decrement numbers

Comparison Operators

  • == — Equal to

  • === — Equal value and equal type

  • != — Not equal

  • !== — Not equal value or not equal type

  • > — Greater than

  • < — Less than

  • >= — Greater than or equal to

  • <= — Less than or equal to

  • ? — Ternary operator

Logical Operators

  • && — Logical and

  • || — Logical or

  • ! — Logical not

Bitwise Operators

  • & — AND statement

  • | — OR statement

  • ~ — NOT

  • ^ — XOR

  • << — Left shift

  • >> — Right shift

  • >>> — Zero fill right shift

Functions

JavaScript functions are blocks of code that perform a certain task. A basic function looks like this:

function name(parameter1, parameter2, parameter3) {

// what the function does

}

As you can see, it consists of the function keyword plus a name. The function's parameters are in the brackets and you have curly brackets around what the function performs. You can create your own, but to make your life easier – there are also a number of default functions.

Outputting Data

A common application for functions is the output of data. For the output, you have the following options:

  • alert() — Output data in an alert box in the browser window

  • confirm() — Opens up a yes/no dialog and returns true/false depending on user click

  • console.log() — Writes information to the browser console, good for debugging purposes

  • document.write() — Write directly to the HTML document

  • prompt() — Creates a dialogue for user input

Global Functions

Global functions are functions built into every browser capable of running JavaScript.

  • decodeURI() — Decodes a Uniform Resource Identifier (URI) created by encodeURI or similar

  • decodeURIComponent() — Decodes a URI component

  • encodeURI() — Encodes a URI into UTF-8

  • encodeURIComponent() — Same but for URI components

  • eval() — Evaluates JavaScript code represented as a string

  • isFinite() — Determines whether a passed value is a finite number

  • isNaN() — Determines whether a value is NaN or not

  • Number() —- Returns a number converted from its argument

  • parseFloat() — Parses an argument and returns a floating-point number

  • parseInt() — Parses its argument and returns an integer

JavaScript Loops

Loops are part of most programming languages. They allow you to execute blocks of code desired number of times with different values:

for (before loop; condition for loop; execute after loop) {

// what to do during the loop

}

You have several parameters to create loops:

  • for — The most common way to create a loop in JavaScript

  • while — Sets up conditions under which a loop executes

  • do while — Similar to the while loop but it executes at least once and performs a check at the end to see if the condition is met to execute again

  • break —Used to stop and exit the cycle at certain conditions

  • continue — Skip parts of the cycle if certain conditions are met

If — Else Statements

These types of statements are easy to understand. Using them, you can set conditions for when your code is executed. If certain conditions apply, something is done, if not — something else is executed.

if (condition) {

// what to do if condition is met

} else {

// what to do if condition is not met

}

A similar concept to if else is the switch statement. However, using the switch you select one of several code blocks to execute.

Strings

Strings are what JavaScript calls to text that does not perform a function but can appear on the screen.

var person = “John Doe”;

In this case, John Doe is the string.

Escape Characters

In JavaScript, strings are marked with single or double-quotes. If you want to use quotation marks in a string, you need to use special characters:

  • \' — Single quote

  • \" — Double quote

Aside from that you also have additional escape characters:

  • \\ — Backslash

  • \b — Backspace

  • \f — Form feed

  • — New line

  • — Carriage return

  • — Horizontal tabulator

  • \v — Vertical tabulator

String Methods

There are many different ways to work with strings:

  • charAt() — Returns a character at a specified position inside a string

  • charCodeAt() — Gives you the Unicode of a character at that position

  • concat() — Concatenates (joins) two or more strings into one

  • fromCharCode() — Returns a string created from the specified sequence of UTF-16 code units

  • indexOf() — Provides the position of the first occurrence of a specified text within a string

  • lastIndexOf() — Same as indexOf() but with the last occurrence, searching backward

  • match() — Retrieves the matches of a string against a search pattern

  • replace() — Find and replace specified text in a string

  • search() — Executes a search for a matching text and returns its position

  • slice() — Extracts a section of a string and returns it as a new string

  • split() — Splits a string object into an array of strings at a specified position

  • substr() — Similar to slice() but extracts a substring depending on a specified number of characters

  • substring() — Also similar to slice() but can't accept negative indices

  • toLowerCase() — Convert strings to lower case

  • toUpperCase() — Convert strings to upper case

  • valueOf() — Returns the primitive value (that has no properties or methods) of a string object

Regular Expression Syntax

Regular expressions are search patterns used to match character combinations in strings. The search pattern can be used for text search and text to replace operations.

Pattern Modifiers

  • e — Evaluate replacement

  • i — Perform case-insensitive matching

  • g — Perform global matching

  • m — Perform multiple line matching

  • s — Treat strings as a single line

  • x — Allow comments and whitespace in the pattern

  • U — Ungreedy pattern

Brackets

  • [abc] — Find any of the characters between the brackets

  • [^abc] — Find any character which is not in the brackets

  • [0-9] — Used to find any digit from 0 to 9

  • [A-z] — Find any character from uppercase A to lowercase z

  • (a|b|c) — Find any of the alternatives separated with |

Metacharacters

  • . — Find a single character, except newline or line terminator

  • \w — Word character

  • \W — Non-word character

  • \d — A digit

  • \D — A non-digit character

  • \s — Whitespace character

  • \S — Non-whitespace character

  • \b — Find a match at the beginning/end of a word

  • \B — A match not at the beginning/end of a word

  • \0 — NUL character

  • — A new line character

  • \f — Form feed character

  • — Carriage return character

  • — Tab character

  • \v — Vertical tab character

  • \xxx — The character specified by an octal number xxx

  • \xdd — Character specified by a hexadecimal number dd

  • \uxxxx — The Unicode character specified by a hexadecimal number XXXX

Quantifiers

  • n+ — Matches any string that contains at least one n

  • n* — Any string that contains zero or more occurrences of n

  • n? — A string that contains zero or one occurrence of n

  • n{X} — String that contains a sequence of X n's

  • n{X,Y} — Strings that contain a sequence of X to Y n's

  • n{X,} — Matches any string that contains a sequence of at least X n's

  • n$ — Any string with n at the end of it

  • ^n — String with n at the beginning of it

  • ?=n — Any string that is followed by a specific string n

  • ?!n — String that is not followed by a specific string ni

Numbers and Math

In JavaScript, you can also work with numbers, constants and perform mathematical functions.

Number Properties

  • MAX_VALUE — The maximum numeric value representable in JavaScript

  • MIN_VALUE — Smallest positive numeric value representable in JavaScript

  • NaN — The “Not-a-Number” value

  • NEGATIVE_INFINITY — The negative Infinity value

  • POSITIVE_INFINITY — Positive Infinity value

Number Methods

  • toExponential() — Returns the string with a rounded number written as exponential notation

  • toFixed() — Returns the string of a number with a specified number of decimals

  • toPrecision() — String of a number written with a specified length

  • toString() — Returns a number as a string

  • valueOf() — Returns a number as a number

Math Properties

  • E — Euler's number

  • LN2 — The natural logarithm of 2

  • LN10 — Natural logarithm of 10

  • LOG2E — Base 2 logarithm of E

  • LOG10E — Base 10 logarithm of E

  • PI — The number PI

  • SQRT1_2 — Square root of 1/2

  • SQRT2 — The square root of 2

Math Methods

  • abs(x) — Returns the absolute (positive) value of x

  • acos(x) — The arccosine of x, in radians

  • asin(x) — Arcsine of x, in radians

  • atan(x) — The arctangent of x as a numeric value

  • atan2(y,x) — Arctangent of the quotient of its arguments

  • ceil(x) — Value of x rounded up to its nearest integer

  • cos(x) — The cosine of x (x is in radians)

  • exp(x) — Value of Ex

  • floor(x) — The value of x rounded down to its nearest integer

  • log(x) — The natural logarithm (base E) of x

  • max(x,y,z,...,n) — Returns the number with the highest value

  • min(x,y,z,...,n) — Same for the number with the lowest value

  • pow(x,y) — X to the power of y

  • random() — Returns a random number between 0 and 1

  • round(x) — The value of x rounded to its nearest integer

  • sin(x) — The sine of x (x is in radians)

  • sqrt(x) — Square root of x

  • tan(x) — The tangent of an angle

Dealing with Dates in JavaScript

You can also work with and modify dates and time with JavaScript. This is the next chapter in the JavaScript cheat sheet.

Setting Dates

  • Date() — Creates a new date object with the current date and time

  • Date(2017, 5, 21, 3, 23, 10, 0) — Create a custom date object. The numbers represent a year, month, day, hour, minutes, seconds, milliseconds. You can omit anything you want except for a year and month.

  • Date("2017-06-23") — Date declaration as a string

Pulling Date and Time Values

  • getDate() — Get the day of the month as a number (1-31)

  • getDay() — The weekday as a number (0-6)

  • getFullYear() — Year as a four-digit number (yyyy)

  • getHours() — Get the hour (0-23)

  • getMilliseconds() — The millisecond (0-999)

  • getMinutes() — Get the minute (0-59)

  • getMonth() — Month as a number (0-11)

  • getSeconds() — Get the second (0-59)

  • getTime() — Get the milliseconds since January 1, 1970

  • getUTCDate() — The day (date) of the month in the specified date according to universal time (also available for day, month, full year, hours, minutes etc.)

  • parse — Parses a string representation of a date and returns the number of milliseconds since January 1, 1970

Set Part of a Date

  • setDate() — Set the day as a number (1-31)

  • setFullYear() — Sets the year (optionally month and day)

  • setHours() — Set the hour (0-23)

  • setMilliseconds() — Set milliseconds (0-999)

  • setMinutes() — Sets the minutes (0-59)

  • setMonth() — Set the month (0-11)

  • setSeconds() — Sets the seconds (0-59)

  • setTime() — Set the time (milliseconds since January 1, 1970)

  • setUTCDate() — Sets the day of the month for a specified date according to universal time (also available for day, month, full year, hours, minutes etc.)

DOM Mode

The DOM is the Document Object Model of a page. It is the code of the structure of a webpage. JavaScript comes with a lot of different ways to create and manipulate HTML elements (called nodes).

Node Properties

  • attributes — Returns a live collection of all attributes registered to an element

  • baseURI — Provides the absolute base URL of an HTML element

  • childNodes — Gives a collection of an element's child nodes

  • firstChild — Returns the first child node of an element

  • lastChild — The last child node of an element

  • nextSibling — Gives you the next node at the same node tree level

  • nodeName —Returns the name of a node

  • nodeType — Returns the type of a node

  • nodeValue — Sets or returns the value of a node

  • ownerDocument — The top-level document object for this node

  • parentNode — Returns the parent node of an element

  • previousSibling — Returns the node immediately preceding the current one

  • textContent — Sets or returns the textual content of a node and its descendants

Node Methods

  • appendChild() — Adds a new child node to an element as the last child node

  • cloneNode() — Clones an HTML element

  • compareDocumentPosition() — Compares the document position of two elements

  • getFeature() — Returns an object which implements the APIs of a specified feature

  • hasAttributes() — Returns true if an element has any attributes, otherwise false

  • hasChildNodes() — Returns true if an element has any child nodes, otherwise false

  • insertBefore() — Inserts a new child node before a specified, existing child node

  • isDefaultNamespace() — Returns true if a specified namespaceURI is the default, otherwise false

  • isEqualNode() — Checks if two elements are equal

  • isSameNode() — Checks if two elements are the same node

  • isSupported() — Returns true if a specified feature is supported on the element

  • lookupNamespaceURI() — Returns the namespace URI associated with a given node

  • lookupPrefix() — Returns a DOMString containing the prefix for a given namespace URI if present

  • normalize() — Joins adjacent text nodes and removes empty text nodes in an element

  • removeChild() — Removes a child node from an element

  • replaceChild() — Replaces a child node in an element

Element Methods

  • getAttribute() — Returns the specified attribute value of an element node

  • getAttributeNS() — Returns string value of the attribute with the specified namespace and name

  • getAttributeNode() — Gets the specified attribute node

  • getAttributeNodeNS() — Returns the attribute node for the attribute with the given namespace and name

  • getElementsByTagName() — Provides a collection of all child elements with the specified tag name

  • getElementsByTagNameNS() — Returns a live HTMLCollection of elements with a certain tag name belonging to the given namespace

  • hasAttribute() — Returns true if an element has any attributes, otherwise false

  • hasAttributeNS() — Provides a true/false value indicating whether the current element in a given namespace has the specified attribute

  • removeAttribute() — Removes a specified attribute from an element

  • removeAttributeNS() — Removes the specified attribute from an element within a certain namespace

  • removeAttributeNode() — Takes away a specified attribute node and returns the removed node

  • setAttribute() — Sets or changes the specified attribute to a specified value

  • setAttributeNS() — Adds a new attribute or changes the value of an attribute with the given namespace and name

  • setAttributeNode() — Sets or changes the specified attribute node

  • setAttributeNodeNS() — Adds a new namespaced attribute node to an element

Working with the User Browser

Besides HTML elements, JavaScript is also able to take into account the user browser and incorporate its properties into the code.

Window Properties

  • closed — Checks whether a window has been closed or not and returns true or false

  • defaultStatus — Sets or returns the default text in the status bar of a window

  • document — Returns the document object for the window

  • frames — Returns all <iframe> elements in the current window

  • history — Provides the History object for the window

  • innerHeight — The inner height of a window's content area

  • innerWidth — The inner width of the content area

  • length — Find out the number of <iframe> elements in the window

  • location — Returns the location object for the window

  • name — Sets or returns the name of a window

  • navigator — Returns the Navigator object for the window

  • opener — Returns a reference to the window that created the window

  • outerHeight — The outer height of a window, including toolbars/scrollbars

  • outerWidth — The outer width of a window, including toolbars/scrollbars

  • pageXOffset — Number of pixels the current document has been scrolled horizontally

  • pageYOffset — Number of pixels the document has been scrolled vertically

  • parent — The parent window of the current window

  • screen — Returns the Screen object for the window

  • screenLeft — The horizontal coordinate of the window (relative to the screen)

  • screenTop — The vertical coordinate of the window

  • screenX — Same as screenLeft but needed for some browsers

  • screenY — Same as screenTop but needed for some browsers

  • self — Returns the current window

  • status — Sets or returns the text in the status bar of a window

  • top — Returns the topmost browser window

Window Methods

  • alert() — Displays an alert box with a message and an OK button

  • blur() — Removes focus from the current window

  • clearInterval() — Clears a timer set with setInterval()

  • clearTimeout() — Clears a timer set with setTimeout()

  • close() — Closes the current window

  • confirm() — Displays a dialogue box with a message and an OK and Cancel button

  • focus() — Sets focus to the current window

  • moveBy() — Moves a window relative to its current position

  • moveTo() — Moves a window to a specified position

  • open() — Opens a new browser window

  • print() — Prints the content of the current window

  • prompt() — Displays a dialogue box that prompts the visitor for input

  • resizeBy() — Resizes the window by the specified number of pixels

  • resizeTo() — Resizes the window to a specified width and height

  • scrollBy() — Scrolls the document by a specified number of pixels

  • scrollTo() — Scrolls the document to specified coordinates

  • setInterval() — Calls a function or evaluates an expression at specified intervals

  • setTimeout() — Calls a function or evaluates an expression after a specified interval

  • stop() — Stops the window from loading

Screen Properties

  • availHeight — Returns the height of the screen (excluding the Windows Taskbar)

  • availWidth — Returns the width of the screen (excluding the Windows Taskbar)

  • colorDepth — Returns the bit depth of the color palette for displaying images

  • height — The total height of the screen

  • pixelDepth — The color resolution of the screen in bits per pixel

  • width — The total width of the screen

JavaScript Events

Events are things that can happen to HTML elements and are performed by the user. The programming language can listen for these events and trigger actions in the code. No JavaScript cheat sheet would be complete without them.

Mouse

  • onclick — The event occurs when the user clicks on an element

  • oncontextmenu — User right-clicks on an element to open a context menu

  • ondblclick — The user double-clicks on an element

  • onmousedown — User presses a mouse button over an element

  • onmouseenter — The pointer moves onto an element

  • onmouseleave — Pointer moves out of an element

  • onmousemove — The pointer is moving while it is over an element

  • onmouseover — When the pointer is moved onto an element or one of its children

  • onmouseout — User moves the mouse pointer out of an element or one of its children

  • onmouseup — The user releases a mouse button while over an element

Keyboard

  • onkeydown — When the user is pressing a key down

  • onkeypress — The moment the user starts pressing a key

  • onkeyup — The user releases a key

Frame

  • onabort — The loading of a media is aborted

  • onbeforeunload — Event occurs before the document is about to be unloaded

  • onerror — An error occurs while loading an external file

  • onhashchange — There have been changes to the anchor part of a URL

  • onload — When an object has loaded

  • onpagehide — The user navigates away from a webpage

  • onpageshow — When the user navigates to a webpage

  • onresize — The document view is resized

  • onscroll — An element's scrollbar is being scrolled

  • onunload — Event occurs when a page has unloaded

Form

  • onblur — When an element loses focus

  • onchange — The content of a form element changes (for <input>, <select> and <textarea>)

  • onfocus — An element gets focus

  • onfocusin — When an element is about to get focus

  • onfocusout — The element is about to lose focus

  • oninput — User input on an element

  • oninvalid — An element is invalid

  • onreset — A form is reset

  • onsearch — The user writes something in a search field (for <input="search">)

  • onselect — The user selects some text (for <input> and <textarea>)

  • onsubmit — A form is submitted

Drag

  • ondrag — An element is dragged

  • ondragend — The user has finished dragging the element

  • ondragenter — The dragged element enters a drop target

  • ondragleave — A dragged element leaves the drop target

  • ondragover — The dragged element is on top of the drop target

  • ondragstart — User starts to drag an element

  • ondrop — Dragged element is dropped on the drop target

Clipboard

  • oncopy — User copies the content of an element

  • oncut — The user cuts an element's content

  • onpaste — A user pastes the content in an element

Media

  • onabort — Media loading is aborted

  • oncanplay — The browser can start playing media (e.g. a file has buffered enough)

  • oncanplaythrough — The browser can play through media without stopping

  • ondurationchange — The duration of the media changes

  • onended — The media has reached its end

  • onerror — Happens when an error occurs while loading an external file

  • onloadeddata — Media data is loaded

  • onloadedmetadata — Metadata (like dimensions and duration) are loaded

  • onloadstart — The browser starts looking for specified media

  • onpause — Media is paused either by the user or automatically

  • onplay — The media has been started or is no longer paused

  • onplaying — Media is playing after having been paused or stopped for buffering

  • onprogress — The browser is in the process of downloading the media

  • onratechange — The playing speed of the media changes

  • onseeked — User is finished moving/skipping to a new position in the media

  • onseeking — The user starts moving/skipping

  • onstalled — The browser is trying to load the media but it is not available

  • onsuspend — The browser is intentionally not loading media

  • ontimeupdate — The playing position has changed (e.g. because of fast forward)

  • onvolumechange — Media volume has changed (including mute)

  • onwaiting — Media paused but expected to resume (for example, buffering)

Animation

  • animationend — A CSS animation is complete

  • animationiteration — CSS animation is repeated

  • animationstart — CSS animation has started

Other

  • transitionend — Fired when a CSS transition has completed

  • onmessage — A message is received through the event source

  • onoffline — The browser starts to work offline

  • ononline — The browser starts to work online

  • onpopstate — When the window's history changes

  • onshow — A <menu> element is shown as a context menu

  • onstorage — A Web Storage area is updated

  • ontoggle — The user opens or closes the <details> element

  • onwheel — Mouse wheel rolls up or down over an element

  • ontouchcancel — Screen-touch is interrupted

  • ontouchend — User's finger is removed from a touch-screen

  • ontouchmove — A finger is dragged across the screen

  • ontouchstart — A finger is placed on the touch-screen

Errors

When working with JavaScript, different errors can occur. There are several ways of handling them:

  • try — Lets you define a block of code to test for errors

  • catch — Set up a block of code to execute in case of an error

  • throw — Create custom error messages instead of the standard JavaScript errors

  • finally — Lets you execute code, after try and catch, regardless of the result

Error Name Values

JavaScript also has a built-in error object. It has two properties:

  • name — Sets or returns the error name

  • message — Sets or returns an error message in a string from

The error property can return six different values as its name:

  • EvalError — An error has occurred in the eval() function

  • RangeError — A number is “out of range”

  • ReferenceError — An illegal reference has occurred

  • SyntaxError — A syntax error has occurred

  • TypeError — A type error has occurred

  • URIError — An encodeURI() error has occurred

Explicit Conversions

The simplest way to perform an explicit type conversion is to use the Boolean(), Number(), and String() functions.

Any value other than null or undefined has a toString() method.

n.toString(2);

binary

n.toString(8);

octal

n.toString(16);

hex

let n = 123456.789;

n.toFixed(0)

“123457”

n.toFixed(5)

“123456.78900”

n.toExponential(3)

“1.235e+5”

n.toPrecision(7)

“123456.8”

n.toPrecision(10)

“123456.7890”

parseInt("3 blind mice")

3

parseFloat(" 3.14 meters")

3.14

parseInt("-12.34")

-12

parseInt("0xFF")

255

Types, Values, and Variables

Resource

URL

MDN

https://developer.mozilla.org/en-US/docs/Web/JavaScript

Run Snippets

https://developers.google.com/web/tools/chrome-devtools/javascript/snippets

Explicit Conversions

The simplest way to perform an explicit type conversion is to use the Boolean(), Number(), and String() functions.

Any value other than null or undefined has a toString() method.

n.toString(2);

binary

n.toString(8);

octal

n.toString(16);

hex

let n = 123456.789;

n.toFixed(0)

“123457”

n.toFixed(5)

“123456.78900”

n.toExponential(3)

“1.235e+5”

n.toPrecision(7)

“123456.8”

n.toPrecision(10)

“123456.7890”

parseInt("3 blind mice")

3

parseFloat(" 3.14 meters")

3.14

parseInt("-12.34")

-12

parseInt("0xFF")

255

parseInt("0xff")

255

parseInt("-0XFF")

-255

parseInt("0.1")

0

parseInt(".1")

NaN: integers can't start with “.”

parseFloat("$72.47")

NaN: numbers can't start with “$”

Supply Radix

parseInt("11", 2)

3

parseInt("ff", 16)

255

parseInt("077", 8)

63

Conversion Idioms

x + ""

String(x)

+x

Number(x)

x-0

Number(x)

!!x

Boolean(x)

Destructuring Assignment

let [x,y] = [1,2];

let x=1, y=2

[x,y] = [x + 1,y + 1];

x = x + 1, y = y + 1

[x,y] = [y,x];

Swap the value of the two variables

Destructuring assignment makes it easy to work with functions that return arrays of values:

let [r,theta] = toPolar(1.0, 1.0);

function toPolar(x, y) {

return [Math.sqrt(x*x+y*y), Math.atan2(y,x)];

}

Variable destructuring in loops:

let o = { x: 1, y: 2 };

for(const [name, value] of Object.entries(o)) {

console.log(name, value); // Prints "x 1" and "y 2"

}

Note: The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs, in the same order as that provided by a for...in loop. (The only important difference is that a for...in loop enumerates properties in the prototype chain as well).

The list of variables on the left can include extra commas to skip certain values on the right

[,x,,y] = [1,2,3,4];

x == 2; y == 4

Note: the last comma does not stand for a value.

To collect all unused or remaining values into a single variable when destructuring an array, use three dots (...) before the last variable name on the left-hand side

let [x, ...y] = [1,2,3,4];

y == [2,3,4]

let [first, ...rest] = "Hello";

first == “H”; rest ==[“e”,”l”,”l”,”o”]

Destructuring assignment can also be performed when the righthand side is an object value.

let transparent = {r: 0.0, g: 0.0, b: 0.0, a: 1.0};

let {r, g, b} = transparent;

r == 0.0; g == 0.0; b == 0.0

const {sin, cos, tan} = Math;

sin=Math.sin, cos=Math.cos, tan=Math.tan

Expressions and Operators

In JavaScript, the values null and undefined are the only two values that do not have properties. In a regular property access expression using . or [], you get a TypeError if the expression on the left evaluates to null or undefined. You can use ?. and ?.[] syntax to guard against errors of this type.

You can also invoke a function using ?.() instead of ().

With the new ?.() invocation syntax, if the expression to the left of the ?. evaluates to null or undefined, then the entire invocation expression evaluates to undefined and no exception is thrown.

Write the function invocation using ?.(), knowing that invocation will only happen if there is actually a value to be invoked

function square(x, log) {

log?.(x); // Call the function if there is one

return x * x;

}

Note that expression x++ is not always the same as x = x + 1.The ++ operator never performs string concatenation: it always converts its operand to a number and increments it. If x is the string "1", ++x is the number 2, but x + 1 is the string "11".

JavaScript objects are compared by reference, not by value. An object is equal to itself, but not to any other object. If two distinct objects have the same number of properties, with the same names and values, they are still not equal. Similarly, two arrays that have the same elements in the same order are not equal to each other.

NaN value is never equal to any other value, including itself! To check whether a value x is NaN, use x !== , or the global isNaN() function.

If both values refer to the same object, array, or function, they are equal. If they refer to different objects, they are not equal, even if both objects have identical properties.

Evaluating Expressions

JavaScript has the ability to interpret strings of JavaScript source code, evaluating them to produce a value.

eval("3+2")

Because of security issues, some web servers use the HTTP “Content-Security-Policy” header to disable eval() for an entire website.

First-Defined (??)

The first-defined operator ?? evaluates to its first defined operand: if its left operand is not null and not undefined, it returns that value.

a ?? b is equivalent to (a !== null && a !== undefined) ? a : b

?? is a useful alternative to ||. The problem with this idiomatic use is that zero, the empty string, and false are all falsy values that may be perfectly valid in some circumstances. In this code example, if maxWidth is zero, that value will be ignored. But if we change the || operator to ??, we end up with an expression where zero is a valid value.

let max = maxWidth || preferences.maxWidth || 500;

let max = maxWidth ?? preferences.maxWidth ?? 500;

delete Operator

Deleting an array element leaves a “hole” in the array and does not change the array's length. The resulting array is sparse.

void Operator

Using the void operator makes sense only if the operand has side effects.

let counter = 0;

const increment = () => void counter++;

increment()

undefined

counter

1

Statements

Expressions are evaluated to produce a value, but statements are executed to make something happen.

Expressions with side effects, such as assignments and function invocations, can stand alone as statements, and when used this way are known as expression statements.

A similar category of statements are the declaration statements that declare new variables and define new functions.

If a function does not have any side effects, there is no sense in calling it, unless it is part of a larger expression or an assignment statement.

for/of

The for/of loop works with iterable objects. Arrays, strings, sets, and maps are iterable.

Array

let data = [1, 2, 3, 4, 5, 6, 7, 8, 9], sum = 0;

for(let element of data) {

sum += element;

}

let text = "Na na na na na na na na";

let wordSet = new Set(text.split(" "));

let unique = [];

for(let word of wordSet) {

unique.push(word);

}

String

let frequency = {};

for(let letter of "mississippi") {

if (frequency[letter]) {

frequency[letter]++;

}

else {

frequency[letter] = 1;

}

}

Map

let m = new Map([[1, "one"]]);

for(let [key, value] of m) {

key // => 1

value // => "one"

}

Objects are not (by default) iterable. Attempting to use for/of on a regular object throws a TypeError at runtime.

If you want to iterate through the properties of an object, you can use the for/in loop.

Note: for/of can be used on objects with Object.entries property, but it will not pick properties from object's prototype.

for/in

for/in loop works with any object after the in.

for(let p in o) {

console.log(o[p]);

}

Note: this will enumerate array indexes, not values.

for(let i in a) console.log(i);

The for/in loop does not actually enumerate all properties of an object. It does not enumerate properties whose names are symbols. And of the properties whose names are strings, it only loops over the enumerableproperties.

with

The with statement runs a block of code as if the properties of a specified object were variables in scope for that code.

The with statement is forbidden in strict mode and should be considered deprecated in non-strict mode: avoid using it whenever possible.

document.forms[0].address.value

with(document.forms[0]) {

name.value = "";

address.value = "";

email.value = "";

}

debugger

If a debugger program is available and is running, then an implementation may (but is not required to) perform some kind of debugging action.

In practice, this statement acts like a breakpoint: execution of JavaScript code stops, and you can use the debugger to print variables' values, examine the call stack, and so on.

Note that it is not enough to have a debugger available: the debugger statement won't start the debugger for you. If you're using a web browser and have the developer tools console open, however, this statement will cause a breakpoint.

use strict

Strict mode is a restricted subset of the language that fixes important language deficiencies and provides stronger error checking and increased security.

The differences between strict mode and non-strict mode are the following:

· The with statement is not allowed in strict mode.

· In strict mode, all variables must be declared: a ReferenceError is thrown if you assign a value to an identifier that is not a declared variable, function, function parameter, catch clause parameter, or property of the global object.

· In non-strict mode, this implicitly declares a global variable by adding a new property to the global object.

· In strict mode, functions invoked as functions (rather than as methods) have a this value of undefined. (In non-strict mode, functions invoked as functions are always passed the global object as their this value.)

· A function is invoked with call() or apply() , the this value is exactly the value passed as the first argument to call() or apply(). (In non-strict mode, null and undefined values are replaced with the global object and non-object values are converted to objects.)

· In strict mode, assignments to non-writable properties and attempts to create new properties on non-extensible objects throw a TypeError. (In non-strict mode, these attempts fail silently.)

· In strict mode, code passed to eval() cannot declare variables or define functions in the caller's scope as it can in non-strict mode. Instead, variable and function definitions live in a new scope created for the eval(). This scope is discarded when the eval() returns.

· In strict mode, the Arguments object in a function holds a static copy of the values passed to the function. In non-strict mode, the Arguments object has “magical” behavior in which elements of the array and named function parameters both refer to the same value.

· In strict mode, a SyntaxError is thrown if the delete operator is followed by an unqualified identifier such as a variable, function, or function parameter. (In non-strict mode, such a delete expression does nothing and evaluates to false.)

· In strict mode, an attempt to delete a non-configurable property throws a TypeError. (In non-strict mode, the attempt fails and the delete expression evaluates to false.)

· In strict mode, it is a syntax error for an object literal to define two or more properties by the same name. (In non-strict mode, no error occurs.)

Objects

In addition to its name and value, each property has three property attributes:

· The writable attribute specifies whether the value of the property can be set.

· The enumerable attribute specifies whether the property name is returned by a for/in loop.

· The configurable attribute specifies whether the property can be deleted and whether its attributes can be altered.

Prototypes

All objects created by object literals have the same prototype object, Object.prototype.

Objects created using the new keyword and a constructor invocation use the value of the prototype property of the constructor function as their prototype.

Object created by new Object() inherits from Object.prototype, just as the object created by {} does. Similarly, the object created by new Array() uses Array.prototype as its prototype, and the object created by new Date() uses Date.prototype as its prototype.

Almost all objects have a prototype, but only a relatively small number of objects have a prototype property. It is these objects with prototype properties that define the prototypes for all the other objects.

Most built-in constructors (and most user-defined constructors) have a prototype that inherits from Object.prototype.

Date.prototype inherits properties from Object.prototype, so a Date object created by new Date() inherits properties from both Date.prototype and Object.prototype. This linked series of prototype objects is known as a prototype chain.

Creating Objects

Objects can be created with object literals, with the new keyword, and with the Object.create() function.

Literal

let empty = {};

let point = { x: 0, y: 0 };

let book = {

"main title": "JavaScript",

"sub-title": "The Definitive Guide",

for: "all audiences",

author: {

firstname: "David", .

surname: "Flanagan"

}

};

new

let o = new Object(); let a = new Array(); let d = new Date(); let r = new Map();

Object.create

let o3 = Object.create(Object.prototype);

Use Object.create to guard against accidental modifications:

let o = { x: "don't change this value" };

library.function(Object.create(o));

Note: the library function can modify the passed in object, but not the original o object

Access Object Properties with an array ([]) notation

let addr = "";

for(let i = 0; i < 4; i++) {

addr += customer[`address${i}`] + "\n";

}

Inheritance

let o = {};

o.x = 1;

let p = Object.create(o);

p.y = 2;

let q = Object.create(p);

q.z = 3;

Property x and y available on object q

q.x + q.y

How to query for property which may be undefined

surname = book && book.author && book.author.surname;

let surname = book?.author?.surname;

Deleting properties

The delete operator only deletes own properties, not inherited ones. (To delete an inherited property, you must delete it from the prototype object in which it is defined. Doing this affects every object that inherits from that prototype.)

delete does not remove properties that have a configurable attribute of false.

Certain properties of built-in objects are non-configurable, as are properties of the global object created by variable declaration and function declaration.

delete Object.prototype

false: property is non-configurable

var x = 1;

delete globalThis.x

false: can't delete this property

function f() {}

delete globalThis.f

false

globalThis.x = 1;

delete globalThis.x

true

Testing properties

To check whether an object has a property with a given name. You can do this with the in operator, with the hasOwnProperty() and propertyIsEnumerable() methods, or simply by querying the property

( != undefined).

in & query

let o = { x: 1 };

"x" in o

true

o.x !== undefined

"y" in o

false

o.y !== undefined

"toString" in o

true: o inherits a toString property

o.toString !== undefined

Advantage of using in: in can distinguish between properties that do not exist and properties that exist but have been set to undefined.

hasOwnProperty

let o = { x: 1 };

o.hasOwnProperty("x")

true

o.hasOwnProperty("y")

false

o.hasOwnProperty("toString")

false: toString is an inherited property

The propertyIsEnumerable() returns true only if the named property is an own property and its enumerable attribute is true.

let o = { x: 1 };

o.propertyIsEnumerable("x")

true

o.propertyIsEnumerable("toString")

false: not an own property

Object.prototype.propertyIsEnumerable("toString")

false: not enumerable

Enumerating properties

To guard against enumerating inherited properties with for/in, you can add an explicit check inside the loop body:

for(let p in o) {

if (!o.hasOwnProperty(p)) continue;

}

for(let p in o) {

if (typeof o[p] === "function") continue;

}

Functions you can use to get an array of property names

· Object.keys() returns an array of the names of the enumerable own properties of an object. It does not include non-enumerable properties, inherited properties, or properties whose name is a Symbol.

· Object.getOwnPropertyNames() works like Object.keys() but returns an array of the names of nonenumerable own properties as well.

· Object.getOwnPropertySymbols() returns own properties whose names are Symbols, whether or not they are enumerable.

· Reflect.ownKeys() returns all own property names, both enumerable and non-enumerable, and both string and Symbol.

Extending Objects

To copy the properties of one object to another object

let target = {x: 1}, source = {y: 2, z: 3};

for(let key of Object.keys(source)) {

target[key] = source[key];

}

One reason to assign properties from one object into another is when you have an object that defines default values for many properties and you want to copy those default properties into another object if a property by that name does not already exist in that object. Using Object.assign() naively will not do what you want:

Object.assign(o, defaults);

overwrites everything in o with defaults

Instead, use one of the following:,

o = Object.assign({}, defaults, o);

o = {...defaults, ...o};

Serializing Objects

The functions JSON.stringify() and JSON.parse() serialize and restore JavaScript objects.

let o = {x: 1, y: {z: [false, null, ""]}};

let s = JSON.stringify(o);

s == '{“x”:1,”y”:{“z”:[false,null,””]}}'

let p = JSON.parse(s);

p == {x: 1, y: {z: [false,null, “”]}}

Object methods

toString(), valueOf(), loLocaleString(), toJSON()

let s = { x: 1, y: 1 }.toString();

s == “[object Object]”

Extended Object Literal Syntax

Shorthand Properties

let x = 1, y = 2;

let o = {

x: x,

y: y

};

←>

let x = 1, y = 2; let o = { x, y };

Computer Property Names

const PROPERTY_NAME = "p1"; function computePropertyName() { return "p" + 2; }

let o = {};

o[PROPERTY_NAME] = 1;

o[computePropertyName()] = 2;

←>

let p = {

[PROPERTY_NAME]: 1,

[computePropertyName()]: 2

};

Symbols as Property Names

const extension = Symbol("my extension symbol");

let o = {

[extension]: {}

};

o[extension].x = 0;

Two Symbols created with the same string argument are still different from one another.

The point of Symbols is not security, but to define a safe extension mechanism for JavaScript objects. If you get an object from third-party code that you do not control and need to add some of your own properties to that object but want to be sure that your properties will not conflict with any properties that may already exist on the object, you can safely use Symbols as your property names.

Spread Operator

You can copy the properties of an existing object into a new object using the “spread operator” … inside an object literal:

let position = { x: 0, y: 0 }; let dimensions = { width: 100, height: 75 }; let rect = { ...position, ...dimensions }; rect.x + rect.y + rect.width + rect.height

Shorthand Methods

let square = {

area: function() {

return this.side * this.side; },

side: 10

};

←>

let square = {

area() {

return this.side * this.side; },

side: 10

};

When you write a method using this shorthand syntax, the property name can take any of the forms that are legal in an object literal: in addition to a regular JavaScript identifier like the name area above, you can also use string literals and computed property names, which can include Symbol property names:

const METHOD_NAME = "m";

const symbol = Symbol();

let weirdMethods = {

"method With Spaces"(x) { return x + 1; },

[METHOD_NAME](x) { return x + 2; },

[symbol](x) { return x + 3; }

};

weirdMethods["method With Spaces"](1)

2

weirdMethods[METHOD_NAME](1)

3

weirdMethods[symbol](1)

4

Property Getters and Setters

let o = {

dataProp: value,

get accessorProp() { return this.dataProp; },

set accessorProp(value) { this.dataProp = value; }

};

Arrays

Creating Arrays

· Array literals

· The … spread operator on an iterable object

· The Array() constructor

· The Array.of() and Array.from() factory methods

Array literals

let empty = [];

let primes = [2, 3, 5, 7, 11];

let misc = [ 1.1, true, "a", ];

let b = [[1, {x: 1, y: 2}], [2, {x: 3, y: 4}]];

If an array literal contains multiple commas in a row, with no value between, the array is sparse

let count = [1,,3];

let undefs = [,,];

Array literal syntax allows an optional trailing comma, so [,,] has a length of 2, not 3.

The Spread Operator

let a = [1, 2, 3];

let b = [0, ...a, 4];

[0, 1, 2, 3, 4]

create a copy of an array — modifying the copy does not change the original

let original = [1,2,3]; let copy = [...original];

let digits = [..."0123456789ABCDEF"];

[“0”,”1",”2",”3",”4",”5",”6",”7",”8",”9",”A”,”B”,”C”,”D”,”E”,”F”]

let letters = [..."hello world"];

[“h”,”e”,”l”,”l””o”,””,”w”,”o””r”,”l”,”d”]

[...new Set(letters)]

[“h”,”e”,”l”,”o”,””,”w”,”r”,”d”]

Array.of()

When the Array() constructor function is invoked with one numeric argument, it uses that argument as an array length. But when invoked with more than one numeric argument, it treats those arguments as elements for the array to be created. This means that the Array() constructor cannot be used to create an array with a single numeric element.

Array.of()

[]

Array.of(10)

[10]

Array.of(1,2,3)

[1, 2, 3]

Array.from()

It is also a simple way to make a copy of an array:

let copy = Array.from(original);

Array.from() is also important because it defines a way to make a true-array copy of an array-like object. Array-like objects are non-array objects that have a numeric length property and have values stored with properties whose names happen to be integers.

let truearray = Array.from(arraylike);

Array.from() also accepts an optional second argument. If you pass a function as the second argument, then as the new array is being built, each element from the source object will be passed to the function you specify, and the return value of the function will be stored in the array instead of the original value.

Reading and Writing Array Elements

What is special about arrays is that when you use property names that are non-negative integers , the array automatically maintains the value of the length property for you.

JavaScript converts the numeric array index you specify to a string — the index 1 becomes the string “1”, then uses that string as a property name.

It is helpful to clearly distinguish an array index from an object property name. All indexes are property names, but only property names that are integers between 0 and 231 are indexes. All arrays are objects, and you can create properties of any name on them. If you use properties that are array indexes, however, arrays have the special behavior of updating their length property as needed.

Note that you can index an array using numbers that are negative or that are not integers. When you do this, the number is converted to a string, and that string is used as the property name. Since the name is not a non-negative integer, it is treated as a regular object property, not an array index.

a[-1.23] = true;

This creates a property named “-1.23”

a["1000"] = 0;

This the 1001st element of the array

a[1.000] = 1;

Array index 1. Same as a[1] = 1;

The fact that array indexes are simply a special type of object property name means that JavaScript arrays have no notion of an “out of bounds” error. When you try to query a nonexistent property of any object, you don't get an error; you simply get undefined.

Sparse Arrays

Sparse arrays can be created with the Array() constructor or simply by assigning to an array index larger than the current array length.

a[1000] = 0;

Assignment adds one element but sets length to 1001.

you can also make an array sparse with the delete operator.

Note that when you omit a value in an array literal (using repeated commas as in [1,,3]), the resulting array is sparse, and the omitted elements simply do not exist

Array Length

if you set the length property to a nonnegative integer n smaller than its current value, any array elements whose index is greater than or equal to n are deleted from the array.

a = [1,2,3,4,5];

a.length = 3;

a is now [1,2,3].

a.length = 0;

Delete all elements. a is [].

a.length = 5;

Length is 5, but no elements, like new Array(5)

You can also set the length property of an array to a value larger than its current value. Doing this does not actually add any new elements to the array; it simply creates a sparse area at the end of the array.

Adding and Deleting Array Elements

let a = [];

a[0] = "zero";

a[1] = "one";

add elements to it.

You can also use the push() method to add one or more values to the end of an array.

You can use the unshift() method to insert a value at the beginning of an array, shifting the existing array elements to higher indexes.

The pop() method is the opposite of push(): it removes the last element of the array and returns it, reducing the length of an array by 1.

Similarly, the shift() method removes and returns the first element of the array, reducing the length by 1 and shifting all elements down to an index one lower than their current index.

You can delete array elements with the delete operator

let a = [1,2,3];

delete a[2];

a now has no element at index 2

2 in a

false

a.length

3: delete does not affect array length

Iterating Arrays

The easiest way to loop through each of the elements of an array (or any iterable object) is with the for/ofloop

let letters = [..."Hello world"];

let string = "";

for(let letter of letters) {

string += letter;

}

It has no special behavior for sparse arrays and simply returns undefined for any array elements that do not exist.

If you want to use a for/of loop for an array and need to know the index of each array element, use the entries() method of the array

let letters = [..."Hello world"];

let everyother = "";

for(let [index, letter] of letters.entries()) {

if (index % 2 === 0) everyother += letter;

}

Another good way to iterate arrays is with forEach(). This is not a new form of the for loop, but an array method that offers a functional approach to array iteration.

let letters = [..."Hello world"];

let uppercase = "";

letters.forEach(letter => {

uppercase += letter.toUpperCase();

});

You can also loop through the elements of an array with a for loop.

for(let i = 0, len = letters.length; i < len; i++) {

// loop body

}

Multidimensional Arrays

Create a multidimensional array

let table = new Array(10);

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

table[i] = new Array(10);

}

for(let row = 0; row < table.length; row++) {

for(let col = 0; col < table[row].length; col++) {

table[row][col] = row * col;

}

}

Array Methods

Array Iterator Methods

First, all of these methods accept a function as their first argument and invoke that function once for each element (or some elements) of the array. If the array is sparse, the function you pass is not invoked for nonexistent elements. In most cases, the function you supply is invoked with three arguments: the value of the array element, the index of the array element, and the array itself.

FOREACH()

let data = [1,2,3,4,5], sum = 0;

data.forEach(value => { sum += value; });

data.forEach(function(v, i, a) {

a[i] = v + 1;

});

15

[2,3,4,5,6]

MAP()

let a = [1, 2, 3]; a.map(x => x*x)

[1, 4, 9]

FILTER()

let a = [5, 4, 3, 2, 1]; a.filter(x => x < 3) a.filter((x,i) => i % 2 === 0)

[2, 1];

[5, 3, 1];

FIND()

FINDINDEX()

let a = [1,2,3,4,5];

a.findIndex(x => x === 3)

a.find(x => x % 5 === 0)

a.find(x => x % 7 === 0)

2

5

undefined

EVERY()

SOME()

let a = [1,2,3,4,5];

a.every(x => x < 10)

a.some(x => x % 2 === 0)

a.some(isNaN)

true

true

false

REDUCE()

ReduceRight()

let a = [1,2,3,4,5];

a.reduce((x,y) => x+y, 0)

a.reduce((x,y) => x*y, 1)

a.reduce((x,y) => (x > y) ? x : y)

15

120

5

Note that map() returns a new array: it does not modify the array it is invoked on. If that array is sparse, your function will not be called for the missing elements, but the returned array will be sparse in the same way as the original array: it will have the same length and the same missing elements.

To close the gaps in a sparse array, you can do this:

let dense = sparse.filter(() => true);

And to close gaps and remove undefined and null elements, you can use filter, like this:

a = a.filter(x => x !== undefined && x !== null);

Unlike filter(), however, find() and findIndex() stop iterating the first time the predicate finds an element. When that happens, find() returns the matching element, and findIndex() returns the index of the matching element. If no matching element is found, find() returns undefined and findIndex()returns -1.

When you invoke reduce() with no initial value, it uses the first element of the array as the initial value.

reduceRight() works just like reduce(), except that it processes the array from highest index to lowest (right-to-left), rather than from lowest to highest. You might want to do this if the reduction operation has right-to-left associativity

Flattening arrays with flat() and flatMap()

[1, [2, 3]].flat()

[1, 2, 3]

[1, [2, [3]]].flat()

[1, 2, [3]]

let a = [1, [2, [3, [4]]]];

a.flat(1)

a.flat(2)

a.flat(3)

a.flat(4)

[1, 2, [3, [4]]]

[1, 2, 3, [4]]

[1, 2, 3, 4]

[1, 2, 3, 4]

let phrases = ["hello world", "the definitive guide"]; let words = phrases.flatMap(phrase => phrase.split(" "));

[“hello”, “world”, “the”, “definitive”, “guide”];

Calling a.flatMap(f) is the same as (but more efficient than) a.map(f).flat():

Adding arrays with concat()

let a = [1,2,3];

a.concat(4, 5)

[1,2,3,4,5]

a.concat([4,5],[6,7])

[1,2,3,4,5,6,7]

Stacks and Queues with push(), pop(), shift(), and unshift()

The push() and pop() methods allow you to work with arrays as if they were stacks. The push() method appends one or more new elements to the end of an array and returns the new length of the array.

The unshift() and shift() methods behave much like push() and pop(), except that they insert and remove elements from the beginning of an array rather than from the end.

You can implement a queue data structure by using push() to add elements at the end of an array and shift() to remove them from the start of the array. Note differences in unshift with single and multiple values.

let a = [];

a.unshift(1)

[1]

a.unshift(2)

[2, 1]

a = [];

a.unshift(1,2)

[1, 2]

Subarrays with slice(), splice(), fill(), and copyWithin()

SLICE()

let a = [1,2,3,4,5];

a.slice(0,3);

a.slice(3);

a.slice(1,-1);

a.slice(-3,-2);

[1,2,3]

[4,5]

[2,3,4]

[3]

SPLICE

let a = [1,2,3,4,5,6,7,8];

a.splice(4)

a.splice(1,2)

a.splice(1,1)

let a = [1,2,3,4,5];

a.splice(2,0,"a","b")

a.splice(2,2,[1,2],3)

[5,6,7,8]; a is now [1,2,3,4]

[2,3]; a is now [1,4]

[4]; a is now [1]

[]; a is now [1,2,”a”,”b”,3,4,5]

[“a”,”b”]; a is now [1,2,[1,2],3,3,4,5]

FILL()

let a = new Array(5);

a.fill(0)

a.fill(9, 1)

a.fill(8, 2, -1)

[0,0,0,0,0]

[0,9,9,9,9]

[0,9,8,8,9]

COPYWITHIN()

let a = [1,2,3,4,5];

a.copyWithin(1)

a.copyWithin(2, 3, 5)

a.copyWithin(0, -2)

[1,1,2,3,4]

[1,1,3,4,4]

[4,4,3,4,4]

splice() is a general-purpose method for inserting or removing elements from an array. splice() can delete elements from an array, insert new elements into an array, or perform both operations at the same time.

The first argument to splice() specifies the array position at which the insertion and/or deletion is to begin. The second argument specifies the number of elements that should be deleted from (spliced out of) the array.

Unlike concat(), splice() inserts arrays themselves, not the elements of those arrays.

copyWithin() copies a slice of an array to a new position within the array. It modifies the array in place and returns the modified array, but it will not change the length of the array.

Array Searching and Sorting Methods

INDEXOF() LASTINDEXOF()

let a = [0,1,2,1,0];

a.indexOf(1)

a.lastIndexOf(1)

a.indexOf(3)

1

3

-1

SORT()

let a = [33, 4, 1111, 222];

a.sort();

a.sort((a,b) => a - b);

Case-insensitive sort

let a = ["ant", "Bug", "cat", "Dog"];

a.sort(); // a == ["Bug","Dog","ant","cat"];

a.sort(function(s,t) {

let a = s.toLowerCase();

let b = t.toLowerCase();

if (a < b) return -1;

if (a > b) return 1;

return 0;

});

[1111, 222, 33, 4];

[4, 33, 222, 1111]

REVERSE()

let a = [1,2,3]; a.reverse();

[3,2,1]

indexOf() and lastIndexOf() compare their argument to the array elements using the equivalent of the === operator. If your array contains objects instead of primitive values, these methods check to see if two references both refer to exactly the same object. If you want to actually look at the content of an object, try using the find() method with your own custom predicate function instead.

indexOf() and lastIndexOf() take an optional second argument that specifies the array index at which to begin the search. Negative values are allowed for the second argument and are treated as an offset from the end of the array.

indexOf() will not detect the NaN value in an array, but includes() will

When sort() is called with no arguments, it sorts the array elements in alphabetical order. To sort an array into some order other than alphabetical, you must pass a comparison function as an argument to sort().

Array to String Conversions

The join() method converts all the elements of an array to strings and concatenates them, returning the resulting string.

let a = [1, 2, 3];

a.join()

a.join(" ")

a.join("")

“1,2,3”

“1 2 3”

“123”

let b = new Array(10);

b.join("-")

“ — — — — -”

Arrays, like all JavaScript objects, have a toString() method. For an array, this method works just like the join() method with no arguments:

[1,2,3].toString()

“1,2,3”

["a", "b", "c"].toString()

“a,b,c”

[1, [2,"c"]].toString()

“1,2,c”

Static Array Functions

Array.isArray([])

true

Array.isArray({})

false

Array-Like Objects

It is often perfectly reasonable to treat any object with a numeric length property and corresponding non-negative integer properties as a kind of array.

let a = {};

let i = 0;

while(i < 10) {

a[i] = i * i;

i++;

}

a.length = i;

// Now iterate through it as if it were a real array

let total = 0;

for(let j = 0; j < a.length; j++) {

total += a[j];

}

Since array-like objects do not inherit from Array.prototype, you cannot invoke array methods on them directly. You can invoke them indirectly using the Function.call method.

let a = {"0": "a", "1": "b", "2": "c", length: 3};

// An array-like object

Array.prototype.join.call(a, "+")

“a+b+c”

Array.prototype.join.call("JavaScript", " ")

“J a v a S c r i p t”

Array.prototype.map.call(a, x => x.toUpperCase())

[“A”,”B”,”C”]

Array.from(a)

[“a”,”b”,”c”]

Strings as Arrays

let s = "test";

s.charAt(0)

t

s[1]

e

Functions

In addition to the arguments, each invocation has another value — the invocation context — that is the value of the this keyword.

Function Declarations

function printprops(o) {

for(let p in o) {

console.log(`${p}: ${o[p]}\n`);

}

}

Function declaration statements are “hoisted” to the top of the enclosing script, function, or block so that functions defined in this way may be invoked from code that appears before the definition.

Function Expressions

const square = function(x) { return x*x; };

const f = function fact(x) {

if (x <= 1) return 1;

return x * fact(x-1);

}

Function expressions can include names, which is useful for recursion

[3,2,1].sort(function(a,b) { return a - b; });

Function expressions can also be used as arguments to other functions

let tensquared = (function(x) {return x*x;}(10));

Function expressions are sometimes defined and immediately invoked

Arrow Functions

const sum = (x, y) => { return x + y; };

const sum = (x, y) => x + y;

no need for return

const polynomial = x => x*x + 2*x + 3;

omit parens with single parameter

const constantFunc = () => 42;

usage for no params

If the body of your arrow function is a single return statement but the expression to be returned is an object literal, then you have to put the object literal inside parentheses to avoid syntactic ambiguity between the curly braces of a function body and the curly braces of an object literal

const f = x => { return { value: x }; };

good

const g = x => ({ value: x });

good

const h = x => { value: x };

returns nothing

const i = x => { v: x, w: x };

syntax error

Arrow functions differ from functions defined in other ways in one critical way: they inherit the value of the this keyword from the environment in which they are defined rather than defining their own invocation context as functions defined in other ways do.

Nested Functions

function hypotenuse(a, b) {

function square(x) { return x*x; }

return Math.sqrt(square(a) + square(b));

}

Invoking Functions

For function invocation in non-strict mode, the invocation context (the this value) is the global object. In strict mode, however, the invocation context is undefined.

const strict = (function() { return !this; }())

Determine if we're in strict mode

Constructor Invocation

A constructor invocation creates a new, empty object that inherits from the object specified by the prototypeproperty of the constructor.

Indirect invocation

JavaScript functions are objects, and like all JavaScript objects, they have methods. Two of these methods, call() and apply(), invoke the function indirectly. Both methods allow you to explicitly specify the this value for the invocation, which means you can invoke any function as a method of any object, even if it is not actually a method of that object.

Function Arguments and Parameters

Optional Parameters and Defaults

When a function is invoked with fewer arguments than declared parameters, the additional parameters are set to their default value, which is normally undefined.

function getPropertyNames(o, a) {

a = a || [];

for(let property in o) a.push(property);

return a;

}

function getPropertyNames(o, a = []) {

for(let property in o) a.push(property);

return a;

}

One interesting case is that, for functions with multiple parameters, you can use the value of a previous parameter to define the default value of the parameters that follow it

const rectangle = (width, height = width*2) => ({width, height});

Rest Parameters and Variable-Length Argument Lists

Rest parameters enable us to write functions that can be invoked with arbitrarily more arguments than parameters.

function max(first=-Infinity, ...rest) {

let maxValue = first;

for(let n of rest) {

if (n > maxValue) {

maxValue = n;

}

}

return maxValue;

}

max(1, 10, 100, 2, 3, 1000, 4, 5, 6)

1000

within the body of a function, the value of a rest parameter will always be an array. The array may be empty, but a rest parameter will never be undefined.

This type of function is called variadic functions, variable arity functions, or vararg functions.

The Arguments Object

Within the body of any function, the identifier arguments refers to the Arguments object for that invocation.

function max(x) {

let maxValue = -Infinity;

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

if (arguments[i] > maxValue)

maxValue = arguments[i];

}

return maxValue;

}

max(1, 10, 100, 2, 3, 1000, 4, 5, 6)

1000

you should avoid using it in any new code you write.

The Spread Operator for Function Calls

let numbers = [5, 2, 10, -1, 9, 100, 1];

Math.min(...numbers)

-1

function timed(f) {

return function(...args) {

console.log(`Entering function ${f.name}`);

let startTime = Date.now();

try {

return f(...args);

}

finally {

console.log(`Exiting ${f.name} after ${Date.now() - startTime}ms`);

}

};

}

// Compute the sum of the numbers between 1 and n by brute force

function benchmark(n) {

let sum = 0;

for(let i = 1; i <= n; i++) sum += i;

return sum;

}

// Now invoke the timed version of that test function

timed(benchmark)(1000000)

Destructuring Function Arguments into Parameters

function vectorAdd(v1, v2) {

return [v1[0] + v2[0], v1[1] + v2[1]];

}

vectorAdd([1,2], [3,4])

←>

function vectorAdd([x1,y1], [x2,y2]) {

return [x1 + x2, y1 + y2];

}

vectorAdd([1,2], [3,4])

function vectorMultiply({x, y}, scalar) {

return { x: x*scalar, y: y*scalar };

}

vectorMultiply({x: 1, y: 2}, 2)

←>

function vectorMultiply({x,y}, scalar) {

return { x: x*scalar, y: y*scalar};

}

vectorMultiply({x: 1, y: 2}, 2)

Argument Types

Adding code to check the types of arguments

function sum(a) {

let total = 0;

for(let element of a) {

if (typeof element !== "number") {

throw new TypeError("sum(): elements must be numbers");

}

total += element;

}

return total;

}

sum([1,2,3])

6

sum(1, 2, 3);

TypeError: 1 is not iterable

sum([1,2,"3"]);

TypeError: element 2 is not a number

Functions as Values

function square(x) { return x * x; }

let s = square;

square(4)

16

s(4)

16

Functions can also be assigned to object properties rather than variables.

let o = {square: function(x) { return x*x; }};

let y = o.square(16);

256

Functions don't even require names at all, as when they're assigned to array elements:

let a = [x => x*x, 20];

a[0](a[1])

400

a[0] accesses first element of the array, which is "x => x*x", (a[1]) passes parameter, which is 20.

Examples of using functions as data

function add(x,y) { return x + y; }

function subtract(x,y) { return x - y; }

function multiply(x,y) { return x * y; }

function divide(x,y) { return x / y; }

function operate(operator, operand1, operand2) {

return operator(operand1, operand2);

}

let i = operate(add, operate(add, 2, 3), operate(multiply, 4,5));

(2+3) + (4*5):

or:

const operators = {

add: (x,y) => x+y,

subtract: (x,y) => x-y,

multiply: (x,y) => x*y,

divide: (x,y) => x/y,

pow: Math.pow

};

function operate2(operation, operand1, operand2) {

if (typeof operators[operation] === "function") {

return operators[operation](operand1, operand2);

}

else throw "unknown operator";

}

operate2("add", "hello", operate2("add", " ", "world"))

// “hello world”

operate2("pow", 10, 2)

100

Defining Your Own Function Properties

When a function needs a “static” variable whose value persists across invocations, it is often convenient to use a property of the function itself.

For example, suppose you want to write a function that returns a unique integer whenever it is invoked. The function must never return the same value twice. In order to manage this, the function needs to keep track of the values it has already returned, and this information must persist across function invocations.

uniqueInteger.counter = 0;

function uniqueInteger() {

return uniqueInteger.counter++;

}

uniqueInteger()

0

uniqueInteger()

1

Compute factorials and cache results as properties of the function itself.

function factorial(n) {

if (Number.isInteger(n) && n > 0) {

if (!(n in factorial)) {

factorial[n] = n * factorial(n-1);

}

return factorial[n];

}

else {

return NaN;

}

}

factorial[1] = 1;

Initialize the cache to hold this base case.

factorial(6)

720

factorial[5]

120; the call above caches this value

Functions as Namespaces

Variables declared within a function are not visible outside of the function. For this reason, it is sometimes useful to define a function simply to act as a temporary namespace in which you can define variables without cluttering the global namespace.

Variables that would have been global become local to the function. Following code defines only a single global variable: the function name chunkNamespace.

function chunkNamespace() {

// Chunk of code goes here

// Any variables defined in the chunk are local to this function

// instead of cluttering up the global namespace.

}

chunkNamespace();

If defining even a single property is too much, you can define and invoke an anonymous function in a single expression — IIEF (immediately invoked function expression)

(function() {

// chunkNamespace() function rewritten as an unnamed expression.

// Chunk of code goes here

}());

Closures

JavaScript uses lexical scoping. This means that functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked.

In order to implement lexical scoping, the internal state of a JavaScript function object must include not only the code of the function but also a reference to the scope in which the function definition appears.

This combination of a function object and a scope (a set of variable bindings) in which the function's variables are resolved is called a closure.

Closures become interesting when they are invoked from a different scope than the one they were defined in. This happens most commonly when a nested function object is returned from the function within which it was defined.

let scope = "global scope";

function checkscope() {

let scope = "local scope";

function f() { return scope; }

return f();

}

checkscope()

“local scope”

let scope = "global scope";

function checkscope() {

let scope = "local scope";

function f() { return scope; }

return f;

}

let s = checkscope()();

“local scope”

Closures capture the local variables of a single function invocation and can use those variables as private state.

let uniqueInteger = (function() {

let counter = 0;

return function() { return counter++; };

}());

uniqueInteger()

0

uniqueInteger()

1

it is the return value of the function that is being assigned to uniqueInteger.

Private variables like counter need not be exclusive to a single closure: it is perfectly possible for two or more nested functions to be defined within the same outer function and share the same scope.

function counter() {

let n = 0;

return {

count: function() { return n++; },

reset: function() { n = 0; }

};

}

let c = counter(), d = counter();

c.count()

0

d.count()

0

c.reset();

c.count()

0

d.count()

1

You can combine this closure technique with property getters and setters

function counter(n) {

return {

get count() { return n++; },

set count(m) {

if (m > n) n = m;

else throw Error("count can only be set to a larger value")

}

};

}

let c = counter(1000);

c.count

1000

c.count

1001

c.count = 2000;

c.count

2000

c.count = 2000;

Error: count can only be set to a larger value

Define a private variable and two nested functions to get and set the value of that variable.

function addPrivateProperty(o, name, predicate) {

let value;

o[`get${name}`] = function() { return value; };

o[`set${name}`] = function(v) {

if (predicate && !predicate(v)) {

throw new TypeError(`set${name}: invalid value ${v}`);

}

else {

value = v;

}

};

}

let o = {};

addPrivateProperty(o, "Name", x => typeof x === "string");

o.setName("Frank");

o.getName()

“Frank”

o.setName(0);

TypeError: try to set a value ofthe wrong type

Function Properties, Methods, and Constructor

Since functions are objects, they can have properties and methods, just like any other object.

The length Property

The read-only length property of a function specifies the arity of the function — the number of parameters it declares in its parameter list, which is usually the number of arguments that the function expects.

The name Property

This property is primarily useful when writing debugging or error messages.

The prototype Property

When a function is used as a constructor, the newly created object inherits properties from the prototype object.

The call() and apply() Methods

call() and apply() allow you to indirectly invoke a function as if it were a method of some other object. The first argument to both call() and apply() is the object on which the function is to be invoked; this argument is the invocation context and becomes the value of the this keyword within the body of the function.

To invoke the function f() as a method of the object o (passing no arguments),

f.call(o);

f.apply(o);

To pass two numbers to the function f() and invoke it as if it were a method of the object o,

f.call(o, 1, 2);

The apply() method is like the call() method, except that the arguments to be passed to the function are specified as an array:

f.apply(o, [1,2]);

The trace() function defined uses the apply() method instead of a spread operator, and by doing that, it is able to invoke the wrapped method with the same arguments and the same this value as the wrapper method

function trace(o, m) {

let original = o[m];

o[m] = function(...args) {

console.log(new Date(), "Entering:", m);

let result = original.apply(this, args);

console.log(new Date(), "Exiting:", m);

return result;

};

}

The bind() Method

The primary purpose of bind() is to bind a function to an object.

function f(y) { return this.x + y; }

let o = { x: 1 };

let g = f.bind(o);

g(2)

3

let p = { x: 10, g };

p.g(2)

3 // g is still bound to o, not p.

The most common use case for calling bind() is to make non-arrow functions behave like arrow functions.

Partial application is a common technique in functional programming and is sometimes called currying.

let sum = (x,y) => x + y;

let succ = sum.bind(null, 1);

succ(2)

3

The toString() Method

Most (but not all) implementations of this toString() method return the complete source code for the function

The Function() Constructor

The Function() constructor is best thought of as a globally scoped version of eval() that defines new variables and functions in its own private scope. You will probably never need to use this constructor in your code.

Higher-Order Functions

A higher-order function is a function that operates on functions, taking one or more functions as arguments and returning a new function.

function not(f) {

return function(...args) {

let result = f.apply(this, args);

return !result;

};

}

const even = x => x % 2 === 0;

A function to determine if a number is even

const odd = not(even);

[1,1,3,5,5].every(odd)

true

Returns a new function that maps one array to another

const map = function(a, ...args) { return a.map(...args); };

function mapper(f) {

return a => map(a, f);

}

const increment = x => x + 1;

const incrementAll = mapper(increment);

incrementAll([1,2,3]

[2,3,4]

Example that takes two functions, f and g, and returns a new function that computes f(g()):

function compose(f, g) {

return function(...args) {

return f.call(this, g.apply(this, args));

};

}

const sum = (x,y) => x+y;

const square = x => x*x;

compose(square, sum)(2,3)

25

Memoization

We defined a factorial function that cached its previously computed results. In functional programming, this kind of caching is called memoization.

Classes

JavaScript's classes and prototype-based inheritance mechanism are substantially different from the classes and class-based inheritance mechanism of Java.

Classes and Prototypes

If we define a prototype object and then use Object.create() to create objects that inherit from it, we have defined a JavaScript class.

Factory function that returns a new range object:

function range(from, to) {

let r = Object.create(range.methods);

r.from = from;

r.to = to;

return r;

}

range.methods = {

includes(x) { return this.from <= x && x <= this.to; },

*[Symbol.iterator]() {

for(let x = Math.ceil(this.from); x <= this.to; x++)

yield x;

},

toString() { return "(" + this.from + "..." + this.to +")"; }

};

let r = range(1,3);

r.includes(2)

true

r.toString()

“(1…3)”

[...r]

[1, 2, 3]

Classes and Constructors

A constructor is a function designed for the initialization of newly created objects.

The critical feature of constructor invocations is that the prototype property of the constructor is used as the prototype of the new object.

While almost all objects have a prototype, only a few objects have a prototype property. It is function objects that have a prototype property.

This means that all objects created with the same constructor function inherit from the same object and are therefore members of the same class.

A Range class using a constructor

function Range(from, to) {

this.from = from;

this.to = to;

}

Range.prototype = {

includes: function(x) { return this.from <= x && x <= this.to; },

[Symbol.iterator]: function*() {

for(let x = Math.ceil(this.from); x <= this.to; x++)

yield x;

},

toString: function() { return "(" + this.from + "..." + this.to + ")"; }

};

let r = new Range(1,3);

r.includes(2)

true

r.toString()

“(1…3)”

[...r]

[1, 2, 3]

Because the Range() constructor is invoked with new, it does not have to call Object.create() or take any action to create a new object.

In the first example, the prototype was range.methods. This was a convenient and descriptive name, but arbitrary. In the second example, the prototype is Range.prototype, and this name is mandatory.

An invocation of the Range() constructor automatically uses Range.prototype as the prototype of the new Range object.

Constructors, Class Identity, and instanceof

Two objects are instances of the same class if and only if they inherit from the same prototype object.

The instanceof operator is not checking whether r was actually initialized by the Range constructor. Instead, it is checking whether r inherits from Range.prototype.

function Strange() {}

Strange.prototype = Range.prototype;

new Strange() instanceof Range

true

If you want to test the prototype chain of an object for a specific prototype and do not want to use the constructor function as an intermediary, you can use the isPrototypeOf() method

range.methods.isPrototypeOf(r);

The constructor Property

Every regular JavaScript function automatically has a prototype property. The value of this property is an object that has a single, non-enumerable constructor property.

The value of the constructor property is the function object

let F = function() {};

let p = F.prototype;

let c = p.constructor;

c === F

true

let o = new F();

o.constructor === F

true

Instances of the Range class, as defined, do not have a constructor property. We can remedy this problem by explicitly adding a constructor to the prototype:

Range.prototype = {

constructor: Range

};

Another common technique that you are likely to see in older JavaScript code is to use the predefined prototype object with its constructor property and add methods to it one at a time with code like this:

Range.prototype.includes = function(x) {

return this.from <= x && x <= this.to;

};

Range.prototype.toString = function() {

return "(" + this.from + "..." + this.to + ")";

};

Classes with the class Keyword

class Range {

constructor(from, to) {

this.from = from;

this.to = to;

}

includes(x) { return this.from <= x && x <= this.to; }

*[Symbol.iterator]() {

for(let x = Math.ceil(this.from); x <= this.to; x++)

yield x;

}

toString() { return `(${this.from}...${this.to})`; }

}

let r = new Range(1,3);

r.includes(2)

true

r.toString()

(1…3)

[...r]

[1, 2, 3]

Although class bodies are superficially similar to object literals, they are not the same thing. In particular, they do not support the definition of properties with name/value pairs.

If your class does not need to do any initialization, you can omit the constructor keyword and its body, and an empty constructor function will be implicitly created for you.

If you want to define a class that subclasses — or inherits from — another class, you can use the extends keyword with the class keyword:

class Span extends Range {

constructor(start, length) {

if (length >= 0) {

super(start, start + length);

}

else {

super(start + length, start);

}

}

}

class declarations have both statement and expression forms

let Square = class { constructor(x) { this.area = x * x; } };

new Square(3).area

9

Static methods

You can define a static method within a class body by prefixing the method declaration with the static keyword. Static methods are defined as properties of the constructor function rather than properties of the prototype object.

static parse(s) {

let matches = s.match(/^\((\d+)\.\.\.(\d+)\)$/);

if (!matches) {

throw new TypeError(`Cannot parse Range from "${s}".`)

}

return new Range(parseInt(matches[1]),

parseInt(matches[2]));

}

The method defined by this code is Range.parse(), not Range.prototype.parse(), and you must invoke it through the constructor, not through an instance:

let r = Range.parse('(1...10)');

Getters, Setters, and other Method Forms

Within a class body, you can define getter and setter methods just as you can in object literals. The only difference is that in class bodies, you don't put a comma after the getter or setter.

Public, Private, and Static Fields

The ES6 standard only allows the creation of methods (including getters, setters, and generators) and static methods; it does not include syntax for defining fields.

If you want to define a field on a class instance, you must do that in the constructor function or in one of the methods. And if you want to define a static field for a class, you must do that outside the class body, after the class has been defined.

Standardization is underway, however, for extended class syntax that allows the definition of instance and static fields, in both public and private forms.

class Buffer {

constructor() {

this.size = 0;

this.capacity = 4096;

this.buffer = new Uint8Array(this.capacity);

}

}

←>

class Buffer {

size = 0;

capacity = 4096;

buffer = new Uint8Array(this.capacity);

}

The same proposal that seeks to standardize these instance fields also defines private (with the # prefix) instance fields.

class Buffer {

#size = 0;

get size() { return this.#size; }

}

A related proposal seeks to standardize the use of the static keyword for fields.

static integerRangePattern = /^\((\d+)\.\.\.(\d+)\)$/;

static parse(s) {

let matches = s.match(Range.integerRangePattern);

if (!matches) {

throw new TypeError(`Cannot parse Range from "${s}".`)

}

return new Range(parseInt(matches[1]), matches[2]);

}

Adding Methods to Existing Classes

We can augment JavaScript classes simply by adding new methods to their prototype objects.

if (!String.prototype.startsWith) {

String.prototype.startsWith = function(s) {

return this.indexOf(s) === 0;

};

}

Number.prototype.times = function(f, context) {

let n = this.valueOf();

for(let i = 0; i < n; i++) f.call(context, i);

};

Subclasses

Subclasses and Prototypes

Span subclass of the Range class. This subclass will work just like a Range, but instead of initializing it with a start and an end, we'll instead specify a start and a distance, or span.

function Span(start, span) {

if (span >= 0) {

this.from = start;

this.to = start + span;

}

else {

this.to = start;

this.from = start + span;

}

}

Ensure that the Span prototype inherits from the Range

Span.prototype = Object.create(Range.prototype);

We don't want to inherit Range.prototype.constructor, so we define our own constructor property:

Span.prototype.constructor = Span;

Span overrides the toString() method

Span.prototype.toString = function() { return `(${this.from}... +${this.to - this.from})`; };

A robust subclassing mechanism needs to allow classes to invoke the methods and constructor of their superclass, but prior to ES6, JavaScript did not have a simple way to do these things.

Subclasses with extends and super

class EZArray extends Array {

get first() { return this[0]; }

get last() { return this[this.length-1]; }

}

let a = new EZArray();

a instanceof EZArray

true

a instanceof Array

true

a.push(1,2,3,4);

a.pop()

4

a.first

1

a.last

3

Array.isArray(a)

true

EZArray.isArray(a)

true

Array.prototype.isPrototypeOf(EZArray.prototype

true

Array.isPrototypeOf(EZArray)

true

Example demonstrates the use of the super keyword to invoke the constructor and methods of the superclass

class TypedMap extends Map {

constructor(keyType, valueType, entries) {

if (entries) {

for(let [k, v] of entries) {

if (typeof k !== keyType || typeof v !== valueType) {

throw new TypeError(`Wrong type for entry [${k}, ${v}]`);

}

}

}

super(entries);

this.keyType = keyType;

this.valueType = valueType;

}

set(key, value) {

if (this.keyType && typeof key !== this.keyType) {

throw new TypeError(`${key} is not of type${this.keyType}`);

}

if (this.valueType && typeof value !== this.valueType)

{

throw new TypeError(`${value} is not of type ${this.valueType}`);

}

return super.set(key, value);

}

}

You may not use the this keyword in your constructor until after you have invoked the superclass constructor with super(). This enforces a rule that superclasses get to initialize themselves before subclasses do.

Once private fields are supported, we could change these properties to #keyType and #valueType so that they could not be altered from the outside.

Class Hierarchies and Abstract Classes

Define abstract classes — classes that do not include a complete implementation — to serve as a common superclass for a group of related subclasses.

Modules

Automating Closure-Based Modularity

Imagine a tool that takes a set of files, wraps the content of each of those files within an immediately invoked function expression, keeps track of the return value of each function, and concatenates everything into one big file.

const modules = {};

function require(moduleName) { return modules[moduleName]; }

modules["sets.js"] = (function() {

const exports = {};

exports.BitSet = class BitSet { ... };

return exports;

}());

modules["stats.js"] = (function() {

const exports = {};

const sum = (x, y) => x + y;

const square = x = > x * x;

exports.mean = function(data) { ... };

exports.stddev = function(data) { ... };

return exports;

}());

writing code like the following to make use of those modules

const stats = require("stats.js");

const BitSet = require("sets.js").BitSet;

// Now write code using those modules

let s = new BitSet(100);

s.insert(10);

s.insert(20);

s.insert(30);

let average = stats.mean([...s]);

Modules in ES6

ES6 adds import and export keywords to JavaScript and finally supports real modularity as a core language feature.

ES6 modularity is conceptually the same as Node modularity: each file is its own module, and constants, variables, functions, and classes defined within a file are private to that module unless they are explicitly exported.

ES6 Exports

To export a constant, variable, function, or class from an ES6 module, simply add the keyword export before the declaration

export const PI = Math.PI;

export function degreesToRadians(d) { return d * PI / 180; }

export class Circle {

constructor(r) { this.r = r; }

area() { return PI * this.r * this.r; }

}

or:

export { Circle, degreesToRadians, PI };

It is common to write modules that export only one value (typically a function or class), and in this case, we usually use export default instead of export

export default class BitSet {

// implementation omitted

}

ES6 Imports

import BitSet from './bitset.js';

import { mean, stddev } from "./stats.js";

When importing from a module that defines many exports, however, you can easily import everything with an import statement like this:

import * as stats from "./stats.js";

With the wildcard import shown in the previous example, the importing module would use the imported mean() and stddev() functions through the stats object, invoking them as stats.mean() and stats.stddev().

Note: not finished.

The JavaScript Standard Library

The Set Class

Sets are not ordered or indexed, and they do not allow duplicates.

let s = new Set();

let t = new Set([1, s]);

let t = new Set(s);

let unique = new Set("Mississippi");

The argument to the Set() constructor need not be an array: any iterable object (including other Set objects) is allowed.

The add() method takes a single argument; if you pass an array, it adds the array itself to the set, not the individual array elements. add() always returns the set it is invoked on, however, so if you want to add multiple values to a set, you can used chained method calls like.

it is very important to understand that set membership is based on strict equality checks, like the === operator performs.

The most important thing we do with sets is not to add and remove elements from them, but to check to see whether a specified value is a member of the set:

let oneDigitPrimes = new Set([2,3,5,7]);

oneDigitPrimes.has(2)

The Set class is iterable, which means that you can use a for/of loop to enumerate all of the elements of a set:

let sum = 0;

for(let p of oneDigitPrimes) {

sum += p; // and add them up

}

Because Set objects are iterable, you can convert them to arrays and argument lists with the … spread operator

[...oneDigitPrimes]

JavaScript Set class always remembers the order that elements were inserted in, and it always uses this order when you iterate a set: the first element inserted will be the first one iterated (assuming you haven't deleted it first), and the most recently inserted element will be the last one iterated.

Set class also implements a forEach() method

let product = 1;

oneDigitPrimes.forEach(n => { product *= n; });

The Map Class

let m = new Map();

let n = new Map([["one", 1],["two", 2]]);

let copy = new Map(n);

let o = { x: 1, y: 2};

let p = new Map(Object.entries(o));

map is a set of keys, each of which has an associated value. This is not quite the same as a set of key/value pairs.

use has() to check whether a map includes the specified key; use delete() to remove a key (and its associated value) from the map; use clear() to remove all key/value pairs from the map; and use the size property to find out how many keys a map contains.

set() method of Map can be chained.

Any JavaScript value can be used as a key or a value in a Map. This includes null, undefined, and NaN, as well as reference types like objects and arrays.

Map compares keys by identity, not by equality.

let m = new Map();

m.set({}, 1);

m.set({}, 2);

Map a different empty object to the number 2.

m.get({})

undefined:

m.set(m, undefined);

m.has(m)

true

m.get(m)

undefined

Iterate over map:

let m = new Map([["x", 1], ["y", 2]]);

[...m]

[[“x”, 1], [“y”, 2]]

for(let [key, value] of m) {...}

Map class iterates in insertion order

If you want to iterate just the keys or just the associated values of a map, use the keys() and values() methods: these return iterable objects that iterate keys and values, in insertion order. (The entries() method returns an iterable object that iterates key/value pairs, but this is exactly the same as iterating the map directly.)

[...m.keys()]

[...m.values()]

[...m.entries()]

Map objects can also be iterated using the forEach()

m.forEach((value, key) => {...}

Note that the value parameter comes before the key parameter.

WeakMap and WeakSet

The WeakMap class is a variant (but not an actual subclass) of the Map class that does not prevent its key values from being garbage collected.

WeakMap keys must be objects or arrays; primitive values are not subject to garbage collection and cannot be used as keys.

WeakMap implements only the get(), set(), has(), and delete() methods. In particular, WeakMap is not iterable and does not define keys(), values(), or forEach(). If WeakMap was iterable, then its keys would be reachable and it wouldn't be weak.

Similarly, WeakMap does not implement the size property because the size of a WeakMap could change at any time as objects are garbage collected

Typed Arrays and Binary Data

They differ from regular arrays in some very important ways

· The elements of a typed array are all numbers. Unlike regular JavaScript numbers, however, typed arrays allow you to specify the type (signed and unsigned integers and IEEE-754 floating point) and size (8 bits to 64 bits) of the numbers to be stored in the array.

· You must specify the length of a typed array when you create it, and that length can never change.

· The elements of a typed array are always initialized to 0 when the array is created.

Int8Array()

Uint8Array()

Uint8ClampedArray()

Int16Array()

Uint32Array()

Uint16Array()

Int32Array()

BigInt64Array()

BigUint64Array()

Float32Array()

let bytes = new Uint8Array(1024);

let matrix = new Float64Array(9);

let sudoku = new Int8Array(81);

Initialize with values

let white = Uint8ClampedArray.of(255, 255, 255, 0);

let ints = Uint32Array.from(white);

one more way to create typed arrays that involves the ArrayBuffer type

let buffer = new ArrayBuffer(1024*1024);

buffer.byteLength

1024*1024

Typed arrays are not true arrays, but they re-implement most array methods, so you can use them pretty much just like you'd use regular arrays:

let ints = new Int16Array(10);

10 short integers

ints.fill(3).map(x=>x*x).join("")

“9999999999”

Remember that typed arrays have fixed lengths, so the length property is read-only, and methods that change the length of the array (such as push(), pop(), unshift(), shift(), and splice()) are not implemented for typed arrays. Methods that alter the contents of an array without changing the length (such as sort(), reverse(), and fill()) are implemented.

Determine Endianess and DataView

let littleEndian = new Int8Array(new Int32Array([1]).buffer)

[0] === 1;

You can use the DataView class, which defines methods for reading and writing values from an ArrayBuffer with explicitly specified byte ordering. Refer to book for more examples.

Pattern Matching with Regular Expressions

RegExp objects may be created with the RegExp() constructor, of course, but they are more often created using a special literal syntax.

let pattern = /s$/;

←>

let pattern = new RegExp("s$");

Regular expressions can also have one or more flag characters that affect how they work

let pattern = /s$/i;

i = case insensitive

Punctuation characters have special meanings in regular expressions: ^ $ . * + ? = ! : | \ / ( ) [ ] { }. Other punctuation characters, such as quotation marks and @, do not have special meaning and simply match themselves literally in a regular expression.

If you use the RegExp() constructor, keep in mind that any backslashes in your regular expression need to be doubled, since strings also use backslashes as an escape character.

Character

Matches

[...]

Any one character between the brackets.

[^...]

Any one character not between the brackets

.

Any character except newline or another Unicode line terminator. Or, if the RegExp uses the s flag, then a period matches any character, including line terminators.

\w

Any ASCII word character. Equivalent to [a-zA-Z0–9_].

\W

Equivalent to [^a-zA-Z0–9_]

\s

Any Unicode whitespace character.

\S

Any character that is not Unicode whitespace.

\d

Equivalent to [0–9].

\D

Equivalent to [⁰-9].

[\b]

A literal backspace (special case).

[\s\d]

Any one whitespace character or digit

REPETITIONS

Character

Meaning

{n,m}

Match the previous item at least n times but no more than m times

{n,}

Match the previous item n or more times.

{n}

Match exactly n occurrences of the previous item.

?

Equivalent to {0,1}.

+

Equivalent to {1,}

*

Equivalent to {0,}.

Example

Description

let r = /\d{2,4}/;

Match between two and four digits

r = /\w{3}\d?/;

Match exactly three word characters and an optional digit

r = /\s+java\s+/;

Match “java” with one or more spaces before and after

r = /[^(]*/;

Match zero or more characters that are not open parens

If you want to match repetitions of more complicated expressions, you'll need to define a group with parentheses

Be careful when using the * and ? repetition characters. Since these characters may match zero instances of whatever precedes them, they are allowed to match nothing.

NON-GREEDY REPETITION

It is also possible to specify that repetition should be done in a non-greedy way. Simply follow the repetition character or characters with a question mark: ??, +?, *?, or even {1,5}?.

String

Pattern

Match

"aaa"

/a+/

"aaa"

"aaa"

/a+?/

"a"

Note that using non-greedy repetition may not always produce the results you expect. This is because regular expression pattern matching is done by findingthe first position in the string at which a match is possible. Since a match is possible starting at the first character of the string, shorter matches starting at subsequent characters are never even considered.

ALTERNATION, GROUPING, AND REFERENCES

Char

Pattern

Pattern

|

/ab|cd|ef/

“ab” or the string “cd” or the string “ef”.

/\d{3}|[a-z]{4}/

either three digits or four lowercase letters.

/a|ab/

matches only the first letter “a”

()

/java(script)?/

matches “java” followed by the optional “script”

/(ab|cd)+|ef/

matches “java” followed by the optional “script”

If the left alternative matches, the right alternative is ignored, even if it would have produced a “better” match

Another purpose of parentheses in regular expressions is to define subpatterns within the complete pattern. When a regular expression is successfully matched against a target string, it is possible to extract the portions of the target string that matched any particular parenthesized subpattern. For example, suppose you are looking for one or more lowercase letters followed by one or more digits. You might use the pattern /[a-z]+\d+/. But suppose you only really care about the digits at the end of each match. If you put that part of the pattern in parentheses (/[a-z]+(\d+)/), you can extract the digits from any matches you find,

A related use of parenthesized subexpressions is to allow you to refer back to a subexpression later in the same regular expression. This is done by following a \ character by a digit or digits. The digits refer to the position of the parenthesized subexpression within the regular expression. For example, \1 refers back to the first subexpression, and \3 refers to the third.

Match

Pattern

zero or more characters within single or double quotes. However, it does not require the opening and closing quotes to match

/['"][^'"]*['"]/

To require the quotes to match,use a reference

/(['"])[^'"]*\1/

Character

Meaning

|

match either the subexpression to the left or the subexpression to the right.

(…)

Grouping: group items into a single unit that can be used with *, +, ?, |, and so on. Also remember the characters that match this group for use with later references

(?:…)

group items into a single unit, but do not remember the characters that match this group.

Note (?:...) syntax:

In pattern "/([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/" \2 refers to the text matched by (fun\w*) since (?:[Ss]cript)?) in not remembered.

SPECIFYING MATCH POSITION

regular expression anchors because they anchor the pattern to a specific position in the search string. The most commonly used anchor elements are ^, which ties the pattern to the beginning of the string, and $, which anchors the pattern to the end of the string.

Example

Pattern

match the word “JavaScript” on a line by itself

/^JavaScript$/

To search for “Java” as a word by itself you can try the pattern /\sJava\s/, which requires a space before and after the word. But there are two problems with this solution. First, it does not match "Java" at the beginning or the end of a string, but only if it appears with space on either side. Second, when this pattern does find a match, the matched string it returns has leading and trailing spaces, which is not quite what's needed. So instead of matching actual space characters with \s, match (or anchor to) word boundaries with \b. The resulting expression is /\bJava\b/.

The element \B anchors the match to a location that is not a word boundary. Thus, the pattern /\B[Ss]cript/ matches "JavaScript" and "postscript", but not "script" or "Scripting".

You can also use arbitrary regular expressions as anchor conditions.

If you include an expression within (?= and ) characters, it is a lookahead assertion, and it specifies that the enclosed characters must match, without actually matching them.

Example

Pattern

Matches

to match the name of a common programming language, but only if it is followed by a colon

/[Jj]ava([Ss]cript)?(?=\:)/

matches the word “JavaScript” in “JavaScript: The DefinitiveGuide”

does not match “Java” in “Java in a Nutshell”

If you instead introduce an assertion with (?!, it is a negative lookahead assertion.

FLAGS

Flags are specified after the second / character of a regular expression literal or as a string passed as the second argument to the RegExp() constructor.

Flag

Meaning

g

“global” — that is,that we intend to use it to find all matches within a string rather than just finding the first match.it does alter the behavior of the String match() method and the RegExp exec() method in important ways.

i

case-insensitive

m

“multiline” mode

s

useful when working with text that includes newlines.Normally, a “.” in a regular expression matches any character except a line terminator. When the s flag is used, however, “.” will match any character, including line terminators.

u

Unicode.

Setting the u flag on a RegExp also allows you to use the new \u{...} escape sequence for Unicode character and also enables the \p{...} notation for Unicode character classes.

y

“sticky”. should match at the beginning of a string or at the first character following the previous match

String Methods for Pattern Matching

Strings support four methods that use regular expressions.

"JavaScript".search(/script/ui)

4

"Python".search(/script/ui)

-1

search() does not support global searches; it ignores the g flag of its regular expression argument.

REPLACE()

text.replace(/javascript/gi, "JavaScript");

No matter how it is capitalized, replace it with the correct capitalization

parenthesized subexpressions of a regular expression are numbered from left to right and that the regular expression remembers the text that each subexpression matches.

to replace quotation marks in a string with other characters:

let quote = /"([^"]*)"/g;

'He said "stop"'.replace(quote, '«$1»')

'He said «stop»'

If your RegExp uses named capture groups, then you can refer to the matching text by name rather than by number:

let quote = /"(?<quotedText>[^"]*)"/g;

'He said "stop"'.replace(quote, '«$<quotedText>»')

'He said «stop»'

Instead of passing a replacement string as the second argument to replace(), you can also pass a function that will be invoked to compute the replacement value.

Example to convert decimal integers in a string to hexadecimal:

let s = "15 times 15 is 225";

s.replace(/\d+/gu, n => parseInt(n).toString(16))

“f times f is e1”

MATCH()

"7 plus 8 equals 15".match(/\d+/g)

[“7”, “8”, “15”]

If the regular expression does not have the g flag set, match() does not do a global search; it simply searches for the first match. In this nonglobal case, match() still returns an array, but the array elements are completely different.

Thus, if match() returns an array a, a[0] contains the complete match, a[1] contains the substring that matched the first parenthesized expression, and so on.

let url = /(\w+):\/\/([\w.]+)\/(\S*)/;

let text = "Visit my blog at http://www.example.com/~david";

let match = text.match(url);

let fullurl, protocol, host, path;

if (match !== null) {

fullurl = match[0];

“http://www.example.com/~david"

protocol = match[1];

“http”

host = match[2];

“www.example.com"

path = match[3];

“~david”

In this non-global case, the array returned by match() also has some object properties in addition to the numbered array elements.

input property refers to the string on which match() was called

The index property is the position within that string at which the match starts.

if the regular expression contains named capture groups, then the returned array also has a groups property whose value is an object.

let url = /(?<protocol>\w+):\/\/(?<host>[\w.]+)\/(?<path>\S*)/;

let text = "Visit my blog at http://www.example.com/~david";

let match = text.match(url);

match[0]

“http://www.example.com/~david"

match.input

text

match.index

17

match.groups.protocol

“http”

match.groups.host

“www.example.com"

match.groups.path

“~david”

There are also important but less dramatic differences in behavior when the y flag is set. Refer to book for examples.

MATCHALL()

Instead of returning an array of matching substrings like match() does, however, it returns an iterator that yields the kind of match objects that match() returns when used with a non-global RegExp.

SPLIT()

"123,456,789".split(",")

[“123”, “456”,”789"]

"1, 2, 3,\n4, 5".split(/\s*,\s*/)

[“1”, “2”, “3”, “4”,”5"]

Surprisingly, if you call split() with a RegExp delimiter and the regular expression includes capturing groups, then the text that matches the capturing groups will be included in the returned array.

const htmlTag = /<([^>]+)>/;

"Testing<br/>1,2,3".split(htmlTag)

[“Testing”, “br/”,”1,2,3"]

The RegExp Class

The RegExp() constructor is useful when a regular expression is being dynamically created and thus cannot be represented with the regular expression literal syntax.

let zipcode = new RegExp("\\d{5}", "g");

let exactMatch = /JavaScript/; let caseInsensitive = new RegExp(exactMatch, "i");

TEST()

Returns true or false by calling exec().

EXEC()

let pattern = /Java/g;

let text = "JavaScript > Java";

let match;

while((match = pattern.exec(text)) !== null) {

console.log(`Matched ${match[0]} at ${match.index}`);

console.log(`Next search begins at ${pattern.lastIndex}`);

}

THE LASTINDEX PROPERTY AND REGEXP REUSE

The use of the lastIndex property with the g and y flags is a particularly awkward part of this API. When you use these flags, you need to be particularly careful when calling the match(), exec(), or test() methods because the behavior of these methods depends on lastIndex, and the value of lastIndex depends on what you have previously done with the RegExp object.

To find the index of all <p> tags within a string of HTML text:

let match, positions = [];

while((match = /<p>/g.exec(html)) !== null) {

positions.push(match.index);

}

If the html string contains at least one <p> tag, then it will loop forever. For each iteration of the loop, we're creating a new RegExp object with lastIndex set to 0, so exec() always begins at the start of the string, and if there is a match, it will keep matching over and over. The solution, of course, is to define the RegExp once, and save it to a variable so that we're using the same RegExp object for each iteration of the loop.

On the other hand, sometimes reusing a RegExp object is the wrong thing to do. Suppose, for example, that we want to loop through all of the words in a dictionary to find words that contain pairs of double letters.

let dictionary = [ "apple", "book", "coffee" ];

let doubleLetterWords = [];

let doubleLetter = /(\w)\1/g;

for(let word of dictionary) {

if (doubleLetter.test(word)) {

doubleLetterWords.push(word);

}

}

doubleLetterWords

[“apple”, “coffee”]: “book” is missing!

Because we set the g flag on the RegExp, the lastIndex property is changed after successful matches, and the test() method (which is based on exec()) starts searching for a match at the position specified by lastIndex. After matching the "pp" in "apple", lastIndex is 3, and so we start searching the word "book" at position 3 and do not see the "oo" that it contains.

Dates and Times

let now = new Date();

The current time

let epoch = new Date(0);

Midnight, January 1st, 1970, GMT

let century = new Date(2100,

0,

1,

2, 3, 4, 5);

Year 2100

January

1st

02:03:04.005, local

let century = new Date(Date.UTC(2100, 0, 1));

Midnight in GMT, January 1, 2100

If you print a date (with console.log(century), for example), it will, by default, be printed in your local time zone. If you want to display a date in UTC, you should explicitly convert it to a string with toUTCString() or toISOString().

if you pass a string to the Date() constructor, it will attempt to parse that string as a date and time specification

let century = new Date("2100-01-01T00:00:00Z");

Once you have a Date object, various get and set methods allow you to query and modify the year, month, day-of-month, hour, minute, second, and millisecond fields of the Date. Each of these methods hastwo forms: one that gets or sets using local time and one that gets or sets using UTC time.

Note that the methods for querying the day-of-month are getDate() and getUTCDate(). The more natural-sounding functions getDay() and getUTCDay() return the day-of-week (0 for Sunday through 6 for Saturday). The day-of-week is read-only, so there is not a corresponding setDay() method.

Timestamps

JavaScript represents dates internally as integers that specify the number of milliseconds since (or before) midnight on January 1, 1970, UTC time.

For any Date object, the getTime() method returns this internal value, and the setTime() method sets it.

d.setTime(d.getTime() + 30000);

add 30 secs

The static Date.now() method returns the current time as a timestamp and is helpful when you want to measure how long your code takes to run

let startTime = Date.now();

reticulateSplines(); // Do some time-consuming operation

let endTime = Date.now();

console.log(`Spline reticulation took ${endTime -startTime}ms.`);

adds three months and two weeks to the current date:

let d = new Date(); d.setMonth(d.getMonth() + 3, d.getDate() + 14);

Formatting and Parsing Date Strings

let d = new Date(2020, 0, 1, 17, 10, 30);

d.toString()

“Wed Jan 01 2020 17:10:30 GMT-0800 (Pacific Standard Time)”

d.toUTCString()

“Thu, 02 Jan 2020 01:10:30 GMT”

d.toLocaleDateString()

“1/1/2020”: 'en-US' locale

d.toLocaleTimeString()

“5:10:30 PM”: 'en-US' locale

d.toISOString()

“2020–01–02T01:10:30.000Z”

there is also a static Date.parse() method that takes a string as its argument, attempts to parse it as a date and time, and returns a timestamp representing that date. Date.parse() is able to parse the same strings that the Date() constructor can and is guaranteed to be able to parse the output of toISOString(), toUTCString(), and toString().

Error Classes

One good reason to use an Error object is that, when you create an Error, it captures the state of the JavaScript stack, and if the exception is uncaught, the stack trace will be displayed with the error message, which will help you debug the issue.

Error objects have two properties: message and name, and a toString() method. Node and all modern browsers also define a stack property on Error objects.

Subclasses are EvalError, RangeError, ReferenceError, SyntaxError, TypeError, and URIError.

You should feel free to define your own Error subclasses that best encapsulate the error conditions of your own program.

class HTTPError extends Error {

constructor(status, statusText, url) {

super(`${status} ${statusText}: ${url}`);

this.status = status;

this.statusText = statusText;

this.url = url;

}

get name() { return "HTTPError"; }

}

let error = new HTTPError(404, "Not Found", "http://example.com/");

error.status

404

error.message

“404 Not Found:http://example.com/"

error.name

HTTPError

JSON Serialization and Parsing

JavaScript supports JSON serialization and deserialization with the two functions JSON.stringify() and JSON.parse().

let o = {s: "", n: 0, a: [true, false, null]};

let s = JSON.stringify(o);

s == '{“s”:””,”n”:0,”a”:[true,false,null]}'

let copy = JSON.parse(s);

copy == {s: “”, n: 0, a:[true, false, null]}

Inefficient way of creating a deep copy of an object

function deepcopy(o) {

return JSON.parse(JSON.stringify(o));

}

Typically, you pass only a single argument to JSON.stringify() and JSON.parse(). Both functions accept an optional second argument that allows us to extend the JSON format.

JSON.stringify() also takes an optional third argument. If you would like your JSONformatted string to be human-readable (if it is being used as a configuration file, for example), then you should pass null as the second argument and pass a number or string as the third argument. If the third argument is a number, then it will use that number of spaces for each indentation level. If the third argument is a string of whitespace (such as '\t'), it will use that string for each level of indent.

JSON Customizations

If JSON.stringify() is asked to serialize a value that is not natively supported by the JSON format, it looks to see if that value has a toJSON() method, and if so, it calls that method and then stringifies the return value in place of the original value. Date objects implement toJSON(): it returns the same string that toISOString() method does.

If you need to re-create Date objects (or modify the parsed object inany other way), you can pass a “reviver” function as the second argument to JSON.parse().

let data = JSON.parse(text, function(key, value) {

if (key[0] === "_") return undefined;

if (typeof value === "string" && /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ$/.test(value)) {

return new Date(value);

}

return value;

});

The Console API

Console functions that print their arguments like console.log() have a little-known feature: if the first argument is a string that includes %s, %i, %d, %f, %o, %O, or %c, then this first argument is treated as format string, and the values of subsequent arguments are substituted into the string in place of the two-character % sequences.

URL API

let url = new URL("https://example.com:8000/path/name?q=term#fragment");

url.href

“https://example.com:8000/path/name?q=term#fragment"

url.origin

“https://example.com:8000"

url.protocol

“https:”

url.host

“example.com:8000”

url.hostname

“example.com

url.port

“8000”

url.pathname

“/path/name”

url.search

“?q=term”

url.hash

“#fragment”

let url = new URL("https://example.com");

url.pathname = "api/search";

Add a path to an API endpoint

url.search = "q=test";

Add a query parameter

url.toString()

“https://example.com/api/search?q=test"

One of the important features of the URL class is that it correctly adds punctuation and escapes special characters in URLs when that is needed

let url = new URL("https://example.com");

url.pathname = "path with spaces";

url.pathname

“/path%20with%20spaces”

url.search = "q=foo#bar";

url.search

“?q=foo%23bar”

url.href

“https://example.com/path%20with%20spaces?q=foo%23bar"

Often, however, HTTP requests encode the values of multiple form fields or multiple API parameters into the query portion of a URL. In this format, the query portion of the URL is a question mark followed by one or more name/value pairs, which are separated from one another by ampersands.

If you want to encode these kinds of name/value pairs into the query portion of a URL, then the searchParams property will be more useful than the search property.

let url = new URL("https://example.com/search");

url.search

“”

url.searchParams.append("q", "term");

url.search

“?q=term”

url.searchParams.set("q", "x");

url.search

“?q=x”

url.searchParams.append("opts", "1");

url.search

“?q=x&opts=1”

The value of the searchParams property is a URLSearchParams object.

let url = new URL("http://example.com");

let params = new URLSearchParams();

params.append("q", "term");

params.append("opts", "exact");

params.toString()

“q=term&opts=exact”

url.search = params;

url.href

“http://example.com/?q=term&opts=exact"

Timers

setTimeout() and setInterval()—that allow programs to ask the browser to invoke a function after a specified amount of time has elapsed or to invoke the function repeatedly at a specified interval.

setTimeout(() => { console.log("Ready..."); }, 1000);

setTimeout(() => { console.log("set..."); }, 2000);

setTimeout(() => { console.log("go!"); }, 3000);

If you want to invoke a function repeatedly, use setInterval()

Both setTimeout() and setInterval() return a value. If you save this value in a variable, you can then use it later to cancel the execution of the function by passing it to clearTimeout() or clearInterval().

let clock = setInterval(() => {

console.clear();

console.log(new Date().toLocaleTimeString());

}, 1000);

setTimeout(() => { clearInterval(clock); }, 10000);

After 10 seconds: stop the repeating code above

Iterators and Generators

The iterator method of an iterable object does not have a conventional name but uses the Symbol, Symbol.iterator as its name. So a simple for/of loop over an iterable object iterable could also be written the hard way, like this:

let iterable = [99];

let iterator = iterable[Symbol.iterator]();

for(let result = iterator.next(); !result.done; result =iterator.next()) {

console.log(result.value) // result.value == 99

}

When you want to iterate though a “partially used” iterator:

let list = [1,2,3,4,5]; let iter = list[Symbol.iterator]();

let head = iter.next().value;

head == 1

let tail = [...iter];

tail == [2,3,4,5]

Implementing Iterable Objects

we will implement the Range class one more time, making it iterable without relying on a generator.

In order to make a class iterable, you must implement a method whose name is the Symbol Symbol.iterator

class Range {

constructor (from, to) {

this.from = from;

this.to = to;

}

has(x) { return typeof x === "number" && this.from <= x && x <= this.to; }

toString() { return `{ x | ${this.from} ≤ x ≤ ${this.to}}`; }

[Symbol.iterator]() {

let next = Math.ceil(this.from);

let last = this.to;

return {

next() {

return (next <= last) ? { value: next++ } : { done: true };

},

[Symbol.iterator]() { return this; }

};

}

}

for(let x of new Range(1,10)) console.log(x);

Logs numbers 1 to 10

[...new Range(-2,2)]

[-2, -1, 0,1, 2]

In addition to making your classes iterable, it can be quite useful to define functions that return iterable values.

Return an iterable object that iterates the result of applying f() to each value from the source iterable

function map(iterable, f) {

let iterator = iterable[Symbol.iterator]();

return {

[Symbol.iterator]() { return this; },

next() {

let v = iterator.next();

if (v.done) {

return v;

}

else {

return { value: f(v.value) };

}

}

};

}

[...map(new Range(1,4), x => x*x)]

[1, 4, 9, 16]

Return an iterable object that filters the specified iterable, iterating only those elements for which the predicate returns true

function filter(iterable, predicate) {

let iterator = iterable[Symbol.iterator]();

return {

[Symbol.iterator]() { return this; },

next() {

for(;;) {

let v = iterator.next();

if (v.done || predicate(v.value)) {

return v;

}

}

}

};

}

[...filter(new Range(1,10), x => x % 2 === 0)]

[2,4,6,8,10]

Generators

Particularly useful when the values to be iterated are not the elements of a data structure, but the result of a computation.

To create a generator, you must first define a generator function — defined with the keyword function* rather than function

When you invoke a generator function, it does not actually execute the function body, but instead returns a generator object. This generator object is an iterator.

Calling its next() method causes the body of the generator function to run from the start (or whatever its current position is) until it reaches a yield statement.

The value of the yield statement becomes the value returned by the next() call on the iterator.

function* oneDigitPrimes() {

yield 2;

yield 3;

yield 5;

yield 7;

}

let primes = oneDigitPrimes();

we get a generator

primes.next().value

2

primes.next().value

3

primes.next().value

5

primes.next().value

7

primes.next().done

true

Generators have a Symbol.iterator method to make them iterable

primes[Symbol.iterator]()

[...oneDigitPrimes()]

[2,3,5,7]

let sum = 0;

for(let prime of oneDigitPrimes()) sum += prime;

sum

17

Like regular functions, however, we can also define generators in expression form.

const seq = function*(from,to) {

for(let i = from; i <= to; i++) yield i;

};

[...seq(3,5)]

[3, 4, 5]

In classes and object literals, we can use shorthand notation to omit the function keyword entirely when we define methods.

let o = {

x: 1, y: 2, z: 3,

*g() {

for(let key of Object.keys(this)) {

yield key;

}

}

};

[...o.g()]

[“x”, “y”, “z”, “g”]

Generators often make it particularly easy to define iterable classes.

*[Symbol.iterator]() {

for(let x = Math.ceil(this.from); x <= this.to; x++)

yield x;

}

Generator Examples

Generators are more interesting if they actually generate the values they yield by doing some kind of computation.

generator function that yields Fibonacci numbers

function* fibonacciSequence() {

let x = 0, y = 1;

for(;;) {

yield y;

[x, y] = [y, x+y];

}

}

If this generator is used with the … spread operator, it will loop until memory is exhausted and the program crashes.

Use it in a for/of loop, however

function fibonacci(n) {

for(let f of fibonacciSequence()) {

if (n-- <= 0) return f;

}

}

fibonacci(20)

10946

This kind of infinite generator becomes more useful with a take() generator like this

function* take(n, iterable) {

let it = iterable[Symbol.iterator]();

while(n-- > 0) {

let next = it.next();

if (next.done) return;

else yield next.value;

}

}

[...take(5, fibonacciSequence())]

[1, 1, 2, 3, 5]

Asynchronous Javascript

Promises, new in ES6, are objects that represent the not-yet-available result of an asynchronous operation.

The keywords async and await were introduced in ES2017 and provide new syntax that simplifies asynchronous programming by allowing you to structure your Promise based code as if it was synchronous.

Asynchronous iterators and the for/await loop were introduced in ES2018 and allow you to work with streams of asynchronous events using simple loops that appear synchronous.

Asynchronous Programming with Callbacks

Timers

setTimeout(checkForUpdates, 60000);

let updateIntervalId = setInterval(checkForUpdates, 60000);

function stopCheckingForUpdates() {

clearInterval(updateIntervalId);

}

Events

Event-driven JavaScript programs register callback functions for specified types of events in specified contexts, and the web browser invokes those functions whenever the specified events occur.

These callback functions are called event handlers or event listeners, and they are registered with addEventListener()

Ask the web browser to return an object representing the HTML <button> element that matches this CSS selector:

let okay = document.querySelector('#confirmUpdateDialogbutton.okay');

Now register a callback function to be invoked when the user clicks on that button

okay.addEventListener('click', applyUpdate);

Network Events

JavaScript running in the browser can fetch data from a web server with code like this:

function getCurrentVersionNumber(versionCallback) {

let request = new XMLHttpRequest();

request.open("GET", "http://www.example.com/api/version");

request.send();

request.onload = function() {

if (request.status === 200) {

let currentVersion = parseFloat(request.responseText);

versionCallback(null, currentVersion);

}

else {

versionCallback(response.statusText, null);

}

};

request.onerror = request.ontimeout = function(e) {

versionCallback(e.type, null);

};

}

Promises

Promises, a core language feature designed to simplify asynchronous programming.

A Promise is an object that represents the result of an asynchronous computation. That result may or may not be ready yet, and the Promise API is intentionally vague about this: there is no way to synchronously get the value of a Promise; you can only ask the Promise to call a callback function when the value is ready.

One real problem with callback-based asynchronous programming is that it is common to end up with callbacks inside callbacks inside callbacks, with lines of code so highly indented that it is difficult to read.

Promises allow this kind of nested callback to be re-expressed as a more linear Promise chain that tends to be easier to read and easier to reason about.

Another problem with callbacks is that they can make handling errors difficult. If an asynchronous function (or an asynchronously invoked callback) throws an exception, there is no way for that exception to propagate back to the initiator of the asynchronous operation. This is a fundamental fact about asynchronous programming: it breaks exception handling. Promises help here by standardizing a way to handle errors and providing a way for errors to propagate correctly through a chain of promises.

Note that Promises represent the future results of single asynchronous computations. They cannot be used to represent repeated asynchronous computations, however.

We can't use Promises to replace setInterval() because that function invokes a callback function repeatedly, which is something that Promises are just not designed to do.

Using Promises

How we would use this Promise returning utility function

getJSON(url).then(jsonData => {

// callback function that will be asynchronously invoked with the parsed JSON value when it becomes available.

});

The Promise object defines a then() instance method. Instead of passing our callback function directly to getJSON(), we instead pass it to the then() method. When the HTTP response arrives, the body of that response is parsed as JSON, and the resulting parsed value is passed to the function that we passed to then().

If you call the then() method of a Promise object multiple times, each of the functions you specify will be called when the promised computation is complete.

Unlike many event listeners, though, a Promise represents a single computation, and each function registered with then() will be invoked only once.

function displayUserProfile(profile) { ...}

getJSON("/api/user/profile").then(displayUserProfile);

HANDLING ERRORS WITH PROMISES

Asynchronous operations, particularly those that involve networking, can typically fail in a number of ways, and robust code has to be written to handle the errors that will inevitably occur.

getJSON("/api/user/profile").then(displayUserProfile, handleProfileError);

if getJSON() runs normally, it passes its result to displayUserProfile(). If there is an error (the user is not logged in, the server is down, the user's internet connection dropped, the request timed out, etc.), then getJSON() passes an Error object to handleProfileError().

In practice, it is rare to see two functions passed to then(). There is a better and more idiomatic way of handling errors when working with Promises.

To understand it, first consider what happens if getJSON() completes normally but an error occurs in displayUserProfile(). That callback function is invoked asynchronously when getJSON() returns, so it is also asynchronous and cannot meaningfully throw an exception (because there is no code on the call stack to handle it).

getJSON("/api/user/profile").then(displayUserProfile).catch(handleProfileError);

With this code, a normal result from getJSON() is still passed to displayUserProfile(), but any error in getJSON() or in displayUserProfile() (including any exceptions thrown by displayUserProfile) get passed to handleProfileError().

Chaining Promises

One of the most important benefits of Promises is that they provide a natural way to express a sequence of asynchronous operations as a linear chain of then() method invocations, without having to nest each operation within the callback of the previous one.

fetch(documentURL)

.then(response => response.json())

.then(document => {return render(document); })

.then(rendered => {cacheInDatabase(rendered); })

.catch(error => handle(error));

has largely been replaced by the newer, Promise-based Fetch API. In its simplest form, this new HTTP API is just the function fetch(). That promise is fulfilled when the HTTP response begins to arrive and the HTTP status and headers are available.

fetch("/api/user/profile")

.then(response => {

if (response.ok &&  response.headers.get("Content-Type") === "application/json") {

// What can we do here? We don't actually have the response body yet.

}

});

But although the initial Promise is fulfilled, the body of the response may not yet have arrived. So these text() and json() methods for accessing the body of the response themselves return Promises.

fetch("/api/user/profile")

.then(response => {

return response.json();

})

.then(profile => {

displayUserProfile(profile);

});

There is a second then() in the chain, which means that the first invocation of the then() method must itself return a Promise. That is not how Promises work, however.

When we write a chain of .then() invocations, we are not registering multiple callbacks on a single Promise object. Instead, each invocation of the then() method returns a new Promise object. That new Promise object is not fulfilled until the function passed to then() is complete.

fetch(theURL)       // task 1; returns promise 1

.then(callback1)  // task 2; returns promise 2

.then(callback2); // task 3; returns promise 3

Resolving Promises

There is actually a fourth Promise object involved as which brings up the point of what it means for a Promise to be “resolved.”

fetch() returns a Promise object which, when fulfilled, passes a Response object to the callback function we register. This Response object has .text(), .json(), and other methods to request the body of the HTTP response in various forms. But since the body may not yet have arrived, these methods must return Promise objects.

“task 2” calls the .json() method and returns its value. This is the fourth Promise object, and it is the return value of the callback1() function.

Let's consider:

function c1(response) {

let p4 = response.json();

return p4;

}

// callback 1

// returns promise 4

function c2(profile) {

displayUserProfile(profile);

}

// callback 2

let p1 = fetch("/api/user/profile");

promise 1, task 1

let p2 = p1.then(c1);

promise 2, task 2

let p3 = p2.then(c2);

promise 3, task 3

In order for Promise chains to work usefully, the output of task 2 must become the input to task 3. The input to task 3 is the body of the URL that was fetched, parsed as a JSON object. But the return value of callback c1 is not a JSON object, but Promise p4 for that JSON object.

When p1 is fulfilled, c1 is invoked, and task 2 begins. And when p2 is fulfilled, c2 is invoked, and task 3 begins.

And when p2 is fulfilled, c2 is invoked, and task 3 begins. But just because task 2 begins when c1 is invoked,it does not mean that task 2 must end when c1 returns.

Promises are about managing asynchronous tasks, and if task 2 is asynchronous, then that task will not be complete by the time the callback returns.

When you pass a callback c to the then() method, then() returns a Promise p and arranges to asynchronously invoke c at some later time. The callback performs some computation and returns a value v. When the callback returns, p is resolved with the value v. When a Promise is resolved with a value that is not itself a Promise, it is immediately fulfilled with that value.

So if c returns a non-Promise, that return value becomes the value of p, p is fulfilled and we are done. But if the return value v is itself a Promise, then p is resolved but not yet fulfilled.

At this stage, p cannot settle until the Promise v settles. If v is fulfilled, then p will be fulfilled to the same value. If v is rejected, then p will be rejected for the same reason. This is what the “resolved” state of a Promise means

the Promise has become associated with, or “locked onto,” another Promise. We don't know yet whether p will be fulfilled or rejected, but our callback c no longer has any control over that. p is “resolved” in the sense that its fate now depends entirely on what happens to Promise v.

Let's bring this back to our URL-fetching example. When c1 returns p4, p2 is resolved. But being resolved is not the same as being fulfilled, so task 3 does not begin yet. When the full body of the HTTP response becomes available, then the .json() method can parse it and use that parsed value to fulfill p4. When p4 is fulfilled, p2 is automatically fulfilled as well, with the same parsed JSON value. At this point, the parsed JSON object is passed to c2, and task 3 begins.

More on Promises and Errors

With synchronous code, if you leave out error-handling code, you'll at least get an exception and a stack trace that you can use to figure out what is going wrong. With asynchronous code, unhandled exceptions will often go unreported, and errors can occur silently, making them much harder to debug. The good news is that the .catch() method makes it easy to handle errors when working with Promises.

THE CATCH AND FINALLY METHODS

The .catch() method of a Promise is simply a shorthand way to call .then() with null as the first argument and an error-handling callback as the second argument.

Normal exceptions don't work with asynchronous code. The .catch() method of Promises is an alternative that does work for asynchronous code.

fetch("/api/user/profile")

.then(response => {

if (!response.ok) {

return null;

}

let type = response.headers.get("content-type");

if (type !== "application/json") {

throw new TypeError(`Expected JSON, got ${type}`);

}

return response.json();

})

.then(profile => {

if (profile) {

displayUserProfile(profile);

}

else {

displayLoggedOutProfilePage();

}

})

.catch(e => {

if (e instanceof NetworkError) {

displayErrorMessage("Check your internet connection.");

}

else if (e instanceof TypeError) {

displayErrorMessage("Something is wrong with our server!");

}

else {

console.error(e);

}

});

p1 is the Promise returned by the fetch() call

p2 is the Promise returned by the first .then() call

c1 is the callback that we pass to that .then() call

p3 is the Promise returned by the second .then() call

c2 is the callback we pass to that call

c3 is the callback that we pass to the .catch() call

The first thing that could fail is the fetch() request itself. Let's say p1 was rejected with a NetworkError object.

We didn't pass an error-handling callback function as the second argument to the .then() call, so p2 rejects as well with the same NetworkError object.

Without a handler, though, p2 is rejected, and then p3 is rejected for the same reason.

At this point, the c3 error-handling callback is called, and the NetworkError-specific code within it runs.

There are a couple of things worth noting about this code. First, notice that the error object thrown with a regular, synchronous throw statement ends up being handled asynchronously with a .catch() method invocation in a Promise chain. This should make it clear why this shorthand method is preferred over passing a second argument to .then(), and also why it is so idiomatic to end Promise chains with a .catch() call.

it is also perfectly valid to use .catch() elsewhere in a Promise chain. If one of the stages in your Promise chain can fail with an error, and if the error is some kind of recoverable error that should not stop the rest of the chain from running, then you can insert a .catch() call in the chain, resulting in code that might look like this:

startAsyncOperation()

.then(doStageTwo)

.catch(recoverFromStageTwoError)

.then(doStageThree)

.then(doStageFour)

.catch(logStageThreeAndFourErrors);

If the callback returns normally, then the .catch() callback will be skipped, and the return value of the previous callback will become the input to the next .then() callback.

Once an error has been passed to a .catch() callback, it stops propagating down the Promise chain. A .catch() callback can throw a new error, but if it returns normally, than that return value is used to resolve and/or fulfill the associated Promise, and the error stops propagating.

Sometimes, in complex network environments, errors can occur more or less at random, and it can be appropriate to handle those errors by simply retrying the asynchronous request.

queryDatabase()

.catch(e => wait(500).then(queryDatabase))

.then(displayTable)

.catch(displayDatabaseError);

Promises in Parallel

Sometimes,we want to execute a number of asynchronous operations in parallel. The function Promise.all() can do this. Promise.all() takes an array of Promise objects as its input and returns a Promise.

The returned Promise will be rejected if any of the input Promises are rejected. Otherwise, it will be fulfilled with an array of the fulfillment values of each of the input Promises.

const urls = [ /* zero or more URLs here */ ];

promises = urls.map(url => fetch(url).then(r => r.text()));

Promise.all(promises)

.then(bodies => { /* do something with the array of strings */ })

.catch(e => console.error(e));

The Promise returned by Promise.all() rejects when any of the input Promises is rejected. This happens immediately upon the first rejection and can happen while other input Promises are still pending. In ES2020, Promise.allSettled() takes an array of input Promises and returns a Promise, just like Promise.all() does. But Promise.allSettled() never rejects the returned Promise, and it does not fulfill that Promise until all of the input Promises have settled. The Promise resolves to an array of objects, with one object for each input Promise. Each of these returned objects has a status property set to "fulfilled" or "rejected." If the status is "fulfilled", then the object will also have a value property that gives the fulfillment value. And if the status is "rejected", then the object will also have a reason property that gives the error or rejection value of the corresponding Promise.

Promise.allSettled([Promise.resolve(1), Promise.reject(2),3]).then(results => {

results[0] // => { status: "fulfilled", value: 1 }

results[1] // => { status: "rejected", reason: 2 }

results[2] // => { status: "fulfilled", value: 3 }

});

Occasionally, you may want to run a number of Promises at once but may only care about the value of the first one to fulfill. In that case, you can use Promise.race() instead of Promise.all(). It returns a Promise that is fulfilled or rejected when the first of the Promises in the input array is fulfilled or rejected.

Making Promises

Promises in Sequence

async and await

These new keywords dramatically simplify the use of Promises and allow us to write Promise-based, asynchronous code that looks like synchronous code that blocks while waiting for network responses or other asynchronous events.

Asynchronous code can't return a value or throw an exception the way that regular synchronous code can. And this is why Promises are designed the way the are. The value of a fulfilled Promise is like the return value of a synchronous function. And the value of a rejected Promise is like a value thrown by a synchronous function.

async and await take efficient, Promise-based code and hide the Promises so that your asynchronous code can be as easy to read and as easy to reason about as inefficient, blocking, synchronous code.

Given a Promise object p, the expression await p waits until p settles. If p fulfills, then the value of await p is the fulfillment value of p. On the other hand, if p is rejected, then the await p expression throws the rejection value of p.

let response = await fetch("/api/user/profile");

let profile = await response.json();

It is critical to understand right away that the await keyword does not cause your program to block and literally do nothing until the specified Promise settles. The code remains asynchronous, and the await simply disguises this fact. This means that any code that uses await is itself asynchronous.

async Functions

Because any code that uses await is asynchronous, there is one critical rule: you can only use the await keyword within functions that have been declared with the async keyword.

async function getHighScore() {

let response = await fetch("/api/user/profile");

let profile = await response.json();

return profile.highScore;

}

Declaring a function async means that the return value of the function will be a Promise even if no Promise-related code appears in the body of the function.

The getHighScore() function is declared async, so it returns a Promise. And because it returns a Promise, we can use the await keyword with it:

displayHighScore(await getHighScore());

Awaiting Multiple Promises

Suppose that we've written our getJSON() function using async:

async function getJSON(url) {

let response = await fetch(url);

let body = await response.json();

return body;

}

And now suppose that we want to fetch two JSON values with this function

let value1 = await getJSON(url1);

let value2 = await getJSON(url2);

The problem with this code is that it is unnecessarily sequential: the fetch of the second URL will not begin until the first fetch is complete. If the second URL does not depend on the value obtained from the firstURL, then we should probably try to fetch the two values at the same time.

let [value1, value2] = await Promise.all([getJSON(url1), getJSON(url2)]);

The for/await Loop

Suppose you have an array of URLs:

const urls = [url1, url2, url3];

You can call fetch() on each URL to get an array of Promises:

const promises = urls.map(url => fetch(url));

We could now use Promise.all() to wait for all the Promises in the array to be fulfilled. But suppose we want the results of the first fetch as soon as they become available and don't want to wait for all the URLs to be fetched.

for(const promise of promises) {

response = await promise;

handle(response);

}

←>

for await (const response of promises) {

handle(response);

}

both examples will only work if they are within functions declared async; a for/await loop is no different than a regular await expression in that way

Last updated