JavaScript is a coding language that defines the behavior of web pages.
JavaScript is a programming language. It is usually used with HTML and CSS, which are not programming languages. The difference is that a programming language defines a process, while a markup language adds context and structure to text.
A programming language is a formal language capable of expressing computable functions. More simply, with JavaScript you can use branching and loops to define a complex process.
A markup language is a formal language that adds structure to normal text. So with HTML alone, you can only define static web pages. HTML describes the data. It contains no logic.
Empty template for an HTML document that includes HTML, CSS, and JavaScript. Save text as filename.html.
<html>
<head>
<!-- head tags here -->
<style type="text/css">
/*css here*/
</style>
</head>
<body>
<!-- page contents here -->
</body>
</html>
<script type="text/javascript">
//javascript here
</script>
JavaScript can also be placed in the head element, but that is not standard practice.
<html>
<head>
<script type="text/javascript">
//javascript here
</script>
</head>
<body>
</body>
</html>
You can also put the JavaScript element directly in the body of the HTML. This is not standard practice. If you do follow this model, place the script tag after all other content so that compiling the JavaScript does not slow down display.
<html>
<body>
<script type="text/javascript">
//javascript here
</script>
</body>
</html>
The convention is to only put references to external JavaScript files in the head.
External javascript files improve loading speeds because they can be cached. They separate the scripts from the HTML which makes both easier to read and maintain, and allows the scripts to be reused by other HTML pages.
myHTML.html
<html>
<head>
<script type="text/javascript" src="myExternalScripts.js"></script>
<script type="text/javascript" src="http://a.really/specific/path.js"></script>
</head>
<body>
<div id="main">
</div>
</body>
</html>
myExternalScripts.js
document.getElementById("main").innerHTML = "JavaScript test";
//comments
/*
multiline comments
*/
JavaScript is loosely typed, meaning all variables have type 'var' and any value can be assigned to any variable.
var x, y, z;
x = 5;
y = "text";
z = [a, "b", 3];
x = y;
Declare several variables in one statement
var x, y, z;
Define several variables in one statement
var x = 5, y = "text", z = [a, "b", 3];
Re-declaring a variable does not change the value - still, don't do this
var x = 5;
var x;
Numbers
var x = 10.50;
var y = 1001;
Strings can be surrounded by single or double quotes.
var x = "string";
var y = 'string';
var z = "inner ' ' quotes without escape characters";
An expression is any combination of values, variables, and operators that compute to a single value.
The computation is called an evaluation.
Ex: The expression "5 * 10" evaluates to "50".
Identifiers include variable names, function names, labels, and keywords.
The first character must be a letter, underscore (_), or dollar sign ($). Usually, start with a letter.
Other characters may be letters, digits, underscores, or dollar signs.
Identifiers are case-sensitive.
By convention, JavaScript identifiers are written in lower camel case. The first character is lowercase, the first character of each subsequent word is uppercase, and there are no spaces or underscores between words.
var inputLastName;
var _countContents;
By convention, JavaScript global variables are named in all uppercase.
By convention, JavaScript constants are named in all uppercase.
Each JavaScript statement ends with a semicolon (;). You can put multiple statements on a single line, but by convention it is usually one line per statement.
var x = 5;
var y = 6; var z = x + y; console.log(z);
A statement can stretch across multiple lines.
JavaScript ignores extra whitespaces. Use them to make your code more legible.
JavaScript defines code blocks with { curly braces }.
The convention in JavaScript is to use Egyptian style braces.
Roman
function Display()
{
//code here
}
Egyptian
function Display() {
//code here
}
Usually code blocks are functions, or loops, or switch statements.
You can also define named code blocks anywhere you want.
To use breaks in this case, you must specify the code block's label.
var x = ["a", "b", "c", "d"];
var text = "";
myLabel: {
text += x[0];
text += x[1];
break myLabel;
text += x[2];
text += x[3];
}
console.log(text); //"ab"
aka Reserved Word
Keywords are special words with meaning in the programming language. You cannot use these words as variable names or function names.
Ex: break, continue, do, while, for, function, if, else, return, switch, try, catch, var
'undefined' is a reserved word. The type of 'undefined' is 'undefined'.
Variables that have been declared but not defined have value 'undefined'.
if(x == undefined) {}
y = undefined;
'null' is a reserved word. It is considered an object. 'null' is not the same as 'undefined'.
var y = null;
null !== undefined
null == undefined
DOM stands for Document Object Model. All major browsers support DOM.
The document object model is an object view of the xml structure of the webpage. <HTML> is the root object, and all other nodes are children of the root, in the tree structure defined by the XML.
Wait for document to load before starting your script:
//this will overwrite any previous document.onload events
document.onload = function() {
};
//this will add you event to the list of event listeners
document.addEventListener("load", function() {
});
With for both the document and the window object to load before starting your script:
//this will overwrite any previous window.onload events
window.onload = function() {
};
//this will add you event to the list of event listeners
window.addEventListener("load", function() {
});
Queries like "document.getElementsByTagName('div')" return HTMLCollections, which are live collections that update as you update the DOM.
A NodeList object is a collection of DOM elements that may or may not be live.
To convert either an HTMLCollection or NodeList to an array (which will not be live):
var collection = document.getElementsByTagName('div');
var array = Array.from(collection);
//or
var array = [...collection];
Lookup element
document.getElementById('id'); //element
document.getElementsByName('name'); //array
document.getElementsByClassName('class'); //array
document.getElementsByTagName('tag'); //array
document.querySelectorAll('.my-class[data-my-custom-attribute="value"][data-two="value2"]'); //array
Create element
var element = document.createElement('div'); //any tag name
Create text element
var element = document.createTextNode('plain text');
Loop through children:
for(var i=0; i<element.children.length; i++)
{
var child = element.children[i];
}
You cannot use Array function "foreach" on an HTMLCollection.
Add child:
element.appendChild(childElement);
element.insertBefore(childElement, siblingElement);
element.insertBefore(childElement, element.firstChild);
Add child after element:
element.parentNode.insertBefore(childElement, element.nextSibling);
Navigate:
var nextSiblingNode = element.nextSibling; //could be an element, or text, or comment
var nextSiblingElement = element.nextElementSibling; //just elements
var previousSiblingNode = element.previousSibling;
var previousSiblingElement = element.previousElementSibling;
"window" is a variable made available to JavaScript by the browser. It gives you access to the current browser window.
Show an alert popup
window.alert("An error");
"console" is a variable made available to JavaScript by the browser. It gives you access to the browser's console window.
console.log("A message");
"document" is a variable made available to JavaScript by the browser. It gives you access to the DOM for your webpage.
var myElement = document.getElementById("myId"); //references HTML tag with id="myId"
Append HTML to the end of your document. This content will be interpreted as HTML.
document.write("some more text<br/>more text on lower line");
Warning: using document.write after the DOM has fully loaded will cause it to overwrite the whole DOM. Subsequent uses will still append.
Editing element inner html:
var myElement = document.getElementById("myId");
myElement.innerHTML = "Edited html";
Editing element attributes:
var myImage = document.getElementById("myImage");
myImage.src = "anotherImage.png";
Editing element styles:
var myDiv = document.getElementById("myDiv");
myDiv.style.fontSize = "25px";
Editing classes
element.classList.add('class');
element.classList.remove('class');
Quickly replace all classes on an element (much faster than removing them individually from the list)
element.className = "class";
var myForm = document.forms["myFormName"];
var myField = myForm["myFieldName"];
var myFieldValue = myField.value;
myFieldValue = document.forms["myFormName"]["myFieldName"].value;
Form validation example. The form won't actually submit until the validation passes.
<form name="myForm" onsubmit="return validateForm()" action="submitForm.php" method="post">
Name: <input type="text" name="firstName" />
<input type="submit" value="Submit" />
</form>
<script>
function validateForm() {
var firstName = document.forms["myForm"]["firstName"].value;
if(firstName == "") {
alert("Name is required.");
return false;
}
return true;
}
</script>
You can add any custom attribute to an HTML element.
By convention, use the prefix "data-" to name custom attributes.
<input id='address' data-address-type='billing' />
Custom attributes with the prefix "data-" can be accessed through javascript.
var addressType = document.getElementById('address').dataset.addressType;
HTML attribute "data-address" becomes JavaScript element.dataset.address.
HTML attribute "data-address-type" becomes JavaScript element.dataset.addressType.
Include an entire other html page within your html page:
<html>
<body>
<object type="text/html" data="otherFileA.html"></object>
<object type="text/html" data="otherFileB.html"></object>
</body>
</html>
The other files will display in their own frame.
Access the DOM of those other files:
var objects = document.getElementsByTagName("object");
for(var i = 0; i < objects.length; i++)
{
var object = objects[i];
var objectDocument = object.contentDocument;
//access "objectDocument" just like local "document"
}
var y = 5;
var x = y;
x += 10; //x = x + 10;
x -= 5; //x = x - 5;
x *= 2; //x = x * 2;
x /= 4; //x = x / 4;
x %= 10; //x = x % 10;
var x = (5 + 6) * 10 / (2 - 4) % 10;
* and / have higher precedence than + and -
Use ( parentheses ) to control precedence.
x++;
++x;
y--;
--y;
var x = "Hello " + name + "."; //Hello John.
var y = "5" + 2 + 3; //523
var z = "2 + 3 + "5"; //55
if(x == y) {} //values are equal
var x = "John";
var y = new String("John");
if(x == y) {} //true because they have the same value
var z = new String("John");
if(y == z) {} //false because objects are compared by reference, and these are different instances
if(y === z) {} //still false, because the objects are different instances
if(x === y) {} //values and types are equal
if(x != y) {} //values are not equal
if(x !== y) {} //values or types are not equal
if(x > y) {} //greater than
if(x < y) {} //less than
if(x >= y) {} //greater than or equal to
if(x <= y) {} //less than or equal to
z = (x > y) ? 5 : 10; //ternary operator - shorthand for if/else
if(a && b) {} //a AND b
if(a || b) {} //a OR b
if(!a) {} //NOT a
if(!!a) {} //convert non-boolean to closest boolean value, search terms "double bang" "truthy"
if(typeof(x) == String) {} //the type of the variable
if(x instanceof String) {} //true if x is of type string
typeof("") == "string"
typeof(0) == "number"
typeof(NaN) == "number"
typeof(Infinity) == "number"
typeof(true) == "boolean"
typeof({}) == "object"
typeof([]) == "object"
typeof(null) == "object"
typeof(undefined) == "undefined"
typeof(function myFunc() {}) == "function"
flag = (a & b); //AND the bit-values together
flag = (a | b); //OR the bit-values together
flag = (!a); //NOT the bit-values
flag = (a ^ b); //XOR the bit-values together
flag = (a << b); //zero-fill left shift a by b
flag = (a << b); //signed right shift a by b
flag = (a >>> b); //zero-fill right shift a by b
if("property" in myObject)
return myObject.property; //or return object[property];
if(2 in myArray)
return myArray[2];
if(!("property" in myObjects))
return null;
Variable declarations are hoisted to the top of the current scope, which means either (A) the top of the enclosing function or (B) the global scope.
Therefore you can use a variable before (earlier in the file) it is declared.
Variables declared with "let" or "const" keywords are NOT hoisted.
console.log(x); //outputs ReferenceError: can't access lexical declaration 'x' before initialization
let x = 1;
console.log(y); //outputs ReferenceError: can't access lexical declaration 'y' before initialization
const y = 2;
Variable initializations are NOT hoisted.
console.log(x); //outputs undefined
var x = 5;
Standard practice is to declare all variables at the top of their scope, regardless of hoisting.
Global scope is available to every part of the script.
Variables declared outside any function or object have global scope.
Function scope is available to all statements within the function.
Variables declared within a function have function scope, also called local scope.
Block scope is available to all statements occurring later within the same block.
A code block is anything enclosed in { }.
Only variables declared as "let" or "const" have block scope.
{
let x = 5;
}
console.log(x); //outputs ReferenceError: x is not defined
{
const y = 6;
}
console.log(y); //outputs ReferenceError: y is not defined
Variables declared as "var" cannot have block scope, even when declared inside a block:
{
var x = 5;
}
console.log(x); //outputs 5
It is called "redeclaring" if you declare a "var" variable both inside and outside a block. Within just { } it does not make a difference.
var x = 5;
{
var x;
console.log(x); //outputs 5
}
console.log(x); //outputs 5
Unless you reinitialize the variable, then it will change the global value.
var x = 5;
{
console.log(x); //outputs 5
var x = 6;
console.log(x); //outputs 6
}
console.log(x); //outputs 6
I don't know the difference between the previous examples and this one, but the behavior is different when you using the grouping operator ( ).
If you don't declare the variable in the inner scope, the global is used.
If you do declare the variable in the inner scope, it is like a whole different variable and the outer variable's value is maintained even if the inner one changes.
var x = 5;
(function() {
console.log(x); //outputs 5
x = 2;
console.log(x); //outputs 2
})();
console.log(x); //outputs 2
(function() {
console.log(x); //outputs undefined because the variable declaration was hoisted
var x = 8;
console.log(x); //outputs 8
})();
console.log(x); //outputs 2
Declaring a "let" or "const" variable will not affect a global "var" variable of the same name.
var x = 5;
var y = 6;
{
let x = 1;
const y = 2;
}
console.log(x); //outputs 5
console.log(y); //outputs 6
You can also nest "let" and "const" variables to reuse the variable names.
let x = 5;
{
let x = 6;
console.log(x); //outputs 6
}
console.log(x); //outputs 5
The value of "let" variables can be changed.
The value of "const" variables cannot be changed.
A container is a collection of data with no methods/behavior. For example, arrays are containers.
Strictly speaking, containers are supposed to be immutable. By that definition, arrays are not containers because you can add/remove elements and edit elements.
A "functor" is a container that can be mapped onto by an unary function (a function that accepts one parameter). The function will be applied to each element of the functor, and a container of the results of each function call will be returned. For example, array.map(function).
An implementation of "map" is expected to return the same container type as the container it is operating on. Thus, you can chain "map" calls.
A data type
var x = 5;
var y = 10.50;
var z = 120e5; //exponential (scientific) notation
(link to WWC floating point lesson for much more details)
All numbers are stored as 64-bit floating points.
Bits 0-51 store the number, bits 52-62 store the exponent, and bit 63 stores the sign.
Integers are accurate up to 15 digits.
The maximum number of decimals is 17.
Floating point arithmetic is not completely accurate.
var x = 999999999999999; //999999999999999
var y = 9999999999999999; //10000000000000000
var z = 0.2 + 0.1; //0.30000000000000004
Therefore, use some form of rounding
var z = (2 + 1) / 10; //0.3
Number.MAX_VALUE
Number.MIN_VALUE
Number.NEGATIVE_INFINITY
Number.NaN
Number.POSITIVE_INFINITY
NaN stands for Not A Number. This result is given when an operation results in an illegal value.
var x = 100 / "Apple"; //NaN
if(isNaN(x)) {} //true
Any operations that includes a NaN will result in NaN. Except some weird ones.
var x = NaN + "5"; //"NaN5"
'Inifity' or '-Infinity' will be returned if you go beyond the range of JavaScript numbers.
if(2 == Infinity) {} //false
if(2/0 == Infinity) {} //true
Numbers prefixed with "0b" are interpreted as binary integers.
var x = 0b10001; //17
Some browsers interpret numerics with a leading zero as octals.
var x = 017; //15
Numerics with prefix '0x' are interpreted as hexadecimal values.
var x = 0xFF; //255
Number
var a = Number(true); //1
var b = Number(false); //0
var c = Number("10"); //10
var d = Number("10 20"); //NaN
var e = Number(Date()); //returns number of milliseconds since 1970/1/1
parseInt only works on strings, and only returns the first integer found
var a = parseInt("10"); //10
var b = parseInt("10.33"); //10
var c = parseInt("10 20 30"); //10
var d = parseInt("in 10 years"); //10
var e = parseInt("no numbers"); //NaN
parseInt defaults to base-10, but you can also specify the base
var f = parseInt("0110110001", 2);
parseFloat only works on strings, and only returns the first number found
var a = parseFloat("10"); //10
var b = parseFloat("10.33"); //10.33
var c = parseFloat("10.20.30"); //10.20
var d = parseFloat("in 10 years"); //10
var e = parseFloat("no numbers"); //NaN
number.toString(base) returns string
var x = 128.toString(); //"128"
var y = 128.toString(16); //"80"
var s = String.fromCharCode(asciiCode);
number.toExponential(decimalPlaces) returns string
var x = (9.656).toExponential(2); //"9.66e+0"
var y = (9.656).toExponential(4); //"9.6560e+0"
Rounds a number to the specified decimal places.
number.toFixed(decimalPlaces) returns string
var x = (9.656).toFixed(0); //"10"
var y = (9.656).toFixed(2); //"9.66"
var z = (9.656).toFixed(4); //"9.6560"
Rounds a number to the specified number of digits.
number.toPrecision(digits) returns string
var x = (9.656).toPrecision(); //"9.656"
var y = (9.656).toPrecision(2); //"9.7"
var z = (9.656).toPrecision(5); //"9.6560"
A data type
var x = "text";
var y = 'text';
Strings are immutable, they cannot be edited, only replaced. Thus, all string methods return a string.
Adding a Number and a String will result in the Number being treated as a String.
var x = "Summation: " + 3 + 5; //"Summation: 35"
JavaScript will attempt to convert Strings to Numbers in specifically numeric operations.
var x = "100" / "20"; //5
Escape character \
var x = "with \"double quotes\" included";
\" double quote
\' single quote
\\ backslash
Outdated escape characters, not really applicable to the web:
\b backspace
\r carriage return
\f form feed
\t tab horizontal
\v tab vertical
Some browser support using a single slash to type text across multiple lines
var x = "skjfhksdfuhskfushkdfusdf\
skdfjshkfusdfhkusdf";
Template literals are string literals with embedded expressions.
Template literals are surrounded by backticks.
var noun = "cat";
var text = `I got a new ${noun} yesterday.`; //I got a new cat yesterday.
var a = 3;
var b = 5:
text = `${a} + ${b} = ${a+b}`; //3 + 5 = 8
Tagged literals:
function myTag(stringArray, expression1, expression2)
{
//stringArray = ['', ' is ', ' than me.']
//expression1 = 'Bob'
//expression2 = 35
var adjective = (expression2 < 30) ? "younger" : "older";
return expression1 + stringArray[1] + adjective + stringArray[2];
}
var name = "Bob";
var age = 35;
console.log(myTag`${name} is ${age} than me.`); //prints 'Bob is older than me.'
Tagged literals can return any data type, not just a string.
You can access raw strings in tagged literals (strings without special characters interpreted):
function myTag(stringArray)
{
console.log(stringArray.raw[0]); //prints the '\n' instead of an endline
}
myTag`text line 1\ntext line 2`;
Raw strings can also be written this way:
console.log(String.raw`text line 1\ntext line 2`);
Length
var x = "text";
var a = x.length; //4
IndexOf
string.indexOf(text) returns int
string.lastIndexOf(text) returns int
var x = "text";
var a = x.indexOf("t"); //0
var b = x.indexOf("ex"); //1
var c = x.indexOf("b"); //-1
var d = x.indexOf("t", 2); //start looking at index 2, so result is 3
var x = "text";
var a = x.lastIndexOf("t"); //3
var b = x.lastIndexOf("t", 2); //start looking at index 2, so result is 0
Search
Similar to indexOf, but more powerful. Can search for regular expressions.
Does not accept a starting index.
Slice
string.slice(startIndexInclusive, endIndexExclusive) returns string
var x = "The quick brown dog...";
var a = x.slice(4, 8); //"quic"
var b = x.slice(-18, -14); //counts from end of string, so "quic"
var c = x.slice(4); //goes to end of string, so "quick brown dog..."
Substring
Like slice, but cannot accept negative indexes.
Substr
string.substr(startIndexInclusive, length) returns string
var x = "The quick brown dog...";
var a = x.substr(4, 8); //"quick br"
var b = x.substr(-10, 8); //counts from end of string, so "quick br"
var c = x.substr(4); //goes to end of string, so "quick brown dog..."
Replace
Replaces the (default) first matching substring found.
Accepts regular expressions.
Is case-sensitive.
string.replace(search, replacement) returns string
var x = "The dog is a dog";
var y = x.replace("dog", "cat"); //"The cat is a dog"
Replace all
var x = "The dog is a dog";
var y = x.replace(/dog/g, "cat"); //"The cat is a cat"
ToUpperCase
string.toUpperCase() returns string
ToLowerCase
string.toLowerCase() returns string
Concat
string.concat(stringA, stringB, ...) returns string
var x = "Hello".concat(" World"); //"Hello World"
var y = "Hello".concat(" ", "Wo", "rld"); //"Hellow World"
CharAt
string.charAt(index) returns string
string.charCodeAt(index) returns int
var x = "Hello".charAt(0); //"H"
var y = "Hello".charCodeAt(0); //72
Accessing strings as if they were arrays is not supported by all browers. You cannot edit a string this way.
var x = "Hello"[0];
Split
Convert a string into an array of strings.
string.split(seperator) returns []
var x = "Hello".split(""); //["H", "e", "l", "l", "o"]
var y = "a,b,c".split(","); //["a", "b", "c"]
var z = "Name|Address|Phone".split("|"); //["Name", "Address", "Phone"]
var a = "Hello".split(); //["Hello"]
Includes
string contains the substring
var containsSubstring = "abcdef".includes("cd");
A data type
var x = true;
var y = false;
Anything with a real value will evaluate to true.
Ex: 100, -3.14, "word", "false", true
Anything without a real value will evaluate to false.
Ex: 0, -0, "", undefined, false, NaN
A reference data type. In Javascript, they are structurally similar to maps/dictionaries.
Objects are made up of properties and methods.
- Properties are name:value pairs.
- Methods are name:function pairs.
Object literal:
var obj = {
firstName: "John",
lastName: "Smith",
fullName: function() {
return this.firstName + " " + this.lastName;
}
};
Object constructor:
var obj = new Object();
obj.firstName = "John";
obj.lastName = "Smith";
obj.fullName = function() {
return this.firstName + " " + this.lastName;
};
Reusable object definition:
function objFactory(lastName, firstName)
{
return {
firstName: firstName,
lastName: lastName,
fullName: function() {
return this.firstName + " " + this.lastName;
}
};
}
var obj = objFactory("Smith", "John");
console.log(obj.fullName()); //outputs "John Smith"
Constructor Design Pattern:
function MyObject(lastName, firstName)
{
this.lastName = lastName;
this.firstName = firstName;
this.fullName = function() {
return this.firstName + " " + this.lastName;
};
}
var obj = new MyObject("Smith", "John");
console.log(obj.fullName()); //outputs "John Smith"
Create from prototype:
var myPrototype = {
firstName: "John",
lastName: "Smith",
fullName: function() {
return this.firstName + " " + this.lastName;
}
};
var myCustomer = Object.create(myPrototype);
console.log(myCustomer.fullName()); //outputs "John Smith"
var x = obj.lastName;
var y = obj["lastName"];
Property names can be strings. You can reference string indexes with dot notation or bracket notation.
var a = { firstName: "John" };
console.log(a.firstName); //outputs "John"
console.log(a["firstName"]); //outputs "John"
var b = { "firstName": "John" };
console.log(b.firstName); //outputs "John"
console.log(b["firstName"]); //outputs "John"
Property names can be numbers. To reference number indexes, bracket notation is required.
var c = { 9: "a number" };
console.log(c[9]); //outputs "a number"
Properties have attributes that all default to true:
- configurable: can be deleted or changed/edited
- enumerable: property can be returned in a for/in loop
- writable: property can be changed/edited
(ECMAScript also has access modifiers (getters/setters))
All properties on built-in prototypes are non-enumerable, by standard.
When adding your own methods or properties to built-in prototypes, it is suggested to use the "defineProperty" method so you can make the property non-enumerable.
Inherited properties are defined on the object's prototype. Inherited properties can be overridden without affecting the prototype.
var prototypeA = { a: "Apple" };
var objectC = Object.create(prototypeA);
console.log(objectC.a); //Apple
objectC.a = "Animal";
console.log(objectC.a); //Animal
console.log(prototypeA.a); //Apple
prototypeA.a = "Almond";
console.log(objectC.a); //Animal
console.log(prototypeA.a); //Almond
Own properties are defined on just an instance of the object. The own properties of a prototype are the inherited properties of a derived object.
Some operations consider all properties in the prototype chain, some consider only the own properties.
var z = obj.fullName();
JSON stands for JavaScript Object Notation. It is the syntax JavaScript expects when defining objects.
Since it is a simple, clear text format, many other programs use JSON as well.
Syntax:
- Data is in name:value pairs
- Data is separated by commas
- Curly braces hold objects
- Square brackets hold arrays
In the strictest format, all labels and values are written as strings.
Ex:
var x = {
"people": [
{ "name": "john" },
{ "name": "dick" },
{ "name": "harry" }
],
"time": "12:30"
};
Convert Javascript object to JSON string:
var obj = { a: 1 };
var x = JSON.stringify(obj);
Convert JSON string into JavaScript object:
var str = "{ a: 1 }";
var x = JSON.parse(str);
Prototypes are like parent classes in C#.
All objects have a "prototype" property. This points to the parent object of this object. Prototypes can point to other prototypes, in what is called the "prototype chain".
Example:
var prototypeA = { a: "Apple" };
var prototypeB = Object.create(prototypeA);
Object.defineProperty(prototypeB, "b", { value: "Banana" });
var objectC = Object.create(prototypeB);
objectC.c = "Citrus";
console.log(objectC.a); //Apple
console.log(objectC.b); //Banana
console.log(objectC.c); //Citrus
console.log(prototypeA.isPrototypeOf(prototypeB)); //true
console.log(prototypeA.isPrototypeOf(objectC)); //true
console.log(prototypeB.isPrototypeOf(objectC)); //true
console.log(prototypeB.isPrototypeOf(prototypeA)); //false
console.log(objectC.isPrototypeOf(prototypeB)); //false
console.log(objectC.isPrototypeOf(prototypeA)); //false
"isPrototypeOf" differs from "instanceof". "prototypeA.isPrototypeOf" will compare "prototypeA" while "instanceof prototypeA" will compare "prototypeA.prototype".
Changes to the prototype immediately affect all derived objects:
prototypeA.a = "Almond";
console.log(objectC.a); //Almond
List all enumerable own properties:
var keys = Object.keys(myObject);
List all own properties:
var keys = Object.getOwnPropertyNames(myObject);
Example:
var prototypeA = { a: "Apple" };
var objectB = Object.create(prototypeA);
objectB.b = "Banana";
Object.defineProperty(objectB, "c", { value: "Carrot", enumerable: false });
console.log(Object.keys(objectB)); //["b"]
console.log(Object.getOwnPropertyNames(objectB)); //["b", "c"]
Check that an object has a particular own property:
var hasProperty = myObject.hasOwnProperty(propertyName);
List all enumerable own AND inherited properties:
for(var propertyName in myObject)
{
}
Check that an object has a particular inherited property:
var hasProperty = ("propertyName" in myObject);
Remove property (only affects current object, not entire prototype chain):
delete myObject.propertyName;
//or
delete myObject["propertyName"];
Set property:
myObject.propertyName = "value";
//or
myObject["propertyName"] = "value";
//or
Object.defineProperty(myObject, "propertyName", { value: "value", enumerable: false });
//or
Object.defineProperty(myObject, { propertyA: { value: "value", enumerable: false }, propertyB: { value: "otherValue", enumerable: true } });
(see also Array Destructuring)
Always surround the whole statement with the grouping operator ( ) so that the { object } is not interpreted as a code block.
- Grouping operator is not needed if the statement starts with a keyword, such as "var".
- If you do use the grouping operator, make sure the previous statement ends with a semi-colon (;) so that this ( ) is not interpreted as function arguments.
Pull out multiple property values by name:
var a, b, c;
({ b, a, c } = { a: 10, b: 20, x: 30 });
console.log(a); // outputs 10
console.log(b); // outputs 20
console.log(c); // outputs undefined because no property named 'c'
Pull out multiple property values by different names:
var {a: x, b: y} = {a: 42, b: true};
console.log(x); // outputs 42
console.log(y); // outputs true
Convert invalid names to valid names:
var { 'fizz-buzz': fizz_buzz } = { 'fizz-buzz': true };
console.log(fizz_buzz); //outputs true
Default values:
var {a = 1, b = 2} = {a: 3};
console.log(a); // outputs 3
console.log(b); // outputs 2
Different names plus default values:
var {a: x = 1, b: y = 2} = {a: 3};
console.log(x); // outputs 3
console.log(y); // outputs 2
Computed property names:
var key = 'x';
var {[key]: name} = {x: 'Bob'};
console.log(name); //outputs Bob
Nested data:
var metadata = {
title: 'Scratchpad',
translations: [
{
locale: 'de',
localization_tags: [],
last_edit: '2014-04-14T08:43:37',
url: '/de/docs/Tools/Scratchpad',
title: 'JavaScript-Umgebung'
},
{
locale: 'en',
localization_tags: [],
last_edit: '2018-11-14T03:02:59',
url: 'google.com',
title: 'Other'
}
]
};
var {title: englishTitle, translations: [{title: localeTitle}, {title: otherTitle}]} = metadata;
console.log(englishTitle); // outputs Scratchpad
console.log(localeTitle); // outputs JavaScript-Umgebung
console.log(otherTitle); // outputs Other
Looping through data:
var people = [
{
name: 'Mike Smith',
family: { mother: 'Jane Smith', father: 'Harry Smith', sister: 'Samantha Smith' },
age: 35
},
{
name: 'Tom Jones',
family: { mother: 'Norah Jones', father: 'Richard Jones', brother: 'Howard Jones' },
age: 25
}
];
for (var {name: n, family: {father: f}} of people)
{
console.log('Name: ' + n + ', Father: ' + f);
}
// outputs Name: Mike Smith, Father: Harry Smith
// outputs Name: Tom Jones, Father: Richard Jones
Default values in function parameters:
function myFunc({size = 'big', coords: {x = 0, y = 0}, radius = 99} = {}) {
console.log(size, x, y, radius);
}
myFunc({ coords: {x: 18}, radius: 22 }); //outputs big 18 0 22
The final "= {}" is so that the function can be called with zero parameters.
Default values in function parameters can refer to earlier parameters:
function myFunc([a, b] = [1, 2], {x: c} = {x: a + b})
{
return a + b + c;
}
console.log(myFunc()); //outputs 6 because 1 + 2 + (1 + 2)
Create clones of objects:
var objA = { a: 0, b: 1, c: { d: 2 } };
var objB = { x: { ...objA } };
console.log(objB); //outputs { x: { a: 0, b: 1, c: { d: 2 } } }
Arrays are a special object type.
var x = ["a", "b", "c"];
console.log(x[1]); //"b"
var x = new Array("a", "b", "c"); //not recommended
console.log(x[1]); //"b"
var y = new Array(40, 100); //creates an array of two integers
var z = new Array(40); //creates an empty array of length 40
A single array can hold elements of different types.
Indexes start at zero.
var x = ["a", "b", "c"];
x[0] = "A";
console.log(x[0]); //"A"
JavaScript does not support associative arrays aka hashes - indexes are always integers, not strings.
If an primitive is expected, arrays are automatically converted to a comma-separated-list.
var x = ["a", "b", "c"];
console.log(x); //"a,b,c"
You can do the same thing explicitly with array.toString()
length = returns length of array
to loop over each element of an array
for(let element of array) {
}
includes - does the array include or contain the value?
var boolResult = array.includes("a");
adding/removing
var x = [];
x.push(element); //add element to end
x.unshift(element); //add element to beginning
x[x.length] = element; //add element to end - not recommended
var y = x.pop() //remove element from end and returns it
var z = x.shift() //remove element from beginning and returns it
toString
var x = ["a", "b", "c"];
var y = x.toString(); //"a,b,c"
join
var x = ["a", "b", "c"];
var y = x.join(" | "); //"a | b | c"
remove or delete
var x = ["a", "b", "c"];
delete x[1]; //changes "b" to undefined
merge or concat
var x = ["a", "b", "c"];
var y = ["d", "e"];
var z = x.concat(y); //["a", "b", "c", "d", "e"]
concat can accept any number of arguments
reverse - edits current array
var x = ["a", "e", "t", "d", "h"];
x.reverse(); //["h", "d", "t", "e", "a"]
slice - copies part of an array into another array
array.slice(startIndex) returns array copied from startIndex to end
var x = ["a", "b", "c", "d", "e", "f"];
var y = x.splice(3); //["d", "e", "f"]
array.slice(startIndex, endIndex) returns array copied from startIndex to endIndex, not including endIndex
var x = ["a", "b", "c", "d", "e", "f"];
var y = x.splice(1, 4); //["b", "c", "d"]
splice - remove x elements starting at startIndex.
array.splice(startIndex, x);
Remove x elements starting at startIndex, and insert any number of new elements starting at startIndex.
array.splice(startIndex, x, newA, newB, newC);
sort
array.sort() defaults to sorting alphabetically, it edits the current array
var x = ["g", "e", "u", "d", "w"];
x.sort(); //["d", "e", "g", "u", "w"]
array.sort(compareFunction) will sort using the function you provide
var x = [45, 3, 21, 6, 9, 11];
x.sort(); //[11, 21, 3, 45, 6, 9]
x.sort(numericCompare); //[3, 6, 9, 11, 21, 45]
function numericCompare(a, b) {
return a - b;
}
The compare function must return a numeric value:
>0 if a>b
0 if a==b
<0 if a<b
sorting an array randomly
function randomCompare() {
return 0.5 - Math.random();
}
array.map(mapFunction) will apply a function to each element of the array and return the array of results
var mappedResult = array1.map((x) => x * 2);
array.filter(filterFunction) will return a new array containing just the elements that returned TRUE in the filterFunction
var filteredResult = array.filter((element) => element.color == "red");
How to verify an object is an array? There are several methods.
This looks the most reliable
if(x instanceof Array) {}
(see also Object Destructuring)
Setting multiple values at once:
var a, b, c;
[a, b, c] = [1, 2, 3];
console.log(a + ", " + b + ", " + c); //outputs 1, 2, 3
Or
var [a, b, c] = [1, 2, 3];
console.log(a + ", " + b + ", " + c); //outputs 1, 2, 3
With rest parameter:
var a, b, c;
[a, b, ...c] = [1, 2, 3, 4, 5, 6];
console.log(a + ", " + b); //outputs 1, 2
console.log(c); //outputs [3, 4, 5, 6]
Provide defaults in case the unpacked value is undefined:
var [a=5, b=7] = [1];
console.log(a + ", " + b); //outputs 1, 7
Swap values in one statement:
var a = 1, b = 3;
console.log(a + ", " + b); //outputs 1, 3
[a, b] = [b, a];
console.log(a + ", " + b); //outputs 3, 1
Parse a return value:
function f() {
return [1, 2, 3, 4];
}
var [a, b] = f();
console.log(a + ", " + b); //outputs 1, 2
var [c,,,d] = f();
console.log(c + ", " + d); //outputs 1, 4
Spread syntax allows an iterable collection (such as an array or string) to be automatically divided into an argument list.
Function parameters:
function sum(x, y, z)
{
return x + y + z;
}
var numbers = [1, 2, 3];
console.log(sum(...numbers)); //outputs 6
function myFunc(v, w, x, y, z)
{
console.log(v, w, x, y, z);
}
var args = [0, 1];
myFunc(-1, ...args, 2, ...[3]); //outputs -1 0 1 2 3
Array literals:
var numbers = [1, 2, 3];
var array = [...numbers, 'A', 'B', 'C'];
console.log(array); //outputs [1, 2, 3, 'A', 'B', 'C']
Clone array: (only goes one level deep)
var arrayA = [1, 2, 3];
var arrayB = [...arrayA];
An object type.
JavaScript string format: Tue Oct 24 2017 20:37:53 GMT-0600 (Mountain Standard Time)
JavaScript integer format: 1508899073536 (milliseconds since 1970/1/1 00:00:00)
Current date
var x = Date();
var y = new Date();
New date
var x = new Date(milliseconds);
var y = new Date(dateString);
var a = new Date(year, month, day);
var b = new Date(year, month, day, hours);
var c = new Date(year, month, day, hours, minutes);
var d = new Date(year, month, day, hours, minutes, seconds);
var e = new Date(year, month, day, hours, minutes, seconds, milliseconds);
Date string examples
var a1 = new Date("2017-11-24"); //ISO International Standard - most reliable across browsers - "YYYY-MM-DD"
var a2 = new Date("2017-11-24T12:00:00Z"); //ISO International Standard - with time and zone
var a3 = new Date("2017-11-24T12:00:00-06:30"); //ISO International Standard - with time and UTC relative zone
var b = new Date("11/24/2017"); //short date - "MM/DD/YYYY" - not supported by all browsers
var c1 = new Date("October 24 2017"); //long date - "MMMM DD YYYY"
var c2 = new Date("Oct 24 2017"); //long date - "MMM DD YYYY"
var c3 = new Date("24 Oct 2017"); //long date - "DD MMM YYYY"
var d = new Date("Tuesday October 24 2014"); //full date
var e = new Date("October 24, 2017 11:13:00");
ISO dates can be set with just YYYY-MM or YYYY.
The timezone defaults to the browser's timezone.
"Z" is the timezone code for UTC aka GMT.
Months range from 0 to 11.
date.toString() returns string
date.toUTCString() returns string
date.toDateString() returns string
var date = new Date();
var x = date.toString(); //Tue Oct 24 2017 20:44:45 GMT-0600 (Mountain Standard Time)
var y = date.toUTCString(); //Wed, 25 Oct 2017 02:45:22 GMT
var z = date.toDateString(); //Tue Oct 24 2017
var x = Date.parse("March 21, 2012"); //milliseconds since 1970/01/01 00:00:00
getFullYear() //yyyy
getDate() //day 1-31
getDay() //weekday 0-6 with 0 as Sunday
getHours() //hours 0-23
getMinutes() //minutes 0-59
getSeconds() //seconds 0-59
getMilliseconds() //milliseconds 0-999
getTime() //milliseconds since 1970/01/01 00:00:00
Everything except getDay() has a "set" conterpart.
Dates can be compared with normal comparison operators.
Math.PI
Math.E
...others
Math.random() returns a random number in range [0, 1) : includes 0, excludes 1
var x = Math.floor(Math.random() * 100); //random value from 0 to 99
general function to get an integer from min to max, both included
function getRandomInteger(min, max)
{
return Math.floor(Math.random() * (max - min + 1) ) + min;
}
var x = Math.min(0, 150, -37, 45.7, -19); //-37
var y = Math.max(0, 150, -37, 45.7, -19); //150
getting the min/max value from an array
var x = [5, 8, 2, 3, 8, 4, 6, 2];
var y = Math.min.apply(null, x); //2
var z = Math.max.apply(null, x); //8
Math.round(number) returns integer
var x = Math.round(4.7); //5
var y = Math.round(4.4); //4
Math.ceil(number) rounds up
var x = Math.ceil(4.7); //5
var y = Math.ceil(4.4); //5
Math.floor(number) rounds down
var x = Math.floor(4.7); //4
var y = Math.floor(4.4); //4
Math.pow(base, exponent) returns number = base ^ exponent
var x = Math.pow(5, 2); //25
Math.sqrt(number) returns number
var x = Math.sqrt(64); //8
Math.abs(number) returns the absolute value of the number
var x = Math.abs(64); //64
var y = Math.abs(-64); //64
sin
cos
tan
asin
acos
atan
exp
log
Can be used with string methods: match, replace, search, split.
Can be used with RegExp methods: exec, test
RegExp.test(string) returns true if a match is found.
RegExp.exec(string) returns the next match, or null. Must be called repeatedly to get all matches.
var regex = new RegExp("b+", "g");
var matches = getMatches(regex, "abbbcbbc");
function getMatches(regex, text)
{
var matches = [];
var match = null;
while((match = regex.exec(text)) !== null)
{
matches.push(match);
}
return matches
}
Warning: there is a flaw in the Javascript RegExp exec process. If the RegExp matches an empty string, which is zero-width, it will not increment the cursor index and will enter an infinite loop.
To avoid that:
var regex = new RegExp("b+", "g");
var matches = getMatches(regex, "abbbcbbc");
function getMatches(regex, text)
{
var matches = [];
var match = null;
while((match = regex.exec(text)) !== null)
{
matches.push(match);
if (match.index === regex.lastIndex)
regex.lastIndex++;
}
return matches
}
You can test these on pattern "a*" and text "a aa aaa".
If the global flag (g) is used, RegExp.exec will return each match then null.
If the global flag is not used, RegExp.exec will return the same first match indefinitely.
You can check the 'flag' property of the RegExp object to determine what flags were used.
if(regexp.flags.indexOf('g') == -1)
{
//only call exec once
}
using match
const text = "abc 123 def";
const regex = new RegExp("(\w{3}) (\d{3})");
const matches = text.match(regex); //array ["abc 123", "abc", "123"] which is the full match then each () capture group
returns null if there are no matches
var regex1 = /b+/g;
var regex2 = new RegExp("b+", "g");
Use the literal for expressions that will not change and will be used many times. It is only compiled once.
Use the constructor for expressions that are only used once or come from user input.
Javascript and C# use the same regular expression patterns. See C# notes, Regular Expression Patterns section.
Or see Lessons section, Regular Expressions in javascript.
Exceptions:
Options:
g = global search
i = case insensitive
m = multiline search
u = interpret pattern as unicode
y = sticky = start at current position in string
Grouping Constructs:
named groups are not allowed, just ordinals
in fact, looks like only basic (subexpressions) are allowed
Substitions
$_ doesn't work for substituting entire input string
Simple:
var text = "abcbbcba";
text = text.replace(/c/g, "C"); //text="abCbbCba"
With ordinal groups:
var text = "There is 1 cat and 11 dogs.";
text = text.replace(/(\D)1(\D)/g, "$1one$2"); //text="There is one cat and 11 dogs."
With custom alterations:
//capitalizes only the first alphabet-character
var text = " this sentence.";
text = text.replace(/(\w)/, w => w.toUpperCase()); //text=" This sentence"
function MyFunction() {
}
function Area(width, height) {
return width * height;
}
Parameters: the variable names listed in the declaration.
Arguments: the values passed into the function.
Returns: undefined by default
Invocation
var area = Area(5, 10);
Function as variable
var myFunction = Area;
console.log(myFunction.name); //outputs Area
Functions can be declared within "if" statements, but this is implemented differently in different Javascript engines so it is not recommended. Use function expressions instead if you need this design pattern.
You can create anonymous functions as expressions.
var multiply = function(a, b) {
return a * b;
}
console.log(multiply(3, 4)); //outputs 12
//The function name defaults to the name of the variable.
console.log(multiply.name); //outputs multiply
An assigned function expression may call itself because the assigned variable is within scope.
var fibonacci = function (num) {
if(num <= 0) return 0;
if(num <= 2) return 1;
return fibonacci(num - 1) + fibonacci(num - 2);
};
console.log(fibonacci(6)); //outputs 8, which is the 6th fibonacci number
Function expressions are frequently used for callbacks.
function run(callback)
{
console.log(callback.name); //outputs empty string
return callback();
}
var x = run(function(){ return 10; });
console.log(x); //outputs 10
Function expressions are frequently used in objects.
var math = {
'myFunc': function () {
console.log('a');
}
};
math.myFunc(); //outputs a
You may specify a name for a function expression. This is recommended to improve legibility of the code, of error messages, and of stack traces.
function run(callback)
{
console.log(callback.name); //outputs display10
return callback();
}
var x = run(function display10(){ return 10; });
console.log(x); //outputs 10
When the function name is specified, the function may call itself recursively. The function cannot be called by name outside of its own scope.
function run(callback)
{
return callback(6);
}
var result = run(function fibonacci(num) {
if(num <= 0) return 0;
if(num <= 2) return 1;
return fibonacci(num - 1) + fibonacci(num - 2);
});
console.log(result); //outputs 8
You can declare and define a function dynamically with the function constructor. This is not recommended as is has security risks and performance issues.
These functions are defined the same way:
var constructedSum = new Function('a', 'b', 'return a + b');
function declaredSum(a, b)
{
return a + b;
}
console.log(constructedSum(2, 6)); // outputs 8
console.log(declaredSum(2, 6)); // outputs 8
The arguments passed to "new Function" are any number of parameter names, ending with the function body.
The parameter names may be specified as one comma-delimited string.
Everything is parsed when the function object is created.
The "new" keyword is optional for some reason.
var constructedSum = Function('a', 'b', 'return a + b');
console.log(constructedSum(2, 6)); // outputs 8
Constructed functions are always in the global scope, no matter where they where created from. They do not create closures to their creation contexts.
var x = 10;
function useConstructor() {
var x = 20;
return new Function('return x;');
}
function useConstructorImmediately() {
var x = 30;
return (new Function('return x;'))();
}
function useExpression() {
var x = 40;
return function() {
return x;
}
}
var a = useConstructor();
var b = useConstructorImmediately();
var c = useExpression();
console.log(a()); //outputs 10
console.log(b); //outputs 10
console.log(c()); //outputs 40
One use of the function constructor is to access the global object from a bound scope:
(function() {
'use strict';
var global = new Function('return this')();
console.log(global === window); //outputs true
console.log(this === window); //outputs false
})();
Arrow function expressions are notably different from other ways of specifying a function:
- They do not create a local "this". They access the "this" from the surrounding scope.
- They do not have a local "arguments" object.
- They do not have a "super" object.
- They cannot be used as constructors (with the "new" keyword).
- They do not have a "prototype" property.
- They cannot be used as generators.
Why use an arrow function?
- the same functionality can be written shorter
- since they do not create their own "this" value, they can be used to simplify specific use cases
Syntax for parameters:
var a = (param1,..,paramN) => { statements };
//these are the same
var b1 = (param1) => { statements };
var b2 = param1 => { statements };
var c = () => { statements };
Default parameters and the rest parameter are supported.
Syntax for function body:
var a = (param1,..,paramN) => { statements };
//these are the same
var b1 = (param1,..,paramN) => one_expression; //"concise body" has an implied return
var b2 = (param1,..,paramN) => { return one_expression }; //"block body" has an explicit return
To return an object literal, wrap it in a grouping operator:
var a = (param1,..,paramN) => ({name: value});
You cannot put line breaks between the parameters and the arrow.
Writing shorter code:
var elements = [ 'Hydrogen', 'Helium', 'Lithium', 'Beryllium' ];
var a = elements.map(function(element) { return element.length; });
var b = elements.map(element => { return element.length; });
var c = elements.map(element => element.length);
var d = elements.map(({ length }) => length); //I'm not sure how this one works; it may be related to destructuring
The old way of accessing an outer "this" value:
function Person() {
var that = this;
that.age = 0;
setInterval(function growUp() {
that.age++;
}, 1000);
}
var person = new Person();
The new way, using arrow functions:
function Person(){
this.age = 0;
setInterval(() => {
this.age++;
}, 1000);
}
var person = new Person();
When to NOT try to use this:
var chopper = {
owner: 'Zed',
getOwner: () => this.owner //won't work because "this" is referring to undefined or the "window" or the global object, depending on the engine
};
console.log(chopper.getOwner());
Even with "use strict" turned on, arrow functions will not create their own "this" value.
Arrow functions don't have their own "arguments" object. They use the one from the surrounding scope.
function plusOne(n) {
var f = () => arguments[0] + n;
return f(1);
}
console.log(plusOne(3)); // outputs 6 because n + n
Use a rest parameter instead:
function plusOne(n) {
var f = (...rest) => rest[0] + n;
return f(1);
}
console.log(plusOne(3)); // outputs 4
If you get parsing errors, wrap the arrow function in the grouping operators:
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {}; // SyntaxError: invalid arrow-function arguments
callback = callback || (() => {}); // ok
Examples of simple lambdas written with arrow function expressions:
var nums = [5, 6, 13, 0, 1, 18, 23];
var sum = nums.reduce((a, b) => a + b); // 66 which is the sum of all the elements
var even = nums.filter(v => v % 2 == 0); // [6, 0, 18] which are all the even values
var double = nums.map(v => v * 2); // [10, 12, 26, 0, 2, 36, 46] which is the double of each value
var anyZero = nums.some(item => item === 0); // true because at least 1 element equaled 0
If you want to use an arrow function for an event handler, you'll need to pass the "event" object as an argument since "this" won't be available.
button.addEventListener('click', function () {
this.classList.toggle('on');
});
//versus
button.addEventListener('click', (event) => {
event.currentTarget.classList.toggle('on');
});
Benefits:
- shorter
- more legible
- requires a function name which is a good coding practice
var collection = {
items: [],
add: function(...items) {
this.items.push(...items);
},
get: function(index) {
return this.items[index];
}
};
collection.add('C', 'Java', 'PHP');
console.log(collection.get(1)); //outputs Java
is the same as
var collection = {
items: [],
add(...items) {
this.items.push(...items);
},
get(index) {
return this.items[index];
}
};
collection.add('C', 'Java', 'PHP');
console.log(collection.get(1)); //outputs Java
Classes always use shorthand method definitions:
class Star {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
var sun = new Star('Sun');
console.log(sun.getName()) //outputs Sun
Computer property names with shorthand method definitions:
var addMethod = 'add';
var getMethod = 'get';
var collection = {
items: [],
[addMethod](...items) {
this.items.push(...items);
},
[getMethod](index) {
return this.items[index];
}
};
collection[addMethod]('C', 'Java', 'PHP');
console.log(collection[getMethod](1)); //outputs Java
A generator function returns an iterator object. The function will not be executed all at once; its state will be saved and execution will step forward to the next "yield" expression each time you call "next()" on the iterator.
Generators end on:
- end of function
- a return statement, in which case the returned value is the last "yielded" value
- an uncaught exception
function* generator(i) {
yield i++;
yield i++;
yield i++;
}
var iterator = generator(0);
console.log(iterator.next().value); //outputs 0
console.log(iterator.next().value); //outputs 1
console.log(iterator.next().value); //outputs 2
console.log(iterator.next().value); //outputs undefined
You can loop through all the yielded values of an iterator:
function* generator() {
yield 'a';
yield 'b';
yield 'c';
}
var str = "";
for (let val of generator()) {
str = str + val;
}
console.log(str); // outputs abc
A generator can be an anonymous expression:
var x = function*(y) {
yield y;
};
Iterator.next() returns an object like { 'value': yielded_value, 'done': false }.
When the last "yield" expression is reached, "done" is returned as false. All future calls to "next()" result in { 'value': undefined, 'done': true }.
If there are any statements after the last "yield" expression, they will be run if you call "next()" one more time. After that, there is nothing left in the generator function to run.
function* generator(i) {
yield i++;
console.log("more code");
}
var iterator = generator(0);
console.log(iterator.next().value); //outputs 0
console.log(iterator.next().value); //outputs "more code" and undefined
console.log(iterator.next().value); //outputs undefined
You can pass an argument in "next()", but I'm not clear on what that does.
But here is an interesting example:
function* generator()
{
var reply = yield 'What is the letter?';
console.log(reply);
reply = yield 'What is the letter after that?'
console.log(reply);
}
var iterator = generator();
console.log(iterator.next().value);
console.log(iterator.next('A').value);
iterator.next('B');
//outputs What is the letter?
//outputs A
//outputs What is the letter after that?
//outputs B
You cannot pass an argument to the FIRST call to "next()" on an iterator.
Yield* can delegate execution to another generator:
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i) {
yield i;
yield* anotherGenerator(i);
yield i + 10;
}
var gen = generator(0);
console.log(gen.next().value); // outputs 0
console.log(gen.next().value); // outputs 1
console.log(gen.next().value); // outputs 2
console.log(gen.next().value); // outputs 3
console.log(gen.next().value); // outputs 10
Or over any iterable, such as an array of values:
function* generator(i) {
yield i;
yield* [-1, -2, -3];
yield i + 10;
}
var gen = generator(0);
console.log(gen.next().value); // outputs 0
console.log(gen.next().value); // outputs -1
console.log(gen.next().value); // outputs -2
console.log(gen.next().value); // outputs -3
console.log(gen.next().value); // outputs 10
Yield can only be used within the scope of a generator, so this is not valid:
function* generator() {
[1, 2].forEach(function (item) {
yield item; // SyntaxError: yield expression is only valid in generators
});
}
var iterator = generator();
console.log(iterator.next());
Example use case: get unlimited random values from a set of options:
function * randomFrom(...arr) {
while (true)
{
yield arr[Math.floor(Math.random() * arr.length)];
}
}
const getRandom = randomFrom(1, 2, 5, 9, 4);
console.log(getRandom.next().value); // returns random value
You can create a generator function dynamically, but it is not recommended due to security and performance issues.
It works like the Function Constructor:
var GeneratorFunction = Object.getPrototypeOf(function*(){}).constructor
var g = new GeneratorFunction('a', 'yield a * 2');
var iterator = g(10);
console.log(iterator.next().value); // 20
Function declarations (with definition) are hoisted to the top of the current scope, which means either (A) the top of the enclosing function or (B) the global scope. Therefore you can call a function before (earlier in the file) it is declared.
myFunc(); //outputs 1
function myFunc()
{
console.log(1);
}
If more than one function has the same name (regardless of parameters), the last function definition will overwrite the earlier ones.
It is standard practice to put all function declarations/definitions at the end of your file, and keep the high-level statements at the beginning.
Only function declarations are hoisted. Function expressions and arrow functions are not hoisted.
Code blocks create a local scope. Functions are a code block, so they have a local scope that includes their arguments.
Functions can still make use of any global variables, provided their names are different from the local variables.
If you assign a value to an undeclared variable, its scope will automatically be global. Don't do this.
Generally avoid global variables, especially in web development. You may be overwriting another script's variable or function, and they could overwrite yours.
You can pass any number of parameters to any function.
If there are extra parameters, they will be ignored.
If there are not enough parameters, the extras will be set to undefined.
A full list of the parameters passed to a function is accessible through the "arguments" object.
Even if there are not enough parameters to capture all the passed arguments, they will all be in the "arguments" object.
function myFunc(a, b, c)
{
console.log(arguments[0]); //outputs value of a
console.log(arguments[1]); //outputs value of b
console.log(arguments[2]); //outputs value of c
}
Default parameters provide a default value for a parameter in case no value is passed in:
function add(a, b=2) {
return a + b;
}
console.log(add(1)); //outputs 3
console.log(add(1, 1)); //outputs 2
console.log(add(1, null)); //outputs 1
A function may have one rest parameter, as the last parameter.
A rest parameter will capture all excess arguments in one array.
function myFunc(a, b, ...rest)
{
console.log(rest);
}
myFunc(1, 2, 3, 4, 5); //outputs [3, 4, 5]
A rest parameter can have any name.
The arity of a function is the number of arguments it expects.
//arity = 2
//sum.length = 2
function sum(a, b)
{
return a + b;
}
//arity = 2
//sum.length = 0
function sum()
{
return arguments[0] + arguments[0];
}
A closure is a function defined inside another function. A closure can only be called by its outer-function.
Update: it may be more correct to say a closure is a any function plus its enclosing scope, since top level functions do have access to the global scope
The inner-function's scope includes itself, the outer-function, and global. I.e. the closure is a combination of the inner-function and the lexical environment it was declared in.
function A(one, two)
{
B();
function B()
{
console.log(one);
console.log(two);
}
}
A(1, 2); //prints 1 2
Closures have access to the outer-function's scope even after the outer-function has returned.
function A(one, two)
{
var three = 3;
return function() {
return one + two + three;
};
}
var addition = A(1, 2);
console.log(addition()); //prints 6
That includes other inner-functions of the outer-function:
function A()
{
function B() { console.log('B'); }
return function() { B(); };
}
A()(); //prints 'B'
Closures do this by storing references to the outer-function's variables. Therefore, if the value of those variables change before the closure is run, the closure will be working with the most up-to-date values.
function property()
{
var property = 10;
return {
getProperty: function() { return property; },
setProperty: function(value) { property = value; }
};
}
var propertyObject = property();
propertyObject.setProperty(12);
console.log(propertyObject.getProperty()); //prints 12
The "getProperty" and "setProperty" here are called "privileged methods" because they are the only way available to access the internal "property".
Closures have access to the scope of their outer-function. If the outer-function is also a closure, then the scopes add up. Essentially, the inner-most closure has access to the scope of all wrapping functions up to the outer-most wrapping function.
function sum(a){
var e = 1;
return function(b){
return function(c){
return function(d){
return a + b + c + d + e;
}
}
}
}
console.log(sum(1)(2)(3)(4)); //prints 11
Also known as Self Executing Anonymous Function.
A function that runs as soon as it is defined, and only runs once.
(function() {
console.log('a');
})();
The parentheses around the function expression are the grouping operator. They create a new scope for the function: global variables can be accessed within the function but variables declared within the function do not pollute the global space.
The last pair of parentheses cause the function to execute immediately.
You can store the function result, but not the function itself.
var result = (function() {
return 5;
})();
console.log(result); //outputs 5
Incorrectly written closure:
function init(array)
{
for(var i = 0; i < array.length; i++)
{
array[i] = function() { return i; }
}
return array;
}
var numbers = init(new Array(5));
console.log(numbers[0]()); //prints 5
console.log(numbers[1]()); //prints 5
console.log(numbers[2]()); //prints 5
Correctly written closure using "immediately invoked function expression":
function init(array)
{
for(var i = 0; i < array.length; i++)
{
array[i] = function() { return i; }()
}
return array;
}
var numbers = init(new Array(5));
console.log(numbers[0]); //prints 0
console.log(numbers[1]); //prints 1
console.log(numbers[2]); //prints 2
Just put a "()" at the end of the function to cause it to execute immediately.
Alternative implementation, passing variables into the immediately invoked function:
function init(array)
{
for(var i = 0; i < array.length; i++)
{
array[i] = function(number) { return number; }(i)
}
return array;
}
var numbers = init(new Array(5));
console.log(numbers[0]); //prints 0
console.log(numbers[1]); //prints 1
console.log(numbers[2]); //prints 2
Alternative implementation, using "let" instead:
function init(array)
{
for(var i = 0; i < array.length; i++)
{
let j = i;
array[i] = function() { return j; }
}
return array;
}
var numbers = init(new Array(5));
console.log(numbers[0]()); //prints 0
console.log(numbers[1]()); //prints 1
console.log(numbers[2]()); //prints 2
"let" defines a block-scoped variable. The scope of the variable is limited to the block/statement/expression on which it is used.
"Global import" is the concept of passing global variables into an Immediately Invoked Function Expression, so that it is clear what global variables you intend to operate on.
Application is the process of applying a function to its arguments to produce a return value.
In partial application, the function and some of its arguments are passed to a second function. The local scope of the second function acts as storage for these values. The second function can be called repeatedly, filling in the missing arguments of the first function with different values each time.
function partialOperation(fullOperation, a, b, c)
{
return function(d, e) {
return fullOperation(a, b, c, d, e);
};
}
function sum(a, b, c, d, e)
{
return a + b + c + d + e;
}
function multiply(a, b, c, d, e)
{
return a * b * c * d * e;
}
var partiallyApplied = partialOperation(sum, 1, 2, 3);
var finalAnswer = partiallyApplied(4, 5); //15
partiallyApplied = partialOperation(multiply, 1, 2, 3);
finalAnswer = partiallyApplied(4, 5); //120
General purpose partial applicator for any number of arguments. This makes use of the build-it "arguments" array that holds all arguments passed into a function.
function partialApplicator(fn)
{
var slice = Array.prototype.slice;
var args = slice.call(arguments, 1); //get all arguments after fn
return function()
{
//concat previous arguments with new arguments and apply all to fn
return fn.apply(this, args.concat(slice.call(arguments, 0)));
}
}
function sum(a, b)
{
return a + b;
}
function multiply(a, b, c, d)
{
return a * b * c * d;
}
var partiallyApplied = partialApplicator(sum, 1);
var finalAnswer = partiallyApplied(2);
console.log(finalAnswer);
partiallyApplied = partialApplicator(multiply, 1, 2);
finalAnswer = partiallyApplied(3, 4);
console.log(finalAnswer);
Currying is transforming a function with N arguments into a chain of functions, each with 1 argument. You end up calling a series of functions, gradually filling in the arguments needed by the original function. This is a different technique than partial application.
Currying is a functional programming concept that is built into other languages, like Haskell or Elm, but is only simulated in Javascript. (In Haskell and Elm, all functions are automatically compiled as a series of curried functions.)
Curried functions will always have this nested, one parameter at a time, structure:
function ABC(a)
{
return function(b)
{
return function(c)
{
return a + b + c;
}
}
}
var result = ABC(1)(2)(3);
console.log(result); //outputs 6
var bc = ABC(1);
var c = bc(2);
result = c(3);
console.log(result); //outputs 6
Curried function structure using arrow functions:
var DEF = d => e => f => d + e + f;
var result = DEF(1)(2)(3);
console.log(result); //outputs 6
"Overloaded Currying": Curried function structure sort of like method overloading:
function sum(...args) {
if (args.length < 2) {
return sum.bind(this, ...args);
}
return args[0] + args[1];
}
console.log(sum(1,2)); //outputs 3
console.log(sum(1)(2)); //outputs 3
Real example:
const tag = t => contents => `<${t}>${contents}</${t}>`;
const boldTag = tag('b');
console.log(boldTag('Apple')); //outputs "<b>Apple</b>"
console.log(boldTag('Banana')); //outputs "<b>Banana</b>"
To automatically turn any function into a curried function:
function curry(fn, numberOfArguments)
{
if(typeof numberOfArguments !== 'number')
{
//function.length is the number of expected arguments
numberOfArguments = fn.length;
}
function getCurriedFunction(previous)
{
return function(arg)
{
//add one argument to array on each invocation
var args = previous.concat(arg);
if(args.length < numberOfArguments)
return getCurriedFunction(args);
//once you have enough arguments, run the function
else
return fn.apply(this, args);
};
}
//init with empty array of arguments
return getCurriedFunction([]);
}
function sum(a, b, c)
{
return a + b + c;
}
var curriedSum = curry(sum);
var result = curriedSum(1)(2)(3);
console.log(result);
Considerations when using currying:
- currying is not compatible with default parameter values
- curried functions are most useful when the first parameters are settings and the last are the data being operated on, which is backwards of the usual order of parameters
With bind, you can set the "this" value for a function, plus the first parameters of the function. A new function is returned. When you invoke the new function, you can provide any remaining parameters.
Syntax:
var newFunction = oldFunction.bind(thisValue, a, b, c);
var result = newFunction(d, e);
Example: setting "this"
function getAttribute(attributeName)
{
return this[attributeName];
}
var customer = { id: 12, name: "Bob", age: "44" };
var getBobAttribute = getAttribute.bind(customer);
console.log(getBobAttribute("name")); //outputs Bob
console.log(getBobAttribute("age")); //outputs 44
A function is composable if it has one parameter and one return value.
Function composition allows you to combine two or more functions into one new function. Function composition is the same concept as method chaining in C# and as piping in Unix command prompt.
"compose" is not a built-in function:
const compose = (...functions) => args => functions.reduceRight((inputArgument, currentFunction) => currentFunction(inputArgument), args);
"pipe" is not a built-in function:
const pipe = (...functions) => args => functions.reduce((inputArgument, currentFunction) => currentFunction(inputArgument), args);
The only difference between compose and pipe is the order they execute functions in. Pipe executes left-to-right (like a Unix command line pipe). Compose executes right-to-left (like a series of nested function calls).
function reverse(text)
{
return text.split('').reverse().join('');
}
function drop(text)
{
return text.substring(0, text.length - 1);
}
function repeat(text)
{
return text + text;
}
console.log(reverse("bird")); //outputs "drib"
console.log(drop("bird")); //outputs "bir"
console.log(repeat("bird")); //outputs "birdbird"
const compose = (...functions) => args => functions.reduceRight((inputArgument, currentFunction) => currentFunction(inputArgument), args);
const pipe = (...functions) => args => functions.reduce((inputArgument, currentFunction) => currentFunction(inputArgument), args);
var dropReverseRepeat = pipe(drop, reverse, repeat);
console.log(dropReverseRepeat("bird")); // outputs "ribrib"
var repeatReverseDrop = compose(drop, reverse, repeat);
console.log(repeatReverseDrop("bird")); // outputs "dribdri"
A function that wraps a call to another function, with arguments, for later use.
A high order function is a function that returns a function.
A pure function is a function with no side effects. It accepts arguments and returns a value, and makes no changes to anything - no database operations, no changes to global values, nothing.
Javascript does not require that functions be pure functions. However, making as many of your functions as possible into pure functions will result in cleaner code (easier to test, better organized).
Trampolining is using a loop that invokes thunk returning functions.
For when you have to run a long recursive operation that would otherwise overflow stack memory.
This is much much slower than recursion.
function trampoline(fn)
{
return function() {
var x = fn.apply(this, arguments);
while(x instanceof Function) {
x = x();
}
return x;
};
}
function range(start, end, result)
{
result = result || [];
result.push(start);
return (start == end) ? result : function() {
return range(((start < end) ? ++start : --start), end, result);
};
}
trampoline(range)(1, 4); //returns [1, 2, 3, 4]
//should this be trampoline(range(1,4)) ?
Monads are a type of functor that simplify mappings from one type to another type. They are used when one type needs to wrapped (lifted) or unwrapped (flattened) before it can be mapped to the other type.
It is easy to line up these functions into a pipeline, because the input/output line up perfectly.
const AtoB = a => b;
const BtoC = b => c;
It is easy to line up these functions into a pipeline, using functions like "map" which operate of functors.
const AtoB = functor(a) => functor(b);
const BtoC = functor(b) => functor(c);
In order to line up these functions into a pipeline, you need to use monads.
const AtoB = a => monad(b);
const BtoC = b => monad(c);
By using monads, "a => monad(c)" can be composed without adding special code to flatten "monad(b)" into "b". It will be handled automatically by the monad.
Example: this won't work because it is not using monads.
//setup
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const label = 'API call composition';
// a => Promise(b)
const getUserById = id => id === 3 ?
Promise.resolve({ name: 'Kurt', role: 'Author' }) :
undefined
;
// b => Promise(c)
const hasPermission = ({ role }) => (
Promise.resolve(role === 'Author')
);
// a => Promise(c) - this won't work automatically
const authUser = compose(hasPermission, getUserById);
authUser(3).then(trace(label)); //outputs API call composition: false
Example: fixed with monads.
//setup
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const label = 'API call composition';
//monad setup
const composeM = chainMethod => (...ms) => (
ms.reduce((f, g) => x => g(x)[chainMethod](f))
);
const composePromises = composeM('then');
// a => Promise(b)
const getUserById = id => id === 3 ?
Promise.resolve({ name: 'Kurt', role: 'Author' }) :
undefined
;
// b => Promise(c)
const hasPermission = ({ role }) => (
Promise.resolve(role === 'Author')
);
// a => Promise(c)
const authUser = composePromises(hasPermission, getUserById);
authUser(3).then(trace(label)); //outputs API call composition: true
Wrapping aka lifting aka unit aka of: the monad must provide a function to wrap a value in the monad.
Unwrapping aka flattening aka join aka chain: the monad must provide a function to unwrap a value from the monad.
Map: since monads are functors, they must support mapping Monad(a) to Monad(b).
Kleisli Composition aka chain: chain means to flatten then map. Given Monad(Monad(a)), Monad(b) is returned.
("chain" seems to sometimes mean the same as "join", and sometimes mean "join then map")
Possible uses of monads:
You want to insert debugging or logging messages into a pipeline. You don't want to edit any of your existing functions.
You could create a pipeline function that outputs the messages before or after each function call.
(actually I'm not sure this counts as a monad, but it a good use of pipe/compose)
The "Maybe" monad is like "Nullable" in C#. It wraps a value or undefined, and lets you operate on either without getting "undefined" errors.
The "List" monad represents a lazy-load list of values.
One implementation is to accept a starting value in the constructor, and a transform function. The starting value is returned first. Each time you ask for a new value, the transformation is applied to the previous value and the result is returned.
(this implementation sounds like a generator function could serve the same purpose)
The "promise" object (in ES6) is an implementation of the "Continuation" monad.
It allows you to line up asynchronous tasks, and the next one will be run when then previous one completes.
The "Proxy" monad wraps a value and includes some special behavior that you want available in addition to the normal behavior of the value. For any function/property that the monad does not recognize, it passes the call to the wrapped value and returns the result.
All the loops can use 'break' to exit the loop early, and 'continue' to skip to the next iteration.
if(x < y) {
z = 5;
}
if(x < y) {
z = 5;
}
else {
z = 7;
}
if(x < y) {
z = 5;
}
else if(x == y) {
z = 6;
}
else {
z = 7;
}
var x = ["a", "b", "c"];
for(var i = 0; i < x.length; i++)
{
console.log(x[i]);
}
For..In will iterate over enumerable properties of an object. Order is not consistent.
For..In will iterate over the indexes of an array. But don't use it, because order is not consistent.
var x = { firstName: "John", lastName: "Smith", age: 34 };
var text = "";
for(field in x) {
text += field + ": " + x[field] + ", ";
}
console.log(text); //"firstName: John, lastName: Smith, age: 34, "
For..Of iterates over "iterable collections". That's anything with a Symbol.iterator property.
It keeps calling Iterator.next() until 'done' is true.
This does not work on objects.
It works well on arrays, strings, NodeLists, and HTMLCollections.
array:
var array = ['a','b','c', 'd'];
for(let element of array)
{
console.log(element);
}
NodeList:
var elements = document.querySelectorAll('.myClass');
for (let element of elements)
{
element.addEventListener('click', doSomething);
}
var x = 1;
while(x < 100) {
console.log(x);
x++;
}
var x = 1;
do {
console.log(x);
x++;
} while(x < 100);
switch(x) {
case 1: y = 7; break;
case 2: z = 62; break;
case 3:
case 4: z = -5; break;
default: y = z - 56; break;
}
JavaScript functions can be triggered by a browser event.
Add events with HTML attributes. Any javascript can be entered into the event attribute, including multiple statements.
<html>
<element event='myFunc()'>
</element>
<element event='console.log("1"); console.log("2");'>
</element>
</html>
<button onclick="document.getElementById('demo').innerHTML = Date()">Time?</button>
Use the 'this' keyword to refer to the current element.
<button onclick="this.innerHTML='Clicked'">Click?</button>
Use the browser-provided 'event' variable to pass the event object to your event handler.
<button onclick="myEventHandler(event)">Click?</button>
Set an event listener programatically.
document.getElementById("myId").addEventListener("click", myEventHandler);
function myEventHandler(event) {
console.log(event);
console.log(this); //'this' is set to the element
}
You can add multiple event handlers to the same event.
Note that this method expects events like "click" instead of "onclick".
Remove an event listener programatically.
document.getElementById("myId").removeEventListener("click", myEventHandler);
Given nested elements that each have a listener for event X, which order should the event handlers be called in?
Bubbling: the inner-most element will run first, working outward. This is the default.
Capturing: the outer-most element will run first, working inward.
document.getElementById("myId").addEventListener("click", myEventHandler, useCapture:true);
Stop event propagation
event.stopPropagation();
Stop default action
event.preventDefault();
Events not listed elsewhere:
onload: when a document finishes loading
onchange: input value changed - for INPUT, SELECT, and TEXTAREA tags
onfocusin: element is about to gain focus
onfocus: element gains focus
onfocusout: element is about to lose focus
onblur: element loses focus
oninput: element gets user input
oninvalid: element is invalid
onreset: form is reset
onsearch: user writes something in a search field
onselect: after a user selects text - for INPUT and TEXTAREA tags
onsubmit: form is submitted
ontoggle: user opens or closes a DETAILS element
oncopy: user copies element contents
oncut: user cuts element contents
onpaste: user pastes content into an element
ontouchstart: finger is placed on touch screen
ontouchmove: finger is dragged across touch screen
ontouchend: finger is removed from touch screen
ontouchcancel: touch is interrupted
onbeforeprint: page is about to be printed
onafterprint: page has started printing, or print dialog is closed
oncanplay: media has buffered enough to begin playing
oncanplaythrough: media has buffered all the way to the end
ondurationchange: duration of the media is changed
onratechange: playing speed of the media is changed
onvolumechange: volume of the media is changed
onended: media file has played to the end
onloadstart: browser begins trying to load media
onloadeddata: media data is loaded
onloadedmetadata: media metadata is loaded
onprogress: browser is currently downloading the media
onstalled: media data is not available
onemptied: media file is not unavailable, or network issue
onsuspend: browser is intentionally not loading media data
onpause: the media is paused and expects to resume (like for buffering)
onplay: media is played
onplaying: media is currently playing
onpause: media is paused
onseeking: user is moving to a new position in the media
onseeked: user finishes moving to a new position in the media
ontimeupdate: media playing position has changed
animationstart: css animation has started
animationiteration: css animation is repeated
animationend: css animation has completed
transitionend: css transition ends
onabort: loading a resource was aborted
onbeforeunload: the document is about to be unloaded
onerror: an error occurred loading a file
onhashchange: the anchor section of the URL changed
onpageshow: user navigates to the page
onpagehide: user leaves the page
onresize: document view is resized
onscroll: element's scrollbar is scrolled
onunload: after a page has been unloaded - for BODY tag only
onmessage: a message is received from the server
onopen: a connection with the server is opened
ononline: browser starts to work online
onoffline: browser starts to work offline
onpopstate: window history changes
onstorage: a web storage area is updated
Methods:
preventDefault(): cancels the event if it is cancelable
(verified for onkeydown and onkeypress, to block a character being typed)
stopImmediatePropagation(): prevents other listeners of the same event from being called
stopPropagaion(): prevents further propagation of event
onclick: left-mouse-click on element
ondblclick: double left-mouse-click on element
oncontextmenu: right-mouse-click on element
onshow: a MENU element is shown as a context (right-click) menu
onmousedown: a mouse button is pressed on an element
onmouseup: a mouse button is released on an element
onwheel: mouse wheel is moved
onmouseenter: mouse moves over element
onmouseleave: mouse moves off element
onmousemove: mouse moves while over an element
onmouseover: mouse moves over element, or one of its children
onmouseout: mouse moves off element, or one of its children
ondragstart: user starts dragging an element
ondrag: element is being dragged
ondragenter: element enters a drop target
ondragover: element is over a drop target
ondragleave: element leaves a drop target
ondrop: element is dropped on a drop target
ondragend: user stops dragging element (mouse button released)
Event Properties:
button = which mouse button was pressed
0: left (or main)
1: middle or wheel
2: right (or secondary)
3: browser back (or fourth)
4: browser forward (or fifth)
buttons = bitwise flag for which mouse buttons were pressed
0: none
1: left (primary)
2: right (secondary)
4: middle (auxiliary)
8: browser back (fourth)
16: browser forward (fifth)
detail = number of times the mouse was clicked
pageX, pageY = x and y coordinates of mouse relative to document
clientX, clientY = x and y coordinates of mouse relative to window
screenX, screenY = x and y coordinates of mouse relative to screen
relatedTarget = ? the element related to the event-triggering-element
altKey, ctrlKey, shiftKey, metaKey = was this key pressed when the event triggered?
which => button
onkeydown:
keyboard key pressed down
will repeat if key is held down
occurs before char is added to the text field
onkeypress:
keyboard key pressed
will only trigger for displayable character keys (will trigger for "T" but not for "Shift" or "Control")
(verified in FireFox that is does occur for Backspace, Delete, and the arrow keys)
will repeat if key is held down
occurs before char is added to text field
onkeyup:
keyboard key released
can only occurs once per key press
occurs after char is added to text field
Event Properties:
key = (character) the character of the key
keyCode = (integer) Unicode character code of the key
charCode = (integer) Unicode character code of the key (KEYPRESS event only)
altKey, ctrlKey, shiftKey, metaKey = was this key pressed when the event triggered?
location = location of key on the keyboard
which => keyCode
How to disable the context menu for an element:
element.addEventListener('mouseup', clickElement);
element.addEventListener('contextmenu', (e) => {e.preventDefault()});
function clickElement(event) {
event.stopPropagation();
event.preventDefault();
}
try {
myFunc(70);
}
catch(err) {
console.log(err.message);
}
finally {
console.log("always run finally");
}
function myFunc(num) {
if(num > 50)
throw { message: "Number must be <= 50." };
console.log(num);
}
You can throw a string, number, boolean, or object. The example above is throwing an object.
When a JavaScript error occurs, it throws the object { name: "", message: "" }
Name can be:
EvalError - eval() threw an error (newer versions of JavaScript throw SyntaxError instead)
RangeError - a number was out of range
ReferenceError - illegal reference
SyntaxError
TypeError
URIError - encodeURI() threw an error
console.log(text)
use debugger window to set breakpoints
set a breakpoint in the code that will open the debugger window
var x = 5;
debugger; //break point
myFunc(x);
There are many many different Javascript unit testing libraries.
QUnit is very easy to get started with because it only requires importing a file, no installations.
Template for results page:
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>QUnit Example</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.19.3.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.19.3.js"></script>
<script src="MyTests.js"></script>
<script src="MyJavascript.js"></script>
</body>
</html>
MyTests.js contents:
QUnit.test("sum: positive integers", function( assert ) {
assert.ok(sum(1, 2) == 3, "Passed!" );
});
MyJavascript.js contents:
function sum(a, b) {
return a + b;
}
Basic assertions available in QUnit; there are more.
QUnit.test("Title of Test", function( assert ) {
assert.ok(boolean, "Boolean True Message" );
assert.notOk(boolean, "Boolean False Message" );
assert.equal(actual-value, expected-value, "Values Match Message" );
assert.propContains(actual-object, expected-object, "props found in expected match those in actual" );
assert.throws(function-that-throws-exception, expected-exception-type, "Expected Exception Thrown Message" );
});
Errata and oddities about the JavaScript interpreter.
'use strict';
This is a literal directive that indicates code should be interpreted in strict mode.
It can be specified at the beginning of a file or a function to give scope over the whole file or function.
- if it is specified anywhere else, it will be ignored
Strict mode means:
- cannot use undeclared variables (so you don't accidentally make a new global variable by misspelling a variable name)
- cannot assign values to a non-writable property, a getter-only property, a non-existing property, a non-existing variable, a non-existing object
- cannot delete variables or objects or functions
- cannot duplicate parameter names
- cannot use octal numeric literals (ex: var x = 010;)
- cannot use octal escape characters (ex: var x = "\010";)
- cannot use keyword "eval" as a variable name, nor "arguments", nor any of these keywords
implements, interface, let, package, private, protected, public, static, yield
- cannot use "with" statement
- "eval" cannot create variables in its scope (ex: eval("var x = 2");)
The JavaScript interpreter will hoist these statements to the top of their scope before running the code:
- variable declarations (but not variable initializations)
- function declarations with their definitions (but not function expressions or arrow functions or constructed functions)
See the sections for Functions and Variables for more details.
Ex
x = 5;
console.log(x);
var x;
is valid because it becomes
var x;
x = 5;
console.log(x);
Only declarations will be hoisted, because the interpreter knows it is not changing logic.
Initializes will not be hoisted.
x = 5;
console.log(x + y);
var x;
var y = 7;
becomes
var x;
x = 5;
console.log(x + y); //y is undefined here
var y = 7;
Promises are like event listeners, but they can only be triggered once, and they can be triggered by a state that has already occurred.
img1.ready().then(function() {
// run if image is already loaded, or becomes loaded
}, function() {
// run if image has failed or does fail to load
});
Promise.all([img1.ready(), img2.ready()]).then(function(resultsArray) {
// run if all are already loaded, or when they all become loaded
// resultsArray holds the results of each promise, in order
}, function() {
// run if one or more failed, or when one fails in the future
});
//succeed or fail based on the first event to happen
Promise.race([img.ready(), img2.ready()])...
A promise is pending until the action succeeds or fails.
A promise is settled once the action succeeds or fails.
A promise is fulfilled if the action succeeds.
A promise is rejected if the action fails.
// options {
// type: 'post',
// url: 'google.com',
// data: { },
// isFormData: false, //defaults to false
// success: callback,
// error: callback
// }
async function ajax(options) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = onReadyStateChange;
if (options.type.toLowerCase() == "get") {
let queryString = buildQueryString(options.data);
let url = options.url;
if (queryString.length > 0)
url += "?" + queryString;
xhr.open(options.type, url);
xhr.send();
}
else {
xhr.open(options.type, options.url);
if (options.isFormData) {
xhr.send(options.data);
}
else {
let formData = new FormData();
Object.entries(options.data).forEach(([key, value], index) => {
buildFormData(formData, key, value);
});
xhr.send(formData);
}
}
function buildQueryString(data) {
let queryString = "";
if(data == undefined)
return queryString;
Object.entries(data).forEach(([key, value], index) => {
queryString += encodeURIComponent(key) + "=" + encodeURIComponent(value) + "&"
});
return queryString;
}
function buildFormData(formData, key, value) {
let entries = Object.entries(value);
if (entries.length == 0 || typeof(value) == 'string') {
formData.append(key, value);
}
else {
entries.forEach(([sub_key, sub_value], index) => {
buildFormData(formData, key+'['+sub_key+']', sub_value);
});
}
}
function onReadyStateChange() {
const DONE = 4; // readyState 4 means the request is done.
const OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) options.success(JSON.parse(xhr.response));
else options.error(xhr, xhr.status, xhr.response);
}
}
}
You cannot "send" data with a "GET" request. You can only use query string parameters with "GET".
You can easily pull data from a form with
let form = document.getElementById("myForm");
let formData = new FormData(form);
xhr.send(formData);
Or build it manually
let formData = new FormData();
formData.append("key", "value");
A module is a collection of related code. Modules provide organization reusable code, so you can specify which widgets are needed by each of your projects.
Modules should have as few dependencies as possible. Ideally, a module does not rely on any code outside the module.
Modules provide a private space for variables, so that you do not pollute the global namespace.
The Module Pattern mimics the idea of classes in Object-Oriented Programming. It allows you to have public and private methods and variables in an object.
An Immediately Invoked Function Expression is one implementation of the Module Pattern (see the Function section below).
Using closures to protect private data is another implementation of the Module Pattern (see the Closure section below).
ES6 introduced native support for modules (called ESM Modules or ECMAScript Modules):
//I'm not sure this example is right
// in file lib/counter.js
var counter = 1;
function increment() { counter++; }
function decrement() { counter--; }
module.exports = {
counter: counter,
increment: increment,
decrement: decrement
};
// in file src/main.js
var counter = require('../lib/counter');
counter.increment();
console.log(counter.counter); //outputs 1
"require" makes a copy of the module, which is disconnected from the original module.
CommonJS is a library for creating modules that can be imported into other projects. You can specify which modules are publicly available with "module.exports" and you can specify which modules to import into your project with "require". CommonJS was created before ESM Modules became available, and is used by Node.js.
CommonJS module:
//in the module file
function myModule()
{
this.hello = () => 'hello';
this.goodbye = () => 'goodbye';
}
module.exports = myModule;
//in the project file
require('myModule');
CommonJS only loads modules synchronously, meaning the web page will be blocked while modules are loaded from the server. CommonJS is designed for server-side use.
Default export
// file A.js
export default myThing;
// file B.js
import myThing from './A';
Non-default export
// file A.js
export const myThing;
// file B.js
import { myThing } from './A';
HTML
<canvas width='200px' height='200px' id='myCanvas'></canvas>
JS
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
canvas.setAttribute('width', 400);
canvas.setAttribute('height', 800);
//do not use canvas.style.width or canvas.style.height - this will not work as expected
Colors
context.fillStyle = '#FF0000';
context.strokeStyle = '#00FF00';
context.fillStyle = 'rgb(5,5,5)';
Solid line (default) or dashed line
context.setLineDash([]); //solid line
context.setLineDash([lineDashLength, lineDashSpacing]); //dashed line
Line width
context.lineWidth = 5;
Patterns
const img = document.getElementById("myImage");
const pattern = context.createPattern(img, "repeat");
context.fillStyle = pattern;
Rotate and Scale Patterns
//html contains <svg id="mySvg"></svg>
const svg = document.getElementById('mySvg');
const matrix = svg.createSVGMatrix();
pattern.setTransform(matrix.rotate(-45).scale(1.5));
//basic shapes
context.fillRect(25, 25, 100, 100);
context.strokeRect(50, 50, 50, 50);
//custom shapes
context.beginPath();
context.moveTo(75, 50);
context.lineTo(100, 75);
context.lineTo(100, 25);
context.fill(); //fill in the shape
context.beginPath();
context.moveTo(75, 50);
context.lineTo(100, 75);
context.lineTo(100, 25);
context.closePath(); //return to origin
context.stroke(); //draw outline of path
//given circle center (x,y) with radius
//draw arc from start to end radians
context.arc(x, y, radius, startRadians, endRadians); //0 radians = east point, clockwise
context.stroke(); //actually draws the arc outline
context.fill(); //actually draws the arc filled in
//draw text
context.font = "12px Georgia";
context.fillText(elementId, 10, 10);
var textWidth = context.measureText(text).width;
only width is available, not height
newer measurement options with height
var metrics = context.measureText(text); //advanced measures
var fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent; //unchanging height of the font
var actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; //height of this specific text

Draw image on canvas
const imageElement = document.getElementById('myImage');
context.drawImage(imageElement, x, y); //top-left corner
context.drawImage(imageElement, x, y, width, height); //can resize image
Use a canvas as an image source
image.style.src = canvas.toDataURL();
div.style.backgroundImage = "url('" + canvas.toDataURL() + "')";
use FileAPI
<input type="file" />
const fileInput = document.querySelector("input[type=file]");
fileInput.addEventListener("change", async () => {
const [file] = fileInput.files;
if (file) {
var fileContents = await file.text();
}
});
not available in all browsers
FileSystemWritableFileStream
async function saveFile() {
try {
const newHandle = await window.showSaveFilePicker();
const writableStream = await newHandle.createWritable();
await writableStream.write("This is my file content");
await writableStream.close();
} catch (err) {
console.error(err.name, err.message);
}
}
A way to save a piece of javascript code, and run it against the current web page.
1) Create a browser bookmark
2) Replace the "Location" with pure javascript
3) Click this bookmark to execute the javascript against the current web page
Ex:
javascript:document.getElementsByTagName("table")[0].rows[0].cells[0].remove()
Scrolls page until element is visible.
element.scrollIntoView();
element.scrollIntoView({ behavior:'auto' }); //jump to position
element.scrollIntoView({ behavior:'smooth'}); //visibly scroll to position
element.scrollIntoView({ inline:'center' }); //scroll element to horiztonal-center
element.scrollIntoView({ block:'center' }); //scroll element to vertical-center
example
document.querySelector('#footer').scrollIntoView({ behavior: 'smooth' });
Examples are based on vertical scrolling.
Event:
window.onscroll = function() {};
scrollableElement.onscroll = function() {};
Element position in browser viewport:
var position = element.getBoundingClientRect();
Has a top, left, width, and height.
Element position in scrollable parent viewport:
var elementYInParent = element.getBoundingClientRect().top - element.parentNode.getBoundingClientRect().top;
Total height of content in scrollable parent:
var contentHeight = element.parentNode.scrollHeight;
Total height of scrollable parent viewport:
var viewportHeight = element.parentNode.getBoundingClientRect().height;
Current position of scrollable parent scroll bar:
var position = element.parentNode.scrollTop;
Possible range of parent scroll positions:
var contentHeight = element.parentNode.scrollHeight;
var viewportHeight = element.parentNode.getBoundingClientRect().height;
var minScrollTop = 0;
var maxScrollTop = contentHeight - viewportHeight;
Scroll element into view:
var parent = element.parentNode;
var elementYInParent = element.getBoundingClientRect().top - element.parentNode.getBoundingClientRect().top;
parent.scrollTop += elementYInParent;
Animate scroll instead of jumping:
var animateScrollTarget = null; //so you aren't heading in two directions at once
function animateScroll() {
if(animateScrollTarget == null)
return;
var element = <selector for the scrollable element>;
if(element.scrollTop == animateScrollTarget)
return;
var movementUnit = 15;
if(Math.abs(element.scrollTop - animateScrollTarget) < movementUnit)
{
element.scrollTop = animateScrollTarget;
return;
}
if(element.scrollTop > animateScrollTarget)
element.scrollTop -= movementUnit;
else
element.scrollTop += movementUnit;
setTimeout(function() {
animateScroll();
}, movementUnit);
}
Jest is a unit test library for JavaScript and TypeScript.
import 'jest-dom/extend-expect';
Describe groups and labels a set of tests.
Describes can be nested inside each other.
Each top-level describe is one test suite.
describe('group label', () => {
// tests in here
});
BeforeEach function will be run once before each direct-child It and Describe nested in this Describe.
describe('group label', () => {
beforeEach(() => {
// running beforeEach
});
it('a', () => {
// runs beforeEach first
// test logic here
});
describe('sub group label', () => {
// runs beforeEach first
it('b', () => {
// test logic here
});
describe('sub sub group label', () => {
it('c', () => {
// test logic here
});
});
});
});
AfterEach function has the same logic, running after each direct-child It and Describe nested in this Describe.
describe('group label', () => {
afterEach(() => {
});
});
There should be no Expects inside a BeforeEach or AfterEach - these are for setup and cleanup only.
It contains one test.
Its must be inside a Describe.
describe('group label', () => {
it('invalid credentials - displays error', () => {
// test logic here
});
});
Override the default timeout on a test:
it('some test', () => {
}, 8000); // 8000 = 8 seconds
To run just one test out of a suite, call it "fit" instead of "it".
- the "f" is for "force"
To skip tests in a suite, call them "xit" instead of "it".
Expect in Jest is like Assert in C#.
Verify element was found (is not undefined or null):
expect(anElement).toBeTruthy();
Verify element was not found:
expect(anElement).toBeNull();
Verify element matches the saved snapshot:
expect(anElement).toMatchSnapshot();
Verify element exists in the baseElement:
expect(anElement).toBeInTheDocument();
Verify value is equal to:
expect(anElement.value).toBe('someValue');
Negation of a condition:
expect(anElement).not.toBeInTheDocument();
Verify values in a form:
let email = 'test@test.com';
let businessName = 'Business Name';
expect(aFormElement).toHaveFormValues({ email, businessName });
const mock = jest.fn(); //empty mock
const mock = jest.fn().mockImplementation((arg) => 42 + arg);
expect(mock).toHaveBeenCalledTimes(1);
There are several ways to mock imports.
Mocking a function import:
//in WidgetApi.ts
export const callService = async (days: number): Promise<Response> => await fetch(`/url/path/getData?days=${days}`, {
method: 'GET'
});
//in Widget.test.ts
import { callService } from './WidgetApi';
const mockCallService = jest.fn();
jest.mock('./WidgetApi', () => {
return jest.fn().mockImplementation(() => {
return {
callService: mockCallService
};
});
});
[Documentation]
possible import statement
import { render } from '@testing-library/react';
getBy* returns first matching node, throws error if no match is found
getAllBy* returns array of matching nodes, throws error if no match is found
queryBy* returns first matching node, returns null if no match is found, throws error if multiple matches are found
queryByAll* returns array of matching nodes, returns empty array if not matches are found
findBy* returns a Promise that resolves when a matching element is found, rejects Promise if 0 or multiple matches are found
- default timeout of 4500ms
- this is a combination of getBy* and waitForElement
findAllBy* returns a Promise that resolves to an array of matching elements, rejects Promise if 0 matches are found
- default timeout of 4500ms
- this is a combination of getAllBy* and waitForElement
All these queries accept a string, regular expression, or function.
*LabelText searches for a matching Label, then returns the element associated with that Label
*PlaceholderText searches for an element with a matching placeholder attribute
*Text searches for an element containing a text node with matching textContent
*AltText searches for an element with a matching alt attribute
*Title searches for an element with a matching title attribute
*DisplayValue searches for an element (input/textarea/select) with a matching display value
*Role searches for an element with a matching role attribute (implicit or explicit)
- ex: Button tags have an implicit button role
- see Default ARIA Roles
*TestId searches for an element with a matching data-testid attribute
Example: ways of matching
const result = getByLabelText(container, 'Plain Text');
const result = getByLabelText(container, /regex/i);
const result = getByLabelText(container, (content, element) => content.startsWith('X')); //function returns true or false for each element
Example: Placeholder options
let options = {
exact: true,
normalizer: NormalizerFn // don't know what this is
};
const result = getByPlaceholderText(element, "Your Name", options);
querySelector searches by any selection criteria
const result = container.querySelector('[data-testid="Text"]');
Fire events to interact with a page.
fireEvent.click(anElement);
const rightClick = { button: 2 };
fireEvent.click(anElement, rightClick);
fireEvent.change(anElement, { target: { value: 'a' } });
fireEvent.keyDown(anElement, { key: 'Enter', code: 13 });
fireEvent.keyPress
fireEvent.keyUp