Basis
client-side JS: run on browsers, respond to user actions such as onclick
server-side JS: run on servers, pretty much like Java servers
Variables
Scopes of variables:
- global: exported variables
- module: visible in the same module (same file)
- function: visible in the same function
- block: visible only in between brackets. different from function-scoped, if you put it in an
if-statement it won't be available outside
Three ways to declare variables:
var: module-scoped / function-scoped by default; initialization optional
let: module-scoped / function-scoped / block-scoped by default; initialization optional
const: module-scoped / function-scoped / block-scoped by default; must be initialized,
read-only
var x = 3;
function countNumber(){
var x = 4;
console.log(x); // evaluates to 4, these two x are essentially the same
}
console.log(x); // this is also 4
let x = 3;
function countNumber(){
let x = 4; // error: cannot declare x twice
console.log(x);
}
console.log(x);
Additional notes:
Hoisted declaration: if you access a
var before it is declared, it's actually ok, but it'll
evaluate to undefined.
Note that this does NOT apply to
let and
const.
Constants cannot have the same name as other functions or variables in the same scope.
Constants allow mutations: you cannot change value, but you can change values inside.
const MY_ARR = [100, 200];
MY_ARR[0] = 200; // this is allowed
MY_ARR = [200, 300]; // this is NOT allowed
Data Types
// strings, numbers, booleans
let str1 = 'hello';
let str2 = "hello";
let num = 5.20;
let success = true;
// BigInt: cannot directly convert into primitives
const a = Number(1n) + 2; // 3
const b = 1n + BigInt(2); // 3n
// arrays: doesn't have to be same data type
let names = ["Amy", 2023, "Bob", 4096, "Carly", true];
// objects
let header = document.querySelector('h1');
Notes on data types:
JS is dynamically typed.
Apart from primitive types (boolean, number...), there are wrappers called literals,
pretty much the same idea as Java primitive and wrappers (int and Integer). Strings, arrays and objects are
literals.
More about numbers:
JS numbers are 64 bit doubles. For numbers that require more digits, use
BigInt.
apart from numeric values, numbers have three other values:
+Infinity,
-Infinity,
NaN.
binary
0b1001, octal
0o1234, hex
0x1234.
exponent
123e-2=1.23,
123e2=12300. Note that e and E are the same.
number object:
const biggestNum = Number.MAX_VALUE;
const smallestNum = Number.MIN_VALUE;
const infiniteNum = Number.POSITIVE_INFINITY;
const negInfiniteNum = Number.NEGATIVE_INFINITY;
const notANum = Number.NaN;
math object:
Math.PI;
Math.sin(3.14);
Math.abs(-3);
Math.pow(2);
Math.floor(2.4);
Math.min(2,4,5);
More about strings:
+ are concats when one of the operands is a string.
string interpolation:
const name = 'Lev', time = 'today';
`Hello ${name}, how are you ${time}?`
Operators
** // exponentiation
>> // signed right shift (left pad with 1 if the number is negative)
>>> // unsigned right shift (left pad with 0 regardless of the sign), BigInt doesn't support this
x??=1; // assigns x=1 if x is null
desctructuring assignment
const foo = ["one", "two", "three"];
// without destructuring
const one = foo[0];
const two = foo[1];
const three = foo[2];
// with destructuring
const [one, two, three] = foo;
arithmatics
1/0 === Infinity;
1/2 === 1.0/2.0;
logical operators (short circuit)
const and1 = "Cat" && "Dog"; // true && true returns Dog
const or1 = "Cat" || "Dog"; // true || true returns Cat
const or2 = false || "Cat"; // f || t returns Cat
const or3 = "Cat" || false; // t || f returns Cat
const not1 = !"Cat"; // !t returns false
delete operator: deletes an object's property. Don't use it on arrays.
delete object.property;
delete object[propertyKey];
delete objectName[index];
// note that you cannot delete system defined properties
delete Math.PI; // can't do this
typeof operator
typeof myFun; // const myFun = new Function("5 + 2"); returns "function"
typeof shape; // const shape = "round"; returns "string"
typeof size; // const size = 1; returns "number"
typeof foo; // const foo = [1,2,3]; returns "object"
typeof null; // returns "object"
typeof doesntExist; // returns "undefined"
in operator: check if the object contains that property
// Arrays
const trees = ["redwood", "bay", "cedar", "oak", "maple"];
0 in trees; // returns true, 0 is index
"bay" in trees; // returns false (CANNOT use in to check if the array contains an element)
"length" in trees; // returns true (length is an Array property)
// Strings
const myString = new String("coral");
"length" in myString; // returns true
// Objects
const mycar = { make: "Honda", model: "Accord", year: 1998 };
"model" in mycar; // returns true
instanceof operator: check if the object is of a type
const theDay = new Date(1995, 12, 17);
if (theDay instanceof Date) {
// statements to execute
}
Control Flow
Never, ever use
== in JS. Use
=== and
!==.
These are considered false in evaluation:
false, undefined, null, 0, NaN, "".
// boolean objects and primitives are different!
const obj = new Boolean(false);
if (obj) // true
if (obj == false) // true
if (obj === false) // false
const pri = false;
if (pri) // false
if (pri == false) // true
if (pri === false) // true
Loops
Let's skip for, while, do-while, and look at the JS version of foreach: two variations, for...in and for...of.
const arr = [3, 5, 7];
arr.foo = "hello";
for (const i in arr) // for...in iterates over keys, which are indexes for arrayss
console.log(i); // "0" "1" "2" "foo"
for (const i of arr) // for...of iterates over values
console.log(i); // Logs: 3 5 7 there is no hello because it only looks at indexed property
Label Statement
Basically an alternative to boolean flag, very similar to
goto in C.
Note that when you use lable on a loop, the value of the iterator does not start all over but continues with
what's left.
// The first for statement is labeled "loop1"
loop1: for (let i = 0; i < 3; i++) {
// The second for statement is labeled "loop2"
loop2: for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1)
continue loop1; // can use lable statement with either continue or break
console.log(`i = ${i}, j = ${j}`);
}
}
// Logs:
// i = 0, j = 0
// i = 0, j = 1
// i = 0, j = 2
// i = 1, j = 0 if you change continue to break, logs stop after this line is printed
// i = 2, j = 0
// i = 2, j = 1
// i = 2, j = 2
Error Handling
funtion myFunc() {
// ...
if (success)
return true;
throw new Error("Execution failed!");
}
try {
myFunc();
} catch (e) {
console.error(e, e.stack); // other alternatives available, e.g., console.error(e.message);
} finally {
// clean up...
// if there is a return statement here, whatever returned here is the return value of this try-catch-finally block
// return statements and throw statements prior to this return will be ignored
}
Nesting try-catch-finally: possible. but the inner block must either has a catch block or a finally block.
Functions
Hoisted declaration
Functions are essentially objects in JS.
Similar to hoisted declaration of variables, function declarations are also hoisted.
You can assume that they appear at the top of the module wherever they are declared.
But note that hoisted declaration DOES NOT apply to function expressions mentioned below.
JS is pass by value, but that doesn't mean you can't change the fields of an object. See below.
function changeStuff(a, b, c)
{
a = a * 10;
b.item = "changed";
c = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num); // 10, num is not changed because it's a primitive
console.log(obj1.item); // changed, you can change the field of a non-primitive such as array or object
console.log(obj2.item); // unchanged, you can change the field, but it's pass by value, you can't change the var itself
Function expressions
Basically assigning a function to a variable, so that you can pass it to other functions.
Since you are treating it as a variable, you can use if-statement on function definition, pretty much like how
you initialize a variable with an if-statement.
function map(f, a) { // takes a function as an argument
const result = new Array(a.length);
for (let i = 0; i < a.length; i++)
result[i] = f(a[i]);
return result;
}
const cube = function (x) { // assign this unnamed function to a variable called cube
return x * x * x; // even if it's named, say function cubeNum(x), it won't work if you call cubeNum()
}; // only works if you refer to this function as cube
const numbers = [0, 1, 2, 5, 10];
console.log(map(cube, numbers)); // [0, 1, 8, 125, 1000]
Function scope and nested functions
A function can access whatever that shares the same scope or with a greater scope. e.g.
A function defined in the global scope can access all variables defined in the global scope.
A function defined inside another function can also access all variables to which the parent function has
access,
but the parent cannot access the child because the child is private to the parent (scope is greater).
function outside(x) {
function inside(y) {
return x + y;
}
return inside;
}
const fnInside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
console.log(fnInside(5)); // 8
console.log(outside(3)(5)); // 8
Parameters and arguments
Arguments can be accessed as an array within a function. Just use
arguments[idx].
function multiply(a, b = 1) { // you can set default parameters, otherwise they are undefined by default
return a * b;
}
console.log(multiply(5)); // 5
function multiply(multiplier, ...theArgs) { // or use rest parameter, which allows an infinite number of arguments passed to this function
return theArgs.map((x) => multiplier * x);
}
const arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
Arrow functions
const a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
const a2 = a.map(function (s) {
return s.length;
});
console.log(a2); // [8, 6, 7, 9]
const a3 = a.map((s) => s.length);
console.log(a3); // [8, 6, 7, 9]
arrow functions vs regular functions declare with
function:
this inside an arrow function always refer to the outer function; but in a regular function
it could refer to many things
- arrow function cannot be used with the
new keyword, i.e., constructors have to be regular
functions
- you don't need to
return in an arrow function