DOM
DOM: Document Object Model
- JavaScript is a light-weight language built to create a web page dynamically running on the browser
How the web page is loaded
User = Browser = Client
The client (through the browser) sends requests to the server
The server sends responses (HTML documents) to the client
The browser should interpret these HTML documents (= rendering)
Why rendering required?
JavaScript cannot understand HTML documents
Thus, the browser should interpret this for JS
DOM Tree
based on the interpreted contents that JS can understand, DOM Tree is organized
Render Tree
DOM Tree + CSSOM (CSS Object Model) Tree
The Render Tree is a hierarchical structure of objects that represents the final document model that is actually rendered in the browser, (1) parsed from HTML, CSS, and JavaScript documents.
In essence, (2) it creates the final version of the document to be drawn on the browser screen. After that, (3) layout calculation and (4) painting are initiated to draw the picture on the browser. Finally, (5) with compositing layers, it is shown on the actual screen.
Browser Rendering Order: (1) ~ (5)
DOM API
DOM is a browser built-in API
A browser provides APIs related to the DOM to access the browser's DOM object
So, we can access & control HTML contents through JS
Nodes of all DOMs have methods (verb) and attributes (noun)
Class
Class: a blueprint to design the object (sketch for desk)
- Purpose: to reuse code
Instance: the actual object based on the class (the actual desk)
// Class - blueprint
class Person {
// Constructor - necessity (noun)
constructor(name, age) {
this.name = name;
this.age = age;
}
// Method - optional (verb)
sayHello() {
console.log(`Hello, my name is ${this.name}.`);
}
sayAge() {
console.log(`I am ${this.age} years old.`);
}
}
// Instance - actual object
const person1 = new Person("John", 30);
const person2 = new Person("Mary", 25);
person1.sayHello();
person1.sayAge();
person2.sayHello();
person2.sayAge();
Getters & Setters
To check the validity of properties
// Getters and Setters
// OOP
// Class -> Object (Instance)
// Properties (in constructor)
// when changing properties, use getters and setters
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
// getter for width
get width() {
// with no underscore, it will be an infinite loop (ERROR: maximum call stack size exceeded)
// underscore usually means private
return this._width;
}
// setter for width
set width(value) {
if (value <= 0) {
console.log("Width must be positive.");
return;
} else if (typeof value !== "number") {
console.log("Width must be a number.");
return;
}
this._width = value;
}
// getter for height
get height() {
return this._height;
}
// setter for height
set height(value) {
if (value <= 0) {
console.log("Height must be positive.");
return;
} else if (typeof value !== "number") {
console.log("Height must be a number.");
return;
}
this._height = value;
}
getArea() {
console.log(`Dimension = ${this.width} * ${this.height} = ${this.width * this.height}`);
}
}
const rectangle1 = new Rectangle(10, 5);
rectangle1.getArea();
const rectangle2 = new Rectangle(20, 10);
rectangle2.getArea();
Inheritance
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} is speaking!`);
}
}
const siwon = new Animal("Siwon");
siwon.speak();
// Inheritance
class Dog extends Animal {
constructor(name, breed) {
super(name); // inherit from super class
this.breed = breed;
}
// overriding: redefined method
speak() {
console.log(`${this.name} is barking!`);
}
}
const dog1 = new Dog("Bobby", "Poodle");
dog1.speak();
Static Method
It is used when we don't need to create an instance of the class
class Calculator {
static add(a, b) {
return a + b;
}
static subtract(a, b) {
return a - b;
}
}
// no instance made
console.log(Calculator.add(1, 2));
console.log(Calculator.subtract(1, 2));
Closure
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
- The lexical environment where the function is declared === the information like variables of the outer function when the function is declared
const x = 1;
function outerFunc() {
const x = 10;
function innerFunc() {
console.log(x); // 10
}
innerFunc();
}
outerFunc();
Where does
x
get referenced?innerFunc()
scope: nox
definedouterFunc()
scope:const x = 10
defined -> reference 10
const x = 1;
// Even though innerFunc() is called inside the outerFunc(),
function outerFunc() {
const x = 10;
innerFunc(); // 1
}
// since innerFunc() and outerFunc() have different scope,
// x does not get referenced from outerFunc().
function innerFunc() {
console.log(x); // 1
}
outerFunc();
- Lexical Scope: JS engine decides the outer scope or the scope depending on where it is "defined", not where it is "called"
Closure and Lexical Environment
In the concept that a (1) nested function can refer to variables of an already terminated (2) outer function when the nested function has a longer lifespan than the outer function, the nested function is called a closure.
const x = 1;
function outer() {
const x = 10;
const inner = function () { // inner is closure function
console.log(x);
};
return inner;
}
const innerFunc = outer();
// outer() already terminated (outer() execution context already popped from call stack)
innerFunc(); // print 10
(1) inner()
- closure function (still reference outer()
's x
variable (10) even though outer()
is terminated)
(2) outer()
In other words, the execution context of the outer function is removed from the execution context stack, but its lexical environment is not destroyed.
How does this work? => Garbage collector does leave the outer function's LE since its LE still gets referenced
Closure Practice
The purpose of Closure: To change, maintain, and encapsulate the state (surrounding LE) safely
// Vulnerable Count
let num = 0; // not safe: defined in Global scope -> let's make it as a local variable
const increase = function () {
return ++num;
};
console.log(increase()); // 1
num = 100; // vulnerable - count state can be 'only changed' under increase()
console.log(increase()); // 101
console.log(increase()); // 102
// Not working Count
const increase = function () {
let num = 0; // we brought this to the local in increase()
return ++num;
};
// can't maintain the previous value of num
console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1
// Count with Closure
const increase = (function () {
let num = 0; // garbage collector does't collect this
// Closure - this closure function can reference the outer increase()'s LE
return function () {
return ++num;
};
})(); // IIFE(Immediately-involked-function)
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
When the above code is executed, the immediately invoked function is immediately called and the function returned by the immediately invoked function is assigned to the
increase
variable.The function assigned to the
increase
variable is a closure that remembers the lexical environment of the immediately invoked function, which is determined by the location where it was defined.The immediately invoked function is destroyed after it is called, but the closure returned by the immediately invoked function is assigned to the
increase
variable and is called.At this point, the closure returned by the immediately invoked function remembers the lexical environment of the immediately invoked function, which is determined by the location where it was defined.
Therefore, the closure returned by the immediately invoked function can refer to and change the count state of the free variable
num
whenever and wherever it is called.num
will not be initialized and is a hidden private variable that cannot be directly accessed from outside, so there is no need to worry about unintended changes as with using a global variable.
// More functionality
const counter = (function () {
let num = 0;
return {
increase() {
return ++num;
},
decrease() {
return num > 0 ? --num : 0;
},
};
})();
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0
Closures are used to safely encapsulate state and prevent unintended changes to it by hiding it and allowing only specific functions to modify the state. This allows for secure state changes and maintenance.
Encapsulation & Information Hiding
Encapsulation: binding properties (object's state) + methods (behavior)
- Java and other programming languages use
public
,private
, andprotected
. But, JS does not have these
// Constructor
function Person(name, age) {
this.name = name; // public
let _age = age; // private
// Instance Method
// Thus, everytime Person object is created, it create duplicates
// : Solution -> prototype
this.sayHi = function () {
console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
};
}
const me = new Person("Choi", 33);
me.sayHi(); // Hi!, My name is Choi. I am 33.
console.log(me.name); // Choi - public
console.log(me._age); // undefined - private
const you = new Person("Lee", 30);
you.sayHi(); // Hi! My name is Lee. I am 30.
console.log(you.name); // Lee - public
console.log(you.age); // undefined - private